import { useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; 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 { BackupRun } from "../types/backups"; interface Props { runs: BackupRun[]; } function formatBytes(bytes: number | null): string { if (bytes === null || bytes === undefined) return "—"; if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`; } function formatDuration(ms: number | null): string { if (ms === null || ms === undefined) return "—"; if (ms < 1000) return `${ms}ms`; if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`; if (ms < 3600_000) return `${(ms / 60_000).toFixed(1)}m`; return `${(ms / 3600_000).toFixed(1)}h`; } function formatTimestamp(ts: number): string { return new Date(ts * 1000).toLocaleString(); } type StatusVariant = "success" | "destructive" | "warning"; /** * Map a run status onto a Badge variant per design §2.3: * `success` → success (chart-2); `failure` → destructive (chart-4); * `in_progress` → warning (chart-3). */ function statusVariant(status: string): StatusVariant { if (status === "success") return "success"; if (status === "failure") return "destructive"; return "warning"; } // Mobile card fields (spec R3.2): job_id is primary; status/duration/size/ // started give the at-a-glance info. See OpenSpec change `mobile-responsive-parity`. const runCardFields: MobileCardField[] = [ { key: "job", label: "Job", render: (r) => r.job_id, primary: true }, { key: "status", label: "Status", render: (r) => {r.status}, }, { key: "duration", label: "Duration", render: (r) => formatDuration(r.duration_ms), }, { key: "size", label: "Size", render: (r) => formatBytes(r.bytes_transferred), }, { key: "started", label: "Started", render: (r) => formatTimestamp(r.started_at), }, ]; export default function BackupRunsTable({ runs }: Props) { const [statusFilter, setStatusFilter] = useState("all"); const isMobile = useIsMobile(); const filteredRuns = statusFilter === "all" ? runs : runs.filter((r) => r.status === statusFilter); return (
{isMobile ? ( r.id} /> ) : (
Job Status Duration Size Started {filteredRuns.map((run) => ( {run.job_id} {run.status} {formatDuration(run.duration_ms)} {formatBytes(run.bytes_transferred)} {formatTimestamp(run.started_at)} ))}
)}
); }