fix: sticky category column and month dropdown selector (#29) #30
2 changed files with 28 additions and 26 deletions
|
|
@ -158,8 +158,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>
|
||||
|
|
@ -173,13 +173,13 @@ export default function BudgetVsActualTable({ data }: BudgetVsActualTableProps)
|
|||
<tr
|
||||
key={`${row.category_id}-${row.is_parent}-${depth}`}
|
||||
className={`border-b border-[var(--border)]/50 ${
|
||||
isTopParent ? "bg-[var(--muted)]/30 font-semibold" :
|
||||
isIntermediateParent ? "bg-[var(--muted)]/15 font-medium" : ""
|
||||
isTopParent ? "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 ${
|
||||
isTopParent ? "px-3 bg-[var(--muted)]/30" :
|
||||
isIntermediateParent ? `${paddingClass} bg-[var(--muted)]/15` :
|
||||
isTopParent ? "px-3 bg-[color-mix(in_srgb,var(--muted)_30%,var(--card))]" :
|
||||
isIntermediateParent ? `${paddingClass} bg-[color-mix(in_srgb,var(--muted)_15%,var(--card))]` :
|
||||
`${paddingClass} bg-[var(--card)]`
|
||||
}`}>
|
||||
<span className="flex items-center gap-2">
|
||||
|
|
@ -213,8 +213,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>
|
||||
|
|
@ -240,8 +240,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>
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
</option>
|
||||
);
|
||||
});
|
||||
})()}
|
||||
{monthOptions.map((opt) => (
|
||||
<option key={opt.key} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</h1>
|
||||
) : (
|
||||
|
|
|
|||
Loading…
Reference in a new issue