"use client"; /** * Komponen pemilih tanggal & jam bersama — satu-satunya tempat aplikasi * memakai `react-datepicker`. Semua field tanggal/jam (form & filter) harus * lewat sini supaya tampilan + locale konsisten. * * - `DateField` → satu tanggal (controlled atau uncontrolled untuk form). * - `DateRangeField` → rentang tanggal (berangkat–pulang, filter). * - `TimeField` → jam "HH:mm" (itinerary). * * Tema visual di-override di `app/globals.css` (blok `.react-datepicker`). */ import { useState } from "react"; import ReactDatePicker, { registerLocale } from "react-datepicker"; import { id as idLocale } from "date-fns/locale"; import "react-datepicker/dist/react-datepicker.css"; import { formatLocalCalendarYmd, localCalendarDateFromYmd, } from "@/lib/trip-dates"; import { isValidTimeFormat } from "@/lib/itinerary"; registerLocale("id", idLocale); const FIELD_CLS = "w-full rounded-xl border border-neutral-200 bg-neutral-50 py-2.5 pl-9 pr-3 text-sm text-neutral-800 placeholder:text-neutral-400 focus:border-primary-500 focus:bg-white"; function CalendarIcon() { return ( ); } function ClockIcon() { return ( ); } interface DateFieldProps { /** Mode controlled. Kalau `undefined`, komponen jalan uncontrolled. */ value?: Date | null; /** Nilai awal untuk mode uncontrolled (mis. di dalam form GET/POST biasa). */ defaultValue?: Date | null; /** * Nilai awal uncontrolled berupa `YYYY-MM-DD`. Dipakai saat parent adalah * Server Component (mis. filter admin) — string di-parse di browser supaya * tidak ada pergeseran timezone server↔klien. */ defaultValueYmd?: string; onChange?: (date: Date | null) => void; /** Kalau diisi, render hidden input `YYYY-MM-DD` supaya ikut ter-submit form. */ name?: string; id?: string; minDate?: Date; maxDate?: Date; placeholder?: string; disabled?: boolean; required?: boolean; /** Dropdown bulan + tahun — cocok untuk tanggal lahir. */ withMonthYearDropdown?: boolean; } /** Pemilih satu tanggal. */ export function DateField({ value, defaultValue = null, defaultValueYmd, onChange, name, id, minDate, maxDate, placeholder = "Pilih tanggal...", disabled = false, required = false, withMonthYearDropdown = false, }: DateFieldProps) { const isControlled = value !== undefined; const [internal, setInternal] = useState( () => (defaultValueYmd ? localCalendarDateFromYmd(defaultValueYmd) : defaultValue) ); const current = isControlled ? value : internal; function handleChange(date: Date | null) { if (!isControlled) setInternal(date); onChange?.(date); } // Default rentang masuk akal untuk picker bulan/tahun (mis. tanggal lahir). const effectiveMin = minDate ?? (withMonthYearDropdown ? new Date(new Date().getFullYear() - 120, 0, 1) : undefined); return (
{name && ( )}
); } interface DateRangeFieldProps { startDate: Date | null; endDate: Date | null; onChange: (start: Date | null, end: Date | null) => void; minDate?: Date; placeholder?: string; id?: string; } /** Pemilih rentang tanggal (berangkat–pulang, filter). */ export function DateRangeField({ startDate, endDate, onChange, minDate, placeholder = "Pilih tanggal...", id, }: DateRangeFieldProps) { return (
{ const [start, end] = dates as [Date | null, Date | null]; onChange(start, end); }} locale="id" dateFormat="dd MMM yyyy" minDate={minDate} isClearable placeholderText={placeholder} className={FIELD_CLS} wrapperClassName="w-full" />
); } function timeStringToDate(value: string): Date | null { if (!isValidTimeFormat(value)) return null; const [h, m] = value.split(":").map(Number); const d = new Date(); d.setHours(h, m, 0, 0); return d; } function dateToTimeString(d: Date): string { const h = String(d.getHours()).padStart(2, "0"); const m = String(d.getMinutes()).padStart(2, "0"); return `${h}:${m}`; } interface TimeFieldProps { /** Jam dalam format "HH:mm", atau "" kalau kosong. */ value: string; onChange: (value: string) => void; id?: string; placeholder?: string; disabled?: boolean; /** Tampilkan tombol clear (untuk jam opsional, mis. jam selesai). */ clearable?: boolean; } /** Pemilih jam "HH:mm" 24-jam dengan interval 15 menit. */ export function TimeField({ value, onChange, id, placeholder = "--:--", disabled = false, clearable = false, }: TimeFieldProps) { return (
onChange(d ? dateToTimeString(d) : "")} showTimeSelect showTimeSelectOnly timeIntervals={15} timeCaption="Jam" dateFormat="HH:mm" timeFormat="HH:mm" locale="id" disabled={disabled} isClearable={clearable && !disabled} placeholderText={placeholder} className={FIELD_CLS} wrapperClassName="w-full" />
); }