All checks were successful
Release / build-and-release (push) Successful in 26m12s
- Add table/chart toggle for Trends, By Category, and Over Time reports - Add "Show amounts" toggle to display values on chart elements - Add filter panel with category checkboxes and source dropdown - Add source filter at SQL level for all chart report queries - Add sticky headers on Dynamic Report and Budget vs Actual tables - Add interactive hover: dimmed non-hovered bars, filtered tooltip, legend hover - Fix comment icon color to match split indicator (orange) (#7) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
77 lines
3.5 KiB
TypeScript
77 lines
3.5 KiB
TypeScript
import { useTranslation } from "react-i18next";
|
|
import type { MonthlyTrendItem } from "../../shared/types";
|
|
|
|
const cadFormatter = (value: number) =>
|
|
new Intl.NumberFormat("en-CA", { style: "currency", currency: "CAD", maximumFractionDigits: 0 }).format(value);
|
|
|
|
function formatMonth(month: string): string {
|
|
const [year, m] = month.split("-");
|
|
const date = new Date(Number(year), Number(m) - 1);
|
|
return date.toLocaleDateString("default", { month: "short", year: "2-digit" });
|
|
}
|
|
|
|
interface MonthlyTrendsTableProps {
|
|
data: MonthlyTrendItem[];
|
|
}
|
|
|
|
export default function MonthlyTrendsTable({ data }: MonthlyTrendsTableProps) {
|
|
const { t } = useTranslation();
|
|
|
|
if (data.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 totals = data.reduce(
|
|
(acc, row) => ({ income: acc.income + row.income, expenses: acc.expenses + row.expenses }),
|
|
{ income: 0, expenses: 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("reports.pivot.month")}
|
|
</th>
|
|
<th className="text-right px-3 py-2 font-medium text-[var(--muted-foreground)] bg-[var(--card)]">
|
|
{t("dashboard.income")}
|
|
</th>
|
|
<th className="text-right px-3 py-2 font-medium text-[var(--muted-foreground)] bg-[var(--card)]">
|
|
{t("dashboard.expenses")}
|
|
</th>
|
|
<th className="text-right px-3 py-2 font-medium text-[var(--muted-foreground)] bg-[var(--card)]">
|
|
{t("dashboard.net")}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{data.map((row) => (
|
|
<tr key={row.month} className="border-b border-[var(--border)]/50">
|
|
<td className="px-3 py-1.5">{formatMonth(row.month)}</td>
|
|
<td className="text-right px-3 py-1.5 text-[var(--positive)]">{cadFormatter(row.income)}</td>
|
|
<td className="text-right px-3 py-1.5 text-[var(--negative)]">{cadFormatter(row.expenses)}</td>
|
|
<td className={`text-right px-3 py-1.5 ${row.income - row.expenses >= 0 ? "text-[var(--positive)]" : "text-[var(--negative)]"}`}>
|
|
{cadFormatter(row.income - row.expenses)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
<tr className="border-t-2 border-[var(--border)] font-bold bg-[var(--muted)]/20">
|
|
<td className="px-3 py-2">{t("common.total")}</td>
|
|
<td className="text-right px-3 py-2 text-[var(--positive)]">{cadFormatter(totals.income)}</td>
|
|
<td className="text-right px-3 py-2 text-[var(--negative)]">{cadFormatter(totals.expenses)}</td>
|
|
<td className={`text-right px-3 py-2 ${totals.income - totals.expenses >= 0 ? "text-[var(--positive)]" : "text-[var(--negative)]"}`}>
|
|
{cadFormatter(totals.income - totals.expenses)}
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|