Improve visual hierarchy of subtotals and totals in all tables

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>
This commit is contained in:
le king fu 2026-03-07 09:10:27 -05:00
parent e1192beca3
commit 861d78eca2
6 changed files with 43 additions and 43 deletions

View file

@ -387,12 +387,12 @@ export default function BudgetTable({ rows, onUpdatePlanned, onSplitEvenly }: Bu
</tr> </tr>
{reorderRows(group, subtotalsOnTop).map((row) => renderRow(row))} {reorderRows(group, subtotalsOnTop).map((row) => renderRow(row))}
<tr className="bg-[var(--muted)]/40 border-b border-[var(--border)]"> <tr className="bg-[var(--muted)]/40 border-b border-[var(--border)]">
<td className="py-2 px-3 sticky left-0 bg-[var(--muted)]/40 z-10 text-xs font-semibold"> <td className="py-2.5 px-3 sticky left-0 bg-[var(--muted)]/40 z-10 text-sm font-semibold">
{t(typeTotalKeys[type])} {t(typeTotalKeys[type])}
</td> </td>
<td className="py-2 px-2 text-right text-xs font-semibold">{formatSigned(sectionAnnualTotal)}</td> <td className="py-2.5 px-2 text-right text-sm font-semibold">{formatSigned(sectionAnnualTotal)}</td>
{sectionMonthTotals.map((total, mIdx) => ( {sectionMonthTotals.map((total, mIdx) => (
<td key={mIdx} className="py-2 px-2 text-right text-xs font-semibold"> <td key={mIdx} className="py-2.5 px-2 text-right text-sm font-semibold">
{formatSigned(total)} {formatSigned(total)}
</td> </td>
))} ))}
@ -401,11 +401,11 @@ export default function BudgetTable({ rows, onUpdatePlanned, onSplitEvenly }: Bu
); );
})} })}
{/* Totals row */} {/* Totals row */}
<tr className="bg-[var(--muted)] font-semibold"> <tr className="bg-[var(--muted)] font-bold border-t-2 border-[var(--border)]">
<td className="py-2.5 px-3 sticky left-0 bg-[var(--muted)] z-10 text-xs">{t("common.total")}</td> <td className="py-3 px-3 sticky left-0 bg-[var(--muted)] z-10 text-sm">{t("common.total")}</td>
<td className="py-2.5 px-2 text-right text-xs">{formatSigned(annualTotal)}</td> <td className="py-3 px-2 text-right text-sm">{formatSigned(annualTotal)}</td>
{monthTotals.map((total, mIdx) => ( {monthTotals.map((total, mIdx) => (
<td key={mIdx} className="py-2.5 px-2 text-right text-xs"> <td key={mIdx} className="py-3 px-2 text-right text-sm">
{formatSigned(total)} {formatSigned(total)}
</td> </td>
))} ))}

View file

