import { useEffect, useState } from 'react'; import { api, SyncPair, Schedule } from '../api/client'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Label } from '@/components/ui/Label'; import { Select, SelectValue, SelectTrigger, SelectContent, SelectItem } from '@/components/ui/Select'; import { Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, ModalFooter, } from '@/components/ui/Modal'; import { PageHeader } from '@/components/ui/PageHeader'; import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell, } from '@/components/ui/Table'; import { EmptyState } from '@/components/ui/EmptyState'; import { Badge } from '@/components/ui/Badge'; import { Card } from '@/components/ui/Card'; import { Trash2, Plus, Clock, Pencil, AlertTriangle } from 'lucide-react'; import { toast } from 'sonner'; import { cn } from '@/lib/utils'; type ScheduleForm = { id: number | undefined; sync_pair_id: number | null; cron_expr: string; enabled: boolean; }; const defaultForm: ScheduleForm = { id: undefined, sync_pair_id: null, cron_expr: '', enabled: true, }; export default function Schedules() { const [schedules, setSchedules] = useState([]); const [syncPairs, setSyncPairs] = useState([]); const [modalOpen, setModalOpen] = useState(false); const [deleteId, setDeleteId] = useState(null); const [form, setForm] = useState(defaultForm); const [loading, setLoading] = useState(false); useEffect(() => { load(); }, []); async function load() { try { const [s, p] = await Promise.all([ api('/api/schedules'), api('/api/sync-pairs'), ]); setSchedules(s); setSyncPairs(p); } catch {} } function openCreate() { setForm(defaultForm); setModalOpen(true); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!form.sync_pair_id) { toast.error('Sync pair is required'); return; } if (!form.cron_expr.trim()) { toast.error('Cron expression is required'); return; } setLoading(true); try { await api(form.id ? `/api/schedules/${form.id}` : '/api/schedules', { method: form.id ? 'PUT' : 'POST', body: { sync_pair_id: form.sync_pair_id, cron_expr: form.cron_expr, enabled: form.enabled, }, }); setModalOpen(false); toast.success(form.id ? 'Schedule updated' : 'Schedule created'); load(); } catch (e: unknown) { toast.error((e as Error).message); } finally { setLoading(false); } } async function handleToggleEnabled(schedule: Schedule) { try { await api(`/api/schedules/${schedule.id}`, { method: 'PUT', body: { cron_expr: schedule.cron_expr, enabled: !schedule.enabled, }, }); toast.success(`Schedule ${schedule.enabled ? 'disabled' : 'enabled'}`); load(); } catch (e: unknown) { toast.error((e as Error).message); } } async function handleDelete() { if (deleteId === null) return; try { await api(`/api/schedules/${deleteId}`, { method: 'DELETE' }); toast.success('Schedule deleted'); setDeleteId(null); load(); } catch (e: unknown) { toast.error((e as Error).message); } } function syncPairName(id: number) { const p = syncPairs.find(p => p.id === id); return p ? p.name : `Sync Pair ${id}`; } function formatNextRun(nextRun: string | null) { if (!nextRun) return 'Not scheduled'; const d = new Date(nextRun); return d.toLocaleString(); } return (
Add Schedule } />
{schedules.length === 0 ? ( } title="No schedules" description="Create a schedule to automate sync pair execution" action={ } /> ) : ( Sync Pair Cron Expression Next Run Status Actions {schedules.map(s => ( {syncPairName(s.sync_pair_id)} {s.cron_expr} {formatNextRun(s.next_run_at)}
))}
)}
Add Schedule Schedule automated sync pair execution using cron syntax. Format: "minute hour day-of-month month day-of-week"
setForm({ ...form, cron_expr: e.target.value })} className="font-mono" />

Format: "m h dom mon dow" (5 fields, no seconds)
Examples: "0 2 * * *" (daily at 2am), "0 */6 * * *" (every 6 hours)

setForm({ ...form, enabled: e.target.checked })} className="h-4 w-4 rounded border-border accent-accent" />
!v && setDeleteId(null)}> Delete Schedule Are you sure you want to delete this schedule?
); }