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:
parent
1c8d754068
commit
979886cb6a
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)}>
|
||||
|
|
|
|||
Loading…
Reference in New Issue