frontend_nyla/src/components/PeriodPicker/PeriodPickerCalendar.tsx
2026-04-21 00:50:42 +02:00

132 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* PeriodPicker - dual-month range calendar (vertically stacked).
*
* Pure presentation; receives a `range` and emits `onPickDate`. Constraint
* checks (min/max/direction) are delegated to `isDateDisabled`.
*/
import React, { useMemo } from 'react';
import { useLanguage } from '../../providers/language/LanguageContext';
import {
addMonthsToDate,
buildMonthCells,
isDateDisabled,
_isSameDay,
} from './PeriodPickerLogic';
import type { PeriodConstraints } from './PeriodPickerTypes';
import styles from './PeriodPicker.module.css';
interface CalendarRange {
from: Date | null;
to: Date | null;
}
interface PeriodPickerCalendarProps {
anchor: Date;
onAnchorChange: (next: Date) => void;
range: CalendarRange;
onPickDate: (d: Date) => void;
constraints: PeriodConstraints;
}
function _monthLabel(d: Date, t: (k: string) => string): string {
switch (d.getMonth()) {
case 0: return `${t('Januar')} ${d.getFullYear()}`;
case 1: return `${t('Februar')} ${d.getFullYear()}`;
case 2: return `${t('März')} ${d.getFullYear()}`;
case 3: return `${t('April')} ${d.getFullYear()}`;
case 4: return `${t('Mai')} ${d.getFullYear()}`;
case 5: return `${t('Juni')} ${d.getFullYear()}`;
case 6: return `${t('Juli')} ${d.getFullYear()}`;
case 7: return `${t('August')} ${d.getFullYear()}`;
case 8: return `${t('September')} ${d.getFullYear()}`;
case 9: return `${t('Oktober')} ${d.getFullYear()}`;
case 10: return `${t('November')} ${d.getFullYear()}`;
case 11: return `${t('Dezember')} ${d.getFullYear()}`;
default: return `${d.getFullYear()}`;
}
}
function _dayOfWeekLabel(idx: number, t: (k: string) => string): string {
switch (idx) {
case 0: return t('Mo');
case 1: return t('Di');
case 2: return t('Mi');
case 3: return t('Do');
case 4: return t('Fr');
case 5: return t('Sa');
case 6: return t('So');
default: return '';
}
}
const PeriodPickerCalendar: React.FC<PeriodPickerCalendarProps> = (props) => {
const { anchor, onAnchorChange, range, onPickDate, constraints } = props;
const { t } = useLanguage();
const monthsToShow = useMemo(() => [anchor, addMonthsToDate(anchor, 1)], [anchor]);
return (
<div className={styles.colCalendar}>
<div className={styles.calNav}>
<button
type="button"
className={styles.calNavBtn}
onClick={() => onAnchorChange(addMonthsToDate(anchor, -1))}
aria-label={t('Vorheriger Monat')}
>
</button>
<span className={styles.calTitle}>
{`${_monthLabel(monthsToShow[0], t)} ${_monthLabel(monthsToShow[1], t)}`}
</span>
<button
type="button"
className={styles.calNavBtn}
onClick={() => onAnchorChange(addMonthsToDate(anchor, 1))}
aria-label={t('Nächster Monat')}
>
</button>
</div>
<div className={styles.calMonths}>
{monthsToShow.map((monthAnchor) => (
<div key={`${monthAnchor.getFullYear()}-${monthAnchor.getMonth()}`} className={styles.calMonth}>
<h5>{_monthLabel(monthAnchor, t)}</h5>
<div className={styles.calGrid} role="grid">
{[0, 1, 2, 3, 4, 5, 6].map((i) => (
<div key={`dow-${i}`} className={styles.dowCell}>{_dayOfWeekLabel(i, t)}</div>
))}
{buildMonthCells(monthAnchor).map((cell) => {
const disabled = isDateDisabled(cell.date, constraints);
const cls: string[] = [styles.dayCell];
if (!cell.inMonth) cls.push(styles.muted);
if (disabled) cls.push(styles.disabled);
if (cell.isToday) cls.push(styles.today);
if (range.from && range.to && cell.date >= range.from && cell.date <= range.to) {
cls.push(styles.inRange);
}
if (range.from && _isSameDay(cell.date, range.from)) cls.push(styles.rangeStart);
if (range.to && _isSameDay(cell.date, range.to)) cls.push(styles.rangeEnd);
return (
<button
type="button"
key={cell.iso}
className={cls.join(' ')}
disabled={disabled}
onClick={() => onPickDate(cell.date)}
>
{cell.date.getDate()}
</button>
);
})}
</div>
</div>
))}
</div>
</div>
);
};
export default PeriodPickerCalendar;