fix: address reviewer feedback (#29)

- Replace semi-transparent backgrounds on sticky columns with opaque
  color-mix equivalents so scrolled content is fully hidden
- Add opaque background to section header sticky td
- Extract IIFE month options in ReportsPage into a useMemo
This commit is contained in:
medic-bot 2026-03-09 20:48:08 -04:00
parent 16c6d02e39
commit 2b2536bc80
2 changed files with 27 additions and 25 deletions

View file

@ -206,8 +206,8 @@ export default function BudgetVsActualTable({ data }: BudgetVsActualTableProps)
const sectionYtdPct = sectionTotals.ytdBudget !== 0 ? sectionTotals.ytdVariation / Math.abs(sectionTotals.ytdBudget) : null;
return (
<Fragment key={section.type}>
<tr className="bg-[var(--muted)]/50">
<td colSpan={9} className="px-3 py-1.5 font-semibold text-[var(--muted-foreground)] uppercase text-xs tracking-wider sticky left-0">
<tr className="bg-[var(--muted)]">
<td colSpan={9} className="px-3 py-1.5 font-semibold text-[var(--muted-foreground)] uppercase text-xs tracking-wider sticky left-0 bg-[var(--muted)]">
{section.label}
</td>
</tr>
@ -220,11 +220,11 @@ export default function BudgetVsActualTable({ data }: BudgetVsActualTableProps)
<tr
key={`${row.category_id}-${row.is_parent}-${depth}`}
className={`border-b border-[var(--border)]/50 ${
isParent && !isIntermediateParent ? "bg-[var(--muted)]/30 font-semibold" :
isIntermediateParent ? "bg-[var(--muted)]/15 font-medium" : ""
isParent && !isIntermediateParent ? "bg-[color-mix(in_srgb,var(--muted)_30%,var(--card))] font-semibold" :
isIntermediateParent ? "bg-[color-mix(in_srgb,var(--muted)_15%,var(--card))] font-medium" : ""
}`}
>
<td className={`py-1.5 sticky left-0 z-10 ${isParent && !isIntermediateParent ? "px-3 bg-[var(--muted)]/30" : isIntermediateParent ? "bg-[var(--muted)]/15" : "bg-[var(--card)]"} ${isParent && !isIntermediateParent ? "px-3" : paddingClass}`}>
<td className={`py-1.5 sticky left-0 z-10 ${isParent && !isIntermediateParent ? "px-3 bg-[color-mix(in_srgb,var(--muted)_30%,var(--card))]" : isIntermediateParent ? "bg-[color-mix(in_srgb,var(--muted)_15%,var(--card))]" : "bg-[var(--card)]"} ${isParent && !isIntermediateParent ? "px-3" : paddingClass}`}>
<span className="flex items-center gap-2">
<span
className="w-2.5 h-2.5 rounded-full shrink-0"
@ -256,8 +256,8 @@ export default function BudgetVsActualTable({ data }: BudgetVsActualTableProps)
</tr>
);
})}
<tr className="border-b border-[var(--border)] bg-[var(--muted)]/40 font-semibold text-sm">
<td className="px-3 py-2.5 sticky left-0 bg-[var(--muted)]/40 z-10">{t(typeTotalKeys[section.type])}</td>
<tr className="border-b border-[var(--border)] bg-[color-mix(in_srgb,var(--muted)_40%,var(--card))] font-semibold text-sm">
<td className="px-3 py-2.5 sticky left-0 bg-[color-mix(in_srgb,var(--muted)_40%,var(--card))] z-10">{t(typeTotalKeys[section.type])}</td>
<td className="text-right px-3 py-2.5 border-l border-[var(--border)]/50">
{cadFormatter(sectionTotals.monthActual)}
</td>
@ -283,8 +283,8 @@ export default function BudgetVsActualTable({ data }: BudgetVsActualTableProps)
);
})}
{/* Grand totals */}
<tr className="border-t-2 border-[var(--border)] font-bold text-sm bg-[var(--muted)]/20">
<td className="px-3 py-3 sticky left-0 bg-[var(--muted)]/20 z-10">{t("common.total")}</td>
<tr className="border-t-2 border-[var(--border)] font-bold text-sm bg-[color-mix(in_srgb,var(--muted)_20%,var(--card))]">
<td className="px-3 py-3 sticky left-0 bg-[color-mix(in_srgb,var(--muted)_20%,var(--card))] z-10">{t("common.total")}</td>
<td className="text-right px-3 py-3 border-l border-[var(--border)]/50">
{cadFormatter(totals.monthActual)}
</td>

View file

@ -92,6 +92,19 @@ export default function ReportsPage() {
return [];
}, [state.tab, state.categorySpending, state.categoryOverTime]);
const monthOptions = useMemo(() => {
const now = new Date();
const currentMonth = now.getMonth();
const currentYear = now.getFullYear();
return Array.from({ length: 24 }, (_, i) => {
const d = new Date(currentYear, currentMonth - i, 1);
const y = d.getFullYear();
const m = d.getMonth() + 1;
const label = new Intl.DateTimeFormat(i18n.language, { month: "long", year: "numeric" }).format(d);
return { key: `${y}-${m}`, value: `${y}-${m}`, label: label.charAt(0).toUpperCase() + label.slice(1) };
});
}, [i18n.language]);
const hasCategories = ["byCategory", "overTime"].includes(state.tab) && filterCategories.length > 0;
const showFilterPanel = hasCategories || (state.tab === "trends" && sources.length > 1);
@ -110,22 +123,11 @@ export default function ReportsPage() {
}}
className="text-2xl font-bold bg-[var(--card)] border border-[var(--border)] rounded-lg px-3 py-1 cursor-pointer hover:bg-[var(--muted)] transition-colors"
>
{(() => {
const now = new Date();
const currentMonth = now.getMonth(); // 0-based
const currentYear = now.getFullYear();
return Array.from({ length: 24 }, (_, i) => {
const d = new Date(currentYear, currentMonth - i, 1);
const y = d.getFullYear();
const m = d.getMonth() + 1;
const label = new Intl.DateTimeFormat(i18n.language, { month: "long", year: "numeric" }).format(d);
return (
<option key={`${y}-${m}`} value={`${y}-${m}`}>
{label.charAt(0).toUpperCase() + label.slice(1)}
{monthOptions.map((opt) => (
<option key={opt.key} value={opt.value}>
{opt.label}
</option>
);
});
})()}
))}
</select>
</h1>
) : (