feat(01-02): wire palette.ts into all 6 dashboard components
- BillsTracker: headerGradient('bill'), InlineEditCell, amountColorClass
- VariableExpenses: headerGradient('variable_expense'), InlineEditCell, amountColorClass, palette bar chart colors
- DebtTracker: headerGradient('debt'), InlineEditCell, amountColorClass
- AvailableBalance: headerGradient('saving'), palette Cell fills, text-3xl center, text-success/text-destructive
- ExpenseBreakdown: headerGradient('variable_expense'), palette Cell fills
- FinancialOverview: overviewHeaderGradient(), hero typography (text-2xl px-6 py-5), palette row tints, amountColorClass
- Remove all PASTEL_COLORS arrays and InlineEditRow private functions
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'
|
||||
import type { BudgetDetail } from '@/lib/api'
|
||||
import { formatCurrency } from '@/lib/format'
|
||||
import { headerGradient, amountColorClass, palette } from '@/lib/palette'
|
||||
import { InlineEditCell } from '@/components/InlineEditCell'
|
||||
|
||||
interface Props {
|
||||
budget: BudgetDetail
|
||||
@@ -24,7 +24,7 @@ export function VariableExpenses({ budget, onUpdate }: Props) {
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="bg-gradient-to-r from-amber-50 to-yellow-50">
|
||||
<CardHeader style={headerGradient('variable_expense')}>
|
||||
<CardTitle>{t('dashboard.variableExpenses')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-6 pt-4">
|
||||
@@ -38,17 +38,27 @@ export function VariableExpenses({ budget, onUpdate }: Props) {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{expenses.map((item) => (
|
||||
<InlineEditRow
|
||||
key={item.id}
|
||||
label={item.category_name}
|
||||
budgeted={item.budgeted_amount}
|
||||
actual={item.actual_amount}
|
||||
currency={budget.currency}
|
||||
onSave={(actual) => onUpdate(item.id, { actual_amount: actual })}
|
||||
/>
|
||||
))}
|
||||
<TableRow className="border-t-2 font-bold bg-amber-50/50">
|
||||
{expenses.map((item) => {
|
||||
const remaining = item.budgeted_amount - item.actual_amount
|
||||
return (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell>{item.category_name}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{formatCurrency(item.budgeted_amount, budget.currency)}
|
||||
</TableCell>
|
||||
<InlineEditCell
|
||||
value={item.actual_amount}
|
||||
currency={budget.currency}
|
||||
onSave={(actual) => onUpdate(item.id, { actual_amount: actual })}
|
||||
className={amountColorClass({ type: 'variable_expense', actual: item.actual_amount, budgeted: item.budgeted_amount })}
|
||||
/>
|
||||
<TableCell className={`text-right ${remaining < 0 ? 'text-destructive' : ''}`}>
|
||||
{formatCurrency(remaining, budget.currency)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
<TableRow className="border-t-2 font-bold">
|
||||
<TableCell>{t('dashboard.budget')}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{formatCurrency(expenses.reduce((s, i) => s + i.budgeted_amount, 0), budget.currency)}
|
||||
@@ -72,8 +82,8 @@ export function VariableExpenses({ budget, onUpdate }: Props) {
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey={t('dashboard.budget')} fill="#fcd34d" radius={[4, 4, 0, 0]} />
|
||||
<Bar dataKey={t('dashboard.actual')} fill="#f9a8d4" radius={[4, 4, 0, 0]} />
|
||||
<Bar dataKey={t('dashboard.budget')} fill={palette.variable_expense.light} radius={[4, 4, 0, 0]} />
|
||||
<Bar dataKey={t('dashboard.actual')} fill={palette.variable_expense.base} radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
@@ -82,61 +92,3 @@ export function VariableExpenses({ budget, onUpdate }: Props) {
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function InlineEditRow({
|
||||
label,
|
||||
budgeted,
|
||||
actual,
|
||||
currency,
|
||||
onSave,
|
||||
}: {
|
||||
label: string
|
||||
budgeted: number
|
||||
actual: number
|
||||
currency: string
|
||||
onSave: (value: number) => Promise<void>
|
||||
}) {
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [value, setValue] = useState(String(actual))
|
||||
|
||||
const handleBlur = async () => {
|
||||
const num = parseFloat(value)
|
||||
if (!isNaN(num) && num !== actual) {
|
||||
await onSave(num)
|
||||
}
|
||||
setEditing(false)
|
||||
}
|
||||
|
||||
const remaining = budgeted - actual
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell>{label}</TableCell>
|
||||
<TableCell className="text-right">{formatCurrency(budgeted, currency)}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{editing ? (
|
||||
<Input
|
||||
type="number"
|
||||
step="0.01"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleBlur()}
|
||||
className="ml-auto w-28 text-right"
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className="cursor-pointer rounded px-2 py-1 hover:bg-muted"
|
||||
onClick={() => { setValue(String(actual)); setEditing(true) }}
|
||||
>
|
||||
{formatCurrency(actual, currency)}
|
||||
</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={`text-right ${remaining < 0 ? 'text-destructive' : ''}`}>
|
||||
{formatCurrency(remaining, currency)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user