Repository: shlinkio/shlink-web-client (ts)

Issue:
Add option for "All visits" at "Default interval to load"
#### Summary

I love the option to set the option of what the default scope. But I am missing the option to set it to "All visits" what would be this option:

![image](https://user-images.githubusercontent.com/17692860/134828743-c1b7f666-0674-408e-a495-e72f7fd469db.png)

This atm cant be default, but I´d wish I would set it.

Pull request description:
Feature/allow all default
Closes #496 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ff545e4a..4ad769c4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).
 
+## [Unreleased]
+### Added
+* [#496](https://github.com/shlinkio/shlink-web-client/issues/496) Allowed to select "all visits" as the default interval for visits.
+
+### Changed
+* *Nothing*
+
+### Deprecated
+* *Nothing*
+
+### Removed
+* *Nothing*
+
+### Fixed
+* *Nothing*
+
+
 ## [3.3.1] - 2021-09-27
 ### Added
 * *Nothing*
diff --git a/src/settings/Visits.tsx b/src/settings/Visits.tsx
index 6100cb5b..a64a9641 100644
--- a/src/settings/Visits.tsx
+++ b/src/settings/Visits.tsx
@@ -14,6 +14,7 @@ export const Visits: FC<VisitsProps> = ({ settings, setVisitsSettings }) => (
     <FormGroup className="mb-0">
       <label>Default interval to load on visits sections:</label>
       <DateIntervalSelector
+        allText="All visits"
         active={settings.visits?.defaultInterval ?? 'last30Days'}
         onChange={(defaultInterval) => setVisitsSettings({ defaultInterval })}
       />
diff --git a/src/utils/dates/DateIntervalDropdownItems.tsx b/src/utils/dates/DateIntervalDropdownItems.tsx
index b1ea2208..57099496 100644
--- a/src/utils/dates/DateIntervalDropdownItems.tsx
+++ b/src/utils/dates/DateIntervalDropdownItems.tsx
@@ -4,11 +4,16 @@ import { DATE_INTERVALS, DateInterval, rangeOrIntervalToString } from './types';
 
 export interface DateIntervalDropdownProps {
   active?: DateInterval;
+  allText: string;
   onChange: (interval: DateInterval) => void;
 }
 
-export const DateIntervalDropdownItems: FC<DateIntervalDropdownProps> = ({ active, onChange }) => (
+export const DateIntervalDropdownItems: FC<DateIntervalDropdownProps> = ({ active, allText, onChange }) => (
   <>
+    <DropdownItem active={active === 'all'} onClick={() => onChange('all')}>
+      {allText}
+    </DropdownItem>
+    <DropdownItem divider />
     {DATE_INTERVALS.map(
       (interval) => (
         <DropdownItem key={interval} active={active === interval} onClick={() => onChange(interval)}>
diff --git a/src/utils/dates/DateIntervalSelector.tsx b/src/utils/dates/DateIntervalSelector.tsx
index 59d741db..047fccf0 100644
--- a/src/utils/dates/DateIntervalSelector.tsx
+++ b/src/utils/dates/DateIntervalSelector.tsx
@@ -3,8 +3,8 @@ import { DropdownBtn } from '../DropdownBtn';
 import { rangeOrIntervalToString } from './types';
 import { DateIntervalDropdownItems, DateIntervalDropdownProps } from './DateIntervalDropdownItems';
 
-export const DateIntervalSelector: FC<DateIntervalDropdownProps> = ({ onChange, active }) => (
-  <DropdownBtn text={rangeOrIntervalToString(active) ?? ''}>
-    <DateIntervalDropdownItems active={active} onChange={onChange} />
+export const DateIntervalSelector: FC<DateIntervalDropdownProps> = ({ onChange, active, allText }) => (
+  <DropdownBtn text={rangeOrIntervalToString(active) ?? allText}>
+    <DateIntervalDropdownItems allText={allText} active={active} onChange={onChange} />
   </DropdownBtn>
 );
diff --git a/src/utils/dates/DateRangeSelector.tsx b/src/utils/dates/DateRangeSelector.tsx
index 4b6212b8..1b1ec95c 100644
--- a/src/utils/dates/DateRangeSelector.tsx
+++ b/src/utils/dates/DateRangeSelector.tsx
@@ -4,10 +4,10 @@ import { DropdownBtn } from '../DropdownBtn';
 import {
   DateInterval,
   DateRange,
-  dateRangeIsEmpty,
   rangeOrIntervalToString,
   intervalToDateRange,
   rangeIsInterval,
+  dateRangeIsEmpty,
 } from './types';
 import DateRangeRow from './DateRangeRow';
 import { DateIntervalDropdownItems } from './DateIntervalDropdownItems';
@@ -29,11 +29,11 @@ export const DateRangeSelector = (
     !rangeIsInterval(initialDateRange) ? initialDateRange : undefined,
   );
   const updateDateRange = (dateRange: DateRange) => {
-    setActiveInterval(undefined);
+    setActiveInterval(dateRangeIsEmpty(dateRange) ? 'all' : undefined);
     setActiveDateRange(dateRange);
     onDatesChange(dateRange);
   };
-  const updateInterval = (dateInterval?: DateInterval) => () => {
+  const updateInterval = (dateInterval: DateInterval) => () => {
     setActiveInterval(dateInterval);
     setActiveDateRange(undefined);
     onDatesChange(intervalToDateRange(dateInterval));
@@ -41,14 +41,11 @@ export const DateRangeSelector = (
 
   return (
     <DropdownBtn disabled={disabled} text={rangeOrIntervalToString(activeInterval ?? activeDateRange) ?? defaultText}>
-      <DropdownItem
-        active={activeInterval === undefined && dateRangeIsEmpty(activeDateRange)}
-        onClick={updateInterval(undefined)}
-      >
-        {defaultText}
-      </DropdownItem>
-      <DropdownItem divider />
-      <DateIntervalDropdownItems active={activeInterval} onChange={(interval) => updateInterval(interval)()} />
+      <DateIntervalDropdownItems
+        allText={defaultText}
+        active={activeInterval}
+        onChange={(interval) => updateInterval(interval)()}
+      />
       <DropdownItem divider />
       <DropdownItem header>Custom:</DropdownItem>
       <DropdownItem text>
diff --git a/src/utils/dates/types/index.ts b/src/utils/dates/types/index.ts
index 88672d36..467a0e8f 100644
--- a/src/utils/dates/types/index.ts
+++ b/src/utils/dates/types/index.ts
@@ -7,14 +7,15 @@ export interface DateRange {
   endDate?: Date | null;
 }
 
-export type DateInterval = 'today' | 'yesterday' | 'last7Days' | 'last30Days' | 'last90Days' | 'last180days' | 'last365Days';
+export type DateInterval = 'all' | 'today' | 'yesterday' | 'last7Days' | 'last30Days' | 'last90Days' | 'last180days' | 'last365Days';
 
 export const dateRangeIsEmpty = (dateRange?: DateRange): boolean => dateRange === undefined
   || isEmpty(filter(Boolean, dateRange as any));
 
-export const rangeIsInterval = (range?: DateRange | DateInterval): range is DateInterval => typeof range === 'string';
+export const rangeIsInterval = (range?: DateRange | DateInterval): range is DateInterval =>
+  typeof range === 'string';
 
-const INTERVAL_TO_STRING_MAP: Record<DateInterval, string> = {
+const INTERVAL_TO_STRING_MAP: Record<DateInterval, string | undefined> = {
   today: 'Today',
   yesterday: 'Yesterday',
   last7Days: 'Last 7 days',
@@ -22,9 +23,10 @@ const INTERVAL_TO_STRING_MAP: Record<DateInterval, string> = {
   last90Days: 'Last 90 days',
   last180days: 'Last 180 days',
   last365Days: 'Last 365 days',
+  all: undefined,
 };
 
-export const DATE_INTERVALS: DateInterval[] = Object.keys(INTERVAL_TO_STRING_MAP) as DateInterval[];
+export const DATE_INTERVALS = Object.keys(INTERVAL_TO_STRING_MAP).filter((value) => value !== 'all') as DateInterval[];
 
 const dateRangeToString = (range?: DateRange): string | undefined => {
   if (!range || dateRangeIsEmpty(range)) {
@@ -43,7 +45,7 @@ const dateRangeToString = (range?: DateRange): string | undefined => {
 };
 
 export const rangeOrIntervalToString = (range?: DateRange | DateInterval): string | undefined => {
-  if (!range) {
+  if (!range || range === 'all') {
     return undefined;
   }
 
@@ -58,7 +60,7 @@ const startOfDaysAgo = (daysAgo: number) => startOfDay(subDays(new Date(), daysA
 const endingToday = (startDate: Date): DateRange => ({ startDate, endDate: endOfDay(new Date()) });
 
 export const intervalToDateRange = (dateInterval?: DateInterval): DateRange => {
-  if (!dateInterval) {
+  if (!dateInterval || dateInterval === 'all') {
     return {};
   }
 


Test patch:
diff --git a/test/utils/dates/DateIntervalDropdownItems.test.tsx b/test/utils/dates/DateIntervalDropdownItems.test.tsx
index 2d15ebd8..d77fb1e4 100644
--- a/test/utils/dates/DateIntervalDropdownItems.test.tsx
+++ b/test/utils/dates/DateIntervalDropdownItems.test.tsx
@@ -8,7 +8,7 @@ describe('<DateIntervalDropdownItems />', () => {
   const onChange = jest.fn();
 
   beforeEach(() => {
-    wrapper = shallow(<DateIntervalDropdownItems active="last180days" onChange={onChange} />);
+    wrapper = shallow(<DateIntervalDropdownItems allText="All" active="last180days" onChange={onChange} />);
   });
 
   afterEach(jest.clearAllMocks);
@@ -16,24 +16,26 @@ describe('<DateIntervalDropdownItems />', () => {
 
   it('renders expected amount of items', () => {
     const items = wrapper.find(DropdownItem);
+    const dividerItems = items.findWhere((item) => !!item.prop('divider'));
 
-    expect(items).toHaveLength(DATE_INTERVALS.length);
+    expect(items).toHaveLength(DATE_INTERVALS.length + 2);
+    expect(dividerItems).toHaveLength(1);
   });
 
   it('sets expected item as active', () => {
-    const items = wrapper.find(DropdownItem);
-    const EXPECTED_ACTIVE_INDEX = 5;
+    const items = wrapper.find(DropdownItem).findWhere((item) => item.prop('active') !== undefined);
+    const EXPECTED_ACTIVE_INDEX = 6;
 
-    expect.assertions(DATE_INTERVALS.length);
+    expect.assertions(DATE_INTERVALS.length + 1);
     items.forEach((item, index) => expect(item.prop('active')).toEqual(index === EXPECTED_ACTIVE_INDEX));
   });
 
   it('triggers onChange callback when selecting an element', () => {
     const items = wrapper.find(DropdownItem);
 
-    items.at(2).simulate('click');
     items.at(4).simulate('click');
-    items.at(1).simulate('click');
+    items.at(6).simulate('click');
+    items.at(3).simulate('click');
     expect(onChange).toHaveBeenCalledTimes(3);
   });
 });
diff --git a/test/utils/dates/DateIntervalSelector.test.tsx b/test/utils/dates/DateIntervalSelector.test.tsx
index 94c068d7..2b73da55 100644
--- a/test/utils/dates/DateIntervalSelector.test.tsx
+++ b/test/utils/dates/DateIntervalSelector.test.tsx
@@ -10,7 +10,7 @@ describe('<DateIntervalSelector />', () => {
   const onChange = jest.fn();
 
   beforeEach(() => {
-    wrapper = shallow(<DateIntervalSelector active={activeInterval} onChange={onChange} />);
+    wrapper = shallow(<DateIntervalSelector allText="All text" active={activeInterval} onChange={onChange} />);
   });
   afterEach(() => wrapper?.unmount());
 
@@ -22,5 +22,6 @@ describe('<DateIntervalSelector />', () => {
     expect(items).toHaveLength(1);
     expect(items.prop('onChange')).toEqual(onChange);
     expect(items.prop('active')).toEqual(activeInterval);
+    expect(items.prop('allText')).toEqual('All text');
   });
 });
diff --git a/test/utils/dates/DateRangeSelector.test.tsx b/test/utils/dates/DateRangeSelector.test.tsx
index cb599c90..ef794918 100644
--- a/test/utils/dates/DateRangeSelector.test.tsx
+++ b/test/utils/dates/DateRangeSelector.test.tsx
@@ -4,12 +4,19 @@ import { Mock } from 'ts-mockery';
 import { DateRangeSelector, DateRangeSelectorProps } from '../../../src/utils/dates/DateRangeSelector';
 import { DateInterval } from '../../../src/utils/dates/types';
 import { DateIntervalDropdownItems } from '../../../src/utils/dates/DateIntervalDropdownItems';
+import DateRangeRow from '../../../src/utils/dates/DateRangeRow';
 
 describe('<DateRangeSelector />', () => {
   let wrapper: ShallowWrapper;
   const onDatesChange = jest.fn();
   const createWrapper = (props: Partial<DateRangeSelectorProps> = {}) => {
-    wrapper = shallow(<DateRangeSelector {...Mock.of<DateRangeSelectorProps>(props)} onDatesChange={onDatesChange} />);
+    wrapper = shallow(
+      <DateRangeSelector
+        {...Mock.of<DateRangeSelectorProps>(props)}
+        defaultText="Default text"
+        onDatesChange={onDatesChange}
+      />,
+    );
 
     return wrapper;
   };
@@ -22,47 +29,48 @@ describe('<DateRangeSelector />', () => {
     const items = wrapper.find(DropdownItem);
     const dateIntervalItems = wrapper.find(DateIntervalDropdownItems);
 
-    expect(items).toHaveLength(5);
+    expect(items).toHaveLength(3);
     expect(dateIntervalItems).toHaveLength(1);
-    expect(items.filter('[divider]')).toHaveLength(2);
+    expect(items.filter('[divider]')).toHaveLength(1);
     expect(items.filter('[header]')).toHaveLength(1);
     expect(items.filter('[text]')).toHaveLength(1);
-    expect(items.filter('[active]')).toHaveLength(1);
   });
 
   it.each([
-    [ undefined, 1, 0 ],
-    [ 'today' as DateInterval, 0, 1 ],
-    [ 'yesterday' as DateInterval, 0, 1 ],
-    [ 'last7Days' as DateInterval, 0, 1 ],
-    [ 'last30Days' as DateInterval, 0, 1 ],
-    [ 'last90Days' as DateInterval, 0, 1 ],
-    [ 'last180days' as DateInterval, 0, 1 ],
-    [ 'last365Days' as DateInterval, 0, 1 ],
-    [{ startDate: new Date() }, 0, 0 ],
-  ])('sets proper element as active based on provided date range', (
-    initialDateRange,
-    expectedActiveItems,
-    expectedActiveIntervalItems,
-  ) => {
+    [ undefined, 0 ],
+    [ 'all' as DateInterval, 1 ],
+    [ 'today' as DateInterval, 1 ],
+    [ 'yesterday' as DateInterval, 1 ],
+    [ 'last7Days' as DateInterval, 1 ],
+    [ 'last30Days' as DateInterval, 1 ],
+    [ 'last90Days' as DateInterval, 1 ],
+    [ 'last180days' as DateInterval, 1 ],
+    [ 'last365Days' as DateInterval, 1 ],
+    [{ startDate: new Date() }, 0 ],
+  ])('sets proper element as active based on provided date range', (initialDateRange, expectedActiveIntervalItems) => {
     const wrapper = createWrapper({ initialDateRange });
-    const items = wrapper.find(DropdownItem).filterWhere((item) => item.prop('active') === true);
     const dateIntervalItems = wrapper.find(DateIntervalDropdownItems).filterWhere(
       (item) => item.prop('active') !== undefined,
     );
 
-    expect(items).toHaveLength(expectedActiveItems);
     expect(dateIntervalItems).toHaveLength(expectedActiveIntervalItems);
   });
 
   it('triggers onDatesChange callback when selecting an element', () => {
     const wrapper = createWrapper();
-    const item = wrapper.find(DropdownItem).at(0);
+    const dates = wrapper.find(DateRangeRow);
     const dateIntervalItems = wrapper.find(DateIntervalDropdownItems);
 
-    item.simulate('click');
-    item.simulate('click');
+    dates.simulate('startDateChange', null);
+    dates.simulate('endDateChange', null);
     dateIntervalItems.simulate('change');
     expect(onDatesChange).toHaveBeenCalledTimes(3);
   });
+
+  it('propagates default text to DateIntervalDropdownItems', () => {
+    const wrapper = createWrapper();
+    const dateIntervalItems = wrapper.find(DateIntervalDropdownItems);
+
+    expect(dateIntervalItems.prop('allText')).toEqual('Default text');
+  });
 });