Added date range selector to short URLs list

This commit is contained in:
Alejandro Celaya 2020-12-14 23:35:31 +01:00
parent 3f245a757e
commit 61a1087d91
5 changed files with 37 additions and 39 deletions

View file

@ -4,9 +4,10 @@ import { isEmpty, pipe } from 'ramda';
import moment from 'moment'; import moment from 'moment';
import SearchField from '../utils/SearchField'; import SearchField from '../utils/SearchField';
import Tag from '../tags/helpers/Tag'; import Tag from '../tags/helpers/Tag';
import DateRangeRow from '../utils/dates/DateRangeRow'; import { DateRangeSelector } from '../utils/dates/DateRangeSelector';
import { formatDate } from '../utils/helpers/date'; import { formatIsoDate } from '../utils/helpers/date';
import ColorGenerator from '../utils/services/ColorGenerator'; import ColorGenerator from '../utils/services/ColorGenerator';
import { DateRange } from '../utils/dates/types';
import { ShortUrlsListParams } from './reducers/shortUrlsListParams'; import { ShortUrlsListParams } from './reducers/shortUrlsListParams';
import './SearchBar.scss'; import './SearchBar.scss';
@ -19,9 +20,12 @@ const dateOrNull = (date?: string) => date ? moment(date) : null;
const SearchBar = (colorGenerator: ColorGenerator) => ({ listShortUrls, shortUrlsListParams }: SearchBarProps) => { const SearchBar = (colorGenerator: ColorGenerator) => ({ listShortUrls, shortUrlsListParams }: SearchBarProps) => {
const selectedTags = shortUrlsListParams.tags ?? []; const selectedTags = shortUrlsListParams.tags ?? [];
const setDate = (dateName: 'startDate' | 'endDate') => pipe( const setDates = pipe(
formatDate(), ({ startDate, endDate }: DateRange) => ({
(date) => listShortUrls({ ...shortUrlsListParams, [dateName]: date }), startDate: formatIsoDate(startDate) ?? undefined,
endDate: formatIsoDate(endDate) ?? undefined,
}),
(dates) => listShortUrls({ ...shortUrlsListParams, ...dates }),
); );
return ( return (
@ -35,11 +39,13 @@ const SearchBar = (colorGenerator: ColorGenerator) => ({ listShortUrls, shortUrl
<div className="mt-3"> <div className="mt-3">
<div className="row"> <div className="row">
<div className="col-lg-8 offset-lg-4 col-xl-6 offset-xl-6"> <div className="col-lg-8 offset-lg-4 col-xl-6 offset-xl-6">
<DateRangeRow <DateRangeSelector
startDate={dateOrNull(shortUrlsListParams.startDate)} defaultText="All short URLs"
endDate={dateOrNull(shortUrlsListParams.endDate)} initialDateRange={{
onStartDateChange={setDate('startDate')} startDate: dateOrNull(shortUrlsListParams.startDate),
onEndDateChange={setDate('endDate')} endDate: dateOrNull(shortUrlsListParams.endDate),
}}
onDatesChange={setDates}
/> />
</div> </div>
</div> </div>

View file

@ -45,7 +45,7 @@ export const ShortUrlsTable = (ShortUrlsRow: FC<ShortUrlsRowProps>) => ({
return <tr><td colSpan={6} className="text-center">Loading...</td></tr>; return <tr><td colSpan={6} className="text-center">Loading...</td></tr>;
} }
if (!loading && isEmpty(shortUrlsList)) { if (!loading && isEmpty(shortUrls?.data)) {
return <tr><td colSpan={6} className="text-center">No results found</td></tr>; return <tr><td colSpan={6} className="text-center">No results found</td></tr>;
} }

View file

@ -16,9 +16,12 @@ export interface DateRangeSelectorProps {
initialDateRange?: DateInterval | DateRange; initialDateRange?: DateInterval | DateRange;
disabled?: boolean; disabled?: boolean;
onDatesChange: (dateRange: DateRange) => void; onDatesChange: (dateRange: DateRange) => void;
defaultText: string;
} }
export const DateRangeSelector = ({ onDatesChange, initialDateRange, disabled = false }: DateRangeSelectorProps) => { export const DateRangeSelector = (
{ onDatesChange, initialDateRange, defaultText, disabled = false }: DateRangeSelectorProps,
) => {
const [ isOpen, toggle ] = useToggle(); const [ isOpen, toggle ] = useToggle();
const [ activeInterval, setActiveInterval ] = useState( const [ activeInterval, setActiveInterval ] = useState(
rangeIsInterval(initialDateRange) ? initialDateRange : undefined, rangeIsInterval(initialDateRange) ? initialDateRange : undefined,
@ -40,35 +43,23 @@ export const DateRangeSelector = ({ onDatesChange, initialDateRange, disabled =
return ( return (
<Dropdown isOpen={isOpen} toggle={toggle} disabled={disabled}> <Dropdown isOpen={isOpen} toggle={toggle} disabled={disabled}>
<DropdownToggle caret className="date-range-selector__btn btn-block" color="primary"> <DropdownToggle caret className="date-range-selector__btn btn-block" color="primary">
{rangeOrIntervalToString(activeInterval ?? activeDateRange) ?? 'All visits'} {rangeOrIntervalToString(activeInterval ?? activeDateRange) ?? defaultText}
</DropdownToggle> </DropdownToggle>
<DropdownMenu className="w-100"> <DropdownMenu className="w-100">
<DropdownItem <DropdownItem
active={activeInterval === undefined && dateRangeIsEmpty(activeDateRange)} active={activeInterval === undefined && dateRangeIsEmpty(activeDateRange)}
onClick={updateInterval(undefined)} onClick={updateInterval(undefined)}
> >
All visits {defaultText}
</DropdownItem> </DropdownItem>
<DropdownItem divider /> <DropdownItem divider />
<DropdownItem active={activeInterval === 'today'} onClick={updateInterval('today')}>Today</DropdownItem> {([ 'today', 'yesterday', 'last7Days', 'last30Days', 'last90Days', 'last180days', 'last365Days' ] as DateInterval[]).map(
<DropdownItem active={activeInterval === 'yesterday'} onClick={updateInterval('yesterday')}> (interval) => (
Yesterday <DropdownItem key={interval} active={activeInterval === interval} onClick={updateInterval(interval)}>
</DropdownItem> {rangeOrIntervalToString(interval)}
<DropdownItem active={activeInterval === 'last7Days'} onClick={updateInterval('last7Days')}> </DropdownItem>
Last 7 days ),
</DropdownItem> )}
<DropdownItem active={activeInterval === 'last30Days'} onClick={updateInterval('last30Days')}>
Last 30 days
</DropdownItem>
<DropdownItem active={activeInterval === 'last90Days'} onClick={updateInterval('last90Days')}>
Last 90 days
</DropdownItem>
<DropdownItem active={activeInterval === 'last180days'} onClick={updateInterval('last180days')}>
Last 180 days
</DropdownItem>
<DropdownItem active={activeInterval === 'last365Days'} onClick={updateInterval('last365Days')}>
Last 365 days
</DropdownItem>
<DropdownItem divider /> <DropdownItem divider />
<DropdownItem header>Custom:</DropdownItem> <DropdownItem header>Custom:</DropdownItem>
<DropdownItem text> <DropdownItem text>

View file

@ -227,6 +227,7 @@ const VisitsStats: FC<VisitsStatsProps> = ({ children, visitsInfo, getVisits, ca
<div className="col-lg-7 col-xl-6"> <div className="col-lg-7 col-xl-6">
<DateRangeSelector <DateRangeSelector
disabled={loading} disabled={loading}
defaultText="All visits"
onDatesChange={({ startDate: newStartDate, endDate: newEndDate }) => { onDatesChange={({ startDate: newStartDate, endDate: newEndDate }) => {
setStartDate(newStartDate ?? null); setStartDate(newStartDate ?? null);
setEndDate(newEndDate ?? null); setEndDate(newEndDate ?? null);

View file

@ -3,7 +3,7 @@ import { Mock } from 'ts-mockery';
import searchBarCreator from '../../src/short-urls/SearchBar'; import searchBarCreator from '../../src/short-urls/SearchBar';
import SearchField from '../../src/utils/SearchField'; import SearchField from '../../src/utils/SearchField';
import Tag from '../../src/tags/helpers/Tag'; import Tag from '../../src/tags/helpers/Tag';
import DateRangeRow from '../../src/utils/dates/DateRangeRow'; import { DateRangeSelector } from '../../src/utils/dates/DateRangeSelector';
import ColorGenerator from '../../src/utils/services/ColorGenerator'; import ColorGenerator from '../../src/utils/services/ColorGenerator';
describe('<SearchBar />', () => { describe('<SearchBar />', () => {
@ -20,10 +20,10 @@ describe('<SearchBar />', () => {
expect(wrapper.find(SearchField)).toHaveLength(1); expect(wrapper.find(SearchField)).toHaveLength(1);
}); });
it('renders a DateRangeRow', () => { it('renders a DateRangeSelector', () => {
wrapper = shallow(<SearchBar shortUrlsListParams={{}} listShortUrls={listShortUrlsMock} />); wrapper = shallow(<SearchBar shortUrlsListParams={{}} listShortUrls={listShortUrlsMock} />);
expect(wrapper.find(DateRangeRow)).toHaveLength(1); expect(wrapper.find(DateRangeSelector)).toHaveLength(1);
}); });
it('renders no tags when the list of tags is empty', () => { it('renders no tags when the list of tags is empty', () => {
@ -60,14 +60,14 @@ describe('<SearchBar />', () => {
expect(listShortUrlsMock).toHaveBeenCalledTimes(1); expect(listShortUrlsMock).toHaveBeenCalledTimes(1);
}); });
it.each([ 'startDateChange', 'endDateChange' ])('updates short URLs list when date range changes', (event) => { it('updates short URLs list when date range changes', () => {
wrapper = shallow( wrapper = shallow(
<SearchBar shortUrlsListParams={{}} listShortUrls={listShortUrlsMock} />, <SearchBar shortUrlsListParams={{}} listShortUrls={listShortUrlsMock} />,
); );
const dateRange = wrapper.find(DateRangeRow); const dateRange = wrapper.find(DateRangeSelector);
expect(listShortUrlsMock).not.toHaveBeenCalled(); expect(listShortUrlsMock).not.toHaveBeenCalled();
dateRange.simulate(event); dateRange.simulate('datesChange', {});
expect(listShortUrlsMock).toHaveBeenCalledTimes(1); expect(listShortUrlsMock).toHaveBeenCalledTimes(1);
}); });
}); });