diff --git a/src/visits/ShortUrlVisits.js b/src/visits/ShortUrlVisits.js index 20b5f504..d18dbd66 100644 --- a/src/visits/ShortUrlVisits.js +++ b/src/visits/ShortUrlVisits.js @@ -31,12 +31,15 @@ const ShortUrlVisits = ( getShortUrlDetail: PropTypes.func, shortUrlDetail: shortUrlDetailType, cancelGetShortUrlVisits: PropTypes.func, + matchMedia: PropTypes.func, }; state = { startDate: undefined, endDate: undefined, showTable: false, + tableIsSticky: false, + isMobileDevice: false, }; loadVisits = (loadDetail = false) => { @@ -54,13 +57,22 @@ const ShortUrlVisits = ( } }; + setIsMobileDevice = () => { + const { matchMedia = window.matchMedia } = this.props; + + this.setState({ isMobileDevice: matchMedia('(max-width: 991px)').matches }); + }; + componentDidMount() { this.timeWhenMounted = new Date().getTime(); this.loadVisits(true); + this.setIsMobileDevice(); + window.addEventListener('resize', this.setIsMobileDevice); } componentWillUnmount() { this.props.cancelGetShortUrlVisits(); + window.removeEventListener('resize', this.setIsMobileDevice); } render() { @@ -155,7 +167,11 @@ const ShortUrlVisits = (
{visits.length > 0 && ( - )} @@ -164,8 +180,14 @@ const ShortUrlVisits = ( {!loading && visits.length > 0 && ( - - + this.setState({ tableIsSticky: true })} + onExiting={() => this.setState({ tableIsSticky: false })} + > + )} diff --git a/src/visits/VisitsTable.js b/src/visits/VisitsTable.js index 0f23837d..44880cf8 100644 --- a/src/visits/VisitsTable.js +++ b/src/visits/VisitsTable.js @@ -19,11 +19,13 @@ import './VisitsTable.scss'; const propTypes = { visits: PropTypes.arrayOf(visitType).isRequired, onVisitSelected: PropTypes.func, + isSticky: PropTypes.bool, + matchMedia: PropTypes.func, }; const PAGE_SIZE = 20; -const visitMatchesSearch = ({ browser, os, referer, location }, searchTerm) => - `${browser} ${os} ${referer} ${location}`.toLowerCase().includes(searchTerm.toLowerCase()); +const visitMatchesSearch = ({ browser, os, referer, country, city }, searchTerm) => + `${browser} ${os} ${referer} ${country} ${city}`.toLowerCase().includes(searchTerm.toLowerCase()); const calculateVisits = (allVisits, page, searchTerm, { field, dir }) => { const end = page * PAGE_SIZE; const start = end - PAGE_SIZE; @@ -49,17 +51,23 @@ const normalizeVisits = map(({ userAgent, date, referer, visitLocation }) => ({ browser: browserFromUserAgent(userAgent), os: osFromUserAgent(userAgent), referer: extractDomain(referer), - location: visitLocation ? `${visitLocation.countryName} - ${visitLocation.cityName}` : '', + country: (visitLocation && visitLocation.countryName) || 'Unknown', + city: (visitLocation && visitLocation.cityName) || 'Unknown', })); -const VisitsTable = ({ visits, onVisitSelected }) => { +const VisitsTable = ({ visits, onVisitSelected, 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 [ page, setPage ] = useState(1); const [ searchTerm, setSearchTerm ] = useState(undefined); const [ order, setOrder ] = useState({ field: undefined, dir: undefined }); const [ currentPage, setCurrentPageVisits ] = useState(calculateVisits(allVisits, page, searchTerm, order)); + const [ isMobileDevice, setIsMobileDevice ] = useState(matchMobile()); const orderByColumn = (field) => () => setOrder({ field, dir: determineOrderDir(field, order.field, order.dir) }); const renderOrderIcon = (field) => { @@ -81,37 +89,52 @@ const VisitsTable = ({ visits, onVisitSelected }) => { useEffect(() => { setCurrentPageVisits(calculateVisits(allVisits, page, searchTerm, order)); }, [ page, searchTerm, order ]); + useEffect(() => { + const listener = () => setIsMobileDevice(matchMobile()); + + window.addEventListener('resize', listener); + + return () => window.removeEventListener('resize', listener); + }, []); return ( - - +
+ - - - - + - - - @@ -119,7 +142,7 @@ const VisitsTable = ({ visits, onVisitSelected }) => { {currentPage.visits.length === 0 && ( - @@ -137,7 +160,8 @@ const VisitsTable = ({ visits, onVisitSelected }) => { - + + @@ -147,17 +171,22 @@ const VisitsTable = ({ visits, onVisitSelected }) => { {currentPage.total >= PAGE_SIZE && ( -
+ + Date {renderOrderIcon('date')} - Location - {renderOrderIcon('location')} + + Country + {renderOrderIcon('country')} + + City + {renderOrderIcon('city')} + Browser {renderOrderIcon('browser')} + OS {renderOrderIcon('os')} + Referrer {renderOrderIcon('referer')}
+
+ No visits found with current filtering
{visit.date} {visit.location}{visit.country}{visit.city} {visit.browser} {visit.os} {visit.referer}
+
-
+
-
+
Visits {currentPage.start + 1} to{' '} {min(currentPage.end, currentPage.total)} of{' '} diff --git a/src/visits/VisitsTable.scss b/src/visits/VisitsTable.scss index 93b05f0c..d248bf59 100644 --- a/src/visits/VisitsTable.scss +++ b/src/visits/VisitsTable.scss @@ -1,4 +1,36 @@ +@import '../utils/base'; + +.visits-table { + margin: 1.5rem 0 0; + position: relative; +} + +.visits-table__sticky { + position: sticky; +} + +.visits-table__header-cell { + cursor: pointer; + top: $headerHeight - 2px; + margin-bottom: 55px; + background-color: white; + z-index: 1; + border: 1px solid #dee2e6; +} + +.visits-table__header-cell--no-action { + cursor: auto; + text-align: center; +} + .visits-table__header-icon { float: right; margin-top: 3px; } + +.visits-table__footer-cell.visits-table__footer-cell { + bottom: 0; + margin-top: 34px; + background-color: white; + padding: .5rem; +} diff --git a/test/visits/GraphCard.test.js b/test/visits/GraphCard.test.js index 2b7d2645..af4e1e97 100644 --- a/test/visits/GraphCard.test.js +++ b/test/visits/GraphCard.test.js @@ -10,12 +10,11 @@ describe('', () => { foo: 123, bar: 456, }; - const matchMedia = () => ({ matches: false }); afterEach(() => wrapper && wrapper.unmount()); it('renders Doughnut when is not a bar chart', () => { - wrapper = shallow(); + wrapper = shallow(); const doughnut = wrapper.find(Doughnut); const horizontal = wrapper.find(HorizontalBar); @@ -43,7 +42,7 @@ describe('', () => { }); it('renders HorizontalBar when is not a bar chart', () => { - wrapper = shallow(); + wrapper = shallow(); const doughnut = wrapper.find(Doughnut); const horizontal = wrapper.find(HorizontalBar); diff --git a/test/visits/ShortUrlVisits.test.js b/test/visits/ShortUrlVisits.test.js index 48c7abc4..92096a08 100644 --- a/test/visits/ShortUrlVisits.test.js +++ b/test/visits/ShortUrlVisits.test.js @@ -31,6 +31,7 @@ describe('', () => { shortUrlVisits={shortUrlVisits} shortUrlDetail={{}} cancelGetShortUrlVisits={identity} + matchMedia={() => ({ matches: false })} /> );