import { t } from "./i18n"; export type CronFreq = "Every hour" | "Every day" | "Weekdays" | "Every week" | "Every month" | "Interval" | "Advanced"; export type CronUnit = "minutes" | "hours" | "days"; export type CronPreset = { freq: CronFreq; n: number; unit: CronUnit; time: string; cron: string }; export interface ScheduleItem { id: string; botId: string; threadId?: string | null; name: string; cron: string; timezone: string; instructions: string; enabled: boolean; human: string; lastRunAt?: string | null; nextRunAt?: string | null; } export const CRON_FREQS: CronFreq[] = ["Every hour", "Every day", "Weekdays", "Every week", "Every month", "Interval", "Advanced"]; const TIMES = ["6:00 AM", "7:00 AM", "8:00 AM", "9:00 AM", "12:00 PM", "3:00 PM", "6:00 PM", "9:00 PM"]; const NUMBERS = [1, 2, 3, 5, 10, 15, 30, 45]; const TIMED: CronFreq[] = ["Every day", "Weekdays", "Every week", "Every month"]; export function defaultCronPreset(): CronPreset { return { freq: "Every day", n: 3, unit: "minutes", time: "9:00 AM", cron: "" }; } export function cronFromPreset(input: CronPreset): string { if (input.freq === "Advanced") return input.cron.trim(); if (input.freq === "Every hour") return "0 * * * *"; if (input.freq === "Interval") { if (!Number.isInteger(input.n) || input.n < 1 || input.n > 365) throw new Error("間隔必須為 1–365 的整數"); return `@every ${input.n}${({minutes:"m",hours:"h",days:"d"})[input.unit]}`; } const { hour, minute } = parseClock(input.time); if (input.freq === "Weekdays") return `${minute} ${hour} * * 1-5`; if (input.freq === "Every week") return `${minute} ${hour} * * 1`; if (input.freq === "Every month") return `${minute} ${hour} 1 * *`; return `${minute} ${hour} * * *`; } export function presetFromCron(cron: string): CronPreset { const base = defaultCronPreset(); const interval = /^@every ([1-9]\d{0,2})([mhd])$/.exec(cron.trim()); if (interval && Number(interval[1]) <= 365) return {...base, freq:"Interval", n:Number(interval[1]), unit:({m:"minutes",h:"hours",d:"days"} as const)[interval[2] as "m"|"h"|"d"]}; const parts = cron.trim().split(/\s+/); if (parts.length !== 5) return { ...base, freq: "Advanced", cron }; const [minute, hour, day, month, dow] = parts; if (month !== "*") return { ...base, freq: "Advanced", cron }; if (minute === "0" && hour === "*" && day === "*" && dow === "*") return { ...base, freq: "Every hour" }; if (!/^\d+$/.test(minute) || !/^\d+$/.test(hour)) return { ...base, freq: "Advanced", cron }; if (Number(hour) > 23 || Number(minute) > 59) return {...base, freq:"Advanced", cron}; const time = formatClock(Number(hour), Number(minute)); if (day === "*" && dow === "1-5") return { ...base, freq: "Weekdays", time }; if (day === "*" && dow === "1") return { ...base, freq: "Every week", time }; if (day === "1" && dow === "*") return { ...base, freq: "Every month", time }; if (day === "*" && dow === "*") return { ...base, freq: "Every day", time }; return { ...base, freq: "Advanced", cron }; } function parseClock(time: string): { hour: number; minute: number } { const [rawH, rest] = time.split(":"); const minute = Number((rest ?? "00").slice(0, 2)); let hour = Number(rawH); if (/pm/i.test(time) && hour < 12) hour += 12; if (/am/i.test(time) && hour === 12) hour = 0; return { hour, minute }; } function formatClock(hour: number, minute: number): string { const period = hour >= 12 ? "PM" : "AM"; const h12 = hour % 12 === 0 ? 12 : hour % 12; return `${h12}:${String(minute).padStart(2, "0")} ${period}`; } function freqLabel(freq: CronFreq): string { return ({ "Every hour": t("schedEveryHour"), "Every day": t("schedEveryDay"), "Weekdays": t("schedWeekdays"), "Every week": t("schedEveryWeek"), "Every month": t("schedEveryMonth"), "Interval": t("schedInterval"), "Advanced": t("schedAdvanced"), })[freq]; } export function ScheduleList({ items, onCreate, onOpen, onRun, runningId, }: { items: ScheduleItem[]; onCreate: () => void; onOpen: (item: ScheduleItem) => void; onRun: (item: ScheduleItem) => void; runningId?: string | null; }) { return (
{t("schedules")}
{items.length === 0 ?

{t("schedEmpty")}

: items.map(item => (
))}
); } export function ScheduleEditor({ draft, timezone, saving, error, onChange, onBack, onSave, onDelete, }: { draft: { name: string; instructions: string; enabled: boolean; preset: CronPreset }; timezone: string; saving: boolean; error: string | null; onChange: (next: { name: string; instructions: string; enabled: boolean; preset: CronPreset }) => void; onBack: () => void; onSave: () => void; onDelete?: () => void; }) { const preset = draft.preset; const times = TIMES.includes(preset.time) ? TIMES : [...TIMES, preset.time]; const numbers = NUMBERS.includes(preset.n) ? NUMBERS : [...NUMBERS, preset.n].sort((a, b) => a - b); function patchPreset(partial: Partial) { onChange({ ...draft, preset: { ...preset, ...partial } }); } return (
{t("schedule")}