From 418526edcdab5bf48b80642bb057af475dfb4762 Mon Sep 17 00:00:00 2001 From: Asadullin Artur Date: Wed, 25 Sep 2024 17:25:51 +0200 Subject: [PATCH 1/7] feat(calendar): change handleClickMonthLabel for range clicked --- .../components/calendar-mobile/Component.tsx | 91 ++++++++++--------- packages/calendar/src/docs/description.mdx | 42 +++++---- packages/calendar/src/docs/development.mdx | 18 ++-- packages/calendar/src/utils.ts | 6 ++ packages/calendar/tsconfig.json | 2 +- 5 files changed, 91 insertions(+), 68 deletions(-) diff --git a/packages/calendar/src/components/calendar-mobile/Component.tsx b/packages/calendar/src/components/calendar-mobile/Component.tsx index dfde9ada25..d0789e5a39 100644 --- a/packages/calendar/src/components/calendar-mobile/Component.tsx +++ b/packages/calendar/src/components/calendar-mobile/Component.tsx @@ -5,10 +5,7 @@ import { ResizeObserver as ResizeObserverPolyfill } from '@juggle/resize-observe import cn from 'classnames'; import endOfDay from 'date-fns/endOfDay'; import isAfter from 'date-fns/isAfter'; -import isSameDay from 'date-fns/isSameDay'; import isSameMonth from 'date-fns/isSameMonth'; -import isThisMonth from 'date-fns/isThisMonth'; -import lastDayOfMonth from 'date-fns/lastDayOfMonth'; import startOfDay from 'date-fns/startOfDay'; import startOfMonth from 'date-fns/startOfMonth'; @@ -26,6 +23,8 @@ import { dateArrayToHashTable, generateMonths, generateWeeks, + getMonthEndTimestamp, + getMonthStartTimestamp, isRangeValue, limitDate, monthName, @@ -180,49 +179,59 @@ export const CalendarMonthOnlyView = ({ return activeMonths.findIndex((m) => isSameMonth(date, m.date)); }, [range.value, range.selectedFrom, activeMonth, activeMonths]); - // заголовок должен становиться активным если выбран весь доступный период в месяце - const isMonthActive = (currentMonthIndex: number) => { - if (value && isRangeValue(value)) { - const { date: initialMonthDate } = activeMonths[initialMonthIndex]; - const { date: currentMonthDate } = activeMonths[currentMonthIndex]; - - const firstAvailableDayOfMonth = startOfMonth(initialMonthDate).getTime(); - /** - * последний доступный день месяца в timestamp - * представлен в виде последнего календарного дня, либо в виде текущей даты для актуального месяца - */ - const lastAvailableDayOfMonth = isThisMonth(initialMonthDate) - ? startOfDay(new Date()).getTime() - : lastDayOfMonth(initialMonthDate).getTime(); - const { dateFrom, dateTo } = value; - - if ( - dateFrom && - dateTo && - isSameMonth(initialMonthDate, currentMonthDate) && - isSameDay(firstAvailableDayOfMonth, dateFrom) && - isSameDay(lastAvailableDayOfMonth, dateTo) - ) { - return true; - } + const isMonthActive = (currentMonthIndex: number): boolean => { + if (!value || !isRangeValue(value) || !value.dateFrom || !value.dateTo) { + return false; } - return false; + const { dateFrom, dateTo } = value; + + const { date: currentMonthDate } = activeMonths[currentMonthIndex]; + const monthStartTimestamp = getMonthStartTimestamp(currentMonthDate); + const monthEndTimestamp = getMonthEndTimestamp(currentMonthDate); + + // Проверяем, что выбранный диапазон полностью покрывает месяц + return dateFrom <= monthStartTimestamp && dateTo >= monthEndTimestamp; }; const handleClickMonthLabel = (index: number) => { - if (onChange) { - const { date } = activeMonths[index]; - const firstAvailableDayOfMonth = startOfMonth(date).getTime(); - const lastAvailableDayOfMonth = isThisMonth(date) - ? startOfDay(new Date()).getTime() - : lastDayOfMonth(date).getTime(); - - if (isMonthActive(index)) { - onChange(); - } else { - onChange(firstAvailableDayOfMonth, lastAvailableDayOfMonth); - } + if (!onChange) return; + + const { date } = activeMonths[index]; + const clickedMonthStartTimestamp = getMonthStartTimestamp(date); + const clickedMonthEndTimestamp = getMonthEndTimestamp(date); + + if (!value || !isRangeValue(value) || !value.dateFrom || !value.dateTo) { + onChange(clickedMonthStartTimestamp, clickedMonthEndTimestamp); + + return; + } + + const { dateFrom, dateTo } = value; + const selectedRangeStartDate = new Date(dateFrom); + const selectedRangeEndDate = new Date(dateTo); + const selectedRangeStartTimestamp = getMonthStartTimestamp(selectedRangeStartDate); + const selectedRangeEndTimestamp = getMonthEndTimestamp(selectedRangeEndDate); + + const isSingleMonthSelected = + isSameMonth(selectedRangeStartDate, selectedRangeEndDate) && + dateFrom <= selectedRangeStartTimestamp && + dateTo >= selectedRangeEndTimestamp; + + const isSameMonthClicked = isSameMonth(selectedRangeStartDate, date); + const isClickedMonthInsideRange = + clickedMonthEndTimestamp >= selectedRangeStartTimestamp && + clickedMonthStartTimestamp <= selectedRangeEndTimestamp; + + if (isSingleMonthSelected && isSameMonthClicked) { + onChange(); + } else if (isClickedMonthInsideRange) { + onChange(clickedMonthStartTimestamp, clickedMonthEndTimestamp); + } else { + const newDateFrom = Math.min(selectedRangeStartTimestamp, clickedMonthStartTimestamp); + const newDateTo = Math.max(selectedRangeEndTimestamp, clickedMonthEndTimestamp); + + onChange(newDateFrom, newDateTo); } }; diff --git a/packages/calendar/src/docs/description.mdx b/packages/calendar/src/docs/description.mdx index 4167010ba5..4efa247adf 100644 --- a/packages/calendar/src/docs/description.mdx +++ b/packages/calendar/src/docs/description.mdx @@ -15,10 +15,10 @@ render(() => { const format = React.useCallback((timestamp) => { if (!timestamp) return ''; - return new Intl.DateTimeFormat("ru-RU", { - year: "numeric", - month: "2-digit", - day: "2-digit" + return new Intl.DateTimeFormat('ru-RU', { + year: 'numeric', + month: '2-digit', + day: '2-digit', }).format(new Date(timestamp)); }, []); @@ -79,10 +79,10 @@ render(() => { const format = React.useCallback((timestamp) => { if (!timestamp) return ''; - return new Intl.DateTimeFormat("ru-RU", { - year: "numeric", - month: "2-digit", - day: "2-digit" + return new Intl.DateTimeFormat('ru-RU', { + year: 'numeric', + month: '2-digit', + day: '2-digit', }).format(new Date(timestamp)); }, []); @@ -136,10 +136,10 @@ render(() => { const format = React.useCallback((timestamp) => { if (!timestamp) return ''; - return new Intl.DateTimeFormat("ru-RU", { - year: "numeric", - month: "2-digit", - day: "2-digit" + return new Intl.DateTimeFormat('ru-RU', { + year: 'numeric', + month: '2-digit', + day: '2-digit', }).format(new Date(timestamp)); }, []); @@ -148,7 +148,9 @@ render(() => { }, [rangeBehavior]); const selectedRange = React.useMemo(() => { - return `${format(value ? value.dateFrom : undefined)} - ${format(value ? value.dateTo : undefined)}`; + return `${format(value ? value.dateFrom : undefined)} - ${format( + value ? value.dateTo : undefined, + )}`; }, [value]); const calendarStyles = { @@ -197,10 +199,10 @@ render(() => { const format = React.useCallback((timestamp) => { if (!timestamp) return ''; - return new Intl.DateTimeFormat("ru-RU", { - year: "numeric", - month: "2-digit", - day: "2-digit" + return new Intl.DateTimeFormat('ru-RU', { + year: 'numeric', + month: '2-digit', + day: '2-digit', }).format(new Date(timestamp)); }, []); @@ -209,7 +211,9 @@ render(() => { }, [rangeBehavior]); const selectedRange = React.useMemo(() => { - return `${format(value ? value.dateFrom : undefined)} - ${format(value ? value.dateTo : undefined)}`; + return `${format(value ? value.dateFrom : undefined)} - ${format( + value ? value.dateTo : undefined, + )}`; }, [value]); const allowSelectionFromEmptyRange = selectionMode === 'singleAndRange'; @@ -696,7 +700,7 @@ render(() => { shape={firstRadioValue} clickableMonth={true} value={value} - onChange={(dateFrom, dateTo) => setValue({dateFrom, dateTo})} + onChange={(dateFrom, dateTo) => setValue({ dateFrom, dateTo })} onClose={() => setOpen(false)} open={open} /> diff --git a/packages/calendar/src/docs/development.mdx b/packages/calendar/src/docs/development.mdx index 0681653c44..f3a7ccfcb7 100644 --- a/packages/calendar/src/docs/development.mdx +++ b/packages/calendar/src/docs/development.mdx @@ -11,7 +11,11 @@ import vars from '!!raw-loader!../vars.css'; ```jsx import { Calendar } from '@alfalab/core-components/calendar'; import { CalendarDesktop } from '@alfalab/core-components/calendar/desktop'; -import { CalendarMobile, CalendarMonthOnlyView, CalendarMonthOnlyViewHeader } from '@alfalab/core-components/calendar/mobile'; +import { + CalendarMobile, + CalendarMonthOnlyView, + CalendarMonthOnlyViewHeader, +} from '@alfalab/core-components/calendar/mobile'; ``` Из индекса импортируются responsive версия компонента. @@ -48,7 +52,7 @@ import { CalendarMobile, CalendarMonthOnlyView, CalendarMonthOnlyViewHeader } fr CalendarMobile, PeriodSlider, CalendarMonthOnlyView, - CalendarMonthOnlyViewHeader + CalendarMonthOnlyViewHeader, }} /> @@ -76,7 +80,7 @@ render(() => { zIndex: 2, width: '100%', padding: 'var(--gap-12) var(--gap-24)', - background: "var(--color-light-base-bg-primary)", + background: 'var(--color-light-base-bg-primary)', borderBottom: '1px solid var(--color-light-neutral-500)', }; @@ -85,11 +89,11 @@ render(() => {
- + ); diff --git a/packages/calendar/src/utils.ts b/packages/calendar/src/utils.ts index 2cb2e37743..d7b98ac212 100644 --- a/packages/calendar/src/utils.ts +++ b/packages/calendar/src/utils.ts @@ -11,6 +11,7 @@ import format from 'date-fns/format'; import isAfter from 'date-fns/isAfter'; import isBefore from 'date-fns/isBefore'; import isSameDay from 'date-fns/isSameDay'; +import isThisMonth from 'date-fns/isThisMonth'; import lastDayOfMonth from 'date-fns/lastDayOfMonth'; import max from 'date-fns/max'; import min from 'date-fns/min'; @@ -308,3 +309,8 @@ export function isRangeValue( ): value is { dateFrom?: number | undefined; dateTo?: number | undefined } { return Boolean(value) && typeof value === 'object'; } + +export const getMonthStartTimestamp = (date: Date) => startOfMonth(date).getTime(); + +export const getMonthEndTimestamp = (date: Date) => + isThisMonth(date) ? startOfDay(new Date()).getTime() : lastDayOfMonth(date).getTime(); diff --git a/packages/calendar/tsconfig.json b/packages/calendar/tsconfig.json index 28093ff9f2..91e41d78ef 100644 --- a/packages/calendar/tsconfig.json +++ b/packages/calendar/tsconfig.json @@ -17,6 +17,6 @@ { "path": "../modal" }, { "path": "../mq" }, { "path": "../shared" }, - { "path": "../typography" }, + { "path": "../typography" } ] } From 02682999e032862d2b894d1a239edff0c10a18d3 Mon Sep 17 00:00:00 2001 From: Asadullin Artur Date: Mon, 30 Sep 2024 18:35:24 +0200 Subject: [PATCH 2/7] feat(calendar): log --- .../calendar/src/components/calendar-mobile/Component.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/calendar/src/components/calendar-mobile/Component.tsx b/packages/calendar/src/components/calendar-mobile/Component.tsx index d0789e5a39..6131a5a7ca 100644 --- a/packages/calendar/src/components/calendar-mobile/Component.tsx +++ b/packages/calendar/src/components/calendar-mobile/Component.tsx @@ -195,6 +195,8 @@ export const CalendarMonthOnlyView = ({ }; const handleClickMonthLabel = (index: number) => { + // eslint-disable-next-line no-console + console.log(handleClickMonthLabel, onChange); if (!onChange) return; const { date } = activeMonths[index]; @@ -346,6 +348,8 @@ export const CalendarMobile = forwardRef( }, ref, ) => { + // eslint-disable-next-line no-console + console.log('CalendarMobile'); const [modalRef, setModalRef] = useState(); const monthOnlyView = selectorView === 'month-only'; From 96f57e22a773a5acffcc30143f055db5d1d05c24 Mon Sep 17 00:00:00 2001 From: Asadullin Artur Date: Tue, 12 Nov 2024 13:32:48 +0200 Subject: [PATCH 3/7] feat(calendar): fix disabled month label --- packages/calendar/src/components/calendar-mobile/Component.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/calendar/src/components/calendar-mobile/Component.tsx b/packages/calendar/src/components/calendar-mobile/Component.tsx index 034736475a..0f4daf1a60 100644 --- a/packages/calendar/src/components/calendar-mobile/Component.tsx +++ b/packages/calendar/src/components/calendar-mobile/Component.tsx @@ -250,7 +250,7 @@ export const CalendarMonthOnlyView = ({ }; const renderMonth = (index: number) => { - const isAfterDate = isAfter(activeMonths[index].date, activeMonth); + const isAfterDate = isAfter(activeMonths[index].date, maxDate ?? new Date()); return (
From 21a94f78513846e1ac5401f9a1d49bd448311db9 Mon Sep 17 00:00:00 2001 From: Asadullin Artur Date: Tue, 19 Nov 2024 12:27:15 +0200 Subject: [PATCH 4/7] feat(calendar): add new if --- .../calendar/src/components/calendar-mobile/Component.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/calendar/src/components/calendar-mobile/Component.tsx b/packages/calendar/src/components/calendar-mobile/Component.tsx index 0f4daf1a60..075a8b32d5 100644 --- a/packages/calendar/src/components/calendar-mobile/Component.tsx +++ b/packages/calendar/src/components/calendar-mobile/Component.tsx @@ -203,7 +203,13 @@ export const CalendarMonthOnlyView = ({ const clickedMonthStartTimestamp = getMonthStartTimestamp(date); const clickedMonthEndTimestamp = getMonthEndTimestamp(date); - if (!value || !isRangeValue(value) || !value.dateFrom || !value.dateTo) { + if ( + !value || + !isRangeValue(value) || + !value.dateFrom || + !value.dateTo || + value.dateFrom === value.dateTo + ) { onChange(clickedMonthStartTimestamp, clickedMonthEndTimestamp); return; From c818db6fe015d584bf847173f6e7dbfe9cdfd244 Mon Sep 17 00:00:00 2001 From: Asadullin Artur Date: Tue, 19 Nov 2024 13:30:09 +0200 Subject: [PATCH 5/7] feat(calendar): add isCorrectMonthRange --- .../src/components/calendar-mobile/Component.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/calendar/src/components/calendar-mobile/Component.tsx b/packages/calendar/src/components/calendar-mobile/Component.tsx index 075a8b32d5..1447940d3c 100644 --- a/packages/calendar/src/components/calendar-mobile/Component.tsx +++ b/packages/calendar/src/components/calendar-mobile/Component.tsx @@ -203,13 +203,7 @@ export const CalendarMonthOnlyView = ({ const clickedMonthStartTimestamp = getMonthStartTimestamp(date); const clickedMonthEndTimestamp = getMonthEndTimestamp(date); - if ( - !value || - !isRangeValue(value) || - !value.dateFrom || - !value.dateTo || - value.dateFrom === value.dateTo - ) { + if (!value || !isRangeValue(value) || !value.dateFrom || !value.dateTo) { onChange(clickedMonthStartTimestamp, clickedMonthEndTimestamp); return; @@ -230,10 +224,12 @@ export const CalendarMonthOnlyView = ({ const isClickedMonthInsideRange = clickedMonthEndTimestamp >= selectedRangeStartTimestamp && clickedMonthStartTimestamp <= selectedRangeEndTimestamp; + const isCorrectMonthRange = + selectedRangeStartTimestamp === dateFrom && selectedRangeEndTimestamp === dateTo; if (isSingleMonthSelected && isSameMonthClicked) { onChange(); - } else if (isClickedMonthInsideRange) { + } else if (isClickedMonthInsideRange || !isCorrectMonthRange) { onChange(clickedMonthStartTimestamp, clickedMonthEndTimestamp); } else { const newDateFrom = Math.min(selectedRangeStartTimestamp, clickedMonthStartTimestamp); From e6c75030793dc968ce94dc39101c4cfebbca71d6 Mon Sep 17 00:00:00 2001 From: Asadullin Artur Date: Mon, 25 Nov 2024 15:10:53 +0200 Subject: [PATCH 6/7] feat(calendar): add comments --- .../components/calendar-mobile/Component.tsx | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/calendar/src/components/calendar-mobile/Component.tsx b/packages/calendar/src/components/calendar-mobile/Component.tsx index 1447940d3c..6e1a87f845 100644 --- a/packages/calendar/src/components/calendar-mobile/Component.tsx +++ b/packages/calendar/src/components/calendar-mobile/Component.tsx @@ -179,6 +179,7 @@ export const CalendarMonthOnlyView = ({ return activeMonths.findIndex((m) => isSameMonth(date, m.date)); }, [range.value, range.selectedFrom, activeMonth, activeMonths]); + // заголовок должен становиться активным, если выбран весь доступный период в месяце const isMonthActive = (currentMonthIndex: number): boolean => { if (!value || !isRangeValue(value) || !value.dateFrom || !value.dateTo) { return false; @@ -195,41 +196,46 @@ export const CalendarMonthOnlyView = ({ }; const handleClickMonthLabel = (index: number) => { - // eslint-disable-next-line no-console - console.log(handleClickMonthLabel, onChange); if (!onChange) return; - const { date } = activeMonths[index]; - const clickedMonthStartTimestamp = getMonthStartTimestamp(date); - const clickedMonthEndTimestamp = getMonthEndTimestamp(date); + const { date: dateActiveMonths } = activeMonths[index]; + // Вычисляем начало и конец месяца, по которому был произведен клик + const clickedMonthStartTimestamp = getMonthStartTimestamp(dateActiveMonths); + const clickedMonthEndTimestamp = getMonthEndTimestamp(dateActiveMonths); + + // Если значение не определено или не является диапазоном, то устанавливаем новый диапазон if (!value || !isRangeValue(value) || !value.dateFrom || !value.dateTo) { onChange(clickedMonthStartTimestamp, clickedMonthEndTimestamp); return; } + // Выбранный диапазон дат const { dateFrom, dateTo } = value; const selectedRangeStartDate = new Date(dateFrom); const selectedRangeEndDate = new Date(dateTo); const selectedRangeStartTimestamp = getMonthStartTimestamp(selectedRangeStartDate); const selectedRangeEndTimestamp = getMonthEndTimestamp(selectedRangeEndDate); + // Проверяем, является ли выбранный диапазон одним и тем же месяцем const isSingleMonthSelected = isSameMonth(selectedRangeStartDate, selectedRangeEndDate) && dateFrom <= selectedRangeStartTimestamp && dateTo >= selectedRangeEndTimestamp; - - const isSameMonthClicked = isSameMonth(selectedRangeStartDate, date); + // Проверяем, является ли кликнутый месяц таким же, что и выбранный диапазон + const isSameMonthClicked = isSameMonth(selectedRangeStartDate, dateActiveMonths); + // Проверяем, находится ли кликнутый месяц внутри выбранного диапазона const isClickedMonthInsideRange = clickedMonthEndTimestamp >= selectedRangeStartTimestamp && clickedMonthStartTimestamp <= selectedRangeEndTimestamp; - const isCorrectMonthRange = + // Проверяем находится ли выбранный диапазон в пределах кликнутого месяца + const isRangeWithinSingleMonth = selectedRangeStartTimestamp === dateFrom && selectedRangeEndTimestamp === dateTo; if (isSingleMonthSelected && isSameMonthClicked) { onChange(); - } else if (isClickedMonthInsideRange || !isCorrectMonthRange) { + } else if (isClickedMonthInsideRange || !isRangeWithinSingleMonth) { onChange(clickedMonthStartTimestamp, clickedMonthEndTimestamp); } else { const newDateFrom = Math.min(selectedRangeStartTimestamp, clickedMonthStartTimestamp); @@ -350,8 +356,6 @@ export const CalendarMobile = forwardRef( }, ref, ) => { - // eslint-disable-next-line no-console - console.log('CalendarMobile'); const [modalRef, setModalRef] = useState(); const monthOnlyView = selectorView === 'month-only'; From d4aae89d76743656eb27370b49d7de19f80570d6 Mon Sep 17 00:00:00 2001 From: Asadullin Artur Date: Thu, 28 Nov 2024 10:36:56 +0200 Subject: [PATCH 7/7] feat(calendar): add changeset --- .changeset/funny-jeans-smile.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/funny-jeans-smile.md diff --git a/.changeset/funny-jeans-smile.md b/.changeset/funny-jeans-smile.md new file mode 100644 index 0000000000..5691b1625a --- /dev/null +++ b/.changeset/funny-jeans-smile.md @@ -0,0 +1,5 @@ +--- +'@alfalab/core-components-calendar': minor +--- + +Изменено поведение пропса clickableMonth. Добавлена возможность выбирать промежуток между месяцами. Первый клик по лейблу месяца выбирает весь месяц. Второй клик на следующий месяц выбирает промежуток между этими двумя месяцами.