import React, { useMemo, useState } from 'react'; import { ChevronLeft, ChevronRight } from 'lucide-react'; import { addMonths, buildMonthCells, formatISODate, formatMonthTitle, isDateInRange, isRangeEnd, isRangeStart, normalizeDateRange, parseISODate, sameCalendarDay, startOfMonth, WEEKDAY_LABELS, type DateRangeValue, } from './dateUtils'; function MonthPanel({ monthDate, start, end, hover, selected, mode, onDayClick, onDayHover, }: { monthDate: Date; start: Date | null; end: Date | null; hover: Date | null; selected: Date | null; mode: 'single' | 'range'; onDayClick: (date: Date) => void; onDayHover: (date: Date | null) => void; }) { const cells = useMemo(() => buildMonthCells(monthDate), [monthDate]); const today = useMemo(() => new Date(), []); return (
{formatMonthTitle(monthDate)}
{WEEKDAY_LABELS.map((label) => ( {label} ))}
{cells.map((cell) => { const key = formatISODate(cell.date); const isToday = sameCalendarDay(cell.date, today); let classNames = ['o2-cal__day']; if (!cell.inMonth) classNames.push('is-outside'); if (isToday) classNames.push('is-today'); if (mode === 'single') { if (selected && sameCalendarDay(cell.date, selected)) classNames.push('is-selected'); } else { const selectedStart = isRangeStart(cell.date, start, end, hover); const selectedEnd = isRangeEnd(cell.date, start, end, hover); const inRange = isDateInRange(cell.date, start, end, hover); if (inRange) classNames.push('is-in-range'); if (selectedStart) classNames.push('is-range-start'); if (selectedEnd) classNames.push('is-range-end'); } return ( ); })}
); } export function O2SingleCalendarPanel({ value, onChange, onComplete, }: { value: string; onChange: (next: string) => void; onComplete?: () => void; }) { const selected = parseISODate(value); const [viewMonth, setViewMonth] = useState(() => startOfMonth(selected || new Date())); return (
undefined} onDayClick={(date) => { onChange(formatISODate(date)); onComplete?.(); }} />
); } export function O2RangeCalendarPanel({ startDate, endDate, onChange, onComplete, }: { startDate: string; endDate: string; onChange: (range: DateRangeValue) => void; onComplete?: () => void; }) { const start = parseISODate(startDate); const end = parseISODate(endDate); const [viewMonth, setViewMonth] = useState(() => startOfMonth(start || end || new Date())); const [hoverDate, setHoverDate] = useState(null); const leftMonth = viewMonth; const rightMonth = addMonths(viewMonth, 1); const handleDayClick = (date: Date) => { const iso = formatISODate(date); if (!startDate || (startDate && endDate)) { onChange({ startDate: iso, endDate: '' }); return; } const next = normalizeDateRange(startDate, iso); onChange(next); onComplete?.(); }; return (
); }