"use client"; import Link from "next/link"; import { useEffect, useState } from "react"; import { useI18n } from "@/lib/i18n/useI18n"; import type { RecapSummaryMachine, RecapTimelineResponse } from "@/lib/recap/types"; import RecapMiniTimeline from "@/components/recap/RecapMiniTimeline"; type Props = { machine: RecapSummaryMachine; rangeStart: string; rangeEnd: string; }; const STATUS_DOT: Record = { running: "bg-emerald-400", "mold-change": "bg-amber-400", stopped: "bg-red-500", offline: "bg-zinc-500", }; function statusLabel(status: RecapSummaryMachine["status"], 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"); } function toInt(value: number | null | undefined) { if (value == null || Number.isNaN(value)) return 0; return Math.max(0, Math.round(value)); } export default function RecapMachineCard({ machine, rangeStart, rangeEnd }: Props) { const { t, locale } = useI18n(); const [timeline, setTimeline] = useState(null); const zeroActivity = machine.goodParts === 0 && machine.scrap === 0 && machine.stopsCount === 0; const primaryMetric = machine.oee == null ? "—" : `${machine.oee.toFixed(1)}%`; const ongoingStopMin = machine.ongoingStopMin ?? 0; const isUrgent = machine.status === "stopped" && ongoingStopMin >= 5; const timelineSegments = timeline?.segments ?? machine.miniTimeline; const timelineStart = timeline?.range.start ?? rangeStart; const timelineEnd = timeline?.range.end ?? rangeEnd; const hasTimelineData = timeline?.hasData ?? timelineSegments.length > 0; const lastSeenLabel = machine.lastActivityMin == null ? t("common.never") : t("recap.card.lastActivity", { min: toInt(machine.lastActivityMin) }); const footerText = machine.activeWorkOrderId ? t("recap.card.activeWorkOrder", { id: machine.activeWorkOrderId }) : lastSeenLabel; const moldMinutes = machine.moldChange?.active ? machine.moldChange.elapsedMin : null; useEffect(() => { let alive = true; async function loadTimeline() { try { const res = await fetch( `/api/recap/${machine.machineId}/timeline?range=24h&compact=1&maxSegments=60`, { cache: "no-store" } ); const json = await res.json().catch(() => null); if (!alive || !res.ok || !json) return; setTimeline(json as RecapTimelineResponse); } catch { } } void loadTimeline(); const timer = window.setInterval(() => { void loadTimeline(); }, 60000); return () => { alive = false; window.clearInterval(timer); }; }, [machine.machineId]); return (
{machine.name}
{machine.location || t("common.na")}
{statusLabel(machine.status, t)}
{primaryMetric}
{t("recap.card.oee")}
{machine.oee == null ?
{t("recap.kpi.noData")}
: null} {zeroActivity ?
{t("recap.card.noProduction")}
: null}
{t("recap.card.good")}: {machine.goodParts} {t("recap.card.scrap")}: {machine.scrap} {t("recap.card.stops")}: {machine.stopsCount}
{machine.moldChange?.active ? (
{t("recap.card.moldChangeActive", { min: toInt(moldMinutes) })}
) : null} {machine.offlineForMin != null && machine.offlineForMin > 10 ? (
{t("recap.banner.offline", { min: toInt(machine.offlineForMin) })}
) : null}
{isUrgent ? t("recap.card.stoppedFor", { min: ongoingStopMin }) + (machine.activeWorkOrderId ? ` · WO ${machine.activeWorkOrderId}` : "") : footerText}
); }