From 2b24dffe9c8637107e2dabe26cb66c6d52e01017 Mon Sep 17 00:00:00 2001 From: Federico Luzzi Date: Tue, 31 Oct 2023 17:17:07 +0000 Subject: [PATCH] fix(ui): seconds granularit and avoid extra use effect loops in date picker (#482) Co-authored-by: luzzifoss --- .changeset/stale-lemons-know.md | 6 ++++ .../date-time-input/picker/index.tsx | 33 ++++++++++++------- packages/ui/src/utils/date.ts | 21 ++++++++++-- 3 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 .changeset/stale-lemons-know.md diff --git a/.changeset/stale-lemons-know.md b/.changeset/stale-lemons-know.md new file mode 100644 index 000000000..ed6928206 --- /dev/null +++ b/.changeset/stale-lemons-know.md @@ -0,0 +1,6 @@ +--- +"@carrot-kpi/ui": patch +--- + +Apply date comparisons with seconds granularity and avoid self loops while +updating minimum and maximum dates in date picker diff --git a/packages/ui/src/components/date-time-input/picker/index.tsx b/packages/ui/src/components/date-time-input/picker/index.tsx index aeda6ebdb..d808c40fd 100644 --- a/packages/ui/src/components/date-time-input/picker/index.tsx +++ b/packages/ui/src/components/date-time-input/picker/index.tsx @@ -9,7 +9,11 @@ import { Typography } from "../../typography"; import { DatePicker, type DatePickerProps } from "../../date-input/picker"; import { enforceDoubleDigits } from "../../../utils/formatting"; import { mergedCva } from "../../../utils/components"; -import { rectifyDate, resolvedValue } from "../../../utils/date"; +import { + getUpdatedMinMaxValue, + rectifyDate, + resolvedValue, +} from "../../../utils/date"; const cellListStyles = mergedCva( [ @@ -89,17 +93,24 @@ export const DateTimePicker = ({ // avoid inconsistent min and max values useEffect(() => { - if (!min || !dayjs(min).isValid()) setMin(minDate); - if (!max || !dayjs(max).isValid()) setMax(maxDate); + setMax((prevMax) => { + const newMax = getUpdatedMinMaxValue(prevMax, maxDate); - if (dayjs(min).isAfter(dayjs(max))) { - setMin(max); - console.warn("inconsistent min and max values", { - min: min?.toISOString(), - max: max?.toISOString(), + setMin((prevMin) => { + let newMin = getUpdatedMinMaxValue(prevMin, minDate); + if (dayjs(newMin).isAfter(dayjs(newMax), "seconds")) { + newMin = newMax; + console.warn("inconsistent min and max values", { + min: newMin?.toISOString(), + max: newMax?.toISOString(), + }); + } + return newMin; }); - } - }, [min, max, minDate, maxDate]); + + return newMax; + }); + }, [maxDate, minDate]); // in case a value change happened, check if we're still // alright with validation and rectify if needed @@ -107,7 +118,7 @@ export const DateTimePicker = ({ if (!value || !onChange) return; const originalValue = dayjs(value); const rectifiedValue = rectifyDate(dayjs(value), min, max); - if (!originalValue.isSame(rectifiedValue)) + if (!originalValue.isSame(rectifiedValue, "seconds")) onChange(rectifiedValue.toDate()); }, [max, min, onChange, value]); diff --git a/packages/ui/src/utils/date.ts b/packages/ui/src/utils/date.ts index 8cad5f1aa..548b74953 100644 --- a/packages/ui/src/utils/date.ts +++ b/packages/ui/src/utils/date.ts @@ -3,6 +3,23 @@ import durationPlugin, { type Duration } from "dayjs/plugin/duration"; dayjs.extend(durationPlugin); +export const getUpdatedMinMaxValue = ( + previousValue?: Date | null, + newValue?: Date | null, +) => { + if (!newValue) return previousValue; + if (!previousValue) return newValue; + + const parsedPreviousValue = dayjs(previousValue); + if ( + !parsedPreviousValue.isValid() || + !parsedPreviousValue.isSame(newValue, "seconds") + ) + return newValue; + + return null; +}; + // our interface for a single cell export interface CalendarCell { text: string; @@ -60,8 +77,8 @@ export const rectifyDate = ( min?: Date | null, max?: Date | null, ) => { - if (!!(min && value.isBefore(min))) return dayjs(min); - if (!!(max && value.isAfter(max))) return dayjs(max); + if (!!(min && value.isBefore(min, "seconds"))) return dayjs(min); + if (!!(max && value.isAfter(max, "seconds"))) return dayjs(max); return value; };