From 61b7f08beba78f102fd840a24ed7abd857822393 Mon Sep 17 00:00:00 2001 From: Asadullin Artur <89352236+kakbutos@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:37:33 +0200 Subject: [PATCH] feat(calendar): select period by clicking on month (#1465) * feat(calendar): change handleClickMonthLabel for range clicked * feat(calendar): log * feat(calendar): fix disabled month label * feat(calendar): add new if * feat(calendar): add isCorrectMonthRange * feat(calendar): add comments * feat(calendar): add changeset --- .changeset/funny-jeans-smile.md | 5 + .../components/calendar-mobile/Component.tsx | 103 +++++++++++------- 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 +- 6 files changed, 107 insertions(+), 69 deletions(-) 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. Добавлена возможность выбирать промежуток между месяцами. Первый клик по лейблу месяца выбирает весь месяц. Второй клик на следующий месяц выбирает промежуток между этими двумя месяцами. diff --git a/packages/calendar/src/components/calendar-mobile/Component.tsx b/packages/calendar/src/components/calendar-mobile/Component.tsx index 744f4235b9..6e1a87f845 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,69 @@ 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: 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, dateActiveMonths); + // Проверяем, находится ли кликнутый месяц внутри выбранного диапазона + const isClickedMonthInsideRange = + clickedMonthEndTimestamp >= selectedRangeStartTimestamp && + clickedMonthStartTimestamp <= selectedRangeEndTimestamp; + // Проверяем находится ли выбранный диапазон в пределах кликнутого месяца + const isRangeWithinSingleMonth = + selectedRangeStartTimestamp === dateFrom && selectedRangeEndTimestamp === dateTo; + + if (isSingleMonthSelected && isSameMonthClicked) { + onChange(); + } else if (isClickedMonthInsideRange || !isRangeWithinSingleMonth) { + onChange(clickedMonthStartTimestamp, clickedMonthEndTimestamp); + } else { + const newDateFrom = Math.min(selectedRangeStartTimestamp, clickedMonthStartTimestamp); + const newDateTo = Math.max(selectedRangeEndTimestamp, clickedMonthEndTimestamp); + + onChange(newDateFrom, newDateTo); } }; @@ -239,7 +258,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 (