"use client"; import { useEffect, useMemo, useState } from "react"; import { useI18n } from "@/lib/i18n/useI18n"; import type { RecapMachineStatus, RecapSummaryResponse } from "@/lib/recap/types"; import RecapMachineCard from "@/components/recap/RecapMachineCard"; type Props = { initialData: RecapSummaryResponse; }; function statusLabel(status: RecapMachineStatus, t: (key: string) => string) { if (status === "running") return t("recap.status.running"); if (status === "mold-change") return t("recap.status.moldChange"); if (status === "stopped") return t("recap.status.stopped"); return t("recap.status.offline"); } export default function RecapGridClient({ initialData }: Props) { const { t } = useI18n(); const [data, setData] = useState(initialData); const [loading, setLoading] = useState(false); const [locationFilter, setLocationFilter] = useState("all"); const [statusFilter, setStatusFilter] = useState<"all" | RecapMachineStatus>("all"); const [nowMs, setNowMs] = useState(() => Date.now()); useEffect(() => { const timer = window.setInterval(() => setNowMs(Date.now()), 1000); return () => window.clearInterval(timer); }, []); useEffect(() => { let alive = true; async function refresh() { setLoading(true); try { const res = await fetch(`/api/recap/summary?hours=${data.range.hours}`, { cache: "no-store" }); const json = await res.json().catch(() => null); if (!alive || !json || !res.ok) return; setData(json as RecapSummaryResponse); } finally { if (alive) setLoading(false); } } const onFocus = () => { void refresh(); }; const interval = window.setInterval(onFocus, 60000); window.addEventListener("focus", onFocus); return () => { alive = false; window.clearInterval(interval); window.removeEventListener("focus", onFocus); }; }, [data.range.hours]); const locationOptions = useMemo(() => { const set = new Set(); for (const machine of data.machines) { if (machine.location) set.add(machine.location); } return [...set].sort((a, b) => a.localeCompare(b)); }, [data.machines]); const filteredMachines = useMemo(() => { return data.machines.filter((machine) => { if (locationFilter !== "all" && machine.location !== locationFilter) return false; if (statusFilter !== "all" && machine.status !== statusFilter) return false; return true; }); }, [data.machines, locationFilter, statusFilter]); const generatedAtMs = new Date(data.generatedAt).getTime(); const freshAgeSec = Number.isFinite(generatedAtMs) ? Math.max(0, Math.floor((nowMs - generatedAtMs) / 1000)) : null; return (

{t("recap.grid.title")}

{t("recap.grid.subtitle")}

{freshAgeSec != null ? (

{t("recap.grid.updatedAgo", { sec: freshAgeSec })}

) : null}
{loading && data.machines.length === 0 ? (
{Array.from({ length: 6 }).map((_, idx) => (
))}
) : null} {loading && data.machines.length > 0 ? (
{t("common.loading")}
) : null} {filteredMachines.length === 0 ? (
{t("recap.grid.empty")}
) : (
{filteredMachines.map((machine) => ( ))}
)}
); }