import { useCallback } from "react"; import { Input } from "@nous-research/ui/ui/components/input"; import { Label } from "@nous-research/ui/ui/components/label"; import { Select, SelectOption } from "@nous-research/ui/ui/components/select"; import { Button } from "@nous-research/ui/ui/components/button"; import { useI18n } from "@/i18n"; import { buildScheduleString, DEFAULT_SCHEDULE_STATE, type IntervalUnit, type ScheduleBuilderState, type ScheduleMode, type Weekday, WEEKDAY_INDEXES, } from "@/lib/schedule"; /** * Human-readable schedule picker for cron job create/edit flows. * * Replaces the raw "type a cron expression" input that lived inline in * ``CronPage``. The picker still emits a single backend-compatible * schedule string (see ``cron/jobs.py::parse_schedule``), but the user * fills out shape-appropriate inputs (time picker, weekday toggles, * datetime-local field) per mode. * * Architecture: * * - The component is fully controlled. Parent owns the * ``ScheduleBuilderState`` and the derived schedule string (built * via ``buildScheduleString`` in render). * - Mode-specific state slots (``timeOfDay``, ``weekdays``, ...) are * preserved across mode switches so flipping back to a previous mode * doesn't erase the user's work. * - The "Custom" mode is an escape hatch — surfacing it as a normal * option (instead of hiding it behind an "advanced" toggle) keeps * power-user workflows discoverable without making everyone scroll * past it. */ export function ScheduleBuilder({ onChange, value }: ScheduleBuilderProps) { const { t } = useI18n(); const cronStrings = t.cron; const modeStrings = cronStrings.scheduleModes; const update = useCallback( (patch: Partial) => { onChange({ ...value, ...patch }); }, [onChange, value], ); const toggleWeekday = useCallback( (day: Weekday) => { const present = value.weekdays.includes(day); update({ weekdays: present ? value.weekdays.filter((d) => d !== day) : [...value.weekdays, day], }); }, [update, value.weekdays], ); return (
{value.mode === "interval" && (
{ const n = parseInt(e.target.value, 10); update({ intervalValue: Number.isFinite(n) && n > 0 ? n : 1, }); }} />
)} {value.mode === "daily" && ( update({ timeOfDay })} /> )} {value.mode === "weekly" && ( <>
{WEEKDAY_INDEXES.map((d) => { const isOn = value.weekdays.includes(d); return ( ); })}
update({ timeOfDay })} /> )} {value.mode === "monthly" && (
{ const n = parseInt(e.target.value, 10); update({ dayOfMonth: Number.isFinite(n) && n >= 1 && n <= 31 ? n : 1, }); }} />
update({ timeOfDay })} />
)} {value.mode === "once" && (
{/* Native datetime-local — emits the exact "YYYY-MM-DDTHH:MM" shape ``parse_schedule`` accepts on the backend. */} update({ onceAt: e.target.value })} />
)} {value.mode === "custom" && (
update({ custom: e.target.value })} className="font-mono-ui" />

{modeStrings.customHint}

)} {/* Inline preview of what we'll send to the backend. Helps users eyeball the result before hitting Create, and keeps the schedule grammar discoverable for the custom mode. */}

{modeStrings.preview}: {buildScheduleString(value) || modeStrings.previewEmpty}

); } function TimeOfDayField({ id, label, onChange, value, }: TimeOfDayFieldProps) { return (
{/* Native time picker is the right tool for "HH:MM" — saves us two separate hour/minute selects, respects user locale's AM/PM preference, and round-trips with ``buildScheduleString`` without parsing. */} onChange(e.target.value)} />
); } export { DEFAULT_SCHEDULE_STATE }; interface ScheduleBuilderProps { onChange: (state: ScheduleBuilderState) => void; value: ScheduleBuilderState; } interface TimeOfDayFieldProps { id: string; label: string; onChange: (value: string) => void; value: string; }