Skip to content

Commit

Permalink
feat(calendar): select period by clicking on month (#1465)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kakbutos authored Dec 6, 2024
1 parent d9bfa05 commit 61b7f08
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 69 deletions.
5 changes: 5 additions & 0 deletions .changeset/funny-jeans-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@alfalab/core-components-calendar': minor
---

Изменено поведение пропса clickableMonth. Добавлена возможность выбирать промежуток между месяцами. Первый клик по лейблу месяца выбирает весь месяц. Второй клик на следующий месяц выбирает промежуток между этими двумя месяцами.
103 changes: 61 additions & 42 deletions packages/calendar/src/components/calendar-mobile/Component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -26,6 +23,8 @@ import {
dateArrayToHashTable,
generateMonths,
generateWeeks,
getMonthEndTimestamp,
getMonthStartTimestamp,
isRangeValue,
limitDate,
monthName,
Expand Down Expand Up @@ -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);
}
};

Expand All @@ -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 (
<div className={styles.daysTable} id={`month-${index}`}>
Expand Down
42 changes: 23 additions & 19 deletions packages/calendar/src/docs/description.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}, []);

Expand Down Expand Up @@ -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));
}, []);

Expand Down Expand Up @@ -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));
}, []);

Expand All @@ -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 = {
Expand Down Expand Up @@ -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));
}, []);

Expand All @@ -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';
Expand Down Expand Up @@ -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}
/>
Expand Down
18 changes: 11 additions & 7 deletions packages/calendar/src/docs/development.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 версия компонента.
Expand Down Expand Up @@ -48,7 +52,7 @@ import { CalendarMobile, CalendarMonthOnlyView, CalendarMonthOnlyViewHeader } fr
CalendarMobile,
PeriodSlider,
CalendarMonthOnlyView,
CalendarMonthOnlyViewHeader
CalendarMonthOnlyViewHeader,
}}
/>

Expand Down Expand Up @@ -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)',
};

Expand All @@ -85,11 +89,11 @@ render(() => {
<div style={headerStyle}>
<CalendarMonthOnlyViewHeader />
</div>
<Gap size="m" direction="vertical" />
<Gap size='m' direction='vertical' />
<CalendarMonthOnlyView
value={value}
onChange={setValue}
showCurrentYearSelector={false}
value={value}
onChange={setValue}
showCurrentYearSelector={false}
/>
</>
);
Expand Down
6 changes: 6 additions & 0 deletions packages/calendar/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
2 changes: 1 addition & 1 deletion packages/calendar/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
{ "path": "../modal" },
{ "path": "../mq" },
{ "path": "../shared" },
{ "path": "../typography" },
{ "path": "../typography" }
]
}

0 comments on commit 61b7f08

Please sign in to comment.