)
```
**i18n additions (en.json):** Add inside the "budgets" object:
```json
"month": "Month",
"year": "Year",
"total": "{{label}} Total"
```
**i18n additions (de.json):** Add inside the "budgets" object:
```json
"month": "Monat",
"year": "Jahr",
"total": "{{label}} Gesamt"
```
cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run buildBudgetListPage uses PageShell, shows locale-aware month names via Intl.DateTimeFormat (no hardcoded English MONTHS array), dialog labels use i18n keys, skeleton replaces null loading state, budgetLabel uses i18n.language locale. Both en.json and de.json have month/year/total keys. Build passes.Task 2: Upgrade BudgetDetailPage with semantic tokens, direction-aware diff, PageShell, group headers, and skeletonsrc/pages/BudgetDetailPage.tsx
**BudgetDetailPage.tsx changes:**
1. **Import additions:** Add:
```tsx
import { cn } from "@/lib/utils"
import { PageShell } from "@/components/shared/PageShell"
import { Skeleton } from "@/components/ui/skeleton"
```
2. **Add direction-aware diff logic:** At module level (above the component), add the same SPENDING_TYPES pattern from CategorySection:
```tsx
const SPENDING_TYPES: CategoryType[] = ["bill", "variable_expense", "debt"]
function isSpendingType(type: CategoryType): boolean {
return SPENDING_TYPES.includes(type)
}
```
3. **Rewrite DifferenceCell:** Replace the entire DifferenceCell component. Change its props: remove `isIncome`, add `type: CategoryType`:
```tsx
function DifferenceCell({
budgeted,
actual,
currency,
type,
}: {
budgeted: number
actual: number
currency: string
type: CategoryType
}) {
const isOver = isSpendingType(type)
? actual > budgeted
: actual < budgeted
const diff = isSpendingType(type)
? budgeted - actual
: actual - budgeted
return (
{formatCurrency(Math.abs(diff), currency)}
{diff < 0 ? " over" : ""}
)
}
```
4. **Update DifferenceCell call sites:** In the grouped.map render:
- Remove the `const isIncome = type === "income"` line.
- Change `` to `` in BOTH places (per-item row and group footer).
5. **Remove TierBadge from BudgetDetailPage:** Per research recommendation, remove the tier column from BudgetDetailPage to reduce visual noise and align with CategorySection display. This is Claude's discretion per CONTEXT.md.
- Remove the TierBadge component definition from BudgetDetailPage (keep it in TemplatePage where it belongs).
- Remove the `{t("categories.type")}` column from the table header.
- Remove the `` from each table row.
- Update the TableFooter `colSpan` accordingly: the first footer cell changes from `colSpan={2}` to no colSpan (or `colSpan={1}`), and the last footer cell changes appropriately.
- Remove the `Badge` import if no longer used elsewhere in this file.
6. **Group header upgrade:** Replace the dot+h2 pattern in grouped.map with:
```tsx
{t(`categories.types.${type}`)}
```
7. **Fix locale for headingLabel:** Update the `headingLabel` function. Destructure `i18n` from `useTranslation`: change `const { t } = useTranslation()` to `const { t, i18n } = useTranslation()`. Then:
```tsx
function headingLabel(): string {
if (!budget) return ""
const [year, month] = budget.start_date.split("-").map(Number)
return new Intl.DateTimeFormat(i18n.language, { month: "long", year: "numeric" }).format(
new Date(year ?? 0, (month ?? 1) - 1, 1)
)
}
```
8. **Fix overall totals section:** The overall totals box at the bottom uses hardcoded `text-green-600`/`text-red-600`. Replace with semantic tokens:
```tsx
= 0 ? "text-on-budget" : "text-over-budget"
)}
>
```
This replaces the inline ternary with `text-green-600 dark:text-green-400` / `text-red-600 dark:text-red-400`.
9. **Fix group footer "Total" label:** The group footer currently has hardcoded English ` Total`:
```tsx
{t(`categories.types.${type}`)} Total
```
Replace with i18n:
```tsx
{t("budgets.total", { label: t(`categories.types.${type}`) })}
```
The `budgets.total` key was added in Task 1's i18n step: `"total": "{{label}} Total"` / `"total": "{{label}} Gesamt"`.
10. **Replace header with PageShell:** Replace the back link + header section. Keep the back link as a child of PageShell:
```tsx
{t("budgets.addItem")}
}
>
{t("budgets.title")}
{/* rest of content */}
```
The `-mt-4` on the back link compensates for PageShell's `gap-6`, pulling it closer to the header.
11. **Skeleton loading:** Replace `if (loading) return null` with:
```tsx
if (loading) return (
{[1, 2, 3].map((i) => (
{[1, 2].map((j) => (
))}
))}
)
```
**IMPORTANT VERIFICATION after changes:** Ensure NO instances of `text-green-600`, `text-red-600`, `text-green-400`, or `text-red-400` remain in BudgetDetailPage.tsx. All color coding must use `text-over-budget`, `text-on-budget`, or `text-muted-foreground`.
cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run build && grep -c "text-green-600\|text-red-600\|text-green-400\|text-red-400" src/pages/BudgetDetailPage.tsx || echo "CLEAN: no hardcoded color classes"BudgetDetailPage uses semantic color tokens (text-over-budget/text-on-budget) with zero instances of text-green-600 or text-red-600. Direction-aware diff logic handles all 6 category types correctly (spending types over when actual > budgeted, income/saving/investment over when actual < budgeted). Left-border accent group headers replace dot headers. Tier badge column removed for cleaner display. Locale-aware month heading. Skeleton loading state. PageShell wraps the page. Overall totals box uses semantic tokens. Group footer total label uses i18n interpolation. Build passes.
- `bun run build` compiles without TypeScript errors
- `bun run lint` passes (or pre-existing errors only)
- `grep -c "text-green-600\|text-red-600" src/pages/BudgetDetailPage.tsx` returns 0 (semantic tokens only)
- `grep -c "text-over-budget\|text-on-budget" src/pages/BudgetDetailPage.tsx` returns at least 2
- `grep -c "return null" src/pages/BudgetListPage.tsx src/pages/BudgetDetailPage.tsx` returns 0 for both
- `grep -c 'toLocaleDateString("en"' src/pages/BudgetDetailPage.tsx src/pages/BudgetListPage.tsx` returns 0 (no hardcoded English locale)
- `grep -c "Intl.DateTimeFormat" src/pages/BudgetListPage.tsx src/pages/BudgetDetailPage.tsx` returns at least 1 for each
- `grep -c "PageShell" src/pages/BudgetListPage.tsx src/pages/BudgetDetailPage.tsx` returns at least 1 for each
- `grep "budgets.month" src/i18n/en.json src/i18n/de.json` returns matches in both
- BudgetListPage: PageShell header, locale-aware month names in dialog and table, skeleton loading, i18n month/year labels
- BudgetDetailPage: PageShell header, semantic color tokens (no hardcoded green/red), direction-aware diff for all 6 category types, left-border accent group headers, no tier column, locale-aware heading, skeleton loading, i18n group total label
- No hardcoded English locale strings ("en") remain in budget page formatting
- No hardcoded Tailwind color classes (text-green-600, text-red-600) remain
- All 9 app pages now use consistent header layout (PageShell or equivalent)
- German locale shows fully translated text on both pages
- `bun run build` passes