Files
redlight/src/components/DateTimePicker.jsx
Michelle ce2cf499dc
Some checks failed
Build & Push Docker Image / build (push) Has been cancelled
feat: implement hide app name feature with toggle in admin settings and update branding context
2026-03-04 10:11:35 +01:00

127 lines
3.9 KiB
JavaScript
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.
import ReactDatePicker from 'react-datepicker';
import { de } from 'date-fns/locale';
import { Calendar as CalendarIcon, Clock, X } from 'lucide-react';
import { forwardRef } from 'react';
import 'react-datepicker/dist/react-datepicker.css';
/**
* Custom DateTimePicker that reads the app's CSS variables and
* fully matches whatever theme is active.
*
* Props:
* value ISO datetime string (or '')
* onChange (isoString) => 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 selected = value ? new Date(value) : null;
const handleChange = (date) => {
if (!date) { onChange(''); return; }
// Produce local datetime string yyyy-MM-ddTHH:mm
const y = date.getFullYear();
const mo = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
const h = String(date.getHours()).padStart(2, '0');
const mi = String(date.getMinutes()).padStart(2, '0');
onChange(`${y}-${mo}-${d}T${h}:${mi}`);
};
const Icon = icon === 'clock' ? Clock : CalendarIcon;
return (
<div className="dtp-wrapper">
{label && (
<label className="block text-sm font-medium text-th-text mb-1.5">
{label}{required && ' *'}
</label>
)}
<div className="dtp-input-wrap">
<Icon size={15} className="dtp-icon" />
<ReactDatePicker
selected={selected}
onChange={handleChange}
showTimeSelect
timeFormat="HH:mm"
timeIntervals={15}
dateFormat="dd.MM.yyyy HH:mm"
locale={de}
minDate={minDate}
required={required}
popperPlacement="bottom-start"
popperModifiers={[
{ name: 'offset', options: { offset: [0, 4] } },
{ name: 'preventOverflow', options: { rootBoundary: 'viewport', tether: false, altAxis: true } },
]}
customInput={<CustomInput />}
calendarClassName="dtp-calendar"
dayClassName={(date) => 'dtp-day'}
timeClassName={() => 'dtp-time-item'}
renderCustomHeader={CalendarHeader}
isClearable
clearButtonClassName="dtp-clear-btn"
wrapperClassName="dtp-dp-wrapper"
/>
</div>
</div>
);
}
// ── Custom Input ─────────────────────────────────────────────────────────────
const CustomInput = forwardRef(({ value, onClick, onClear, ...rest }, ref) => (
<button
ref={ref}
type="button"
onClick={onClick}
className="dtp-custom-input"
{...rest}
>
<span className={value ? 'dtp-value' : 'dtp-placeholder'}>
{value || 'Datum & Uhrzeit wählen …'}
</span>
</button>
));
CustomInput.displayName = 'CustomInput';
// ── Custom Header ─────────────────────────────────────────────────────────────
function CalendarHeader({
date,
decreaseMonth, increaseMonth,
prevMonthButtonDisabled, nextMonthButtonDisabled,
}) {
const label = date.toLocaleString('de', { month: 'long', year: 'numeric' });
return (
<div className="dtp-header">
<button
type="button"
onClick={decreaseMonth}
disabled={prevMonthButtonDisabled}
className="dtp-nav-btn"
aria-label="Vorheriger Monat"
>
</button>
<span className="dtp-month-label">{label}</span>
<button
type="button"
onClick={increaseMonth}
disabled={nextMonthButtonDisabled}
className="dtp-nav-btn"
aria-label="Nächster Monat"
>
</button>
</div>
);
}