@ -256,26 +256,26 @@ export default function BudgetVsActualTable({ data }: BudgetVsActualTableProps)
</tr> </tr>
); );
})} })}
<tr className="border-b border-[var(--border)] bg-[var(--muted)]/40 font-semibold"> <tr className="border-b border-[var(--border)] bg-[var(--muted)]/40 font-semibold text-sm">
<td className="px-3 py-2 text-xs">{t(typeTotalKeys[section.type])}</td> <td className="px-3 py-2.5">{t(typeTotalKeys[section.type])}</td>
<td className="text-right px-3 py-2 border-l border-[var(--border)]/50"> <td className="text-right px-3 py-2.5 border-l border-[var(--border)]/50">
{cadFormatter(sectionTotals.monthActual)} {cadFormatter(sectionTotals.monthActual)}
</td> </td>
<td className="text-right px-3 py-2">{cadFormatter(sectionTotals.monthBudget)}</td> <td className="text-right px-3 py-2.5">{cadFormatter(sectionTotals.monthBudget)}</td>
<td className={`text-right px-3 py-2 ${variationColor(sectionTotals.monthVariation)}`}> <td className={`text-right px-3 py-2.5 ${variationColor(sectionTotals.monthVariation)}`}>
{cadFormatter(sectionTotals.monthVariation)} {cadFormatter(sectionTotals.monthVariation)}
</td> </td>
<td className={`text-right px-3 py-2 ${variationColor(sectionTotals.monthVariation)}`}> <td className={`text-right px-3 py-2.5 ${variationColor(sectionTotals.monthVariation)}`}>
{pctFormatter(sectionMonthPct)} {pctFormatter(sectionMonthPct)}
</td> </td>
<td className="text-right px-3 py-2 border-l border-[var(--border)]/50"> <td className="text-right px-3 py-2.5 border-l border-[var(--border)]/50">
{cadFormatter(sectionTotals.ytdActual)} {cadFormatter(sectionTotals.ytdActual)}
</td> </td>
<td className="text-right px-3 py-2">{cadFormatter(sectionTotals.ytdBudget)}</td> <td className="text-right px-3 py-2.5">{cadFormatter(sectionTotals.ytdBudget)}</td>
<td className={`text-right px-3 py-2 ${variationColor(sectionTotals.ytdVariation)}`}> <td className={`text-right px-3 py-2.5 ${variationColor(sectionTotals.ytdVariation)}`}>
{cadFormatter(sectionTotals.ytdVariation)} {cadFormatter(sectionTotals.ytdVariation)}
</td> </td>
<td className={`text-right px-3 py-2 ${variationColor(sectionTotals.ytdVariation)}`}> <td className={`text-right px-3 py-2.5 ${variationColor(sectionTotals.ytdVariation)}`}>
{pctFormatter(sectionYtdPct)} {pctFormatter(sectionYtdPct)}
</td> </td>
</tr> </tr>
@ -283,26 +283,26 @@ export default function BudgetVsActualTable({ data }: BudgetVsActualTableProps)
); );
})} })}
{/* Grand totals */} {/* Grand totals */}
<tr className="border-t-2 border-[var(--border)] font-bold bg-[var(--muted)]/20"> <tr className="border-t-2 border-[var(--border)] font-bold text-sm bg-[var(--muted)]/20">
<td className="px-3 py-2">{t("common.total")}</td> <td className="px-3 py-3">{t("common.total")}</td>
<td className="text-right px-3 py-2 border-l border-[var(--border)]/50"> <td className="text-right px-3 py-3 border-l border-[var(--border)]/50">
{cadFormatter(totals.monthActual)} {cadFormatter(totals.monthActual)}
</td> </td>
<td className="text-right px-3 py-2">{cadFormatter(totals.monthBudget)}</td> <td className="text-right px-3 py-3">{cadFormatter(totals.monthBudget)}</td>
<td className={`text-right px-3 py-2 ${variationColor(totals.monthVariation)}`}> <td className={`text-right px-3 py-3 ${variationColor(totals.monthVariation)}`}>
{cadFormatter(totals.monthVariation)} {cadFormatter(totals.monthVariation)}
</td> </td>
<td className={`text-right px-3 py-2 ${variationColor(totals.monthVariation)}`}> <td className={`text-right px-3 py-3 ${variationColor(totals.monthVariation)}`}>
{pctFormatter(totalMonthPct)} {pctFormatter(totalMonthPct)}
</td> </td>
<td className="text-right px-3 py-2 border-l border-[var(--border)]/50"> <td className="text-right px-3 py-3 border-l border-[var(--border)]/50">
{cadFormatter(totals.ytdActual)} {cadFormatter(totals.ytdActual)}
</td> </td>
<td className="text-right px-3 py-2">{cadFormatter(totals.ytdBudget)}</td> <td className="text-right px-3 py-3">{cadFormatter(totals.ytdBudget)}</td>
<td className={`text-right px-3 py-2 ${variationColor(totals.ytdVariation)}`}> <td className={`text-right px-3 py-3 ${variationColor(totals.ytdVariation)}`}>
{cadFormatter(totals.ytdVariation)} {cadFormatter(totals.ytdVariation)}
</td> </td>
<td className={`text-right px-3 py-2 ${variationColor(totals.ytdVariation)}`}> <td className={`text-right px-3 py-3 ${variationColor(totals.ytdVariation)}`}>
{pctFormatter(totalYtdPct)} {pctFormatter(totalYtdPct)}
</td> </td>
</tr> </tr>

View file

