Section subtotals: text-sm font-semibold with more padding. Grand totals: text-sm font-bold with border-t-2 and extra padding. Applied consistently across BudgetTable, BudgetVsActualTable, MonthlyTrendsTable, CategoryOverTimeTable, CategoryTable, and DynamicReportTable. Closes #14 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
74 lines
3.1 KiB
TypeScript
74 lines
3.1 KiB
TypeScript
import { useTranslation } from "react-i18next";
|
|
import type { CategoryBreakdownItem } from "../../shared/types";
|
|
|
|
const cadFormatter = (value: number) =>
|
|
new Intl.NumberFormat("en-CA", { style: "currency", currency: "CAD", maximumFractionDigits: 0 }).format(value);
|
|
|
|
interface CategoryTableProps {
|
|
data: CategoryBreakdownItem[];
|
|
hiddenCategories?: Set<string>;
|
|
}
|
|
|
|
export default function CategoryTable({ data, hiddenCategories }: CategoryTableProps) {
|
|
const { t } = useTranslation();
|
|
|
|
const visibleData = hiddenCategories?.size
|
|
? data.filter((d) => !hiddenCategories.has(d.category_name))
|
|
: data;
|
|
|
|
if (visibleData.length === 0) {
|
|
return (
|
|
<div className="bg-[var(--card)] border border-[var(--border)] rounded-xl p-8 text-center text-[var(--muted-foreground)]">
|
|
{t("dashboard.noData")}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const grandTotal = visibleData.reduce((sum, row) => sum + row.total, 0);
|
|
|
|
return (
|
|
<div className="bg-[var(--card)] border border-[var(--border)] rounded-xl overflow-hidden">
|
|
<div className="overflow-x-auto overflow-y-auto" style={{ maxHeight: "calc(100vh - 220px)" }}>
|
|
<table className="w-full text-sm">
|
|
<thead className="sticky top-0 z-20">
|
|
<tr className="border-b border-[var(--border)] bg-[var(--card)]">
|
|
<th className="text-left px-3 py-2 font-medium text-[var(--muted-foreground)] bg-[var(--card)]">
|
|
{t("budget.category")}
|
|
</th>
|
|
<th className="text-right px-3 py-2 font-medium text-[var(--muted-foreground)] bg-[var(--card)]">
|
|
{t("common.total")}
|
|
</th>
|
|
<th className="text-right px-3 py-2 font-medium text-[var(--muted-foreground)] bg-[var(--card)]">
|
|
%
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{visibleData.map((row) => (
|
|
<tr key={row.category_id ?? "uncategorized"} className="border-b border-[var(--border)]/50">
|
|
<td className="px-3 py-1.5">
|
|
<span className="flex items-center gap-2">
|
|
<span
|
|
className="w-2.5 h-2.5 rounded-full shrink-0"
|
|
style={{ backgroundColor: row.category_color }}
|
|
/>
|
|
{row.category_name}
|
|
</span>
|
|
</td>
|
|
<td className="text-right px-3 py-1.5">{cadFormatter(row.total)}</td>
|
|
<td className="text-right px-3 py-1.5 text-[var(--muted-foreground)]">
|
|
{grandTotal !== 0 ? `${((row.total / grandTotal) * 100).toFixed(1)}%` : "—"}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
<tr className="border-t-2 border-[var(--border)] font-bold text-sm bg-[var(--muted)]/20">
|
|
<td className="px-3 py-3">{t("common.total")}</td>
|
|
<td className="text-right px-3 py-3">{cadFormatter(grandTotal)}</td>
|
|
<td className="text-right px-3 py-3 text-[var(--muted-foreground)]">100%</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|