"use client"; import { useMemo } from "react"; import { cn } from "@multica/ui/lib/utils"; import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, } from "@multica/ui/components/ui/select"; export type TriggerFrequency = "hourly" | "daily" | "weekdays" | "weekly" | "custom"; export interface TriggerConfig { frequency: TriggerFrequency; time: string; // HH:MM dayOfWeek: number; // 0=Sun … 6=Sat cronExpression: string; // only used when frequency === "custom" timezone: string; // IANA } // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- const FREQUENCIES: { value: TriggerFrequency; label: string }[] = [ { value: "hourly", label: "Hourly" }, { value: "daily", label: "Daily" }, { value: "weekdays", label: "Weekdays" }, { value: "weekly", label: "Weekly" }, { value: "custom", label: "Custom" }, ]; const DAYS_OF_WEEK = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; const COMMON_TIMEZONES = [ "UTC", "America/New_York", "America/Chicago", "America/Denver", "America/Los_Angeles", "America/Sao_Paulo", "Europe/London", "Europe/Paris", "Europe/Berlin", "Europe/Moscow", "Asia/Dubai", "Asia/Kolkata", "Asia/Singapore", "Asia/Shanghai", "Asia/Tokyo", "Asia/Seoul", "Australia/Sydney", "Pacific/Auckland", ]; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- export function getLocalTimezone(): string { try { return Intl.DateTimeFormat().resolvedOptions().timeZone; } catch { return "UTC"; } } function getTimezoneOffset(tz: string): string { if (tz === "UTC") return "UTC"; try { const parts = new Intl.DateTimeFormat("en-US", { timeZone: tz, timeZoneName: "shortOffset", }).formatToParts(new Date()); return parts.find((p) => p.type === "timeZoneName")?.value ?? tz; } catch { return tz; } } function getTimezoneLabel(tz: string): string { if (tz === "UTC") return "UTC"; const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz; return `${city} (${getTimezoneOffset(tz)})`; } function formatTime12h(time: string): string { const [h, m] = time.split(":"); const hour = parseInt(h ?? "9", 10); const min = parseInt(m ?? "0", 10); const ampm = hour >= 12 ? "PM" : "AM"; return `${hour % 12 || 12}:${min.toString().padStart(2, "0")} ${ampm}`; } // --------------------------------------------------------------------------- // Public helpers // --------------------------------------------------------------------------- export function getDefaultTriggerConfig(): TriggerConfig { return { frequency: "daily", time: "09:00", dayOfWeek: 1, cronExpression: "0 9 * * 1-5", timezone: getLocalTimezone(), }; } export function toCronExpression(cfg: TriggerConfig): string { const [h, m] = cfg.time.split(":"); const hour = parseInt(h ?? "9", 10); const min = parseInt(m ?? "0", 10); switch (cfg.frequency) { case "hourly": return `${min} * * * *`; case "daily": return `${min} ${hour} * * *`; case "weekdays": return `${min} ${hour} * * 1-5`; case "weekly": return `${min} ${hour} * * ${cfg.dayOfWeek}`; case "custom": return cfg.cronExpression; } } export function describeTrigger(cfg: TriggerConfig): string { const offset = getTimezoneOffset(cfg.timezone); switch (cfg.frequency) { case "hourly": { const min = parseInt(cfg.time.split(":")[1] ?? "0", 10); return `Runs every hour at :${min.toString().padStart(2, "0")}`; } case "daily": return `Runs daily at ${formatTime12h(cfg.time)} ${offset}`; case "weekdays": return `Runs weekdays at ${formatTime12h(cfg.time)} ${offset}`; case "weekly": return `Runs every ${DAYS_OF_WEEK[cfg.dayOfWeek]} at ${formatTime12h(cfg.time)} ${offset}`; case "custom": return `Custom schedule: ${cfg.cronExpression}`; } } // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- export function TriggerConfigSection({ config, onChange, }: { config: TriggerConfig; onChange: (config: TriggerConfig) => void; }) { const timezones = useMemo(() => { const local = getLocalTimezone(); const set = new Set(COMMON_TIMEZONES); return set.has(local) ? COMMON_TIMEZONES : [local, ...COMMON_TIMEZONES]; }, []); return (
{/* Frequency tabs */}
{FREQUENCIES.map((f) => ( ))}
{config.frequency === "custom" ? ( /* Custom cron input */
onChange({ ...config, cronExpression: e.target.value })} placeholder="0 9 * * 1-5" className="mt-1 w-full rounded-md border bg-background px-3 py-2 text-sm font-mono outline-none focus:ring-1 focus:ring-ring" />

Standard 5-field cron (min hour dom month dow)

) : ( <> {/* Time + Timezone row */}
{config.frequency === "hourly" ? (
{ const min = Math.max(0, Math.min(59, parseInt(e.target.value) || 0)); onChange({ ...config, time: `00:${min.toString().padStart(2, "0")}` }); }} className="mt-1 w-full rounded-md border bg-background px-3 py-2 text-sm font-mono outline-none focus:ring-1 focus:ring-ring" />
) : ( <>
onChange({ ...config, time: e.target.value || config.time })} className="mt-1 w-full rounded-md border bg-background px-3 py-2 text-sm font-mono outline-none focus:ring-1 focus:ring-ring" />
)}
{/* Day-of-week selector for weekly */} {config.frequency === "weekly" && (
{DAYS_OF_WEEK.map((day, i) => ( ))}
)} )} {/* Human-readable preview */}

{describeTrigger(config)}

); }