@ -80,8 +80,8 @@ export default function CategoryOverTimeTable({ data, hiddenCategories }: Catego
</tr> </tr>
); );
})} })}
<tr className="border-t-2 border-[var(--border)] font-bold bg-[var(--muted)]/20"> <tr className="border-t-2 border-[var(--border)] font-bold text-sm bg-[var(--muted)]/20">
<td className="px-3 py-2 sticky left-0 bg-[var(--muted)]/20 z-10">{t("common.total")}</td> <td className="px-3 py-3 sticky left-0 bg-[var(--muted)]/20 z-10">{t("common.total")}</td>
{months.map((month) => { {months.map((month) => {
const monthData = data.data.find((d) => d.month === month); const monthData = data.data.find((d) => d.month === month);
const monthTotal = visibleCategories.reduce( const monthTotal = visibleCategories.reduce(
@ -89,12 +89,12 @@ export default function CategoryOverTimeTable({ data, hiddenCategories }: Catego
0, 0,
); );
return ( return (
<td key={month} className="text-right px-3 py-2"> <td key={month} className="text-right px-3 py-3">
{cadFormatter(monthTotal)} {cadFormatter(monthTotal)}
</td> </td>
); );
})} })}
<td className="text-right px-3 py-2 border-l border-[var(--border)]/50"> <td className="text-right px-3 py-3 border-l border-[var(--border)]/50">
{cadFormatter( {cadFormatter(
visibleCategories.reduce( visibleCategories.reduce(
(sum, cat) => sum + data.data.reduce((s, d) => s + ((d as Record<string, unknown>)[cat] as number || 0), 0), (sum, cat) => sum + data.data.reduce((s, d) => s + ((d as Record<string, unknown>)[cat] as number || 0), 0),

View file

@ -61,10 +61,10 @@ export default function CategoryTable({ data, hiddenCategories }: CategoryTableP
</td> </td>
</tr> </tr>
))} ))}
<tr className="border-t-2 border-[var(--border)] font-bold bg-[var(--muted)]/20"> <tr className="border-t-2 border-[var(--border)] font-bold text-sm bg-[var(--muted)]/20">
<td className="px-3 py-2">{t("common.total")}</td> <td className="px-3 py-3">{t("common.total")}</td>
<td className="text-right px-3 py-2">{cadFormatter(grandTotal)}</td> <td className="text-right px-3 py-3">{cadFormatter(grandTotal)}</td>
<td className="text-right px-3 py-2 text-[var(--muted-foreground)]">100%</td> <td className="text-right px-3 py-3 text-[var(--muted-foreground)]">100%</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

@ -192,13 +192,13 @@ export default function DynamicReportTable({ config, result }: DynamicReportTabl
)) ))
)} )}
{/* Grand total */} {/* Grand total */}
<tr className="border-t-2 border-[var(--border)] font-bold bg-[var(--muted)]/20"> <tr className="border-t-2 border-[var(--border)] font-bold text-sm bg-[var(--muted)]/20">
<td colSpan={rowDims.length || 1} className="px-3 py-2"> <td colSpan={rowDims.length || 1} className="px-3 py-3">
{t("reports.pivot.total")} {t("reports.pivot.total")}
</td> </td>
{colValues.map((colVal) => {colValues.map((colVal) =>
measures.map((m) => ( measures.map((m) => (
<td key={`total-${colVal}-${m}`} className="text-right px-3 py-2 border-l border-[var(--border)]/50"> <td key={`total-${colVal}-${m}`} className="text-right px-3 py-3 border-l border-[var(--border)]/50">
{cadFormatter(grandTotals[colVal]?.[m] || 0)} {cadFormatter(grandTotals[colVal]?.[m] || 0)}
</td> </td>
)) ))

View file

@ -61,11 +61,11 @@ export default function MonthlyTrendsTable({ data }: MonthlyTrendsTableProps) {
</td> </td>
</tr> </tr>
))} ))}
<tr className="border-t-2 border-[var(--border)] font-bold bg-[var(--muted)]/20"> <tr className="border-t-2 border-[var(--border)] font-bold text-sm bg-[var(--muted)]/20">
<td className="px-3 py-2">{t("common.total")}</td> <td className="px-3 py-3">{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-3 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-3 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)]"}`}> <td className={`text-right px-3 py-3 ${totals.income - totals.expenses >= 0 ? "text-[var(--positive)]" : "text-[var(--negative)]"}`}>
{cadFormatter(totals.income - totals.expenses)} {cadFormatter(totals.income - totals.expenses)}
</td> </td>
</tr> </tr>