mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Added date range selector to short URLs list
This commit is contained in:
parent
3f245a757e
commit
61a1087d91
5 changed files with 37 additions and 39 deletions
|
@ -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>
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue