import React, { useState, useEffect, useRef, useCallback } from 'react'; import { createPortal } from 'react-dom'; import { format, addMonths, subMonths, startOfMonth, endOfMonth, startOfWeek, endOfWeek, addDays, isSameMonth, isSameDay } from 'date-fns'; import { enUS, de, fr, es, it } from 'date-fns/locale'; interface SimpleDatePickerProps { selected: Date; onSelect: (date: Date) => void; onClose: () => void; language?: string; anchorRef?: React.RefObject; } export default function SimpleDatePicker({ selected, onSelect, onClose, language = 'en', anchorRef }: SimpleDatePickerProps) { const [currentMonth, setCurrentMonth] = useState(new Date(selected)); const modalRef = useRef(null); const localeMap: Record = { de, fr, es, it }; const locale = localeMap[language] || enUS; const [position, setPosition] = useState<{ top: number; left: number } | null>(null); const updatePosition = useCallback(() => { if (anchorRef?.current) { const rect = anchorRef.current.getBoundingClientRect(); setPosition({ top: rect.bottom + 8, left: rect.left + rect.width / 2 - 144, // 144 = half of 18rem (288px) }); } }, [anchorRef]); useEffect(() => { updatePosition(); window.addEventListener('resize', updatePosition); window.addEventListener('scroll', updatePosition, true); return () => { window.removeEventListener('resize', updatePosition); window.removeEventListener('scroll', updatePosition, true); }; }, [updatePosition]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (modalRef.current && !modalRef.current.contains(event.target as Node)) { // Also check if click is on the anchor/trigger button if (anchorRef?.current && anchorRef.current.contains(event.target as Node)) return; onClose(); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [onClose, anchorRef]); const renderHeader = () => (
{format(currentMonth, 'MMMM yyyy', { locale })}
); const renderDays = () => { const days = []; const startDate = startOfWeek(currentMonth, { weekStartsOn: 1 }); for (let i = 0; i < 7; i++) { days.push(
{format(addDays(startDate, i), 'EEEEEE', { locale })}
); } return
{days}
; }; const renderCells = () => { const monthStart = startOfMonth(currentMonth); const monthEnd = endOfMonth(monthStart); const startDate = startOfWeek(monthStart, { weekStartsOn: 1 }); const endDate = endOfWeek(monthEnd, { weekStartsOn: 1 }); const cells = []; let day = startDate; while (day <= endDate) { const row = []; for (let i = 0; i < 7; i++) { const cloneDay = day; const isSelected = isSameDay(day, selected); const isToday = isSameDay(day, new Date()); const isCurrentMonth = isSameMonth(day, monthStart); const isDaySelected = isSelected; row.push(
{ onSelect(cloneDay); onClose(); }} style={{ height: '2rem', width: '2rem', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '0.875rem', borderRadius: '50%', cursor: 'pointer', transition: 'all 0.2s', margin: '0 auto', color: !isCurrentMonth ? 'var(--weekly-text-light, #d1d5db)' : isDaySelected ? 'white' : 'var(--weekly-text, #374151)', background: isDaySelected ? 'black' : 'transparent', fontWeight: isDaySelected ? 'bold' : 'normal', boxShadow: isDaySelected ? '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)' : 'none', transform: isDaySelected ? 'scale(1.1)' : 'none', ...(isToday && !isDaySelected ? { color: '#ef4444', fontWeight: 'bold' } : {}) }} onMouseEnter={(e) => { if (!isDaySelected) e.currentTarget.style.backgroundColor = 'var(--weekly-hover-bg, #f3f4f6)'; }} onMouseLeave={(e) => { if (!isDaySelected) e.currentTarget.style.backgroundColor = 'transparent'; }} > {format(day, 'd')}
); day = addDays(day, 1); } cells.push(row); } return (
{cells.flat()}
); }; // Use fixed positioning via portal if we have an anchor, otherwise fallback to absolute const usePortal = !!anchorRef?.current && !!position; const pickerContent = (
{renderHeader()} {renderDays()} {renderCells()}
); if (usePortal) { return createPortal(pickerContent, document.body); } return pickerContent; }