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 (