import { useEffect, useRef } from 'react'; import flatpickr from 'flatpickr'; import 'flatpickr/dist/flatpickr.min.css'; import { German } from 'flatpickr/dist/l10n/de.js'; import { Calendar as CalendarIcon, Clock } from 'lucide-react'; // Register German as default locale flatpickr.localize(German); /** * Themed DateTimePicker using flatpickr. * flatpickr uses position:fixed for its calendar dropdown — no overflow, * no scroll issues, no Popper.js needed. CSS variables drive all theming. * * Props: * value – local datetime string 'YYYY-MM-DDTHH:mm' (or '') * onChange – (localDatetimeString) => void * label – string * required – bool * minDate – Date | null * icon – 'calendar' (default) | 'clock' */ export default function DateTimePicker({ value, onChange, label, required = false, minDate = null, icon = 'calendar', }) { const inputRef = useRef(null); const fpRef = useRef(null); // Always keep a current ref to onChange so flatpickr's closure never goes stale const onChangeRef = useRef(onChange); useEffect(() => { onChangeRef.current = onChange; }); useEffect(() => { if (!inputRef.current) return; fpRef.current = flatpickr(inputRef.current, { enableTime: true, time_24hr: true, dateFormat: 'd.m.Y H:i', minuteIncrement: 15, minDate: minDate || undefined, defaultDate: value || undefined, appendTo: document.body, // portal to body → never clipped static: false, onChange: (selectedDates) => { if (selectedDates.length === 0) { onChangeRef.current(''); return; } const d = selectedDates[0]; const y = d.getFullYear(); const mo = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); const h = String(d.getHours()).padStart(2, '0'); const mi = String(d.getMinutes()).padStart(2, '0'); onChangeRef.current(`${y}-${mo}-${day}T${h}:${mi}`); }, }); return () => fpRef.current?.destroy(); }, []); // eslint-disable-line react-hooks/exhaustive-deps // Sync value from outside useEffect(() => { if (!fpRef.current) return; const current = fpRef.current.selectedDates[0]; const incoming = value ? new Date(value) : null; // Only setDate if actually different (avoid loops) if (incoming && (!current || Math.abs(incoming - current) > 60000)) { fpRef.current.setDate(incoming, false); } else if (!incoming && current) { fpRef.current.clear(false); } }, [value]); // Sync minDate useEffect(() => { if (!fpRef.current) return; fpRef.current.set('minDate', minDate || undefined); }, [minDate]); const Icon = icon === 'clock' ? Clock : CalendarIcon; return (
{label && ( )}
); }