import { Badge } from "@/components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { MobileCardRow, type MobileCardField, } from "@/components/ui/mobile-card"; import { useIsMobile } from "../hooks/useIsMobile"; import type { BackupJob, BackupRun } from "../types/backups"; interface Props { jobs: BackupJob[]; latestRuns: Map; } function formatInterval(seconds: number | null): string { if (!seconds) return "N/A"; if (seconds < 60) return `${seconds}s`; if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`; return `${Math.floor(seconds / 86400)}d`; } function formatTimestamp(ts: number | null): string { if (!ts) return "Never"; return new Date(ts * 1000).toLocaleString(); } type StatusVariant = "success" | "destructive" | "warning" | "secondary"; /** * Map a job/run status onto a Badge variant per design §2.3: * `success` → success (chart-2); `failure` → destructive (chart-4); * `in_progress` → warning (chart-3); unknown → secondary (neutral accent). */ function statusVariant(status: string): StatusVariant { if (status === "success") return "success"; if (status === "failure") return "destructive"; if (status === "in_progress") return "warning"; return "secondary"; } // Mobile card fields (spec R3.2): job name is primary; source/schedule/status // give at-a-glance context. See OpenSpec change `mobile-responsive-parity`. interface JobCardRow { job: BackupJob; status: string; run_started: number | null; } const jobCardFields: MobileCardField[] = [ { key: "name", label: "Name", render: (r) => r.job.name, primary: true }, { key: "source", label: "Source", render: (r) => r.job.source ?? "—", }, { key: "schedule", label: "Schedule", render: (r) => formatInterval(r.job.schedule_interval_seconds), }, { key: "status", label: "Last status", render: (r) => {r.status}, }, ]; export default function BackupJobsTable({ jobs, latestRuns }: Props) { const isMobile = useIsMobile(); if (isMobile) { const cardRows: JobCardRow[] = jobs.map((job) => { const run = latestRuns.get(job.id); return { job, status: run?.status ?? "unknown", run_started: run?.started_at ?? null, }; }); return ( r.job.id} /> ); } return (
Name Source Target Schedule Last Status Last Run Next Expected {jobs.map((job) => { const run = latestRuns.get(job.id); const status = run?.status ?? "unknown"; const nextExpected = run && job.schedule_interval_seconds ? run.started_at + job.schedule_interval_seconds : null; return ( {job.name} {job.source ?? "—"} {job.target ?? "—"} {formatInterval(job.schedule_interval_seconds)} {status} {formatTimestamp(run?.started_at ?? null)} {formatTimestamp(nextExpected)} ); })}
); }