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 ( 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"> <div className="rounded-lg border border-border/60 bg-background/50 p-4">
{statusLoading ? ( {statusLoading ? (
<p className="text-sm text-muted-foreground">Loading</p> <p className="text-sm text-muted-foreground">Loading</p>

View File

@ -44,15 +44,15 @@ function displayStatus(status) {
} }
function statusTone(status) { function statusTone(status) {
if (status === 'paid' || status === 'autodraft') return 'bg-emerald-500/15 text-emerald-500 border-emerald-500/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 'bg-muted text-muted-foreground border-border'; if (status === 'skipped') return 'border-border bg-muted/80 text-muted-foreground';
if (status === 'late' || status === 'missed') return 'bg-destructive/15 text-destructive border-destructive/25'; if (status === 'late' || status === 'missed') return 'border-destructive/30 bg-destructive/15 text-destructive';
return 'bg-primary/10 text-primary border-primary/25'; return 'border-primary/30 bg-primary/15 text-primary';
} }
function LegendItem({ className, label }) { function LegendItem({ className, label }) {
return ( 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)} /> <span className={cn('h-2.5 w-2.5 rounded-full border', className)} />
{label} {label}
</span> </span>
@ -61,15 +61,15 @@ function LegendItem({ className, label }) {
function MoneyMetric({ icon: Icon, label, value, hint, valueClassName }) { function MoneyMetric({ icon: Icon, label, value, hint, valueClassName }) {
return ( 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"> <div className="flex items-center gap-2">
<Icon className="h-4 w-4 text-muted-foreground" /> <Icon className="h-4 w-4 text-foreground/70" />
<p className="truncate text-xs font-semibold uppercase tracking-wide text-muted-foreground">{label}</p> <p className="truncate text-xs font-semibold uppercase tracking-wide text-foreground/70">{label}</p>
</div> </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)} {fmt(value)}
</p> </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> </div>
); );
} }
@ -114,29 +114,29 @@ function MoneyMap({ summaryData, loading }) {
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-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={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={CalendarDays} label="Assigned Bills" value={assigned} hint={`${summary.expense_count || 0} active bills`} />
<MoneyMetric <MoneyMetric
icon={CircleDollarSign} icon={CircleDollarSign}
label="After Bills" label="After Bills"
value={remaining} value={remaining}
hint={`${fmt(paid)} already paid`} 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>
<div className="grid gap-2 text-sm md:grid-cols-3"> <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="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>
<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="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>
<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="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>
</div> </div>
</CardContent> </CardContent>
@ -159,7 +159,7 @@ function SummaryProgress({ summary }) {
<CardContent> <CardContent>
<div className="flex items-end justify-between gap-4"> <div className="flex items-end justify-between gap-4">
<div> <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)} {fmt(summary?.paid_total)}
<span className="mx-2 text-sm font-normal text-muted-foreground">/</span> <span className="mx-2 text-sm font-normal text-muted-foreground">/</span>
<span className="text-base text-muted-foreground">{fmt(summary?.expected_total)}</span> <span className="text-base text-muted-foreground">{fmt(summary?.expected_total)}</span>
@ -201,7 +201,7 @@ function DayIndicators({ day, moneyMarker }) {
return ( return (
<div className="mt-auto flex flex-wrap gap-1"> <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" />} {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" />} {(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" />} {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" />} {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(); const today = todayStr();
return ( return (
<Card className="overflow-hidden"> <Card className="overflow-hidden border-border/80 bg-card/95">
<div className="grid grid-cols-7 border-b border-border/70 bg-muted/30"> <div className="grid grid-cols-7 border-b border-border/70 bg-muted/45">
{WEEKDAYS.map(day => ( {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} {day}
</div> </div>
))} ))}
@ -230,7 +230,7 @@ function CalendarGrid({ data, selectedDate, onSelectDay, moneyMarkers }) {
<div className="grid grid-cols-7"> <div className="grid grid-cols-7">
{cells.map(cell => { {cells.map(cell => {
if (cell.type === 'blank') { 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; const day = cell.day;
@ -248,24 +248,24 @@ function CalendarGrid({ data, selectedDate, onSelectDay, moneyMarkers }) {
type="button" type="button"
onClick={() => onSelectDay(day)} onClick={() => onSelectDay(day)}
className={cn( 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', 'focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50',
hasActivity && 'bg-primary/[0.03] hover:bg-accent/60', hasActivity && 'bg-primary/[0.06] hover:bg-accent/70',
isPaidDay && 'bg-emerald-500/[0.07]', isPaidDay && 'bg-emerald-500/[0.10]',
hasMissed && 'bg-destructive/[0.08]', hasMissed && 'bg-destructive/[0.10]',
isSelected && 'ring-2 ring-primary ring-inset', isSelected && 'ring-2 ring-primary ring-inset bg-primary/[0.09]',
)} )}
aria-label={`View ${fmtDate(day.date)}`} aria-label={`View ${fmtDate(day.date)}`}
> >
<div className="flex items-start justify-between gap-1"> <div className="flex items-start justify-between gap-1">
<span className={cn( <span className={cn(
'flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium sm:text-sm', '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 bg-primary/10 text-primary', isToday && 'border border-primary/60 bg-primary/15 text-primary',
)}> )}>
{day.day} {day.day}
</span> </span>
{summary.due_count > 0 && ( {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} {summary.due_count}
</span> </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"> <div className="mt-1 hidden min-w-0 space-y-0.5 sm:block">
{moneyMarker && ( {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)} +{fmt(moneyMarker.amount)}
</p> </p>
)} )}
{day.bills_due.slice(0, 2).map(bill => ( {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} {bill.name}
</p> </p>
))} ))}
{day.bills_due.length > 2 && ( {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> </div>
@ -340,7 +340,7 @@ function DebtPayoffGlance({ projection }) {
<div className="mt-3 grid grid-cols-2 gap-2 text-sm"> <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"> <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="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` : '—'} {targetMonths ? `${targetMonths} mo` : '—'}
</p> </p>
</div> </div>
@ -358,7 +358,7 @@ function DebtPayoffGlance({ projection }) {
<Trophy className="h-4 w-4 text-amber-500" /> <Trophy className="h-4 w-4 text-amber-500" />
<p className="text-xs text-muted-foreground">Time saved</p> <p className="text-xs text-muted-foreground">Time saved</p>
</div> </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` : '—'} {monthsSaved !== undefined ? `${monthsSaved} mo` : '—'}
</p> </p>
</div> </div>
@ -367,7 +367,7 @@ function DebtPayoffGlance({ projection }) {
<TrendingDown className="h-4 w-4 text-teal-500" /> <TrendingDown className="h-4 w-4 text-teal-500" />
<p className="text-xs text-muted-foreground">Interest</p> <p className="text-xs text-muted-foreground">Interest</p>
</div> </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>
</div> </div>
@ -383,11 +383,11 @@ function DebtPayoffGlance({ projection }) {
<div className="grid grid-cols-2 gap-2 text-sm"> <div className="grid grid-cols-2 gap-2 text-sm">
<div className="rounded-lg bg-muted/30 p-3"> <div className="rounded-lg bg-muted/30 p-3">
<p className="text-xs text-muted-foreground">Full plan</p> <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>
<div className="rounded-lg bg-muted/30 p-3"> <div className="rounded-lg bg-muted/30 p-3">
<p className="text-xs text-muted-foreground">Interest saved</p> <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) : '—'} {comparison ? fmt(comparison.interest_saved) : '—'}
</p> </p>
</div> </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"> <h3 className="text-xs font-semibold uppercase tracking-wide text-teal-600 dark:text-teal-300">
Available Money Available Money
</h3> </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)} +{fmt(moneyMarker.amount)}
</p> </p>
<p className="text-xs text-muted-foreground">{moneyMarker.label}</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"> <div className="space-y-2">
{day.bills_due.map(bill => ( {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="flex items-start justify-between gap-3">
<div className="min-w-0"> <div className="min-w-0">
<p className="truncate text-sm font-medium">{bill.name}</p> <p className="truncate text-sm font-semibold text-foreground">{bill.name}</p>
<p className="mt-0.5 text-xs text-muted-foreground">{bill.category_name || 'Uncategorized'}</p> <p className="mt-0.5 text-xs font-medium text-muted-foreground">{bill.category_name || 'Uncategorized'}</p>
</div> </div>
<Badge variant="outline" className={cn('shrink-0 capitalize', statusTone(bill.status))}> <Badge variant="outline" className={cn('shrink-0 capitalize', statusTone(bill.status))}>
{displayStatus(bill.status)} {displayStatus(bill.status)}
</Badge> </Badge>
</div> </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> <div>
<p>Expected</p> <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>
<div> <div>
<p>Paid</p> <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>
<div> <div>
<p>Due</p> <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> </div>
</div> </div>
@ -489,12 +489,12 @@ function DayDetailDialog({ day, open, onOpenChange, moneyMarker }) {
) : ( ) : (
<div className="space-y-2"> <div className="space-y-2">
{day.payments.map(payment => ( {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"> <div className="min-w-0">
<p className="truncate text-sm font-medium">{payment.bill_name}</p> <p className="truncate text-sm font-semibold text-foreground">{payment.bill_name}</p>
<p className="text-xs text-muted-foreground">{payment.method || 'Payment'}</p> <p className="text-xs font-medium text-muted-foreground">{payment.method || 'Payment'}</p>
</div> </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>
))} ))}
</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-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-muted-foreground/50 bg-muted-foreground/50" label="Skipped" />
<LegendItem className="border-destructive bg-destructive" label="Missed/Late" /> <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" /> <span className="h-5 w-5 rounded-full border border-primary bg-primary/10" />
Today Today
</span> </span>
@ -707,11 +707,11 @@ export default function CalendarPage() {
<div className="grid grid-cols-2 gap-2 text-sm"> <div className="grid grid-cols-2 gap-2 text-sm">
<div className="rounded-lg bg-muted/40 p-3"> <div className="rounded-lg bg-muted/40 p-3">
<p className="text-xs text-muted-foreground">Due</p> <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>
<div className="rounded-lg bg-muted/40 p-3"> <div className="rounded-lg bg-muted/40 p-3">
<p className="text-xs text-muted-foreground">Paid</p> <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>
</div> </div>
<Button className="w-full" size="sm" onClick={() => setDetailOpen(true)}> <Button className="w-full" size="sm" onClick={() => setDetailOpen(true)}>