import { useState, useRef, useEffect, Fragment } from "react"; import { useTranslation } from "react-i18next"; import type { BudgetRow } from "../../shared/types"; const fmt = new Intl.NumberFormat("en-CA", { style: "currency", currency: "CAD" }); interface BudgetTableProps { rows: BudgetRow[]; onUpdatePlanned: (categoryId: number, amount: number) => void; } export default function BudgetTable({ rows, onUpdatePlanned }: BudgetTableProps) { const { t } = useTranslation(); const [editingCategoryId, setEditingCategoryId] = useState(null); const [editingValue, setEditingValue] = useState(""); const inputRef = useRef(null); useEffect(() => { if (editingCategoryId !== null && inputRef.current) { inputRef.current.focus(); inputRef.current.select(); } }, [editingCategoryId]); const handleStartEdit = (row: BudgetRow) => { setEditingCategoryId(row.category_id); setEditingValue(row.planned === 0 ? "" : String(row.planned)); }; const handleSave = () => { if (editingCategoryId === null) return; const amount = parseFloat(editingValue) || 0; onUpdatePlanned(editingCategoryId, amount); setEditingCategoryId(null); }; const handleCancel = () => { setEditingCategoryId(null); }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") handleSave(); if (e.key === "Escape") handleCancel(); }; // Group rows by type const grouped: Record = {}; for (const row of rows) { const key = row.category_type; if (!grouped[key]) grouped[key] = []; grouped[key].push(row); } const typeOrder = ["expense", "income", "transfer"] as const; const typeLabelKeys: Record = { expense: "budget.expenses", income: "budget.income", transfer: "budget.transfers", }; const totalPlanned = rows.reduce((s, r) => s + r.planned, 0); const totalActual = rows.reduce((s, r) => s + Math.abs(r.actual), 0); const totalDifference = totalPlanned - totalActual; if (rows.length === 0) { return (

{t("budget.noCategories")}

); } return (
{typeOrder.map((type) => { const group = grouped[type]; if (!group || group.length === 0) return null; return ( {group.map((row) => ( ))} ); })}
{t("budget.category")} {t("budget.planned")} {t("budget.actual")} {t("budget.difference")}
{t(typeLabelKeys[type])}
{row.category_name}
{editingCategoryId === row.category_id ? ( setEditingValue(e.target.value)} onBlur={handleSave} onKeyDown={handleKeyDown} className="w-full text-right bg-[var(--background)] border border-[var(--border)] rounded px-2 py-1 text-sm focus:outline-none focus:ring-1 focus:ring-[var(--primary)]" /> ) : ( )} {row.actual === 0 ? ( ) : ( fmt.format(Math.abs(row.actual)) )} {row.planned === 0 && row.actual === 0 ? ( ) : ( = 0 ? "text-[var(--positive)]" : "text-[var(--negative)]" } > {fmt.format(row.difference)} )}
{t("common.total")} {fmt.format(totalPlanned)} {fmt.format(totalActual)} = 0 ? "text-[var(--positive)]" : "text-[var(--negative)]" } > {fmt.format(totalDifference)}
); }