"use client"; import { useState, useMemo, useEffect } from "react"; import { Search, TrendingUp, TrendingDown, LineChart as LineChartIcon, BarChart3, Scale } from "lucide-react"; import { ZAR2 } from "@/lib/utils"; import { MachineLineChart, type ChartView } from "@/components/MachineLineChart"; import { computeProfit } from "@/lib/profit"; type Item = { id: string; code: string; name: string; bucket: { income: (number | null)[]; fixed: (number | null)[]; running: (number | null)[] }; totalIncome: number; totalProfit: number; }; const VIEW_KEY = "mp:chartsView"; export function ChartsGrid({ items }: { items: Item[] }) { const [query, setQuery] = useState(""); const [filter, setFilter] = useState<"all" | "profitable" | "loss">("all"); const [view, setViewState] = useState("lines"); // Persist view in localStorage (Part A.5) useEffect(() => { try { const stored = localStorage.getItem(VIEW_KEY); if (stored === "lines" || stored === "stack" || stored === "profit") setViewState(stored); } catch {} }, []); const setView = (v: ChartView) => { setViewState(v); try { localStorage.setItem(VIEW_KEY, v); } catch {} }; const filtered = useMemo(() => { let list = items; if (query.trim()) { const q = query.toLowerCase(); list = list.filter((i) => i.code.toLowerCase().includes(q) || i.name.toLowerCase().includes(q)); } if (filter === "profitable") list = list.filter((i) => i.totalProfit > 0); if (filter === "loss") list = list.filter((i) => i.totalProfit < 0); list = [...list].sort((a, b) => b.totalIncome - a.totalIncome); return list; }, [items, query, filter]); const stats = useMemo(() => { const profitable = items.filter((i) => i.totalProfit > 0).length; const totalIncome = items.reduce((s, i) => s + i.totalIncome, 0); return { profitable, total: items.length, totalIncome }; }, [items]); // Legend swatches per view (Part A.5) const legend = view === "profit" ? [{ color: "var(--color-chart-running)", label: "Profit / Loss" }] : [ { color: "var(--color-chart-income)", label: "Income" }, { color: "var(--color-chart-fixed)", label: "Fixed" }, { color: "var(--color-chart-running)", label: "Running" }, ]; return ( <>

Machine Charts

{stats.total} machines · {stats.profitable} profitable · {ZAR2(stats.totalIncome)} total income

{legend.map((l) => (
{l.label}
))}
{/* Toolbar */}
setQuery(e.target.value)} placeholder="Search by code or name…" className="w-full h-9 pl-9 pr-3 bg-gray-50 dark:bg-white/5 border border-transparent rounded-lg text-sm placeholder:text-gray-400 focus:bg-white dark:focus:bg-[#0f1419] focus:border-[#30638E] focus:outline-none" />
{[ { id: "all", label: "All" }, { id: "profitable", label: "Profitable" }, { id: "loss", label: "Loss-making" }, ].map((f) => ( ))}
View {[ { id: "lines", label: "Lines", icon: LineChartIcon }, { id: "stack", label: "Cost stack", icon: BarChart3 }, { id: "profit", label: "Profit", icon: Scale }, ].map((v) => ( ))}
{filtered.length === 0 ? (
No machines match "{query}"
) : (
{filtered.map((item) => { const positive = computeProfit({ income: item.totalIncome, fixed: 0, running: 0 }) >= 0 || item.totalIncome > 0; // Profit uses computeProfit() — Part A.6 const profit = computeProfit({ income: item.totalIncome, fixed: 0, running: 0 }); const positiveProfit = profit >= 0; return (
{item.code}

{item.name}

{positiveProfit ? : } {positiveProfit ? "+" : ""}{ZAR2(profit)}
YTD income {ZAR2(item.totalIncome)}
); })}
)} ); }