import { isEmpty, propEq, values } from 'ramda'; import React, { useState, useEffect, useMemo, FC } from 'react'; import { Button, Card, Collapse, Progress } from 'reactstrap'; import classNames from 'classnames'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faChevronDown as chevronDown } from '@fortawesome/free-solid-svg-icons'; import moment from 'moment'; import DateRangeRow from '../utils/DateRangeRow'; import Message from '../utils/Message'; import { formatDate } from '../utils/helpers/date'; import { useToggle } from '../utils/helpers/hooks'; import { ShlinkVisitsParams } from '../utils/services/types'; import SortableBarGraph from './helpers/SortableBarGraph'; import GraphCard from './helpers/GraphCard'; import LineChartCard from './helpers/LineChartCard'; import VisitsTable from './VisitsTable'; import { NormalizedVisit, Stats, VisitsInfo } from './types'; import OpenMapModalBtn from './helpers/OpenMapModalBtn'; import { normalizeVisits, processStatsFromVisits } from './services/VisitsParser'; export interface VisitsStatsProps { matchMedia?: (query: string) => MediaQueryList; getVisits: (params: Partial) => void; visitsInfo: VisitsInfo; cancelGetVisits: () => void; } type HighlightableProps = 'referer' | 'country' | 'city'; const highlightedVisitsToStats = ( highlightedVisits: NormalizedVisit[], prop: HighlightableProps, ): Stats => highlightedVisits.reduce((acc, highlightedVisit) => { if (!acc[highlightedVisit[prop]]) { acc[highlightedVisit[prop]] = 0; } acc[highlightedVisit[prop]] += 1; return acc; }, {}); const format = formatDate(); let selectedBar: string | undefined; const VisitsStats: FC = ( { children, visitsInfo, getVisits, cancelGetVisits, matchMedia = window.matchMedia }, ) => { const [ startDate, setStartDate ] = useState(null); const [ endDate, setEndDate ] = useState(null); const [ showTable, toggleTable ] = useToggle(); const [ tableIsSticky, , setSticky, unsetSticky ] = useToggle(); const [ highlightedVisits, setHighlightedVisits ] = useState([]); const [ highlightedLabel, setHighlightedLabel ] = useState(); const [ isMobileDevice, setIsMobileDevice ] = useState(false); const { visits, loading, loadingLarge, error, progress } = visitsInfo; const showTableControls = !loading && visits.length > 0; const normalizedVisits = useMemo(() => normalizeVisits(visits), [ visits ]); const { os, browsers, referrers, countries, cities, citiesForMap } = useMemo( () => processStatsFromVisits(normalizedVisits), [ normalizedVisits ], ); const mapLocations = values(citiesForMap); const determineIsMobileDevice = () => setIsMobileDevice(matchMedia('(max-width: 991px)').matches); const setSelectedVisits = (selectedVisits: NormalizedVisit[]) => { selectedBar = undefined; setHighlightedVisits(selectedVisits); }; const highlightVisitsForProp = (prop: HighlightableProps) => (value: string) => { const newSelectedBar = `${prop}_${value}`; if (selectedBar === newSelectedBar) { setHighlightedVisits([]); setHighlightedLabel(undefined); selectedBar = undefined; } else { setHighlightedVisits(normalizedVisits.filter(propEq(prop, value))); setHighlightedLabel(value); selectedBar = newSelectedBar; } }; useEffect(() => { determineIsMobileDevice(); window.addEventListener('resize', determineIsMobileDevice); return () => { cancelGetVisits(); window.removeEventListener('resize', determineIsMobileDevice); }; }, []); useEffect(() => { getVisits({ startDate: format(startDate) ?? undefined, endDate: format(endDate) ?? undefined }); }, [ startDate, endDate ]); const renderVisitsContent = () => { if (loadingLarge) { return ( This is going to take a while... :S ); } if (loading) { return ; } if (error) { return ( An error occurred while loading visits :( ); } if (isEmpty(visits)) { return There are no visits matching current filter :(; } return (
mapLocations.length > 0 && } sortingItems={{ name: 'City name', amount: 'Visits amount', }} onClick={highlightVisitsForProp('city')} />
); }; return ( {children}
{showTableControls && ( )}
{showTableControls && ( )}
{renderVisitsContent()}
); }; export default VisitsStats;