From 5a61246183b68bac9d8aaf58d713863fb6d08f3f Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 4 Oct 2024 12:06:22 +0100 Subject: [PATCH 01/40] Comment out unnecessary props old date range picker --- .../DatePickers/DateRangePicker/index.tsx | 34 +++++++++---------- .../DatePickers/DateRangePicker/index2.tsx | 0 2 files changed, 17 insertions(+), 17 deletions(-) create mode 100644 front/app/components/admin/DatePickers/DateRangePicker/index2.tsx diff --git a/front/app/components/admin/DatePickers/DateRangePicker/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/index.tsx index 40886e0acae2..e5432400ee39 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/index.tsx @@ -46,14 +46,14 @@ export type Dates = { }; interface Props { - startDate: Moment | null; - endDate: Moment | null; - onDatesChange: ({ startDate, endDate }: Dates) => void; - minDate?: Moment; + startDate: Moment | null; // + endDate: Moment | null; // + onDatesChange: ({ startDate, endDate }: Dates) => void; // + minDate?: Moment; // maxDate?: Moment; - startDatePlaceholderText?: string; - endDatePlaceholderText?: string; - excludeDates?: Moment[]; + // startDatePlaceholderText?: string; + // endDatePlaceholderText?: string; + // excludeDates?: Moment[]; } const DateRangePicker = ({ @@ -62,10 +62,10 @@ const DateRangePicker = ({ onDatesChange, minDate, maxDate, - startDatePlaceholderText, - endDatePlaceholderText, - excludeDates, -}: Props) => { +}: // startDatePlaceholderText, +// endDatePlaceholderText, +// excludeDates, +Props) => { const locale = useLocale(); if (isNilOrError(locale)) return null; @@ -106,8 +106,8 @@ const DateRangePicker = ({ const convertedEndDate = endDate ? moment(endDate).toDate() : null; const convertedMinDate = minDate ? moment(minDate).toDate() : null; const convertedMaxDate = maxDate ? moment(maxDate).toDate() : null; - const convertedExcludeDates = - excludeDates?.map((date) => moment(date).toDate()) || []; + // const convertedExcludeDates = + // excludeDates?.map((date) => moment(date).toDate()) || []; return ( @@ -120,8 +120,8 @@ const DateRangePicker = ({ endDate={convertedEndDate} minDate={convertedMinDate} locale={localeUsed} - excludeDates={convertedExcludeDates} - placeholderText={startDatePlaceholderText} + // excludeDates={convertedExcludeDates} + // placeholderText={startDatePlaceholderText} // This makes sure we adjust date based on the passed locale. dateFormat="P" popperClassName="e2e-start-date-popper" @@ -139,11 +139,11 @@ const DateRangePicker = ({ endDate={convertedEndDate} minDate={convertedStartDate} maxDate={convertedMaxDate} - excludeDates={convertedExcludeDates} + // excludeDates={convertedExcludeDates} locale={localeUsed} // This makes sure we adjust date based on the passed locale. dateFormat="P" - placeholderText={endDatePlaceholderText} + // placeholderText={endDatePlaceholderText} popperClassName="e2e-end-date-popper" autoComplete="off" /> diff --git a/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx b/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx new file mode 100644 index 000000000000..e69de29bb2d1 From 90ad995ca006a1c3e34022100b82fc0c59fa1019 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 4 Oct 2024 12:12:03 +0100 Subject: [PATCH 02/40] Move DateRange type to shared folder --- .../DatePhasePicker/Calendar/utils/generateModifiers.ts | 4 +++- .../DatePhasePicker/Calendar/utils/getStartEndMonth.ts | 2 +- .../DatePhasePicker/Calendar/utils/getUpdatedRange.ts | 4 +++- .../DatePhasePicker/Calendar/utils/rangesValid.ts | 4 +++- .../DatePickers/DatePhasePicker/Calendar/utils/utils.ts | 3 ++- .../DatePhasePicker/DatePhasePicker.stories.tsx | 3 ++- .../admin/DatePickers/DatePhasePicker/Input.tsx | 2 +- .../DatePhasePicker/isSelectedRangeOpenEnded.ts | 2 +- .../DatePickers/DatePhasePicker/patchDisabledRanges.ts | 2 +- .../admin/DatePickers/DatePhasePicker/typings.ts | 5 +---- .../admin/DatePickers/DateRangePicker/index2.tsx | 9 +++++++++ .../app/components/admin/DatePickers/_shared/typings.ts | 4 ++++ .../projects/project/phaseSetup/components/utils.ts | 2 +- 13 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 front/app/components/admin/DatePickers/_shared/typings.ts diff --git a/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/generateModifiers.ts b/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/generateModifiers.ts index 70efb722952a..433a3a1661e5 100644 --- a/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/generateModifiers.ts +++ b/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/generateModifiers.ts @@ -1,6 +1,8 @@ import { differenceInDays, addDays } from 'date-fns'; -import { DateRange, ClosedDateRange } from '../../typings'; +import { DateRange } from 'components/admin/DatePickers/_shared/typings'; + +import { ClosedDateRange } from '../../typings'; import { rangesValid } from './rangesValid'; import { allAreClosedDateRanges } from './utils'; diff --git a/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/getStartEndMonth.ts b/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/getStartEndMonth.ts index f31ea14e6cc8..aad017786f89 100644 --- a/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/getStartEndMonth.ts +++ b/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/getStartEndMonth.ts @@ -1,6 +1,6 @@ import { addYears } from 'date-fns'; -import { DateRange } from '../../typings'; +import { DateRange } from 'components/admin/DatePickers/_shared/typings'; interface GetStartMonthProps { startMonth?: Date; diff --git a/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/getUpdatedRange.ts b/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/getUpdatedRange.ts index ca47c6163b5d..f16b498cac88 100644 --- a/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/getUpdatedRange.ts +++ b/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/getUpdatedRange.ts @@ -1,6 +1,8 @@ import { addDays } from 'date-fns'; -import { ClosedDateRange, DateRange } from '../../typings'; +import { DateRange } from 'components/admin/DatePickers/_shared/typings'; + +import { ClosedDateRange } from '../../typings'; import { rangesValid } from './rangesValid'; import { isClosedDateRange } from './utils'; diff --git a/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/rangesValid.ts b/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/rangesValid.ts index 47bb88b73098..c2704df239ed 100644 --- a/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/rangesValid.ts +++ b/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/rangesValid.ts @@ -1,6 +1,8 @@ import { differenceInDays } from 'date-fns'; -import { DateRange, ClosedDateRange } from '../../typings'; +import { DateRange } from 'components/admin/DatePickers/_shared/typings'; + +import { ClosedDateRange } from '../../typings'; import { allAreClosedDateRanges, isClosedDateRange } from './utils'; diff --git a/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/utils.ts b/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/utils.ts index edaf24afe124..07c33a4b83da 100644 --- a/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/utils.ts +++ b/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/utils/utils.ts @@ -1,4 +1,5 @@ -import { DateRange, ClosedDateRange } from '../../typings'; +import { DateRange } from '../../../_shared/typings'; +import { ClosedDateRange } from '../../typings'; export const isClosedDateRange = (range: DateRange): range is ClosedDateRange => !!range.to; diff --git a/front/app/components/admin/DatePickers/DatePhasePicker/DatePhasePicker.stories.tsx b/front/app/components/admin/DatePickers/DatePhasePicker/DatePhasePicker.stories.tsx index acf9bb318008..aa9718eb683a 100644 --- a/front/app/components/admin/DatePickers/DatePhasePicker/DatePhasePicker.stories.tsx +++ b/front/app/components/admin/DatePickers/DatePhasePicker/DatePhasePicker.stories.tsx @@ -1,7 +1,8 @@ import React, { useState } from 'react'; +import { DateRange } from '../_shared/typings'; + import { patchDisabledRanges } from './patchDisabledRanges'; -import { DateRange } from './typings'; import DatePhasePicker from '.'; diff --git a/front/app/components/admin/DatePickers/DatePhasePicker/Input.tsx b/front/app/components/admin/DatePickers/DatePhasePicker/Input.tsx index c4feb490d180..19585f6b503f 100644 --- a/front/app/components/admin/DatePickers/DatePhasePicker/Input.tsx +++ b/front/app/components/admin/DatePickers/DatePhasePicker/Input.tsx @@ -6,9 +6,9 @@ import { useIntl } from 'utils/cl-intl'; import InputContainer from '../_shared/InputContainer'; import sharedMessages from '../_shared/messages'; +import { DateRange } from '../_shared/typings'; import messages from './messages'; -import { DateRange } from './typings'; interface Props { selectedRange: Partial; diff --git a/front/app/components/admin/DatePickers/DatePhasePicker/isSelectedRangeOpenEnded.ts b/front/app/components/admin/DatePickers/DatePhasePicker/isSelectedRangeOpenEnded.ts index ed741d752209..31107e62eb26 100644 --- a/front/app/components/admin/DatePickers/DatePhasePicker/isSelectedRangeOpenEnded.ts +++ b/front/app/components/admin/DatePickers/DatePhasePicker/isSelectedRangeOpenEnded.ts @@ -1,4 +1,4 @@ -import { DateRange } from './typings'; +import { DateRange } from '../_shared/typings'; export const isSelectedRangeOpenEnded = ( { from, to }: Partial, diff --git a/front/app/components/admin/DatePickers/DatePhasePicker/patchDisabledRanges.ts b/front/app/components/admin/DatePickers/DatePhasePicker/patchDisabledRanges.ts index e8d90be92ba1..f417246c7daa 100644 --- a/front/app/components/admin/DatePickers/DatePhasePicker/patchDisabledRanges.ts +++ b/front/app/components/admin/DatePickers/DatePhasePicker/patchDisabledRanges.ts @@ -1,6 +1,6 @@ import { addDays } from 'date-fns'; -import { DateRange } from './typings'; +import { DateRange } from '../_shared/typings'; /** * Utility to handle the case where the last disabled range is open, diff --git a/front/app/components/admin/DatePickers/DatePhasePicker/typings.ts b/front/app/components/admin/DatePickers/DatePhasePicker/typings.ts index 58d3b0713fbe..debe28d6f1d1 100644 --- a/front/app/components/admin/DatePickers/DatePhasePicker/typings.ts +++ b/front/app/components/admin/DatePickers/DatePhasePicker/typings.ts @@ -1,7 +1,4 @@ -export type DateRange = { - from: Date; - to?: Date; -}; +import { DateRange } from '../_shared/typings'; export type ClosedDateRange = { from: Date; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx b/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx index e69de29bb2d1..64633b6e4783 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx @@ -0,0 +1,9 @@ +import React from 'react'; + +interface Props {} + +const DateRangePicker = (_props: Props) => { + return <>; +}; + +export default DateRangePicker; diff --git a/front/app/components/admin/DatePickers/_shared/typings.ts b/front/app/components/admin/DatePickers/_shared/typings.ts new file mode 100644 index 000000000000..ee5d47b49332 --- /dev/null +++ b/front/app/components/admin/DatePickers/_shared/typings.ts @@ -0,0 +1,4 @@ +export type DateRange = { + from: Date; + to?: Date; +}; diff --git a/front/app/containers/Admin/projects/project/phaseSetup/components/utils.ts b/front/app/containers/Admin/projects/project/phaseSetup/components/utils.ts index d6992d675153..3246f8f44465 100644 --- a/front/app/containers/Admin/projects/project/phaseSetup/components/utils.ts +++ b/front/app/containers/Admin/projects/project/phaseSetup/components/utils.ts @@ -1,6 +1,6 @@ import { addDays } from 'date-fns'; -import { DateRange } from 'components/admin/DatePickers/DatePhasePicker/typings'; +import { DateRange } from 'components/admin/DatePickers/_shared/typings'; export const getDefaultMonth = ( { from }: Partial, From e3797355a18a7b15cd99ae2c074bdfee625f3a38 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 4 Oct 2024 12:14:53 +0100 Subject: [PATCH 03/40] Scaffold some stuff (WIP) --- .../admin/DatePickers/DateRangePicker/Input.tsx | 7 +++++++ .../admin/DatePickers/DateRangePicker/index2.tsx | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 front/app/components/admin/DatePickers/DateRangePicker/Input.tsx diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Input.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Input.tsx new file mode 100644 index 000000000000..f17dbd5188dc --- /dev/null +++ b/front/app/components/admin/DatePickers/DateRangePicker/Input.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const Input = () => { + return <>; +}; + +export default Input; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx b/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx index 64633b6e4783..ae057680b540 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx @@ -1,6 +1,13 @@ import React from 'react'; -interface Props {} +import { DateRange } from '../_shared/typings'; + +interface Props { + selectedRange: Partial; + startMonth?: Date; + endMonth?: Date; + onUpdateRange: (range: DateRange) => void; +} const DateRangePicker = (_props: Props) => { return <>; From c64933f0f213f6a4334ea9cfe9cb2ae4d9b94a1b Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 4 Oct 2024 12:20:16 +0100 Subject: [PATCH 04/40] Create shared ClickOutsideContainer --- .../DatePickers/DatePhasePicker/index.tsx | 17 ++++------- .../DatePickers/DateSinglePicker/index.tsx | 17 ++++------- .../_shared/ClickOutsideContainer.tsx | 28 +++++++++++++++++++ 3 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 front/app/components/admin/DatePickers/_shared/ClickOutsideContainer.tsx diff --git a/front/app/components/admin/DatePickers/DatePhasePicker/index.tsx b/front/app/components/admin/DatePickers/DatePhasePicker/index.tsx index c10852c01dcd..8be5b344d05b 100644 --- a/front/app/components/admin/DatePickers/DatePhasePicker/index.tsx +++ b/front/app/components/admin/DatePickers/DatePhasePicker/index.tsx @@ -1,9 +1,8 @@ import React, { useState } from 'react'; import { Tooltip, Box } from '@citizenlab/cl2-component-library'; -import styled from 'styled-components'; -import ClickOutside from 'utils/containers/clickOutside'; +import ClickOutsideContainer from '../_shared/ClickOutsideContainer'; import Calendar from './Calendar'; import Input from './Input'; @@ -12,13 +11,6 @@ import { Props } from './typings'; const WIDTH = '620px'; -const StyledClickOutside = styled(ClickOutside)` - div.tippy-box { - max-width: ${WIDTH} !important; - padding: 8px; - } -`; - const DatePhasePicker = ({ selectedRange, disabledRanges = [], @@ -35,7 +27,10 @@ const DatePhasePicker = ({ ); return ( - setCalendarOpen(false)}> + setCalendarOpen(false)} + > @@ -59,7 +54,7 @@ const DatePhasePicker = ({ onClick={() => setCalendarOpen((open) => !open)} /> - + ); }; diff --git a/front/app/components/admin/DatePickers/DateSinglePicker/index.tsx b/front/app/components/admin/DatePickers/DateSinglePicker/index.tsx index f69e45100f67..f8ca915bcb3a 100644 --- a/front/app/components/admin/DatePickers/DateSinglePicker/index.tsx +++ b/front/app/components/admin/DatePickers/DateSinglePicker/index.tsx @@ -1,9 +1,8 @@ import React, { useState } from 'react'; import { Tooltip, Box } from '@citizenlab/cl2-component-library'; -import styled from 'styled-components'; -import ClickOutside from 'utils/containers/clickOutside'; +import ClickOutsideContainer from '../_shared/ClickOutsideContainer'; import Calendar from './Calendar'; import Input from './Input'; @@ -11,13 +10,6 @@ import { Props } from './typings'; const WIDTH = '310px'; -const StyledClickOutside = styled(ClickOutside)` - div.tippy-box { - max-width: ${WIDTH} !important; - padding: 8px; - } -`; - const DateSinglePicker = ({ id, disabled, @@ -30,7 +22,10 @@ const DateSinglePicker = ({ const [calendarOpen, setCalendarOpen] = useState(false); return ( - setCalendarOpen(false)}> + setCalendarOpen(false)} + > @@ -57,7 +52,7 @@ const DateSinglePicker = ({ onClick={() => setCalendarOpen(true)} /> - + ); }; diff --git a/front/app/components/admin/DatePickers/_shared/ClickOutsideContainer.tsx b/front/app/components/admin/DatePickers/_shared/ClickOutsideContainer.tsx new file mode 100644 index 000000000000..b9b19d19ff51 --- /dev/null +++ b/front/app/components/admin/DatePickers/_shared/ClickOutsideContainer.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import styled from 'styled-components'; + +import ClickOutside from 'utils/containers/clickOutside'; + +const StyledClickOutside = styled(ClickOutside)<{ width: string }>` + div.tippy-box { + max-width: ${({ width }) => width} !important; + padding: 8px; + } +`; + +interface Props { + width: string; + onClickOutside: () => void; + children: React.ReactNode; +} + +const ClickOutsideContainer = ({ width, onClickOutside, children }: Props) => { + return ( + + {children} + + ); +}; + +export default ClickOutsideContainer; From a982bd305327d794bb32e7c7479e7828cf395ec4 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 4 Oct 2024 12:26:17 +0100 Subject: [PATCH 05/40] Create input and story --- .../DateRangePicker.stories.tsx | 31 +++++++++++++ .../DatePickers/DateRangePicker/Input.tsx | 32 +++++++++++++- .../DatePickers/DateRangePicker/index2.tsx | 44 +++++++++++++++++-- 3 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 front/app/components/admin/DatePickers/DateRangePicker/DateRangePicker.stories.tsx diff --git a/front/app/components/admin/DatePickers/DateRangePicker/DateRangePicker.stories.tsx b/front/app/components/admin/DatePickers/DateRangePicker/DateRangePicker.stories.tsx new file mode 100644 index 000000000000..4dd5e9cd0437 --- /dev/null +++ b/front/app/components/admin/DatePickers/DateRangePicker/DateRangePicker.stories.tsx @@ -0,0 +1,31 @@ +import React, { useState } from 'react'; + +import { DateRange } from '../_shared/typings'; + +import DateRangePicker from './index2'; + +import type { Meta } from '@storybook/react'; + +const meta = { + title: 'DateRangePicker', + component: DateRangePicker, +} satisfies Meta; + +export default meta; + +const WrapperStandard = () => { + const [selectedRange, setSelectedRange] = useState>({}); + + return ( + + ); +}; + +export const Standard = { + render: () => { + return ; + }, +}; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Input.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Input.tsx index f17dbd5188dc..dcfb83dc4e39 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Input.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Input.tsx @@ -1,7 +1,35 @@ import React from 'react'; -const Input = () => { - return <>; +import { Box, Icon } from '@citizenlab/cl2-component-library'; + +import { useIntl } from 'utils/cl-intl'; + +import InputContainer from '../_shared/InputContainer'; +import sharedMessages from '../_shared/messages'; +import { DateRange } from '../_shared/typings'; + +interface Props { + selectedRange: Partial; + onClick: () => void; +} + +const Input = ({ selectedRange, onClick }: Props) => { + const { formatMessage } = useIntl(); + const selectDate = formatMessage(sharedMessages.selectDate); + + return ( + + + {selectedRange.from + ? selectedRange.from.toLocaleDateString() + : selectDate} + + + + {selectedRange.to ? selectedRange.to.toLocaleDateString() : selectDate} + + + ); }; export default Input; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx b/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx index ae057680b540..1002ad259ccf 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx @@ -1,7 +1,14 @@ -import React from 'react'; +import React, { useState } from 'react'; +import { Tooltip, Box } from '@citizenlab/cl2-component-library'; + +import ClickOutsideContainer from '../_shared/ClickOutsideContainer'; import { DateRange } from '../_shared/typings'; +import Input from './Input'; + +const WIDTH = '620px'; + interface Props { selectedRange: Partial; startMonth?: Date; @@ -9,8 +16,39 @@ interface Props { onUpdateRange: (range: DateRange) => void; } -const DateRangePicker = (_props: Props) => { - return <>; +const DateRangePicker = ({ selectedRange }: Props) => { + const [calendarOpen, setCalendarOpen] = useState(false); + + return ( + setCalendarOpen(false)} + > + + TEST + {/* */} + + } + placement="bottom" + visible={calendarOpen} + width="1200px" + > + setCalendarOpen((open) => !open)} + /> + + + ); }; export default DateRangePicker; From f0542714a629923c9a31e3812417c52fc98e4dab Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 4 Oct 2024 12:37:22 +0100 Subject: [PATCH 06/40] Add Calendar (WIP) --- .../DateRangePicker/Calendar/index.tsx | 37 +++++++++++++++++++ .../DatePickers/DateRangePicker/index2.tsx | 25 ++++++------- .../DatePickers/DateRangePicker/typings.ts | 8 ++++ 3 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx create mode 100644 front/app/components/admin/DatePickers/DateRangePicker/typings.ts diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx new file mode 100644 index 000000000000..e196f6b3dec9 --- /dev/null +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx @@ -0,0 +1,37 @@ +import React from 'react'; + +import { colors } from '@citizenlab/cl2-component-library'; +import { DayPicker } from 'react-day-picker'; +import 'react-day-picker/style.css'; +import styled from 'styled-components'; + +import useLocale from 'hooks/useLocale'; + +import { getLocale } from '../../_shared/locales'; +import { Props } from '../typings'; + +const DayPickerStyles = styled.div` + .rdp-root { + --rdp-accent-color: ${colors.teal700}; + --rdp-accent-background-color: ${colors.teal100}; + } +`; + +const Calendar = ({ selectedRange, onUpdateRange }: Props) => { + const locale = useLocale(); + + return ( + + + + ); +}; + +export default Calendar; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx b/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx index 1002ad259ccf..d39546d21807 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx @@ -3,20 +3,19 @@ import React, { useState } from 'react'; import { Tooltip, Box } from '@citizenlab/cl2-component-library'; import ClickOutsideContainer from '../_shared/ClickOutsideContainer'; -import { DateRange } from '../_shared/typings'; +import Calendar from './Calendar'; import Input from './Input'; +import { Props } from './typings'; const WIDTH = '620px'; -interface Props { - selectedRange: Partial; - startMonth?: Date; - endMonth?: Date; - onUpdateRange: (range: DateRange) => void; -} - -const DateRangePicker = ({ selectedRange }: Props) => { +const DateRangePicker = ({ + selectedRange, + startMonth, + endMonth, + onUpdateRange, +}: Props) => { const [calendarOpen, setCalendarOpen] = useState(false); return ( @@ -27,15 +26,13 @@ const DateRangePicker = ({ selectedRange }: Props) => { - TEST - {/* */} + /> } placement="bottom" diff --git a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts new file mode 100644 index 000000000000..ead3a9656e06 --- /dev/null +++ b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts @@ -0,0 +1,8 @@ +import { DateRange } from '../_shared/typings'; + +export interface Props { + selectedRange: Partial; + startMonth?: Date; + endMonth?: Date; + onUpdateRange: (range: DateRange) => void; +} From d490a26d2453cfd59f00773be8e2c70e657f376d Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 4 Oct 2024 12:39:54 +0100 Subject: [PATCH 07/40] Fix font size issue in Calendar --- .../admin/DatePickers/DateRangePicker/Calendar/index.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx index e196f6b3dec9..3064942e88b0 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx @@ -15,6 +15,11 @@ const DayPickerStyles = styled.div` --rdp-accent-color: ${colors.teal700}; --rdp-accent-background-color: ${colors.teal100}; } + + .rdp-selected > button.rdp-day_button { + font-size: 14px; + font-weight: normal; + } `; const Calendar = ({ selectedRange, onUpdateRange }: Props) => { From c598f47db353b8a117b595d3b98e6fad47e66b4b Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 4 Oct 2024 12:42:09 +0100 Subject: [PATCH 08/40] Fix bug when trying to deselect date range --- .../admin/DatePickers/DateRangePicker/Calendar/index.tsx | 4 +++- .../components/admin/DatePickers/DateRangePicker/typings.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx index 3064942e88b0..a0c0853034f7 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx @@ -33,7 +33,9 @@ const Calendar = ({ selectedRange, onUpdateRange }: Props) => { captionLayout="dropdown" locale={getLocale(locale)} selected={{ from: selectedRange.from, to: selectedRange.to }} - onSelect={onUpdateRange} + onSelect={(newRange) => { + onUpdateRange(newRange ?? {}); + }} /> ); diff --git a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts index ead3a9656e06..159c6f03af43 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts +++ b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts @@ -4,5 +4,5 @@ export interface Props { selectedRange: Partial; startMonth?: Date; endMonth?: Date; - onUpdateRange: (range: DateRange) => void; + onUpdateRange: (range: Partial) => void; } From d826eef260c38b546d52b4827379fb8d941f0e04 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 4 Oct 2024 12:46:10 +0100 Subject: [PATCH 09/40] Add start and end month --- .../DateRangePicker/Calendar/index.tsx | 20 ++++++++++++++++++- .../DateSinglePicker/Calendar/index.tsx | 3 +-- .../utils => _shared}/getStartEndMonth.ts | 0 3 files changed, 20 insertions(+), 3 deletions(-) rename front/app/components/admin/DatePickers/{DateSinglePicker/Calendar/utils => _shared}/getStartEndMonth.ts (100%) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx index a0c0853034f7..17db85bece7f 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx @@ -7,6 +7,7 @@ import styled from 'styled-components'; import useLocale from 'hooks/useLocale'; +import { getEndMonth, getStartMonth } from '../../_shared/getStartEndMonth'; import { getLocale } from '../../_shared/locales'; import { Props } from '../typings'; @@ -22,9 +23,24 @@ const DayPickerStyles = styled.div` } `; -const Calendar = ({ selectedRange, onUpdateRange }: Props) => { +const Calendar = ({ + selectedRange, + startMonth: _startMonth, + endMonth: _endMonth, + onUpdateRange, +}: Props) => { const locale = useLocale(); + const startMonth = getStartMonth({ + startMonth: _startMonth, + selectedDate: selectedRange.from, + }); + + const endMonth = getEndMonth({ + endMonth: _endMonth, + selectedDate: selectedRange.to, + }); + return ( { numberOfMonths={2} captionLayout="dropdown" locale={getLocale(locale)} + startMonth={startMonth} + endMonth={endMonth} selected={{ from: selectedRange.from, to: selectedRange.to }} onSelect={(newRange) => { onUpdateRange(newRange ?? {}); diff --git a/front/app/components/admin/DatePickers/DateSinglePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateSinglePicker/Calendar/index.tsx index 7fd5826be341..d4268587c785 100644 --- a/front/app/components/admin/DatePickers/DateSinglePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateSinglePicker/Calendar/index.tsx @@ -7,11 +7,10 @@ import styled from 'styled-components'; import useLocale from 'hooks/useLocale'; +import { getEndMonth, getStartMonth } from '../../_shared/getStartEndMonth'; import { getLocale } from '../../_shared/locales'; import { CalendarProps } from '../typings'; -import { getEndMonth, getStartMonth } from './utils/getStartEndMonth'; - const DayPickerStyles = styled.div` .rdp-root { --rdp-accent-color: ${colors.teal700}; diff --git a/front/app/components/admin/DatePickers/DateSinglePicker/Calendar/utils/getStartEndMonth.ts b/front/app/components/admin/DatePickers/_shared/getStartEndMonth.ts similarity index 100% rename from front/app/components/admin/DatePickers/DateSinglePicker/Calendar/utils/getStartEndMonth.ts rename to front/app/components/admin/DatePickers/_shared/getStartEndMonth.ts From 1fc8c5cb1e41d352766e013c370d314f1af58cbc Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 4 Oct 2024 12:47:50 +0100 Subject: [PATCH 10/40] Add default month --- .../admin/DatePickers/DateRangePicker/Calendar/index.tsx | 2 ++ .../components/admin/DatePickers/DateRangePicker/index2.tsx | 3 ++- .../components/admin/DatePickers/DateRangePicker/typings.ts | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx index 17db85bece7f..76d0ebf427ce 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx @@ -27,6 +27,7 @@ const Calendar = ({ selectedRange, startMonth: _startMonth, endMonth: _endMonth, + defaultMonth, onUpdateRange, }: Props) => { const locale = useLocale(); @@ -50,6 +51,7 @@ const Calendar = ({ locale={getLocale(locale)} startMonth={startMonth} endMonth={endMonth} + defaultMonth={defaultMonth} selected={{ from: selectedRange.from, to: selectedRange.to }} onSelect={(newRange) => { onUpdateRange(newRange ?? {}); diff --git a/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx b/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx index d39546d21807..8cc58adf7244 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx @@ -14,6 +14,7 @@ const DateRangePicker = ({ selectedRange, startMonth, endMonth, + defaultMonth, onUpdateRange, }: Props) => { const [calendarOpen, setCalendarOpen] = useState(false); @@ -30,7 +31,7 @@ const DateRangePicker = ({ selectedRange={selectedRange} startMonth={startMonth} endMonth={endMonth} - // defaultMonth={defaultMonth} + defaultMonth={defaultMonth} onUpdateRange={onUpdateRange} /> diff --git a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts index 159c6f03af43..e2adcdf9fb63 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts +++ b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts @@ -4,5 +4,6 @@ export interface Props { selectedRange: Partial; startMonth?: Date; endMonth?: Date; + defaultMonth?: Date; onUpdateRange: (range: Partial) => void; } From 9a9d445528298e3d93f2fe0b938f92c833e51447 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 4 Oct 2024 13:02:02 +0100 Subject: [PATCH 11/40] Replace old DateRangePicker in most places --- .../DateRangePicker.stories.tsx | 2 +- .../DatePickers/DateRangePicker/index.tsx | 182 ++++-------------- .../DatePickers/DateRangePicker/index2.tsx | 52 ----- .../dashboard/components/TimeControl.tsx | 9 +- .../participation/ParticipationDateRange.tsx | 13 +- .../project/traffic/TrafficDatesRange.tsx | 13 +- .../_shared/ChartWidgetSettings.tsx | 13 +- .../CreateReportModal/index.tsx | 15 +- .../CreateReportModal/utils.ts | 20 +- 9 files changed, 92 insertions(+), 227 deletions(-) delete mode 100644 front/app/components/admin/DatePickers/DateRangePicker/index2.tsx diff --git a/front/app/components/admin/DatePickers/DateRangePicker/DateRangePicker.stories.tsx b/front/app/components/admin/DatePickers/DateRangePicker/DateRangePicker.stories.tsx index 4dd5e9cd0437..8d46a345fe31 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/DateRangePicker.stories.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/DateRangePicker.stories.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { DateRange } from '../_shared/typings'; -import DateRangePicker from './index2'; +import DateRangePicker from '.'; import type { Meta } from '@storybook/react'; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/index.tsx index e5432400ee39..8cc58adf7244 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/index.tsx @@ -1,153 +1,51 @@ -import React from 'react'; -import 'react-datepicker/dist/react-datepicker.css'; +import React, { useState } from 'react'; -import { - Box, - Icon, - colors, - fontSizes, -} from '@citizenlab/cl2-component-library'; -import moment, { Moment } from 'moment'; -import DatePicker from 'react-datepicker'; -import styled from 'styled-components'; +import { Tooltip, Box } from '@citizenlab/cl2-component-library'; -import useLocale from 'hooks/useLocale'; +import ClickOutsideContainer from '../_shared/ClickOutsideContainer'; -import { isNilOrError } from 'utils/helperUtils'; +import Calendar from './Calendar'; +import Input from './Input'; +import { Props } from './typings'; -const StylingWrapper = styled.div` - display: flex; - align-items: center; - - .react-datepicker-wrapper { - border-radius: ${(props) => props.theme.borderRadius}; - border: solid 1px ${colors.borderDark}; - background: white; - padding: 10px 8px; - - &:hover { - border-color: ${colors.black}; - } - - input[type='text'] { - color: ${colors.textPrimary}; - font-size: ${fontSizes.base}px; - line-height: normal; - font-weight: 400; - background: transparent; - width: 100%; - } - } -`; - -export type Dates = { - startDate: Moment | null; - endDate: Moment | null; -}; - -interface Props { - startDate: Moment | null; // - endDate: Moment | null; // - onDatesChange: ({ startDate, endDate }: Dates) => void; // - minDate?: Moment; // - maxDate?: Moment; - // startDatePlaceholderText?: string; - // endDatePlaceholderText?: string; - // excludeDates?: Moment[]; -} +const WIDTH = '620px'; const DateRangePicker = ({ - startDate, - endDate, - onDatesChange, - minDate, - maxDate, -}: // startDatePlaceholderText, -// endDatePlaceholderText, -// excludeDates, -Props) => { - const locale = useLocale(); - - if (isNilOrError(locale)) return null; - const localeUsed = locale === 'en' ? 'en-GB' : locale; - - const handleOnChangeStartDate = (newStartDate: Date | null) => { - // with this check, we don't allow removing a date - // (forcing users to pick a date if changes need to persist) - if (newStartDate) { - onDatesChange({ - startDate: moment(newStartDate), - endDate: - endDate && endDate < moment(newStartDate) - ? // if the new start date is after the currently selected end date, - // we set the end date to the new start date - moment(newStartDate) - : endDate, - }); - } - }; - - const handleOnChangeEndDate = (newEndDate: Date | null) => { - // with this check, we don't allow removing a date - // (forcing users to pick a date if changes need to persist) - if (newEndDate) { - onDatesChange({ - // we don’t need a check here as we do in handleOnChangeStartDate - // because of the minDate prop in the end date DatePicker - startDate, - endDate: moment(newEndDate), - }); - } - }; - - // Passing null to moment() crashes this component. Calling toDate on this returns "Invalid date", - // which crashes DatePicker. - const convertedStartDate = startDate ? moment(startDate).toDate() : null; - const convertedEndDate = endDate ? moment(endDate).toDate() : null; - const convertedMinDate = minDate ? moment(minDate).toDate() : null; - const convertedMaxDate = maxDate ? moment(maxDate).toDate() : null; - // const convertedExcludeDates = - // excludeDates?.map((date) => moment(date).toDate()) || []; + selectedRange, + startMonth, + endMonth, + defaultMonth, + onUpdateRange, +}: Props) => { + const [calendarOpen, setCalendarOpen] = useState(false); return ( - - - - - - - + setCalendarOpen(false)} + > + + + + } + placement="bottom" + visible={calendarOpen} + width="1200px" + > + setCalendarOpen((open) => !open)} + /> + + ); }; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx b/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx deleted file mode 100644 index 8cc58adf7244..000000000000 --- a/front/app/components/admin/DatePickers/DateRangePicker/index2.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React, { useState } from 'react'; - -import { Tooltip, Box } from '@citizenlab/cl2-component-library'; - -import ClickOutsideContainer from '../_shared/ClickOutsideContainer'; - -import Calendar from './Calendar'; -import Input from './Input'; -import { Props } from './typings'; - -const WIDTH = '620px'; - -const DateRangePicker = ({ - selectedRange, - startMonth, - endMonth, - defaultMonth, - onUpdateRange, -}: Props) => { - const [calendarOpen, setCalendarOpen] = useState(false); - - return ( - setCalendarOpen(false)} - > - - - - } - placement="bottom" - visible={calendarOpen} - width="1200px" - > - setCalendarOpen((open) => !open)} - /> - - - ); -}; - -export default DateRangePicker; diff --git a/front/app/containers/Admin/dashboard/components/TimeControl.tsx b/front/app/containers/Admin/dashboard/components/TimeControl.tsx index 267a5396aa71..44883140823e 100644 --- a/front/app/containers/Admin/dashboard/components/TimeControl.tsx +++ b/front/app/containers/Admin/dashboard/components/TimeControl.tsx @@ -192,10 +192,13 @@ const TimeControl = ({ /> ); diff --git a/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx b/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx index 539d62392b0c..177befbe2053 100644 --- a/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx +++ b/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx @@ -43,9 +43,16 @@ const ParticipationDatesRange = ({ {formatMessage(messages.selectPeriod)} { + handleChangeTimeRange({ + startDate: from ? moment(from) : null, + endDate: to ? moment(to) : null, + }); + }} /> diff --git a/front/app/containers/Admin/projects/project/traffic/TrafficDatesRange.tsx b/front/app/containers/Admin/projects/project/traffic/TrafficDatesRange.tsx index bba79bf6f533..d54054b9e8d2 100644 --- a/front/app/containers/Admin/projects/project/traffic/TrafficDatesRange.tsx +++ b/front/app/containers/Admin/projects/project/traffic/TrafficDatesRange.tsx @@ -44,9 +44,16 @@ const TrafficDatesRange = ({ {formatMessage(messages.selectPeriod)} { + handleChangeTimeRange({ + startDate: from ? moment(from) : null, + endDate: to ? moment(to) : null, + }); + }} /> diff --git a/front/app/containers/Admin/reporting/components/ReportBuilder/Widgets/ChartWidgets/_shared/ChartWidgetSettings.tsx b/front/app/containers/Admin/reporting/components/ReportBuilder/Widgets/ChartWidgets/_shared/ChartWidgetSettings.tsx index 553caa325f6e..a2421b028130 100644 --- a/front/app/containers/Admin/reporting/components/ReportBuilder/Widgets/ChartWidgets/_shared/ChartWidgetSettings.tsx +++ b/front/app/containers/Admin/reporting/components/ReportBuilder/Widgets/ChartWidgets/_shared/ChartWidgetSettings.tsx @@ -127,9 +127,16 @@ export const DateRangeInput = ({ {label ?? formatMessage(messages.analyticsChartDateRange)} { + handleChangeTimeRange({ + startDate: from ? moment(from) : null, + endDate: to ? moment(to) : null, + }); + }} /> ); diff --git a/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/index.tsx b/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/index.tsx index 270dcdc144ec..255be4ef378a 100644 --- a/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/index.tsx +++ b/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/index.tsx @@ -14,9 +14,8 @@ import useAddReport from 'api/reports/useAddReport'; import reportTitleIsTaken from 'containers/Admin/reporting/utils/reportTitleIsTaken'; -import DateRangePicker, { - Dates, -} from 'components/admin/DatePickers/DateRangePicker'; +import { DateRange } from 'components/admin/DatePickers/_shared/typings'; +import DateRangePicker from 'components/admin/DatePickers/DateRangePicker'; import Button from 'components/UI/Button'; import Error from 'components/UI/Error'; import Modal from 'components/UI/Modal'; @@ -44,7 +43,7 @@ const CreateReportModal = ({ open, onClose }: Props) => { const [selectedProjectId, setSelectedProjectId] = useState< string | undefined >(); - const [dates, setDates] = useState({ startDate: null, endDate: null }); + const [dates, setDates] = useState>({}); const [errorMessage, setErrorMessage] = useState(); const { formatMessage } = useIntl(); @@ -53,7 +52,7 @@ const CreateReportModal = ({ open, onClose }: Props) => { const blockSubmit = reportTitleTooShort || (template === 'project' ? selectedProjectId === undefined : false) || - (template === 'platform' ? !dates.startDate || !dates.endDate : false); + (template === 'platform' ? !dates.from || !dates.to : false); const handleProjectFilter = (option: IOption) => { setSelectedProjectId(option.value === '' ? undefined : option.value); @@ -125,11 +124,7 @@ const CreateReportModal = ({ open, onClose }: Props) => { )} {template === 'platform' && ( - + )} {errorMessage && ( diff --git a/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/utils.ts b/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/utils.ts index 54ef783bf7ef..5e1ea10db64d 100644 --- a/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/utils.ts +++ b/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/utils.ts @@ -1,6 +1,7 @@ +import { format } from 'date-fns'; import { RouteType } from 'routes'; -import { Dates } from 'components/admin/DatePickers/DateRangePicker'; +import { DateRange } from 'components/admin/DatePickers/_shared/typings'; import { Template } from './typings'; @@ -8,14 +9,14 @@ interface Params { reportId: string; selectedProjectId: string | undefined; template: Template; - dates: Dates; + dates: Partial; } export const getRedirectUrl = ({ reportId, selectedProjectId, template, - dates: { startDate, endDate }, + dates: { from, to }, }: Params) => { const reportBuilderRoute = '/admin/reporting/report-builder'; const reportRoute = `${reportBuilderRoute}/${reportId}/editor`; @@ -26,13 +27,12 @@ export const getRedirectUrl = ({ params = `?templateProjectId=${selectedProjectId}`; } - if (template === 'platform' && startDate && endDate) { - const startDateParam = `startDatePlatformReport=${startDate.format( - 'YYYY-MM-DD' - )}`; - const endDateParam = `endDatePlatformReport=${endDate.format( - 'YYYY-MM-DD' - )}`; + if (template === 'platform' && from && to) { + const startDateFormat = format(from, 'yyyy-MM-dd'); + const endDateFormat = format(to, 'yyyy-MM-dd'); + + const startDateParam = `startDatePlatformReport=${startDateFormat}`; + const endDateParam = `endDatePlatformReport=${endDateFormat}`; params = `?${startDateParam}&${endDateParam}`; } From 09ef6e1506291c6bf32b8ab60ee4724a89330323 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 4 Oct 2024 13:03:32 +0100 Subject: [PATCH 12/40] _ From 356ae41ef44f535a75ae2cd486a9b349bb24dbe7 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Sun, 20 Oct 2024 12:30:57 +0200 Subject: [PATCH 13/40] Make behavior date range picker more similar to phase one --- .../DateRangePicker/Calendar/index.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx index 76d0ebf427ce..93dec272ca67 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { colors } from '@citizenlab/cl2-component-library'; -import { DayPicker } from 'react-day-picker'; +import { DayPicker, PropsBase } from 'react-day-picker'; import 'react-day-picker/style.css'; import styled from 'styled-components'; @@ -42,6 +42,18 @@ const Calendar = ({ selectedDate: selectedRange.to, }); + const handleDayClick: PropsBase['onDayClick'] = (day: Date) => { + if (!selectedRange.from || selectedRange.to) { + onUpdateRange({ from: day, to: undefined }); + return; + } + + onUpdateRange({ + from: selectedRange.from, + to: day, + }); + }; + return ( { - onUpdateRange(newRange ?? {}); - }} + onDayClick={handleDayClick} + // This NOOP is necessary because otherwise the + // DayPicker will rely on its internal state to manage the selected + // range rather than being controlled by our state. + onSelect={NOOP} /> ); }; +const NOOP = () => {}; + export default Calendar; From 18887e05b880a0597a1617dbf7fa61288cf07cda Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Sun, 20 Oct 2024 12:41:11 +0200 Subject: [PATCH 14/40] Add disabled prop + story --- .../DateRangePicker/Calendar/index.tsx | 2 ++ .../DateRangePicker.stories.tsx | 22 +++++++++++++++++++ .../DatePickers/DateRangePicker/index.tsx | 2 ++ .../DatePickers/DateRangePicker/typings.ts | 3 +++ 4 files changed, 29 insertions(+) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx index 93dec272ca67..9b6c65046e19 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx @@ -28,6 +28,7 @@ const Calendar = ({ startMonth: _startMonth, endMonth: _endMonth, defaultMonth, + disabled, onUpdateRange, }: Props) => { const locale = useLocale(); @@ -65,6 +66,7 @@ const Calendar = ({ endMonth={endMonth} defaultMonth={defaultMonth} selected={{ from: selectedRange.from, to: selectedRange.to }} + disabled={disabled} onDayClick={handleDayClick} // This NOOP is necessary because otherwise the // DayPicker will rely on its internal state to manage the selected diff --git a/front/app/components/admin/DatePickers/DateRangePicker/DateRangePicker.stories.tsx b/front/app/components/admin/DatePickers/DateRangePicker/DateRangePicker.stories.tsx index 8d46a345fe31..c8fd959df0f9 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/DateRangePicker.stories.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/DateRangePicker.stories.tsx @@ -1,5 +1,7 @@ import React, { useState } from 'react'; +import { getMonth, addDays } from 'date-fns'; + import { DateRange } from '../_shared/typings'; import DateRangePicker from '.'; @@ -29,3 +31,23 @@ export const Standard = { return ; }, }; + +const WrapperDisabled = () => { + const [selectedRange, setSelectedRange] = useState>({}); + const month = new Date(getMonth(new Date())); + + return ( + + ); +}; + +export const Disabled = { + render: () => { + return ; + }, +}; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/index.tsx index 8cc58adf7244..0dd4c797551b 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/index.tsx @@ -15,6 +15,7 @@ const DateRangePicker = ({ startMonth, endMonth, defaultMonth, + disabled, onUpdateRange, }: Props) => { const [calendarOpen, setCalendarOpen] = useState(false); @@ -32,6 +33,7 @@ const DateRangePicker = ({ startMonth={startMonth} endMonth={endMonth} defaultMonth={defaultMonth} + disabled={disabled} onUpdateRange={onUpdateRange} /> diff --git a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts index e2adcdf9fb63..4b885f76e689 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts +++ b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts @@ -1,3 +1,5 @@ +import { PropsBase } from 'react-day-picker'; + import { DateRange } from '../_shared/typings'; export interface Props { @@ -5,5 +7,6 @@ export interface Props { startMonth?: Date; endMonth?: Date; defaultMonth?: Date; + disabled?: PropsBase['disabled']; onUpdateRange: (range: Partial) => void; } From 354418666ea51edd8871d677742633d56ad72c7d Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Sun, 20 Oct 2024 12:44:36 +0200 Subject: [PATCH 15/40] Replace TimeControl --- .../Admin/dashboard/components/TimeControl.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/front/app/containers/Admin/dashboard/components/TimeControl.tsx b/front/app/containers/Admin/dashboard/components/TimeControl.tsx index 44883140823e..fc4722823ea1 100644 --- a/front/app/containers/Admin/dashboard/components/TimeControl.tsx +++ b/front/app/containers/Admin/dashboard/components/TimeControl.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { Icon, Dropdown, colors } from '@citizenlab/cl2-component-library'; +import { getMonth } from 'date-fns'; import moment, { Moment } from 'moment'; import styled from 'styled-components'; @@ -196,9 +197,21 @@ const TimeControl = ({ from: startAtMoment ? startAtMoment.toDate() : undefined, to: endAtMoment ? endAtMoment.toDate() : undefined, }} - onDatesChange={handleDatesChange} startMonth={minDate?.toDate()} - // disabled={[] TODO} + disabled={ + minDate + ? { + from: new Date(getMonth(minDate.toDate())), + to: minDate.toDate(), + } + : undefined + } + onUpdateRange={({ from, to }) => { + handleDatesChange({ + startDate: from ? moment(from) : null, + endDate: to ? moment(to) : null, + }); + }} /> ); From f5c8acd4c45c99c4f5729b2836fe33007fa74ca6 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 09:20:19 -0300 Subject: [PATCH 16/40] Put back start month --- .../admin/DatePickers/DateSinglePicker/Calendar/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/app/components/admin/DatePickers/DateSinglePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateSinglePicker/Calendar/index.tsx index 22fd08789e8d..97d4b2900dd6 100644 --- a/front/app/components/admin/DatePickers/DateSinglePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateSinglePicker/Calendar/index.tsx @@ -33,7 +33,7 @@ const Calendar = ({ onChange, }: CalendarProps) => { const locale = useLocale(); - const startMonth = new Date(1900, 0); + const startMonth = _startMonth ?? new Date(1900, 0); const endMonth = getEndMonth({ endMonth: _endMonth, selectedDate }); const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; From d664a43289cbecf888f5ee83e0d3c3539ba3187c Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 09:23:43 -0300 Subject: [PATCH 17/40] Fix old usage of getStartMonth --- .../admin/DatePickers/DateRangePicker/Calendar/index.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx index 9b6c65046e19..e6de008ba3f3 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx @@ -7,7 +7,7 @@ import styled from 'styled-components'; import useLocale from 'hooks/useLocale'; -import { getEndMonth, getStartMonth } from '../../_shared/getStartEndMonth'; +import { getEndMonth } from '../../_shared/getStartEndMonth'; import { getLocale } from '../../_shared/locales'; import { Props } from '../typings'; @@ -33,11 +33,7 @@ const Calendar = ({ }: Props) => { const locale = useLocale(); - const startMonth = getStartMonth({ - startMonth: _startMonth, - selectedDate: selectedRange.from, - }); - + const startMonth = _startMonth ?? new Date(1900, 0); const endMonth = getEndMonth({ endMonth: _endMonth, selectedDate: selectedRange.to, From 335c04e8a198154cde4bf809fd4805d533a77a9b Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 09:36:20 -0300 Subject: [PATCH 18/40] Fix bug in Calendar --- .../DatePickers/DateRangePicker/Calendar/index.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx index e6de008ba3f3..1da0410c281f 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx @@ -40,11 +40,21 @@ const Calendar = ({ }); const handleDayClick: PropsBase['onDayClick'] = (day: Date) => { + // If we have no date range yet, + // OR we already have a fully set date range, + // Set 'from' to the clicked day and remove 'to'. if (!selectedRange.from || selectedRange.to) { onUpdateRange({ from: day, to: undefined }); return; } + // If we clicked a day before the current 'from' date, + // we set the 'from' date to the clicked day and remove 'to'. + if (selectedRange.from > day) { + onUpdateRange({ from: day, to: undefined }); + return; + } + onUpdateRange({ from: selectedRange.from, to: day, From a3d643931c2e70d8e879882344b635cd9e8fa058 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 10:33:11 -0300 Subject: [PATCH 19/40] Allow selecting start and end of range individually --- .../DateRangePicker/Calendar/index.tsx | 27 ++++------ .../Calendar/utils/getUpdatedRange.ts | 48 +++++++++++++++++ .../DatePickers/DateRangePicker/Input.tsx | 35 ------------- .../DateRangePicker/Input/DateButton.tsx | 44 ++++++++++++++++ .../DateRangePicker/Input/InputContainer.tsx | 52 +++++++++++++++++++ .../DateRangePicker/Input/index.tsx | 49 +++++++++++++++++ .../DatePickers/DateRangePicker/index.tsx | 19 +++++-- .../DatePickers/DateRangePicker/typings.ts | 3 ++ 8 files changed, 220 insertions(+), 57 deletions(-) create mode 100644 front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.ts delete mode 100644 front/app/components/admin/DatePickers/DateRangePicker/Input.tsx create mode 100644 front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx create mode 100644 front/app/components/admin/DatePickers/DateRangePicker/Input/InputContainer.tsx create mode 100644 front/app/components/admin/DatePickers/DateRangePicker/Input/index.tsx diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx index 1da0410c281f..e09d81b32daa 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx @@ -11,6 +11,8 @@ import { getEndMonth } from '../../_shared/getStartEndMonth'; import { getLocale } from '../../_shared/locales'; import { Props } from '../typings'; +import { getUpdatedRange } from './utils/getUpdatedRange'; + const DayPickerStyles = styled.div` .rdp-root { --rdp-accent-color: ${colors.teal700}; @@ -29,6 +31,7 @@ const Calendar = ({ endMonth: _endMonth, defaultMonth, disabled, + selectionMode, onUpdateRange, }: Props) => { const locale = useLocale(); @@ -40,25 +43,15 @@ const Calendar = ({ }); const handleDayClick: PropsBase['onDayClick'] = (day: Date) => { - // If we have no date range yet, - // OR we already have a fully set date range, - // Set 'from' to the clicked day and remove 'to'. - if (!selectedRange.from || selectedRange.to) { - onUpdateRange({ from: day, to: undefined }); - return; - } - - // If we clicked a day before the current 'from' date, - // we set the 'from' date to the clicked day and remove 'to'. - if (selectedRange.from > day) { - onUpdateRange({ from: day, to: undefined }); - return; - } + if (!selectionMode) return; // Should not be possible in practice - onUpdateRange({ - from: selectedRange.from, - to: day, + const nextRange = getUpdatedRange({ + selectedRange, + selectionMode, + clickedDate: day, }); + + onUpdateRange(nextRange); }; return ( diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.ts b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.ts new file mode 100644 index 000000000000..d24ddf44b226 --- /dev/null +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.ts @@ -0,0 +1,48 @@ +import { DateRange } from 'components/admin/DatePickers/_shared/typings'; + +import { SelectionMode } from '../../typings'; + +interface GetUpdatedRangeParams { + selectedRange: Partial; + selectionMode: SelectionMode; + clickedDate: Date; +} + +export const getUpdatedRange = ({ + selectedRange: { from, to }, + selectionMode, + clickedDate, +}: GetUpdatedRangeParams): Partial => { + // If a 'from' date has been chosen, and the user + // clicks any date before that, we ignore the selection + // mode and just reset the date to a date before. + if (from && !to && clickedDate < from) { + return { + from: clickedDate, + to: undefined, + }; + } + + // The same happens if only a 'to' date has been selected + // and the user clicks any date after that. + if (to && !from && clickedDate > to) { + return { + from: undefined, + to: clickedDate, + }; + } + + // In any other case, we just set the date according to the + // selection mode. + if (selectionMode === 'from') { + return { + from: clickedDate, + to, + }; + } else { + return { + from, + to: clickedDate, + }; + } +}; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Input.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Input.tsx deleted file mode 100644 index dcfb83dc4e39..000000000000 --- a/front/app/components/admin/DatePickers/DateRangePicker/Input.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; - -import { Box, Icon } from '@citizenlab/cl2-component-library'; - -import { useIntl } from 'utils/cl-intl'; - -import InputContainer from '../_shared/InputContainer'; -import sharedMessages from '../_shared/messages'; -import { DateRange } from '../_shared/typings'; - -interface Props { - selectedRange: Partial; - onClick: () => void; -} - -const Input = ({ selectedRange, onClick }: Props) => { - const { formatMessage } = useIntl(); - const selectDate = formatMessage(sharedMessages.selectDate); - - return ( - - - {selectedRange.from - ? selectedRange.from.toLocaleDateString() - : selectDate} - - - - {selectedRange.to ? selectedRange.to.toLocaleDateString() : selectDate} - - - ); -}; - -export default Input; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx new file mode 100644 index 000000000000..270c9fe2f63f --- /dev/null +++ b/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx @@ -0,0 +1,44 @@ +import React from 'react'; + +import { Box, colors, stylingConsts } from '@citizenlab/cl2-component-library'; +import styled from 'styled-components'; + +const StyledButton = styled(Box)<{ isSelected: boolean }>` + ${({ isSelected }) => + isSelected + ? ` + background-color: ${colors.teal200}; + ` + : ` + &:hover { + background-color: ${colors.teal100}; + } + `} +`; + +interface Props { + children: string; + isSelected: boolean; + ml?: string; + onClick: () => void; +} + +const DateButton = ({ children, isSelected, ml, onClick }: Props) => { + return ( + + {children} + + ); +}; + +export default DateButton; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Input/InputContainer.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Input/InputContainer.tsx new file mode 100644 index 000000000000..f03bad8586dc --- /dev/null +++ b/front/app/components/admin/DatePickers/DateRangePicker/Input/InputContainer.tsx @@ -0,0 +1,52 @@ +import React from 'react'; + +import { + defaultInputStyle, + colors, + fontSizes, + Icon, +} from '@citizenlab/cl2-component-library'; +import styled from 'styled-components'; + +const Container = styled.div<{ disabled: boolean }>` + ${defaultInputStyle}; + display: flex; + flex-direction: row; + align-items: center; + font-size: ${fontSizes.base}px; + padding: 4px 8px; + + color: ${colors.grey800}; + + ${({ disabled }) => + disabled + ? ` + cursor: not-allowed; + color: ${colors.grey500}; + svg { + fill: ${colors.grey500}; + } + ` + : ''} +`; + +interface Props { + id?: string; + disabled?: boolean; + children: React.ReactNode; +} + +const InputContainer = ({ id, disabled = false, children }: Props) => { + return ( + + {children} + + + ); +}; + +export default InputContainer; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Input/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Input/index.tsx new file mode 100644 index 000000000000..11c8c3d52e26 --- /dev/null +++ b/front/app/components/admin/DatePickers/DateRangePicker/Input/index.tsx @@ -0,0 +1,49 @@ +import React from 'react'; + +import { Icon } from '@citizenlab/cl2-component-library'; + +import { useIntl } from 'utils/cl-intl'; + +import sharedMessages from '../../_shared/messages'; +import { DateRange } from '../../_shared/typings'; +import { SelectionMode } from '../typings'; + +import DateButton from './DateButton'; +import InputContainer from './InputContainer'; + +interface Props { + selectedRange: Partial; + selectionMode?: SelectionMode; + onClickFrom: () => void; + onClickTo: () => void; +} + +const Input = ({ + selectedRange, + selectionMode, + onClickFrom, + onClickTo, +}: Props) => { + const { formatMessage } = useIntl(); + const selectDate = formatMessage(sharedMessages.selectDate); + + return ( + + + {selectedRange.from + ? selectedRange.from.toLocaleDateString() + : selectDate} + + + + {selectedRange.to ? selectedRange.to.toLocaleDateString() : selectDate} + + + ); +}; + +export default Input; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/index.tsx index 0dd4c797551b..724f708101f5 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/index.tsx @@ -6,7 +6,7 @@ import ClickOutsideContainer from '../_shared/ClickOutsideContainer'; import Calendar from './Calendar'; import Input from './Input'; -import { Props } from './typings'; +import { Props, SelectionMode } from './typings'; const WIDTH = '620px'; @@ -18,12 +18,14 @@ const DateRangePicker = ({ disabled, onUpdateRange, }: Props) => { - const [calendarOpen, setCalendarOpen] = useState(false); + const [selectionMode, setSelectionMode] = useState(); return ( setCalendarOpen(false)} + onClickOutside={() => { + setSelectionMode(undefined); + }} > } placement="bottom" - visible={calendarOpen} + visible={!!selectionMode} width="1200px" > setCalendarOpen((open) => !open)} + selectionMode={selectionMode} + onClickFrom={() => { + setSelectionMode('from'); + }} + onClickTo={() => { + setSelectionMode('to'); + }} /> diff --git a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts index 4b885f76e689..6f73f5ae397b 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts +++ b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts @@ -8,5 +8,8 @@ export interface Props { endMonth?: Date; defaultMonth?: Date; disabled?: PropsBase['disabled']; + selectionMode?: SelectionMode; onUpdateRange: (range: Partial) => void; } + +export type SelectionMode = 'from' | 'to'; From 41bf228085ff31a38c78efc1143f4ff183bc7d64 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 10:50:28 -0300 Subject: [PATCH 20/40] Calculate next selection mode --- .../DateRangePicker/Calendar/index.tsx | 9 ++++ .../Calendar/utils/getNextSelectionMode.ts | 23 ++++++++++ .../Calendar/utils/getUpdatedRange.ts | 43 ++++++++++--------- .../DateRangePicker/Input/InputContainer.tsx | 17 +++++++- .../DatePickers/DateRangePicker/index.tsx | 1 + .../DatePickers/DateRangePicker/typings.ts | 1 + 6 files changed, 72 insertions(+), 22 deletions(-) create mode 100644 front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getNextSelectionMode.ts diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx index e09d81b32daa..9819d906e8bf 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx @@ -11,6 +11,7 @@ import { getEndMonth } from '../../_shared/getStartEndMonth'; import { getLocale } from '../../_shared/locales'; import { Props } from '../typings'; +import { getNextSelectionMode } from './utils/getNextSelectionMode'; import { getUpdatedRange } from './utils/getUpdatedRange'; const DayPickerStyles = styled.div` @@ -33,6 +34,7 @@ const Calendar = ({ disabled, selectionMode, onUpdateRange, + onUpdateSelectionMode, }: Props) => { const locale = useLocale(); @@ -52,6 +54,13 @@ const Calendar = ({ }); onUpdateRange(nextRange); + + const nextSelectionMode = getNextSelectionMode({ + selectionMode, + selectedRange: nextRange, + }); + + onUpdateSelectionMode(nextSelectionMode); }; return ( diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getNextSelectionMode.ts b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getNextSelectionMode.ts new file mode 100644 index 000000000000..4a7b7cfaa015 --- /dev/null +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getNextSelectionMode.ts @@ -0,0 +1,23 @@ +import { DateRange } from 'components/admin/DatePickers/_shared/typings'; + +import { SelectionMode } from '../../typings'; + +interface GetNextSelectionModeParams { + selectedRange: Partial; + selectionMode: SelectionMode; +} + +export const getNextSelectionMode = ({ + selectedRange, + selectionMode, +}: GetNextSelectionModeParams) => { + if (selectedRange.from && !selectedRange.to) { + return 'to'; + } + + if (!selectedRange.from && selectedRange.to) { + return 'from'; + } + + return selectionMode; +}; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.ts b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.ts index d24ddf44b226..730af4a4ea04 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.ts +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.ts @@ -13,36 +13,37 @@ export const getUpdatedRange = ({ selectionMode, clickedDate, }: GetUpdatedRangeParams): Partial => { - // If a 'from' date has been chosen, and the user - // clicks any date before that, we ignore the selection - // mode and just reset the date to a date before. - if (from && !to && clickedDate < from) { + if (selectionMode === 'from') { + // If you are in the 'from' selection mode, + // but you click a date after the 'to' date, + // we will set the 'from' date but + // remove the 'to' date. + if (from && to && clickedDate > to) { + return { + from: clickedDate, + to: undefined, + }; + } + return { from: clickedDate, - to: undefined, + to, }; } - // The same happens if only a 'to' date has been selected - // and the user clicks any date after that. - if (to && !from && clickedDate > to) { + // If you are the 'to' selection mode, + // but you click a date before the 'from' + // date, we will set the 'to' date but + // remove the 'from' date. + if (from && to && clickedDate < from) { return { from: undefined, to: clickedDate, }; } - // In any other case, we just set the date according to the - // selection mode. - if (selectionMode === 'from') { - return { - from: clickedDate, - to, - }; - } else { - return { - from, - to: clickedDate, - }; - } + return { + from, + to: clickedDate, + }; }; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Input/InputContainer.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Input/InputContainer.tsx index f03bad8586dc..1e95fd11d0d5 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Input/InputContainer.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Input/InputContainer.tsx @@ -15,6 +15,7 @@ const Container = styled.div<{ disabled: boolean }>` align-items: center; font-size: ${fontSizes.base}px; padding: 4px 8px; + cursor: default; color: ${colors.grey800}; @@ -27,7 +28,21 @@ const Container = styled.div<{ disabled: boolean }>` fill: ${colors.grey500}; } ` - : ''} + : ` + &:hover, + &:focus { + color: ${colors.black}; + } + + svg { + fill: ${colors.grey700}; + } + + &:hover svg, + &:focus svg { + fill: ${colors.black}; + } + `} `; interface Props { diff --git a/front/app/components/admin/DatePickers/DateRangePicker/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/index.tsx index 724f708101f5..48fef5f60723 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/index.tsx @@ -38,6 +38,7 @@ const DateRangePicker = ({ disabled={disabled} selectionMode={selectionMode} onUpdateRange={onUpdateRange} + onUpdateSelectionMode={setSelectionMode} /> } diff --git a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts index 6f73f5ae397b..198f12bb0e74 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts +++ b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts @@ -10,6 +10,7 @@ export interface Props { disabled?: PropsBase['disabled']; selectionMode?: SelectionMode; onUpdateRange: (range: Partial) => void; + onUpdateSelectionMode: (selectionMode: SelectionMode) => void; } export type SelectionMode = 'from' | 'to'; From 5fae54e34950f1343bbaf3198eca84d9ae23def2 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 11:11:17 -0300 Subject: [PATCH 21/40] Add tests for new behavior --- .../Calendar/utils/getUpdatedRange.test.ts | 117 ++++++++++++++++++ .../Calendar/utils/getUpdatedRange.ts | 8 +- 2 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.test.ts diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.test.ts b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.test.ts new file mode 100644 index 000000000000..088bd5b982d2 --- /dev/null +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.test.ts @@ -0,0 +1,117 @@ +import { getUpdatedRange } from './getUpdatedRange'; + +describe('getUpdatedRange', () => { + describe('selectionMode = from', () => { + const selectionMode = 'from'; + + it('if no dates set, should set from date', () => { + const selectedRange = { from: undefined, to: undefined }; + const clickedDate = new Date(2021, 1, 1); + const result = getUpdatedRange({ + selectedRange, + selectionMode, + clickedDate, + }); + expect(result).toEqual({ from: clickedDate, to: undefined }); + }); + + it('if only from date set, should overwrite from date', () => { + const selectedRange = { from: new Date(2022, 1, 1), to: undefined }; + const clickedDate = new Date(2021, 1, 1); + const result = getUpdatedRange({ + selectedRange, + selectionMode, + clickedDate, + }); + expect(result).toEqual({ from: clickedDate, to: undefined }); + }); + + describe('if only to date set', () => { + const to = new Date(2022, 1, 1); + + it('if clicked date is before to date, should set from date', () => { + const selectedRange = { from: undefined, to }; + const clickedDate = new Date(2021, 1, 1); + const result = getUpdatedRange({ + selectedRange, + selectionMode, + clickedDate, + }); + expect(result).toEqual({ from: clickedDate, to }); + }); + + it('if clicked date is after to date, should set from date and remove to date', () => { + const selectedRange = { from: undefined, to }; + const clickedDate = new Date(2023, 1, 1); + const result = getUpdatedRange({ + selectedRange, + selectionMode, + clickedDate, + }); + expect(result).toEqual({ from: clickedDate, to: undefined }); + }); + + it('if clicked date is equal to to date, should set from date and remove to date', () => { + const selectedRange = { from: undefined, to }; + const clickedDate = new Date(2022, 1, 1); + const result = getUpdatedRange({ + selectedRange, + selectionMode, + clickedDate, + }); + expect(result).toEqual({ from: clickedDate, to: undefined }); + }); + }); + + describe('if both dates set', () => { + const from = new Date(2021, 1, 1); + const to = new Date(2022, 1, 1); + const selectedRange = { from, to }; + + it('if clicked date is before to date, should set from date', () => { + const clickedDate = new Date(2020, 1, 1); + const result = getUpdatedRange({ + selectedRange, + selectionMode, + clickedDate, + }); + expect(result).toEqual({ from: clickedDate, to }); + }); + + it('if clicked date is after to date, should set from date and remove to date', () => { + const clickedDate = new Date(2023, 1, 1); + const result = getUpdatedRange({ + selectedRange, + selectionMode, + clickedDate, + }); + expect(result).toEqual({ from: clickedDate, to: undefined }); + }); + + it('if clicked date is equal to to date, should set from date and remove to date', () => { + const clickedDate = new Date(2022, 1, 1); + const result = getUpdatedRange({ + selectedRange, + selectionMode, + clickedDate, + }); + expect(result).toEqual({ from: clickedDate, to: undefined }); + }); + }); + }); + + describe('selectionMode = to', () => { + const selectionMode = 'from'; + + it('if no dates set, should set to date', () => { + const selectedRange = { from: undefined, to: undefined }; + const clickedDate = new Date(2021, 1, 1); + const result = getUpdatedRange({ + selectedRange, + selectionMode, + clickedDate, + }); + expect(result).toEqual({ from: undefined, to: clickedDate }); + }); + }); +}); diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.ts b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.ts index 730af4a4ea04..766252e87f71 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.ts +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.ts @@ -15,10 +15,10 @@ export const getUpdatedRange = ({ }: GetUpdatedRangeParams): Partial => { if (selectionMode === 'from') { // If you are in the 'from' selection mode, - // but you click a date after the 'to' date, + // but you click a date equal to or after the 'to' date, // we will set the 'from' date but // remove the 'to' date. - if (from && to && clickedDate > to) { + if (to && clickedDate >= to) { return { from: clickedDate, to: undefined, @@ -32,10 +32,10 @@ export const getUpdatedRange = ({ } // If you are the 'to' selection mode, - // but you click a date before the 'from' + // but you click a date equal to or before the 'from' // date, we will set the 'to' date but // remove the 'from' date. - if (from && to && clickedDate < from) { + if (from && clickedDate <= from) { return { from: undefined, to: clickedDate, From 0f7cc4ca84509f96a34c7bd72ab73274c3eebf87 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 11:18:35 -0300 Subject: [PATCH 22/40] Tweak spacing --- .../admin/DatePickers/DateRangePicker/Input/DateButton.tsx | 7 +++---- .../DatePickers/DateRangePicker/Input/InputContainer.tsx | 5 ++++- .../admin/DatePickers/DateRangePicker/Input/index.tsx | 2 +- .../admin/DatePickers/_shared/InputContainer.tsx | 1 + 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx index 270c9fe2f63f..1b896bc070cd 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx @@ -19,19 +19,18 @@ const StyledButton = styled(Box)<{ isSelected: boolean }>` interface Props { children: string; isSelected: boolean; - ml?: string; + mr?: string; onClick: () => void; } -const DateButton = ({ children, isSelected, ml, onClick }: Props) => { +const DateButton = ({ children, isSelected, mr, onClick }: Props) => { return ( ` flex-direction: row; align-items: center; font-size: ${fontSizes.base}px; - padding: 4px 8px; + padding-left: 4px; + padding-right: 10px; + padding-top: 8px; + padding-bottom: 8px; cursor: default; color: ${colors.grey800}; diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Input/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Input/index.tsx index 11c8c3d52e26..4de2119b06f9 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Input/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Input/index.tsx @@ -37,7 +37,7 @@ const Input = ({ {selectedRange.to ? selectedRange.to.toLocaleDateString() : selectDate} diff --git a/front/app/components/admin/DatePickers/_shared/InputContainer.tsx b/front/app/components/admin/DatePickers/_shared/InputContainer.tsx index b4e8edace322..d294cd0c8044 100644 --- a/front/app/components/admin/DatePickers/_shared/InputContainer.tsx +++ b/front/app/components/admin/DatePickers/_shared/InputContainer.tsx @@ -13,6 +13,7 @@ const Container = styled.button<{ disabled: boolean }>` cursor: pointer; display: flex; flex-direction: row; + align-items: center; font-size: ${fontSizes.base}px; color: ${colors.grey800}; From 81ad8c9da01b3b0c097d0ba37f355e0131974e05 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 11:36:05 -0300 Subject: [PATCH 23/40] Make it possible to clear dates --- .../DateRangePicker/Calendar/index.tsx | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx index 9819d906e8bf..53ae0688f82c 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { colors } from '@citizenlab/cl2-component-library'; +import { Box, colors, Button } from '@citizenlab/cl2-component-library'; import { DayPicker, PropsBase } from 'react-day-picker'; import 'react-day-picker/style.css'; import styled from 'styled-components'; @@ -80,6 +80,38 @@ const Calendar = ({ // DayPicker will rely on its internal state to manage the selected // range rather than being controlled by our state. onSelect={NOOP} + footer={ + + {selectedRange.from && ( + + )} + {selectedRange.to && ( + + )} + + } /> ); From c59309d61c239def7eb60c14ec3fb756b0b0644a Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 11:43:29 -0300 Subject: [PATCH 24/40] _ --- .../DateRangePicker/Calendar/index.tsx | 12 ++++++--- .../DateRangePicker/Calendar/messages.ts | 12 +++++++++ .../DatePickers/DateRangePicker/typings.ts | 5 +++- .../participation/ParticipationDateRange.tsx | 26 ++++++++++--------- 4 files changed, 38 insertions(+), 17 deletions(-) create mode 100644 front/app/components/admin/DatePickers/DateRangePicker/Calendar/messages.ts diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx index 53ae0688f82c..18745fcb12ff 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx @@ -7,10 +7,13 @@ import styled from 'styled-components'; import useLocale from 'hooks/useLocale'; +import { useIntl } from 'utils/cl-intl'; + import { getEndMonth } from '../../_shared/getStartEndMonth'; import { getLocale } from '../../_shared/locales'; -import { Props } from '../typings'; +import { CalendarProps } from '../typings'; +import messages from './messages'; import { getNextSelectionMode } from './utils/getNextSelectionMode'; import { getUpdatedRange } from './utils/getUpdatedRange'; @@ -35,8 +38,9 @@ const Calendar = ({ selectionMode, onUpdateRange, onUpdateSelectionMode, -}: Props) => { +}: CalendarProps) => { const locale = useLocale(); + const { formatMessage } = useIntl(); const startMonth = _startMonth ?? new Date(1900, 0); const endMonth = getEndMonth({ @@ -93,7 +97,7 @@ const Calendar = ({ onUpdateRange({ from: undefined, to: selectedRange.to }); }} > - Clear start date + {formatMessage(messages.clearStartDate)} )} {selectedRange.to && ( @@ -107,7 +111,7 @@ const Calendar = ({ onUpdateRange({ from: selectedRange.from, to: undefined }); }} > - Clear end date + {formatMessage(messages.clearEndDate)} )} diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/messages.ts b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/messages.ts new file mode 100644 index 000000000000..9a621ae0fe16 --- /dev/null +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/messages.ts @@ -0,0 +1,12 @@ +import { defineMessages } from 'react-intl'; + +export default defineMessages({ + clearStartDate: { + id: 'app.components.admin.DatePickers.DateRangePicker.Calendar.clearStartDate', + defaultMessage: 'Clear start date', + }, + clearEndDate: { + id: 'app.components.admin.DatePickers.DateRangePicker.Calendar.clearEndDate', + defaultMessage: 'Clear end date', + }, +}); diff --git a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts index 198f12bb0e74..2e80ba98f3bd 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts +++ b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts @@ -8,8 +8,11 @@ export interface Props { endMonth?: Date; defaultMonth?: Date; disabled?: PropsBase['disabled']; - selectionMode?: SelectionMode; onUpdateRange: (range: Partial) => void; +} + +export interface CalendarProps extends Props { + selectionMode?: SelectionMode; onUpdateSelectionMode: (selectionMode: SelectionMode) => void; } diff --git a/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx b/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx index 177befbe2053..3a4a6c7d5097 100644 --- a/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx +++ b/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx @@ -42,18 +42,20 @@ const ParticipationDatesRange = ({ {formatMessage(messages.selectPeriod)} - { - handleChangeTimeRange({ - startDate: from ? moment(from) : null, - endDate: to ? moment(to) : null, - }); - }} - /> + + { + handleChangeTimeRange({ + startDate: from ? moment(from) : null, + endDate: to ? moment(to) : null, + }); + }} + /> + Date: Tue, 24 Dec 2024 14:31:38 -0300 Subject: [PATCH 25/40] _ --- .../DatePhasePicker/Calendar/index.tsx | 3 +- .../DatePickers/DateRangePicker/typings.ts | 8 ++--- .../DateSinglePicker/Calendar/index.tsx | 3 +- .../admin/DatePickers/_shared/typings.ts | 6 ++-- .../participation/ParticipationDateRange.tsx | 33 +++++++------------ front/app/utils/dateUtils.ts | 15 +++++++++ front/package-lock.json | 7 ++-- front/package.json | 1 + 8 files changed, 43 insertions(+), 33 deletions(-) diff --git a/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/index.tsx index 589434076dfb..1a03ad9136b5 100644 --- a/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DatePhasePicker/Calendar/index.tsx @@ -8,6 +8,8 @@ import styled from 'styled-components'; import useLocale from 'hooks/useLocale'; +import { userTimezone } from 'utils/dateUtils'; + import { getLocale } from '../../_shared/locales'; import { Props } from '../typings'; @@ -144,7 +146,6 @@ const Calendar = ({ defaultMonth, onUpdateRange, }: Props) => { - const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; const startMonth = getStartMonth({ startMonth: _startMonth, selectedRange, diff --git a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts index 2e80ba98f3bd..bbe89b5a6cae 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts +++ b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts @@ -1,12 +1,12 @@ -import { PropsBase } from 'react-day-picker'; +import { PropsBase, TZDate } from 'react-day-picker'; import { DateRange } from '../_shared/typings'; export interface Props { selectedRange: Partial; - startMonth?: Date; - endMonth?: Date; - defaultMonth?: Date; + startMonth?: TZDate; + endMonth?: TZDate; + defaultMonth?: TZDate; disabled?: PropsBase['disabled']; onUpdateRange: (range: Partial) => void; } diff --git a/front/app/components/admin/DatePickers/DateSinglePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateSinglePicker/Calendar/index.tsx index 97d4b2900dd6..0b0269bb9ab5 100644 --- a/front/app/components/admin/DatePickers/DateSinglePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateSinglePicker/Calendar/index.tsx @@ -7,6 +7,8 @@ import styled from 'styled-components'; import useLocale from 'hooks/useLocale'; +import { userTimezone } from 'utils/dateUtils'; + import { getEndMonth } from '../../_shared/getStartEndMonth'; import { getLocale } from '../../_shared/locales'; import { CalendarProps } from '../typings'; @@ -35,7 +37,6 @@ const Calendar = ({ const locale = useLocale(); const startMonth = _startMonth ?? new Date(1900, 0); const endMonth = getEndMonth({ endMonth: _endMonth, selectedDate }); - const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; return ( diff --git a/front/app/components/admin/DatePickers/_shared/typings.ts b/front/app/components/admin/DatePickers/_shared/typings.ts index ee5d47b49332..34a17be11ab8 100644 --- a/front/app/components/admin/DatePickers/_shared/typings.ts +++ b/front/app/components/admin/DatePickers/_shared/typings.ts @@ -1,4 +1,6 @@ +import { TZDate } from '@date-fns/tz'; + export type DateRange = { - from: Date; - to?: Date; + from: TZDate; + to?: TZDate; }; diff --git a/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx b/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx index 3a4a6c7d5097..f27d877461f9 100644 --- a/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx +++ b/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx @@ -1,12 +1,12 @@ import React, { useState } from 'react'; import { Box, Text } from '@citizenlab/cl2-component-library'; -import moment, { Moment } from 'moment'; import { useParams } from 'react-router-dom'; import DateRangePicker from 'components/admin/DatePickers/DateRangePicker'; import { useIntl } from 'utils/cl-intl'; +import { parseBackendDateString, toBackendDateString } from 'utils/dateUtils'; import messages from './messages'; import ParticipationReportPreview from './ParticipationReportPreview'; @@ -22,19 +22,10 @@ const ParticipationDatesRange = ({ const { formatMessage } = useIntl(); - const [startAt, setStartAt] = useState(defaultStartDate); - const [endAt, setEndAt] = useState(defaultEndDate); - - const handleChangeTimeRange = ({ - startDate, - endDate, - }: { - startDate: Moment | null; - endDate: Moment | null; - }) => { - setStartAt(startDate?.format('YYYY-MM-DD')); - setEndAt(endDate?.format('YYYY-MM-DD')); - }; + const [startAt, setStartAt] = useState( + parseBackendDateString(defaultStartDate) + ); + const [endAt, setEndAt] = useState(parseBackendDateString(defaultEndDate)); return (
@@ -45,14 +36,12 @@ const ParticipationDatesRange = ({ { - handleChangeTimeRange({ - startDate: from ? moment(from) : null, - endDate: to ? moment(to) : null, - }); + setStartAt(from); + setEndAt(to); }} /> @@ -60,8 +49,8 @@ const ParticipationDatesRange = ({
diff --git a/front/app/utils/dateUtils.ts b/front/app/utils/dateUtils.ts index 7264501189c7..5c47c664c796 100644 --- a/front/app/utils/dateUtils.ts +++ b/front/app/utils/dateUtils.ts @@ -1,5 +1,6 @@ import { isString } from 'lodash-es'; import moment, { unitOfTime, Moment } from 'moment-timezone'; +import { TZDate } from 'react-day-picker'; import { SupportedLocale } from 'typings'; import { IEventData } from 'api/events/types'; @@ -249,3 +250,17 @@ export function calculateRoundedEndDate( endDate.setMinutes(startDate.getMinutes() + durationInMinutes); return endDate; } + +export const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + +export const parseBackendDateString = (dateString?: string) => { + if (!dateString) return undefined; + // backend date format: YYYY-MM-DD + // parse to midnight in user's timezone + return new TZDate(dateString, userTimezone); +}; + +export const toBackendDateString = (date?: TZDate) => { + if (!date) return undefined; + return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; +}; diff --git a/front/package-lock.json b/front/package-lock.json index ee53cd7b6b45..dae607a279d5 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -13,6 +13,7 @@ "@babel/runtime": "7.x", "@craftjs/core": "0.2.0-beta.5", "@craftjs/utils": "0.2.0-beta.5", + "@date-fns/tz": "^1.2.0", "@esri/arcgis-rest-request": "4.2.0", "@hookform/resolvers": "3.1.0", "@jsonforms/core": "3.0.0-beta.0", @@ -2427,9 +2428,9 @@ } }, "node_modules/@date-fns/tz": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.1.2.tgz", - "integrity": "sha512-Xmg2cPmOPQieCLAdf62KtFPU9y7wbQDq1OAzrs/bEQFvhtCPXDiks1CHDE/sTXReRfh/MICVkw/vY6OANHUGiA==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz", + "integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==" }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", diff --git a/front/package.json b/front/package.json index 3629ee12e24c..8b19b50c1f98 100644 --- a/front/package.json +++ b/front/package.json @@ -58,6 +58,7 @@ "@babel/runtime": "7.x", "@craftjs/core": "0.2.0-beta.5", "@craftjs/utils": "0.2.0-beta.5", + "@date-fns/tz": "^1.2.0", "@esri/arcgis-rest-request": "4.2.0", "@hookform/resolvers": "3.1.0", "@jsonforms/core": "3.0.0-beta.0", From 5ba716861e2e6ccd3538fe0c3d693b300fcff6c0 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 15:06:19 -0300 Subject: [PATCH 26/40] _ --- front/app/utils/dateUtils.ts | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/front/app/utils/dateUtils.ts b/front/app/utils/dateUtils.ts index 5c47c664c796..e0cad7312fc6 100644 --- a/front/app/utils/dateUtils.ts +++ b/front/app/utils/dateUtils.ts @@ -1,6 +1,5 @@ import { isString } from 'lodash-es'; import moment, { unitOfTime, Moment } from 'moment-timezone'; -import { TZDate } from 'react-day-picker'; import { SupportedLocale } from 'typings'; import { IEventData } from 'api/events/types'; @@ -253,14 +252,37 @@ export function calculateRoundedEndDate( export const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; -export const parseBackendDateString = (dateString?: string) => { - if (!dateString) return undefined; - // backend date format: YYYY-MM-DD - // parse to midnight in user's timezone - return new TZDate(dateString, userTimezone); +// Why do we need this function? +// The backend sends dates in the format "YYYY-MM-DD" without a time component. +// When we parse this date in the frontend, it is interpreted as +// midnight in UTC. +// This means that if we are west of UTC, e.g. in Brazil, +// The date will be interpreted as 21:00 the previous day. +// This function makes sure that the date is always interpreted as midnight in the user's timezone. +const backendDatestringRegex = /^\d{4}-\d{2}-\d{2}$/; + +export const parseBackendDateString = (dateString: string) => { + if (!dateString.match(backendDatestringRegex)) { + throw new Error('Invalid date string'); + } + + const day = dateString.split('-').map(Number)[2]; + const date = new Date(dateString); + + const parsedDay = date.getDate(); + + if (day === parsedDay) { + date.setHours(0, 0, 0, 0); + } else if (day === parsedDay + 1) { + date.setHours(24, 0, 0, 0); + } else { + throw new Error('Invalid state'); + } + + return date; }; -export const toBackendDateString = (date?: TZDate) => { +export const toBackendDateString = (date?: Date) => { if (!date) return undefined; return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; }; From cc80601778c828373d6e526aaac0b05397fd5f1d Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 15:06:59 -0300 Subject: [PATCH 27/40] _ --- .../admin/DatePickers/DateRangePicker/typings.ts | 8 ++++---- front/app/components/admin/DatePickers/_shared/typings.ts | 6 ++---- front/package-lock.json | 1 - front/package.json | 1 - 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts index bbe89b5a6cae..2e80ba98f3bd 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts +++ b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts @@ -1,12 +1,12 @@ -import { PropsBase, TZDate } from 'react-day-picker'; +import { PropsBase } from 'react-day-picker'; import { DateRange } from '../_shared/typings'; export interface Props { selectedRange: Partial; - startMonth?: TZDate; - endMonth?: TZDate; - defaultMonth?: TZDate; + startMonth?: Date; + endMonth?: Date; + defaultMonth?: Date; disabled?: PropsBase['disabled']; onUpdateRange: (range: Partial) => void; } diff --git a/front/app/components/admin/DatePickers/_shared/typings.ts b/front/app/components/admin/DatePickers/_shared/typings.ts index 34a17be11ab8..ee5d47b49332 100644 --- a/front/app/components/admin/DatePickers/_shared/typings.ts +++ b/front/app/components/admin/DatePickers/_shared/typings.ts @@ -1,6 +1,4 @@ -import { TZDate } from '@date-fns/tz'; - export type DateRange = { - from: TZDate; - to?: TZDate; + from: Date; + to?: Date; }; diff --git a/front/package-lock.json b/front/package-lock.json index dae607a279d5..6ec5db55cd3e 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -13,7 +13,6 @@ "@babel/runtime": "7.x", "@craftjs/core": "0.2.0-beta.5", "@craftjs/utils": "0.2.0-beta.5", - "@date-fns/tz": "^1.2.0", "@esri/arcgis-rest-request": "4.2.0", "@hookform/resolvers": "3.1.0", "@jsonforms/core": "3.0.0-beta.0", diff --git a/front/package.json b/front/package.json index 8b19b50c1f98..3629ee12e24c 100644 --- a/front/package.json +++ b/front/package.json @@ -58,7 +58,6 @@ "@babel/runtime": "7.x", "@craftjs/core": "0.2.0-beta.5", "@craftjs/utils": "0.2.0-beta.5", - "@date-fns/tz": "^1.2.0", "@esri/arcgis-rest-request": "4.2.0", "@hookform/resolvers": "3.1.0", "@jsonforms/core": "3.0.0-beta.0", From 4490f1cb15b5302c66726926fa7fd69021fc8637 Mon Sep 17 00:00:00 2001 From: CircleCI Date: Tue, 24 Dec 2024 18:09:59 +0000 Subject: [PATCH 28/40] Translations updated by CI (extract-intl) --- front/app/translations/admin/en.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/front/app/translations/admin/en.json b/front/app/translations/admin/en.json index 82d98fb6f683..fdb87b89649d 100644 --- a/front/app/translations/admin/en.json +++ b/front/app/translations/admin/en.json @@ -120,6 +120,8 @@ "app.components.admin.ContentBuilder.Widgets.SurveyQuestionResultWidget.untilNow": "{date} until now", "app.components.admin.DatePhasePicker.Input.openEnded": "Open ended", "app.components.admin.DatePhasePicker.Input.selectDate": "Select date", + "app.components.admin.DatePickers.DateRangePicker.Calendar.clearEndDate": "Clear end date", + "app.components.admin.DatePickers.DateRangePicker.Calendar.clearStartDate": "Clear start date", "app.components.admin.DateTimePicker.time": "Time", "app.components.admin.Graphs": "No data available with the current filters.", "app.components.admin.Graphs.noDataShort": "No data available.", From eb78309f93c098d6fa14c26480ea8dfb677f1415 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 15:11:02 -0300 Subject: [PATCH 29/40] _ --- .../participation/ParticipationDateRange.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx b/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx index f27d877461f9..740437b13ea3 100644 --- a/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx +++ b/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx @@ -22,10 +22,8 @@ const ParticipationDatesRange = ({ const { formatMessage } = useIntl(); - const [startAt, setStartAt] = useState( - parseBackendDateString(defaultStartDate) - ); - const [endAt, setEndAt] = useState(parseBackendDateString(defaultEndDate)); + const [startAt, setStartAt] = useState(defaultStartDate); + const [endAt, setEndAt] = useState(defaultEndDate); return (
@@ -36,12 +34,12 @@ const ParticipationDatesRange = ({ { - setStartAt(from); - setEndAt(to); + setStartAt(toBackendDateString(from)); + setEndAt(toBackendDateString(to)); }} /> @@ -49,8 +47,8 @@ const ParticipationDatesRange = ({
From ab449a2358be4593d49f479718e7b3a815d4a845 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 15:15:39 -0300 Subject: [PATCH 30/40] _ --- front/app/utils/dateUtils.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/front/app/utils/dateUtils.ts b/front/app/utils/dateUtils.ts index e0cad7312fc6..357ec4db6b6e 100644 --- a/front/app/utils/dateUtils.ts +++ b/front/app/utils/dateUtils.ts @@ -273,10 +273,8 @@ export const parseBackendDateString = (dateString: string) => { if (day === parsedDay) { date.setHours(0, 0, 0, 0); - } else if (day === parsedDay + 1) { - date.setHours(24, 0, 0, 0); } else { - throw new Error('Invalid state'); + date.setHours(24, 0, 0, 0); } return date; @@ -284,5 +282,11 @@ export const parseBackendDateString = (dateString: string) => { export const toBackendDateString = (date?: Date) => { if (!date) return undefined; - return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; + const monthNumber = date.getMonth() + 1; + const dayNumber = date.getDate(); + + const month = monthNumber < 10 ? `0${monthNumber}` : monthNumber; + const day = dayNumber < 10 ? `0${dayNumber}` : dayNumber; + + return `${date.getFullYear()}-${month}-${day}`; }; From 377577c5e9e9e0d48f8f7e4312f2a1a8ce753358 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 15:38:01 -0300 Subject: [PATCH 31/40] _ --- .../admin/GraphCards/_utils/query.ts | 5 +-- .../participation/ParticipationDateRange.tsx | 4 +- .../project/traffic/TrafficDatesRange.tsx | 41 +++++++------------ .../_shared/ChartWidgetSettings.tsx | 28 ++++++------- front/app/utils/dateUtils.ts | 4 +- 5 files changed, 34 insertions(+), 48 deletions(-) diff --git a/front/app/components/admin/GraphCards/_utils/query.ts b/front/app/components/admin/GraphCards/_utils/query.ts index fa2940d46382..09ac3e2e078c 100644 --- a/front/app/components/admin/GraphCards/_utils/query.ts +++ b/front/app/components/admin/GraphCards/_utils/query.ts @@ -65,10 +65,7 @@ const getLastPeriod = (resolution: IResolution) => { return moment().subtract({ days: 1 }).format('YYYY-MM-DD'); }; -export const getComparedTimeRange = ( - startAt: string | Moment, - endAt: string | Moment -) => { +export const getComparedTimeRange = (startAt?: string, endAt?: string) => { if (!startAt || !endAt) return {}; const startAtMoment = moment(startAt, 'YYYY-MM-DD'); diff --git a/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx b/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx index 740437b13ea3..d1a120c84bfc 100644 --- a/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx +++ b/front/app/containers/Admin/projects/project/participation/ParticipationDateRange.tsx @@ -34,8 +34,8 @@ const ParticipationDatesRange = ({ { setStartAt(toBackendDateString(from)); diff --git a/front/app/containers/Admin/projects/project/traffic/TrafficDatesRange.tsx b/front/app/containers/Admin/projects/project/traffic/TrafficDatesRange.tsx index d54054b9e8d2..f55f3b1511e1 100644 --- a/front/app/containers/Admin/projects/project/traffic/TrafficDatesRange.tsx +++ b/front/app/containers/Admin/projects/project/traffic/TrafficDatesRange.tsx @@ -1,13 +1,13 @@ import React, { useState } from 'react'; import { Box, Text } from '@citizenlab/cl2-component-library'; -import moment, { Moment } from 'moment'; import { useParams } from 'react-router-dom'; import DateRangePicker from 'components/admin/DatePickers/DateRangePicker'; import Warning from 'components/UI/Warning'; import { useIntl } from 'utils/cl-intl'; +import { toBackendDateString, parseBackendDateString } from 'utils/dateUtils'; import messages from './messages'; import TrafficReportPreview from './TrafficReportPreview'; @@ -23,19 +23,8 @@ const TrafficDatesRange = ({ const { formatMessage } = useIntl(); - const [startAt, setStartAt] = useState(defaultStartDate); - const [endAt, setEndAt] = useState(defaultEndDate); - - const handleChangeTimeRange = ({ - startDate, - endDate, - }: { - startDate: Moment | null; - endDate: Moment | null; - }) => { - setStartAt(startDate?.format('YYYY-MM-DD')); - setEndAt(endDate?.format('YYYY-MM-DD')); - }; + const [startAt, setStartAt] = useState(defaultStartDate); + const [endAt, setEndAt] = useState(defaultEndDate); return (
@@ -43,18 +32,18 @@ const TrafficDatesRange = ({ {formatMessage(messages.selectPeriod)} - { - handleChangeTimeRange({ - startDate: from ? moment(from) : null, - endDate: to ? moment(to) : null, - }); - }} - /> + + { + setStartAt(toBackendDateString(from)); + setEndAt(toBackendDateString(to)); + }} + /> + diff --git a/front/app/containers/Admin/reporting/components/ReportBuilder/Widgets/ChartWidgets/_shared/ChartWidgetSettings.tsx b/front/app/containers/Admin/reporting/components/ReportBuilder/Widgets/ChartWidgets/_shared/ChartWidgetSettings.tsx index a2421b028130..3dcc09742b49 100644 --- a/front/app/containers/Admin/reporting/components/ReportBuilder/Widgets/ChartWidgets/_shared/ChartWidgetSettings.tsx +++ b/front/app/containers/Admin/reporting/components/ReportBuilder/Widgets/ChartWidgets/_shared/ChartWidgetSettings.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { Box, Text } from '@citizenlab/cl2-component-library'; import { useNode } from '@craftjs/core'; -import moment, { Moment } from 'moment'; import { IOption, Multiloc } from 'typings'; import DateRangePicker from 'components/admin/DatePickers/DateRangePicker'; @@ -10,6 +9,7 @@ import { getComparedTimeRange } from 'components/admin/GraphCards/_utils/query'; import InputMultilocWithLocaleSwitcher from 'components/UI/InputMultilocWithLocaleSwitcher'; import { useIntl } from 'utils/cl-intl'; +import { parseBackendDateString, toBackendDateString } from 'utils/dateUtils'; import ProjectFilter from '../../_shared/ProjectFilter'; import messages from '../messages'; @@ -70,15 +70,13 @@ export const DateRangeInput = ({ const { formatMessage } = useIntl(); const { actions: { setProp }, - startAtMoment, - endAtMoment, + startAt, + endAt, compareStartAt, compareEndAt, } = useNode((node) => ({ - startAtMoment: node.data.props.startAt - ? moment(node.data.props.startAt) - : null, - endAtMoment: node.data.props.endAt ? moment(node.data.props.endAt) : null, + startAt: node.data.props.startAt, + endAt: node.data.props.endAt, compareStartAt: node.data.props.compareStartAt, compareEndAt: node.data.props.compareEndAt, })); @@ -89,12 +87,12 @@ export const DateRangeInput = ({ startDate, endDate, }: { - startDate: Moment | null; - endDate: Moment | null; + startDate: string | undefined; + endDate: string | undefined; }) => { setProp((props: ChartWidgetProps) => { - props.startAt = startDate?.format('YYYY-MM-DD'); - props.endAt = endDate?.format('YYYY-MM-DD'); + props.startAt = startDate; + props.endAt = endDate; }); if (resetComparePeriod) { @@ -128,13 +126,13 @@ export const DateRangeInput = ({ { handleChangeTimeRange({ - startDate: from ? moment(from) : null, - endDate: to ? moment(to) : null, + startDate: toBackendDateString(from), + endDate: toBackendDateString(to), }); }} /> diff --git a/front/app/utils/dateUtils.ts b/front/app/utils/dateUtils.ts index 357ec4db6b6e..331ce20e40d5 100644 --- a/front/app/utils/dateUtils.ts +++ b/front/app/utils/dateUtils.ts @@ -261,7 +261,9 @@ export const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; // This function makes sure that the date is always interpreted as midnight in the user's timezone. const backendDatestringRegex = /^\d{4}-\d{2}-\d{2}$/; -export const parseBackendDateString = (dateString: string) => { +export const parseBackendDateString = (dateString?: string) => { + if (!dateString) return undefined; + if (!dateString.match(backendDatestringRegex)) { throw new Error('Invalid date string'); } From 653e5e7ea711b7dd4a419bd6e9b08f8bc3647a79 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 15:41:19 -0300 Subject: [PATCH 32/40] _ --- .../DateRangePicker/Calendar/index.tsx | 3 ++- .../DatePickers/DateRangePicker/index.tsx | 10 ++++--- .../DatePickers/DateRangePicker/typings.ts | 1 + .../_shared/ChartWidgetSettings.tsx | 27 ++++++++++--------- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx index 18745fcb12ff..a56f279d059e 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/index.tsx @@ -36,6 +36,7 @@ const Calendar = ({ defaultMonth, disabled, selectionMode, + numberOfMonths = 2, onUpdateRange, onUpdateSelectionMode, }: CalendarProps) => { @@ -71,7 +72,7 @@ const Calendar = ({ { const [selectionMode, setSelectionMode] = useState(); + const width = numberOfMonths === 1 ? '310px' : '620px'; + return ( { setSelectionMode(undefined); }} > + diff --git a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts index 2e80ba98f3bd..bea2ced9573a 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts +++ b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts @@ -8,6 +8,7 @@ export interface Props { endMonth?: Date; defaultMonth?: Date; disabled?: PropsBase['disabled']; + numberOfMonths?: number; onUpdateRange: (range: Partial) => void; } diff --git a/front/app/containers/Admin/reporting/components/ReportBuilder/Widgets/ChartWidgets/_shared/ChartWidgetSettings.tsx b/front/app/containers/Admin/reporting/components/ReportBuilder/Widgets/ChartWidgets/_shared/ChartWidgetSettings.tsx index 3dcc09742b49..d4154e6bedf4 100644 --- a/front/app/containers/Admin/reporting/components/ReportBuilder/Widgets/ChartWidgets/_shared/ChartWidgetSettings.tsx +++ b/front/app/containers/Admin/reporting/components/ReportBuilder/Widgets/ChartWidgets/_shared/ChartWidgetSettings.tsx @@ -124,18 +124,21 @@ export const DateRangeInput = ({ {label ?? formatMessage(messages.analyticsChartDateRange)} - { - handleChangeTimeRange({ - startDate: toBackendDateString(from), - endDate: toBackendDateString(to), - }); - }} - /> + + { + handleChangeTimeRange({ + startDate: toBackendDateString(from), + endDate: toBackendDateString(to), + }); + }} + /> + ); }; From 174de70ec5143f523e9a38a6bafdb9b5ef1f443e Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 15:46:12 -0300 Subject: [PATCH 33/40] Fix report builder date select --- .../ReportBuilderPage/CreateReportModal/index.tsx | 2 +- .../ReportBuilderPage/CreateReportModal/utils.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/index.tsx b/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/index.tsx index 255be4ef378a..1723f9b1de58 100644 --- a/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/index.tsx +++ b/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/index.tsx @@ -123,7 +123,7 @@ const CreateReportModal = ({ open, onClose }: Props) => { )} {template === 'platform' && ( - + )} diff --git a/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/utils.ts b/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/utils.ts index 5e1ea10db64d..0d703f8b7c8b 100644 --- a/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/utils.ts +++ b/front/app/containers/Admin/reporting/components/ReportBuilderPage/CreateReportModal/utils.ts @@ -1,8 +1,9 @@ -import { format } from 'date-fns'; import { RouteType } from 'routes'; import { DateRange } from 'components/admin/DatePickers/_shared/typings'; +import { toBackendDateString } from 'utils/dateUtils'; + import { Template } from './typings'; interface Params { @@ -28,8 +29,8 @@ export const getRedirectUrl = ({ } if (template === 'platform' && from && to) { - const startDateFormat = format(from, 'yyyy-MM-dd'); - const endDateFormat = format(to, 'yyyy-MM-dd'); + const startDateFormat = toBackendDateString(from); + const endDateFormat = toBackendDateString(to); const startDateParam = `startDatePlatformReport=${startDateFormat}`; const endDateParam = `endDatePlatformReport=${endDateFormat}`; From cb56f7b7eabf59d057a31ff96613864fd4a079ce Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 15:55:25 -0300 Subject: [PATCH 34/40] Handle date strings with time embedded --- front/app/utils/dateUtils.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/front/app/utils/dateUtils.ts b/front/app/utils/dateUtils.ts index 331ce20e40d5..b1b7c87d69ff 100644 --- a/front/app/utils/dateUtils.ts +++ b/front/app/utils/dateUtils.ts @@ -261,8 +261,21 @@ export const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; // This function makes sure that the date is always interpreted as midnight in the user's timezone. const backendDatestringRegex = /^\d{4}-\d{2}-\d{2}$/; -export const parseBackendDateString = (dateString?: string) => { - if (!dateString) return undefined; +export const parseBackendDateString = (_dateString?: string) => { + if (!_dateString) return undefined; + + let dateString = _dateString; + + // Sometimes, e.g. in the craftjson layouts, + // we still have old reports using datestrings like + // 2023-01-13T14:54:51.5151 + // This was an implementation bug- we should have used + // the yyyy-MM-DD from the start. + // But for now, we need to handle this case. + // TODO: fix this properly in a migration. + if (dateString.length > 10) { + dateString = dateString.slice(0, 10); + } if (!dateString.match(backendDatestringRegex)) { throw new Error('Invalid date string'); From 827fc3e24c3367b9c36f9b8dfa81982517365477 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Tue, 24 Dec 2024 15:58:23 -0300 Subject: [PATCH 35/40] Only allow one or two months --- .../app/components/admin/DatePickers/DateRangePicker/typings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts index bea2ced9573a..99cfae3b7557 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/typings.ts +++ b/front/app/components/admin/DatePickers/DateRangePicker/typings.ts @@ -8,7 +8,7 @@ export interface Props { endMonth?: Date; defaultMonth?: Date; disabled?: PropsBase['disabled']; - numberOfMonths?: number; + numberOfMonths?: 1 | 2; onUpdateRange: (range: Partial) => void; } From c8c8cbb34858faa729164d9364216334687c21bf Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 27 Dec 2024 11:41:21 -0300 Subject: [PATCH 36/40] Fix test --- .../DateRangePicker/Calendar/utils/getUpdatedRange.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.test.ts b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.test.ts index 088bd5b982d2..81c4b3730f89 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.test.ts +++ b/front/app/components/admin/DatePickers/DateRangePicker/Calendar/utils/getUpdatedRange.test.ts @@ -101,7 +101,7 @@ describe('getUpdatedRange', () => { }); describe('selectionMode = to', () => { - const selectionMode = 'from'; + const selectionMode = 'to'; it('if no dates set, should set to date', () => { const selectedRange = { from: undefined, to: undefined }; From ffddad877dbcb9526882cbbeb6a21e163a8534e9 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 3 Jan 2025 10:34:24 -0300 Subject: [PATCH 37/40] Add changes from other branch --- .../DatePickers/DateRangePicker/Input/DateButton.tsx | 10 +++++++++- .../admin/DatePickers/DateRangePicker/Input/index.tsx | 6 +++++- .../admin/DatePickers/DateRangePicker/index.tsx | 8 ++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx index 1b896bc070cd..8ff5703a2ec6 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx @@ -18,14 +18,22 @@ const StyledButton = styled(Box)<{ isSelected: boolean }>` interface Props { children: string; + className?: string; isSelected: boolean; mr?: string; onClick: () => void; } -const DateButton = ({ children, isSelected, mr, onClick }: Props) => { +const DateButton = ({ + children, + className, + isSelected, + mr, + onClick, +}: Props) => { return ( - + {selectedRange.from ? selectedRange.from.toLocaleDateString() : selectDate} diff --git a/front/app/components/admin/DatePickers/DateRangePicker/index.tsx b/front/app/components/admin/DatePickers/DateRangePicker/index.tsx index bae616c519b4..005b11628247 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/index.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/index.tsx @@ -52,10 +52,14 @@ const DateRangePicker = ({ selectedRange={selectedRange} selectionMode={selectionMode} onClickFrom={() => { - setSelectionMode('from'); + selectionMode === 'from' + ? setSelectionMode(undefined) + : setSelectionMode('from'); }} onClickTo={() => { - setSelectionMode('to'); + selectionMode === 'to' + ? setSelectionMode(undefined) + : setSelectionMode('to'); }} /> From b9548fb79d13e97afa9df76ad03e855402430734 Mon Sep 17 00:00:00 2001 From: Luuc van der Zee Date: Fri, 3 Jan 2025 10:35:34 -0300 Subject: [PATCH 38/40] Improve contrast issue --- .../admin/DatePickers/DateRangePicker/Input/DateButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx b/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx index 8ff5703a2ec6..2513c5427be1 100644 --- a/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx +++ b/front/app/components/admin/DatePickers/DateRangePicker/Input/DateButton.tsx @@ -7,7 +7,7 @@ const StyledButton = styled(Box)<{ isSelected: boolean }>` ${({ isSelected }) => isSelected ? ` - background-color: ${colors.teal200}; + background-color: ${colors.teal100}; ` : ` &:hover { From 7d3ba3055b6c1aca417f7400b26ec345beaddecb Mon Sep 17 00:00:00 2001 From: luucvanderzee Date: Tue, 7 Jan 2025 15:00:28 +0000 Subject: [PATCH 39/40] Update front/app/utils/dateUtils.ts Co-authored-by: Edwin --- front/app/utils/dateUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/app/utils/dateUtils.ts b/front/app/utils/dateUtils.ts index b1b7c87d69ff..0d966d900415 100644 --- a/front/app/utils/dateUtils.ts +++ b/front/app/utils/dateUtils.ts @@ -259,7 +259,7 @@ export const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; // This means that if we are west of UTC, e.g. in Brazil, // The date will be interpreted as 21:00 the previous day. // This function makes sure that the date is always interpreted as midnight in the user's timezone. -const backendDatestringRegex = /^\d{4}-\d{2}-\d{2}$/; +const backendDatestringRegex = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/; export const parseBackendDateString = (_dateString?: string) => { if (!_dateString) return undefined; From 5dfc427bbccd4bdad827002e133037aa81cec9f4 Mon Sep 17 00:00:00 2001 From: luucvanderzee Date: Tue, 7 Jan 2025 15:02:40 +0000 Subject: [PATCH 40/40] Update front/app/utils/dateUtils.ts Co-authored-by: Edwin