style: CalendarPage readability pass + DataPage fix

CalendarPage.jsx:
- Tightened day numbers, due-count badges, bill labels inside cells
- Crisper color contrast for paid/due/missed states
- Cleaner grid surfaces and borders for row/day tracking
- Switched font-mono values to tracker-number style

SeedDemoDataSection.jsx:
- Fixed render logic for data page
This commit is contained in:
null 2026-05-28 21:40:27 -05:00
parent 1c8d754068
commit 979886cb6a
2 changed files with 56 additions and 56 deletions

View File

@ -62,7 +62,7 @@ export default function SeedDemoDataSection({ onSeeded }) {
};
return (
<SectionCard title="Demo Data" subtitle="Seed your database with demo data for testing" icon={Sparkles}>
<SectionCard title="Demo Data" subtitle="Seed your database with demo data for testing">
<div className="rounded-lg border border-border/60 bg-background/50 p-4">
{statusLoading ? (
<p className="text-sm text-muted-foreground">Loading</p>

View File

@ -44,15 +44,15 @@ function displayStatus(status) {
}
function statusTone(status) {
if (status === 'paid' || status === 'autodraft') return 'bg-emerald-500/15 text-emerald-500 border-emerald-500/25';
if (status === 'skipped') return 'bg-muted text-muted-foreground border-border';
if (status === 'late' || status === 'missed') return 'bg-destructive/15 text-destructive border-destructive/25';
return 'bg-primary/10 text-primary border-primary/25';
if (status === 'paid' || status === 'autodraft') return 'border-emerald-500/30 bg-emerald-500/15 text-emerald-700 dark:text-emerald-300';
if (status === 'skipped') return 'border-border bg-muted/80 text-muted-foreground';
if (status === 'late' || status === 'missed') return 'border-destructive/30 bg-destructive/15 text-destructive';
return 'border-primary/30 bg-primary/15 text-primary';
}
function LegendItem({ className, label }) {
return (
<span className="inline-flex items-center gap-1.5 text-xs text-muted-foreground">
<span className="inline-flex items-center gap-1.5 text-xs font-medium text-foreground/75">
<span className={cn('h-2.5 w-2.5 rounded-full border', className)} />
{label}
</span>
@ -61,15 +61,15 @@ function LegendItem({ className, label }) {
function MoneyMetric({ icon: Icon, label, value, hint, valueClassName }) {
return (
<div className="min-w-0 rounded-lg border border-border/60 bg-background/55 p-3">
<div className="min-w-0 rounded-lg border border-border/70 bg-background/70 p-3">
<div className="flex items-center gap-2">
<Icon className="h-4 w-4 text-muted-foreground" />
<p className="truncate text-xs font-semibold uppercase tracking-wide text-muted-foreground">{label}</p>
<Icon className="h-4 w-4 text-foreground/70" />
<p className="truncate text-xs font-semibold uppercase tracking-wide text-foreground/70">{label}</p>
</div>
<p className={cn('mt-2 font-mono text-xl font-bold tracking-tight', valueClassName || 'text-foreground')}>
<p className={cn('tracker-number mt-2 text-xl font-bold tracking-tight', valueClassName || 'text-foreground')}>
{fmt(value)}
</p>
{hint && <p className="mt-1 truncate text-xs text-muted-foreground">{hint}</p>}
{hint && <p className="mt-1 truncate text-xs font-medium text-muted-foreground">{hint}</p>}
</div>
);
}
@ -114,29 +114,29 @@ function MoneyMap({ summaryData, loading }) {
<CardContent className="space-y-4">
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
<MoneyMetric icon={Banknote} label="Available" value={available} hint="1st + 15th + extra" />
<MoneyMetric icon={PiggyBank} label="Extra Income" value={extraIncome} hint="Extra beyond paychecks" valueClassName={extraIncome > 0 ? 'text-teal-500' : ''} />
<MoneyMetric icon={PiggyBank} label="Extra Income" value={extraIncome} hint="Extra beyond paychecks" valueClassName={extraIncome > 0 ? 'text-teal-600 dark:text-teal-300' : ''} />
<MoneyMetric icon={CalendarDays} label="Assigned Bills" value={assigned} hint={`${summary.expense_count || 0} active bills`} />
<MoneyMetric
icon={CircleDollarSign}
label="After Bills"
value={remaining}
hint={`${fmt(paid)} already paid`}
valueClassName={remaining >= 0 ? 'text-emerald-500' : 'text-destructive'}
valueClassName={remaining >= 0 ? 'text-emerald-600 dark:text-emerald-300' : 'text-destructive'}
/>
</div>
<div className="grid gap-2 text-sm md:grid-cols-3">
<div className="flex items-center justify-between gap-3 rounded-md bg-muted/35 px-3 py-2">
<div className="flex items-center justify-between gap-3 rounded-md bg-muted/45 px-3 py-2">
<span className="text-muted-foreground">1st available</span>
<span className="font-mono font-semibold">{fmt(starting.first_amount)}</span>
<span className="tracker-number font-semibold">{fmt(starting.first_amount)}</span>
</div>
<div className="flex items-center justify-between gap-3 rounded-md bg-muted/35 px-3 py-2">
<div className="flex items-center justify-between gap-3 rounded-md bg-muted/45 px-3 py-2">
<span className="text-muted-foreground">15th available</span>
<span className="font-mono font-semibold">{fmt(starting.fifteenth_amount)}</span>
<span className="tracker-number font-semibold">{fmt(starting.fifteenth_amount)}</span>
</div>
<div className="flex items-center justify-between gap-3 rounded-md bg-muted/35 px-3 py-2">
<div className="flex items-center justify-between gap-3 rounded-md bg-muted/45 px-3 py-2">
<span className="text-muted-foreground">Monthly income</span>
<span className="truncate font-mono font-semibold">{fmt(summaryData?.income?.amount)}</span>
<span className="tracker-number truncate font-semibold">{fmt(summaryData?.income?.amount)}</span>
</div>
</div>
</CardContent>
@ -159,7 +159,7 @@ function SummaryProgress({ summary }) {
<CardContent>
<div className="flex items-end justify-between gap-4">
<div>
<p className="font-mono text-2xl font-semibold tracking-tight">
<p className="tracker-number text-2xl font-semibold tracking-tight">
{fmt(summary?.paid_total)}
<span className="mx-2 text-sm font-normal text-muted-foreground">/</span>
<span className="text-base text-muted-foreground">{fmt(summary?.expected_total)}</span>
@ -201,7 +201,7 @@ function DayIndicators({ day, moneyMarker }) {
return (
<div className="mt-auto flex flex-wrap gap-1">
{moneyMarker && <span className="h-1.5 w-1.5 rounded-full bg-teal-500 ring-1 ring-teal-500/30" title="Available money" />}
{hasPaid && <span className="h-1.5 w-1.5 rounded-full bg-emerald-500" title="Paid" />}
{hasPaid && <span className="h-1.5 w-1.5 rounded-full bg-emerald-400 ring-1 ring-emerald-400/30" title="Paid" />}
{(hasDue || paymentOnly) && <span className="h-1.5 w-1.5 rounded-full bg-primary" title="Due or payment" />}
{hasSkipped && <span className="h-1.5 w-1.5 rounded-full bg-muted-foreground/50" title="Skipped" />}
{hasMissed && <span className="h-1.5 w-1.5 rounded-full bg-destructive" title="Missed or late" />}
@ -218,10 +218,10 @@ function CalendarGrid({ data, selectedDate, onSelectDay, moneyMarkers }) {
const today = todayStr();
return (
<Card className="overflow-hidden">
<div className="grid grid-cols-7 border-b border-border/70 bg-muted/30">
<Card className="overflow-hidden border-border/80 bg-card/95">
<div className="grid grid-cols-7 border-b border-border/70 bg-muted/45">
{WEEKDAYS.map(day => (
<div key={day} className="px-1 py-2 text-center text-[11px] font-semibold uppercase tracking-wide text-muted-foreground sm:text-xs">
<div key={day} className="px-1 py-2 text-center text-[11px] font-semibold uppercase tracking-wide text-foreground/70 sm:text-xs">
{day}
</div>
))}
@ -230,7 +230,7 @@ function CalendarGrid({ data, selectedDate, onSelectDay, moneyMarkers }) {
<div className="grid grid-cols-7">
{cells.map(cell => {
if (cell.type === 'blank') {
return <div key={cell.key} className="min-h-16 border-b border-r border-border/50 bg-muted/10 sm:min-h-24" />;
return <div key={cell.key} className="min-h-16 border-b border-r border-border/60 bg-background/30 sm:min-h-24" />;
}
const day = cell.day;
@ -248,24 +248,24 @@ function CalendarGrid({ data, selectedDate, onSelectDay, moneyMarkers }) {
type="button"
onClick={() => onSelectDay(day)}
className={cn(
'flex min-h-16 flex-col border-b border-r border-border/50 p-1.5 text-left transition-colors sm:min-h-24 sm:p-2',
'flex min-h-16 flex-col border-b border-r border-border/60 bg-card/70 p-1.5 text-left transition-colors sm:min-h-24 sm:p-2',
'focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50',
hasActivity && 'bg-primary/[0.03] hover:bg-accent/60',
isPaidDay && 'bg-emerald-500/[0.07]',
hasMissed && 'bg-destructive/[0.08]',
isSelected && 'ring-2 ring-primary ring-inset',
hasActivity && 'bg-primary/[0.06] hover:bg-accent/70',
isPaidDay && 'bg-emerald-500/[0.10]',
hasMissed && 'bg-destructive/[0.10]',
isSelected && 'ring-2 ring-primary ring-inset bg-primary/[0.09]',
)}
aria-label={`View ${fmtDate(day.date)}`}
>
<div className="flex items-start justify-between gap-1">
<span className={cn(
'flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium sm:text-sm',
isToday && 'border border-primary bg-primary/10 text-primary',
'tracker-number flex h-6 w-6 items-center justify-center rounded-full text-xs font-semibold text-foreground/85 sm:text-sm',
isToday && 'border border-primary/60 bg-primary/15 text-primary',
)}>
{day.day}
</span>
{summary.due_count > 0 && (
<span className="rounded bg-background/75 px-1 font-mono text-[10px] text-muted-foreground">
<span className="tracker-number rounded border border-border/60 bg-background/90 px-1 text-[10px] font-semibold text-foreground/70">
{summary.due_count}
</span>
)}
@ -273,17 +273,17 @@ function CalendarGrid({ data, selectedDate, onSelectDay, moneyMarkers }) {
<div className="mt-1 hidden min-w-0 space-y-0.5 sm:block">
{moneyMarker && (
<p className="truncate font-mono text-[11px] font-semibold text-teal-500">
<p className="tracker-number truncate text-[11px] font-semibold text-teal-600 dark:text-teal-300">
+{fmt(moneyMarker.amount)}
</p>
)}
{day.bills_due.slice(0, 2).map(bill => (
<p key={bill.bill_id} className="truncate text-[11px] text-muted-foreground">
<p key={bill.bill_id} className="truncate text-[11px] font-medium text-foreground/80">
{bill.name}
</p>
))}
{day.bills_due.length > 2 && (
<p className="text-[11px] text-muted-foreground">+{day.bills_due.length - 2} more</p>
<p className="text-[11px] font-medium text-muted-foreground">+{day.bills_due.length - 2} more</p>
)}
</div>
@ -340,7 +340,7 @@ function DebtPayoffGlance({ projection }) {
<div className="mt-3 grid grid-cols-2 gap-2 text-sm">
<div className="rounded-lg border border-emerald-500/15 bg-background/65 p-3">
<p className="text-xs text-muted-foreground">Target runway</p>
<p className="font-mono font-semibold text-emerald-600 dark:text-emerald-300">
<p className="tracker-number font-semibold text-emerald-600 dark:text-emerald-300">
{targetMonths ? `${targetMonths} mo` : '—'}
</p>
</div>
@ -358,7 +358,7 @@ function DebtPayoffGlance({ projection }) {
<Trophy className="h-4 w-4 text-amber-500" />
<p className="text-xs text-muted-foreground">Time saved</p>
</div>
<p className="mt-2 font-mono font-semibold text-amber-600 dark:text-amber-300">
<p className="tracker-number mt-2 font-semibold text-amber-600 dark:text-amber-300">
{monthsSaved !== undefined ? `${monthsSaved} mo` : '—'}
</p>
</div>
@ -367,7 +367,7 @@ function DebtPayoffGlance({ projection }) {
<TrendingDown className="h-4 w-4 text-teal-500" />
<p className="text-xs text-muted-foreground">Interest</p>
</div>
<p className="mt-2 font-mono font-semibold">{fmt(snowball.total_interest_paid)}</p>
<p className="tracker-number mt-2 font-semibold">{fmt(snowball.total_interest_paid)}</p>
</div>
</div>
@ -383,11 +383,11 @@ function DebtPayoffGlance({ projection }) {
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="rounded-lg bg-muted/30 p-3">
<p className="text-xs text-muted-foreground">Full plan</p>
<p className="font-mono font-semibold">{snowball.months_to_freedom} mo</p>
<p className="tracker-number font-semibold">{snowball.months_to_freedom} mo</p>
</div>
<div className="rounded-lg bg-muted/30 p-3">
<p className="text-xs text-muted-foreground">Interest saved</p>
<p className="font-mono font-semibold text-emerald-500">
<p className="tracker-number font-semibold text-emerald-600 dark:text-emerald-300">
{comparison ? fmt(comparison.interest_saved) : '—'}
</p>
</div>
@ -435,7 +435,7 @@ function DayDetailDialog({ day, open, onOpenChange, moneyMarker }) {
<h3 className="text-xs font-semibold uppercase tracking-wide text-teal-600 dark:text-teal-300">
Available Money
</h3>
<p className="mt-1 font-mono text-lg font-semibold text-teal-600 dark:text-teal-300">
<p className="tracker-number mt-1 text-lg font-semibold text-teal-600 dark:text-teal-300">
+{fmt(moneyMarker.amount)}
</p>
<p className="text-xs text-muted-foreground">{moneyMarker.label}</p>
@ -450,28 +450,28 @@ function DayDetailDialog({ day, open, onOpenChange, moneyMarker }) {
) : (
<div className="space-y-2">
{day.bills_due.map(bill => (
<div key={bill.bill_id} className="rounded-lg border border-border/60 bg-background/60 p-3">
<div key={bill.bill_id} className="rounded-lg border border-border/70 bg-background/75 p-3">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<p className="truncate text-sm font-medium">{bill.name}</p>
<p className="mt-0.5 text-xs text-muted-foreground">{bill.category_name || 'Uncategorized'}</p>
<p className="truncate text-sm font-semibold text-foreground">{bill.name}</p>
<p className="mt-0.5 text-xs font-medium text-muted-foreground">{bill.category_name || 'Uncategorized'}</p>
</div>
<Badge variant="outline" className={cn('shrink-0 capitalize', statusTone(bill.status))}>
{displayStatus(bill.status)}
</Badge>
</div>
<div className="mt-3 grid grid-cols-3 gap-2 text-xs text-muted-foreground">
<div className="mt-3 grid grid-cols-3 gap-2 text-xs font-medium text-muted-foreground">
<div>
<p>Expected</p>
<p className="font-mono text-sm text-foreground">{fmt(bill.effective_amount)}</p>
<p className="tracker-number text-sm font-semibold text-foreground">{fmt(bill.effective_amount)}</p>
</div>
<div>
<p>Paid</p>
<p className="font-mono text-sm text-emerald-500">{fmt(bill.paid_amount)}</p>
<p className="tracker-number text-sm font-semibold text-emerald-600 dark:text-emerald-300">{fmt(bill.paid_amount)}</p>
</div>
<div>
<p>Due</p>
<p className="font-mono text-sm text-foreground">{fmtDate(bill.due_date)}</p>
<p className="tracker-number text-sm font-semibold text-foreground">{fmtDate(bill.due_date)}</p>
</div>
</div>
</div>
@ -489,12 +489,12 @@ function DayDetailDialog({ day, open, onOpenChange, moneyMarker }) {
) : (
<div className="space-y-2">
{day.payments.map(payment => (
<div key={payment.payment_id} className="flex items-center justify-between gap-3 rounded-lg border border-border/60 bg-background/60 p-3">
<div key={payment.payment_id} className="flex items-center justify-between gap-3 rounded-lg border border-border/70 bg-background/75 p-3">
<div className="min-w-0">
<p className="truncate text-sm font-medium">{payment.bill_name}</p>
<p className="text-xs text-muted-foreground">{payment.method || 'Payment'}</p>
<p className="truncate text-sm font-semibold text-foreground">{payment.bill_name}</p>
<p className="text-xs font-medium text-muted-foreground">{payment.method || 'Payment'}</p>
</div>
<span className="font-mono text-sm text-emerald-500">{fmt(payment.amount)}</span>
<span className="tracker-number text-sm font-semibold text-emerald-600 dark:text-emerald-300">{fmt(payment.amount)}</span>
</div>
))}
</div>
@ -640,7 +640,7 @@ export default function CalendarPage() {
<LegendItem className="border-teal-500 bg-teal-500 ring-1 ring-teal-500/30" label="Available money" />
<LegendItem className="border-muted-foreground/50 bg-muted-foreground/50" label="Skipped" />
<LegendItem className="border-destructive bg-destructive" label="Missed/Late" />
<span className="inline-flex items-center gap-1.5 text-xs text-muted-foreground">
<span className="inline-flex items-center gap-1.5 text-xs font-medium text-foreground/75">
<span className="h-5 w-5 rounded-full border border-primary bg-primary/10" />
Today
</span>
@ -707,11 +707,11 @@ export default function CalendarPage() {
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="rounded-lg bg-muted/40 p-3">
<p className="text-xs text-muted-foreground">Due</p>
<p className="font-mono font-semibold">{fmt(selectedDay.status_summary.total_due)}</p>
<p className="tracker-number font-semibold">{fmt(selectedDay.status_summary.total_due)}</p>
</div>
<div className="rounded-lg bg-muted/40 p-3">
<p className="text-xs text-muted-foreground">Paid</p>
<p className="font-mono font-semibold text-emerald-500">{fmt(selectedDay.status_summary.total_paid)}</p>
<p className="tracker-number font-semibold text-emerald-600 dark:text-emerald-300">{fmt(selectedDay.status_summary.total_paid)}</p>
</div>
</div>
<Button className="w-full" size="sm" onClick={() => setDetailOpen(true)}>