diff --git a/src/visits/ShortUrlVisits.js b/src/visits/ShortUrlVisits.js index eddb9d30..fd6026e2 100644 --- a/src/visits/ShortUrlVisits.js +++ b/src/visits/ShortUrlVisits.js @@ -31,7 +31,15 @@ const propTypes = { matchMedia: PropTypes.func, }; -const highlightedVisitToStats = (highlightedVisit, prop) => highlightedVisit && { [highlightedVisit[prop]]: 1 }; +const highlightedVisitsToStats = (highlightedVisits, prop) => highlightedVisits.reduce((acc, highlightedVisit) => { + if (!acc[highlightedVisit[prop]]) { + acc[highlightedVisit[prop]] = 0; + } + + acc[highlightedVisit[prop]] += 1; + + return acc; +}, {}); const format = formatDate(); let memoizationId; let timeWhenMounted; @@ -51,7 +59,7 @@ const ShortUrlVisits = ({ processStatsFromVisits }, OpenMapModalBtn) => { const [ endDate, setEndDate ] = useState(undefined); const [ showTable, toggleTable ] = useToggle(); const [ tableIsSticky, , setSticky, unsetSticky ] = useToggle(); - const [ highlightedVisit, setHighlightedVisit ] = useState(undefined); + const [ highlightedVisits, setHighlightedVisits ] = useState([]); const [ isMobileDevice, setIsMobileDevice ] = useState(false); const determineIsMobileDevice = () => setIsMobileDevice(matchMedia('(max-width: 991px)').matches); @@ -124,7 +132,7 @@ const ShortUrlVisits = ({ processStatsFromVisits }, OpenMapModalBtn) => { title="Referrers" stats={referrers} withPagination={false} - highlightedStats={highlightedVisitToStats(highlightedVisit, 'referer')} + highlightedStats={highlightedVisitsToStats(highlightedVisits, 'referer')} sortingItems={{ name: 'Referrer name', amount: 'Visits amount', @@ -135,7 +143,7 @@ const ShortUrlVisits = ({ processStatsFromVisits }, OpenMapModalBtn) => { { mapLocations.length > 0 && @@ -198,7 +206,7 @@ const ShortUrlVisits = ({ processStatsFromVisits }, OpenMapModalBtn) => { onEntered={setSticky} onExiting={unsetSticky} > - + )} diff --git a/src/visits/VisitsTable.js b/src/visits/VisitsTable.js index 70f31002..bd4dcaf1 100644 --- a/src/visits/VisitsTable.js +++ b/src/visits/VisitsTable.js @@ -19,7 +19,7 @@ import './VisitsTable.scss'; const propTypes = { visits: PropTypes.arrayOf(visitType).isRequired, - onVisitSelected: PropTypes.func, + onVisitsSelected: PropTypes.func, isSticky: PropTypes.bool, matchMedia: PropTypes.func, }; @@ -51,14 +51,14 @@ const normalizeVisits = map(({ userAgent, date, referer, visitLocation }) => ({ city: (visitLocation && visitLocation.cityName) || 'Unknown', })); -const VisitsTable = ({ visits, onVisitSelected, isSticky = false, matchMedia = window.matchMedia }) => { +const VisitsTable = ({ visits, onVisitsSelected, isSticky = false, matchMedia = window.matchMedia }) => { const allVisits = normalizeVisits(visits); const headerCellsClass = classNames('visits-table__header-cell', { 'visits-table__sticky': isSticky, }); const matchMobile = () => matchMedia('(max-width: 767px)').matches; - const [ selectedVisit, setSelectedVisit ] = useState(undefined); + const [ selectedVisits, setSelectedVisits ] = useState([]); const [ isMobileDevice, setIsMobileDevice ] = useState(matchMobile()); const [ searchTerm, setSearchTerm ] = useState(undefined); const [ order, setOrder ] = useState({ field: undefined, dir: undefined }); @@ -77,8 +77,8 @@ const VisitsTable = ({ visits, onVisitSelected, isSticky = false, matchMedia = w ); useEffect(() => { - onVisitSelected && onVisitSelected(selectedVisit); - }, [ selectedVisit ]); + onVisitsSelected && onVisitsSelected(selectedVisits); + }, [ selectedVisits ]); useEffect(() => { const listener = () => setIsMobileDevice(matchMobile()); @@ -88,6 +88,7 @@ const VisitsTable = ({ visits, onVisitSelected, isSticky = false, matchMedia = w }, []); useEffect(() => { setPage(1); + setSelectedVisits([]); }, [ searchTerm ]); return ( @@ -95,11 +96,14 @@ const VisitsTable = ({ visits, onVisitSelected, isSticky = false, matchMedia = w setSelectedVisits( + selectedVisits.length < resultSet.total ? resultSet.visitsGroups.flat() : [] + )} > - + 0 })} /> Date @@ -140,26 +144,32 @@ const VisitsTable = ({ visits, onVisitSelected, isSticky = false, matchMedia = w )} - {resultSet.visitsGroups[page - 1] && resultSet.visitsGroups[page - 1].map((visit, index) => ( - setSelectedVisit(selectedVisit === visit ? undefined : visit)} - > - - {selectedVisit === visit && } - - - {visit.date} - - {visit.country} - {visit.city} - {visit.browser} - {visit.os} - {visit.referer} - - ))} + {resultSet.visitsGroups[page - 1] && resultSet.visitsGroups[page - 1].map((visit, index) => { + const isSelected = selectedVisits.includes(visit); + + return ( + setSelectedVisits( + isSelected ? selectedVisits.filter((v) => v !== visit) : [ ...selectedVisits, visit ] + )} + > + + {isSelected && } + + + {visit.date} + + {visit.country} + {visit.city} + {visit.browser} + {visit.os} + {visit.referer} + + ); + })} {resultSet.total > PAGE_SIZE && ( diff --git a/src/visits/VisitsTable.scss b/src/visits/VisitsTable.scss index b1365b98..9249df8b 100644 --- a/src/visits/VisitsTable.scss +++ b/src/visits/VisitsTable.scss @@ -17,11 +17,6 @@ } } -.visits-table__header-cell--no-action { - cursor: auto; - text-align: center; -} - .visits-table__header-icon { float: right; margin-top: 3px; diff --git a/test/visits/VisitsTable.test.js b/test/visits/VisitsTable.test.js index e8fbab27..b9485e1c 100644 --- a/test/visits/VisitsTable.test.js +++ b/test/visits/VisitsTable.test.js @@ -63,7 +63,7 @@ describe('', () => { expect(paginator).toHaveLength(0); }); - it('selects a row when clicked', () => { + it('selected rows are highlighted', () => { const wrapper = createWrapper(rangeOf(10, () => ({ userAgent: '', date: '', referer: '' }))); expect(wrapper.find('.text-primary')).toHaveLength(0); @@ -72,9 +72,19 @@ describe('', () => { expect(wrapper.find('.text-primary')).toHaveLength(2); expect(wrapper.find('.table-primary')).toHaveLength(1); wrapper.find('tr').at(3).simulate('click'); + expect(wrapper.find('.text-primary')).toHaveLength(3); + expect(wrapper.find('.table-primary')).toHaveLength(2); + wrapper.find('tr').at(3).simulate('click'); expect(wrapper.find('.text-primary')).toHaveLength(2); expect(wrapper.find('.table-primary')).toHaveLength(1); - wrapper.find('tr').at(3).simulate('click'); + + // Select all + wrapper.find('thead').find('th').at(0).simulate('click'); + expect(wrapper.find('.text-primary')).toHaveLength(11); + expect(wrapper.find('.table-primary')).toHaveLength(10); + + // Select none + wrapper.find('thead').find('th').at(0).simulate('click'); expect(wrapper.find('.text-primary')).toHaveLength(0); expect(wrapper.find('.table-primary')).toHaveLength(0); });