"use client"; import { useEffect, useMemo, useRef, useState } from "react"; import Link from "next/link"; import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis, } from "recharts"; import { useI18n } from "@/lib/i18n/useI18n"; type MachineRow = { id: string; name: string; location?: string | null; }; type ImpactSummary = { currency: string; totals: { total: number; slowCycle: number; microstop: number; macrostop: number; scrap: number; }; byDay: Array<{ day: string; total: number; slowCycle: number; microstop: number; macrostop: number; scrap: number; }>; }; type ImpactResponse = { ok: boolean; currencySummaries: ImpactSummary[]; }; function formatMoney(value: number, currency: string, locale: string) { if (!Number.isFinite(value)) return "--"; try { return new Intl.NumberFormat(locale, { style: "currency", currency, maximumFractionDigits: 0, }).format(value); } catch { return `${value.toFixed(0)} ${currency}`; } } export default function FinancialClient({ initialRole = null, initialMachines = [], initialImpact = null, }: { initialRole?: string | null; initialMachines?: MachineRow[]; initialImpact?: ImpactResponse | null; }) { const { locale, t } = useI18n(); const [role, setRole] = useState(initialRole); const [machines, setMachines] = useState(() => initialMachines); const [impact, setImpact] = useState(initialImpact); const [range, setRange] = useState("7d"); const [machineFilter, setMachineFilter] = useState(""); const [locationFilter, setLocationFilter] = useState(""); const [skuFilter, setSkuFilter] = useState(""); const [currencyFilter, setCurrencyFilter] = useState(""); const [loading, setLoading] = useState(() => initialMachines.length === 0); const skipInitialImpactRef = useRef(true); const locations = useMemo(() => { const seen = new Set(); for (const m of machines) { if (!m.location) continue; seen.add(m.location); } return Array.from(seen).sort(); }, [machines]); useEffect(() => { if (initialRole != null) return; let alive = true; async function loadMe() { try { const res = await fetch("/api/me", { cache: "no-store" }); const data = await res.json().catch(() => ({})); if (!alive) return; setRole(data?.membership?.role ?? null); } catch { if (alive) setRole(null); } } loadMe(); return () => { alive = false; }; }, [initialRole]); useEffect(() => { if (initialMachines.length) { setLoading(false); return; } let alive = true; async function loadMachines() { try { const res = await fetch("/api/machines", { cache: "no-store" }); const json = await res.json().catch(() => ({})); if (!alive) return; setMachines(json.machines ?? []); } catch { if (!alive) return; } finally { if (alive) setLoading(false); } } loadMachines(); return () => { alive = false; }; }, [initialMachines]); useEffect(() => { let alive = true; const controller = new AbortController(); async function loadImpact() { if (role == null) return; if (role !== "OWNER") return; const isDefault = range === "7d" && !machineFilter && !locationFilter && !skuFilter && !currencyFilter; if (skipInitialImpactRef.current) { skipInitialImpactRef.current = false; if (initialImpact && isDefault) return; } const params = new URLSearchParams(); params.set("range", range); if (machineFilter) params.set("machineId", machineFilter); if (locationFilter) params.set("location", locationFilter); if (skuFilter) params.set("sku", skuFilter); if (currencyFilter) params.set("currency", currencyFilter); try { const res = await fetch(`/api/financial/impact?${params.toString()}`, { cache: "no-store", signal: controller.signal, }); const json = await res.json().catch(() => ({})); if (!alive) return; setImpact(json); } catch { if (alive) setImpact(null); } } loadImpact(); return () => { alive = false; controller.abort(); }; }, [currencyFilter, initialImpact, locationFilter, machineFilter, range, role, skuFilter]); const selectedSummary = impact?.currencySummaries?.[0] ?? null; const chartData = selectedSummary?.byDay ?? []; const exportQuery = useMemo(() => { const params = new URLSearchParams(); params.set("range", range); if (machineFilter) params.set("machineId", machineFilter); if (locationFilter) params.set("location", locationFilter); if (skuFilter) params.set("sku", skuFilter); if (currencyFilter) params.set("currency", currencyFilter); return params.toString(); }, [range, machineFilter, locationFilter, skuFilter, currencyFilter]); const htmlHref = `/api/financial/export/pdf?${exportQuery}`; const csvHref = `/api/financial/export/excel?${exportQuery}`; if (role && role !== "OWNER") { return (
{t("financial.ownerOnly")}
); } return (

{t("financial.title")}

{t("financial.subtitle")}

{t("financial.costsMoved")}{" "} {t("financial.costsMovedLink")} .
{(impact?.currencySummaries ?? []).slice(0, 4).map((summary) => (
{t("financial.totalLoss")}
{formatMoney(summary.totals.total, summary.currency, locale)}
{t("financial.currencyLabel", { currency: summary.currency })}
))} {!impact?.currencySummaries?.length && (
{t("financial.noImpact")}
)}

{t("financial.chart.title")}

{t("financial.chart.subtitle")}

{["24h", "7d", "30d"].map((value) => ( ))}

{t("financial.filters.title")}

setSkuFilter(event.target.value)} placeholder={t("financial.filters.skuPlaceholder")} />
setCurrencyFilter(event.target.value.toUpperCase())} placeholder={t("financial.filters.currencyPlaceholder")} />
{loading &&
{t("financial.loadingMachines")}
}
); }