fix(tracker): budget display and payment progress fixes
This commit is contained in:
parent
71e783a799
commit
955fb96aec
|
|
@ -25,12 +25,12 @@ export function PaymentProgress({ row, threshold, onOpen, onMarkFullAmount, comp
|
|||
onClick={onOpen}
|
||||
className={cn(
|
||||
'w-full rounded-md text-left transition-colors hover:bg-accent/60 focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50',
|
||||
compact ? 'p-2' : 'px-2 py-1.5',
|
||||
compact ? 'p-2' : 'px-2 py-1',
|
||||
)}
|
||||
title="View payment history"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<span className={cn('tracker-number text-[13px] font-semibold', summary.paid > 0 ? 'text-emerald-300' : 'text-muted-foreground/85')}>
|
||||
<span className={cn('tracker-number text-xs font-semibold', summary.paid > 0 ? 'text-emerald-300' : 'text-muted-foreground/85')}>
|
||||
{amountLabel}
|
||||
</span>
|
||||
{summary.count > 1 && (
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ function SortableHead({ sortKey, activeSortKey, sortDir, onSort, children, class
|
|||
return (
|
||||
<TableHead
|
||||
aria-sort={active ? (sortDir === TRACKER_SORT_ASC ? 'ascending' : 'descending') : 'none'}
|
||||
className={cn('py-2.5', className)}
|
||||
className={cn('h-11 py-2.5', className)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSort?.(sortKey)}
|
||||
className={cn(
|
||||
'group inline-flex h-8 items-center gap-1.5 rounded-md px-2 text-xs font-medium uppercase tracking-wider transition-all',
|
||||
'group inline-flex h-8 items-center gap-1.5 rounded-md px-2 text-[11px] font-semibold uppercase tracking-[0.08em] transition-all',
|
||||
'hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60',
|
||||
active ? 'bg-accent/70 text-foreground shadow-sm' : 'text-muted-foreground'
|
||||
)}
|
||||
|
|
@ -217,15 +217,15 @@ export function TrackerBucket({ label, rows, year, month, refresh, onEditBill, l
|
|||
<TableRow className="border-border/80 bg-background/30 hover:bg-background/30">
|
||||
<SortableHead sortKey="name" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[18%]">Bill</SortableHead>
|
||||
<SortableHead sortKey="due" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[10%]">Due</SortableHead>
|
||||
<SortableHead sortKey="expected" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[10%] text-right">Expected</SortableHead>
|
||||
<SortableHead sortKey="previous" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[10%] text-right">Last Month</SortableHead>
|
||||
<SortableHead sortKey="expected" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[10%] text-center">Expected</SortableHead>
|
||||
<SortableHead sortKey="previous" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[10%] text-center">Last Month</SortableHead>
|
||||
<SortableHead sortKey="paid" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[10%] text-center">Paid</SortableHead>
|
||||
<SortableHead sortKey="paid_date" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[10%]">Paid Date</SortableHead>
|
||||
<SortableHead sortKey="status" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[9%] text-center">Status</SortableHead>
|
||||
<TableHead className="w-[10%] py-2.5 text-center text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||
<TableHead className="h-11 w-[10%] py-2.5 text-center text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
||||
Action
|
||||
</TableHead>
|
||||
<TableHead className="w-[23%] py-2.5 text-xs font-medium uppercase tracking-wider text-muted-foreground border-l border-border/80 pl-4">
|
||||
<TableHead className="h-11 w-[23%] py-2.5 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground border-l border-border/80 pl-4">
|
||||
Notes
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
|
|
@ -241,8 +241,8 @@ export function TrackerBucket({ label, rows, year, month, refresh, onEditBill, l
|
|||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="w-[10%] py-3"><div className="h-3 w-20 rounded-md bg-muted" /></TableCell>
|
||||
<TableCell className="w-[10%] py-3 text-right"><div className="h-3 w-20 ml-auto rounded-md bg-muted" /></TableCell>
|
||||
<TableCell className="w-[10%] py-3 text-right"><div className="h-3 w-20 ml-auto rounded-md bg-muted" /></TableCell>
|
||||
<TableCell className="w-[10%] py-3 text-center"><div className="mx-auto h-3 w-20 rounded-md bg-muted" /></TableCell>
|
||||
<TableCell className="w-[10%] py-3 text-center"><div className="mx-auto h-3 w-20 rounded-md bg-muted" /></TableCell>
|
||||
<TableCell className="w-[10%] py-3"><div className="mx-auto h-7 w-24 rounded-md bg-muted" /></TableCell>
|
||||
<TableCell className="w-[10%] py-3"><div className="h-7 w-24 rounded-md bg-muted" /></TableCell>
|
||||
<TableCell className="w-[9%] py-3"><div className="mx-auto h-5 w-20 rounded-md bg-muted" /></TableCell>
|
||||
|
|
|
|||
|
|
@ -305,12 +305,12 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
|
|||
style={{ animationDelay: `${index * 40}ms` }}
|
||||
>
|
||||
{/* Bill name + category + monthly notes (if set) */}
|
||||
<TableCell className="w-[18%] py-3">
|
||||
<TableCell className="w-[18%] py-2.5">
|
||||
<div className="flex min-w-0 items-center gap-2.5">
|
||||
<div className="flex shrink-0 items-center gap-0.5">
|
||||
<GripVertical
|
||||
className={cn(
|
||||
'h-4 w-4 text-muted-foreground/55',
|
||||
'h-3.5 w-3.5 text-muted-foreground/55',
|
||||
moveControls?.enabled && 'cursor-grab group-active:cursor-grabbing',
|
||||
!moveControls?.enabled && 'opacity-30',
|
||||
)}
|
||||
|
|
@ -347,7 +347,7 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
|
|||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={cn(
|
||||
'min-w-0 truncate text-base font-bold leading-tight text-foreground transition-colors',
|
||||
'min-w-0 truncate text-sm font-semibold leading-tight text-foreground transition-colors',
|
||||
'hover:underline decoration-muted-foreground/50 underline-offset-2',
|
||||
isSkipped && 'line-through',
|
||||
)}
|
||||
|
|
@ -355,7 +355,7 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
|
|||
{row.name}
|
||||
</a>
|
||||
) : (
|
||||
<span className={cn('min-w-0 truncate text-base font-bold leading-tight text-foreground', isSkipped && 'line-through')}>
|
||||
<span className={cn('min-w-0 truncate text-sm font-semibold leading-tight text-foreground', isSkipped && 'line-through')}>
|
||||
{row.name}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -427,7 +427,7 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
|
|||
</TooltipProvider>
|
||||
<Button
|
||||
size="icon" variant="ghost"
|
||||
className="h-7 w-7 shrink-0 opacity-100 transition-opacity text-muted-foreground hover:text-foreground hover:bg-accent lg:opacity-0 lg:group-hover:opacity-100"
|
||||
className="h-6 w-6 shrink-0 opacity-100 transition-opacity text-muted-foreground hover:text-foreground hover:bg-accent lg:opacity-0 lg:group-hover:opacity-100"
|
||||
title="Edit bill"
|
||||
onClick={() => onEditBill?.(row)}
|
||||
>
|
||||
|
|
@ -435,7 +435,7 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
|
|||
</Button>
|
||||
</div>
|
||||
{row.category_name && (
|
||||
<p className="mt-1 truncate text-[11px] font-medium leading-none text-muted-foreground/75">{row.category_name}</p>
|
||||
<p className="mt-0.5 truncate text-[11px] font-medium leading-none text-muted-foreground/75">{row.category_name}</p>
|
||||
)}
|
||||
{/* Monthly notes shown inline under the bill name */}
|
||||
{row.monthly_notes && (
|
||||
|
|
@ -449,7 +449,7 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
|
|||
</TableCell>
|
||||
|
||||
{/* Due */}
|
||||
<TableCell className="tracker-number w-[10%] py-3 text-[13px] font-medium text-foreground/75">
|
||||
<TableCell className="tracker-number w-[10%] py-2.5 text-[13px] font-medium text-foreground/75">
|
||||
{editingDue ? (
|
||||
<input
|
||||
type="number"
|
||||
|
|
@ -478,7 +478,7 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
|
|||
</TableCell>
|
||||
|
||||
{/* Expected / Actual — shows actual_amount in amber when it overrides the template */}
|
||||
<TableCell className="tracker-number w-[10%] py-3 text-right text-[13px] font-semibold">
|
||||
<TableCell className="tracker-number w-[10%] py-2.5 text-center text-[13px] font-semibold">
|
||||
{editingExpected ? (
|
||||
<input
|
||||
type="number"
|
||||
|
|
@ -491,23 +491,23 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
|
|||
if (e.key === 'Enter') e.currentTarget.blur();
|
||||
if (e.key === 'Escape') { setEditingExpected(false); }
|
||||
}}
|
||||
className="tracker-number w-24 rounded border border-border bg-transparent px-1 py-0.5 text-right text-sm font-semibold text-foreground outline-none focus:ring-[2px] focus:ring-ring/50"
|
||||
className="tracker-number mx-auto w-24 rounded border border-border bg-transparent px-1 py-0.5 text-center text-sm font-semibold text-foreground outline-none focus:ring-[2px] focus:ring-ring/50"
|
||||
/>
|
||||
) : effectiveActual != null ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setExpectedDraft(String(effectiveActual)); setEditingExpected(true); }}
|
||||
className="rounded px-1 py-0.5 text-amber-300 transition-colors hover:bg-accent"
|
||||
className="mx-auto rounded px-1 py-0.5 text-amber-300 transition-colors hover:bg-accent"
|
||||
title={`Monthly override — click to edit. Template default: ${fmt(row.expected_amount)}`}
|
||||
>
|
||||
{fmt(effectiveActual)}
|
||||
</button>
|
||||
) : (
|
||||
<div className="flex flex-col items-end gap-0.5">
|
||||
<div className="mx-auto flex w-[68px] flex-col items-center gap-0.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setExpectedDraft(String(row.expected_amount)); setEditingExpected(true); }}
|
||||
className="rounded px-1 py-0.5 text-foreground/85 transition-colors hover:bg-accent hover:text-foreground"
|
||||
className="rounded px-1 py-0.5 text-center text-foreground/85 transition-colors hover:bg-accent hover:text-foreground"
|
||||
title="Click to edit expected amount"
|
||||
>
|
||||
{fmt(row.expected_amount)}
|
||||
|
|
@ -522,7 +522,7 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
|
|||
const min = Math.min(...vals);
|
||||
const max = Math.max(...vals);
|
||||
const range = max - min || 1;
|
||||
const W = 44, H = 16;
|
||||
const W = 40, H = 12;
|
||||
const pts = vals.map((v, i) => {
|
||||
const x = (i / (vals.length - 1)) * W;
|
||||
const y = H - ((v - min) / range) * H;
|
||||
|
|
@ -550,12 +550,12 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
|
|||
</TableCell>
|
||||
|
||||
{/* Previous month paid */}
|
||||
<TableCell className="tracker-number w-[10%] py-3 text-right text-[13px] font-medium text-muted-foreground/80">
|
||||
<TableCell className="tracker-number w-[10%] py-2.5 text-center text-[13px] font-medium text-muted-foreground/80">
|
||||
{row.previous_month_paid > 0 ? fmt(row.previous_month_paid) : '—'}
|
||||
</TableCell>
|
||||
|
||||
{/* Amount paid — mismatch now compares against threshold */}
|
||||
<TableCell className="w-[10%] py-3 text-center">
|
||||
<TableCell className="w-[10%] py-2.5 text-center">
|
||||
<PaymentProgress
|
||||
row={row}
|
||||
threshold={threshold}
|
||||
|
|
@ -566,7 +566,7 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
|
|||
</TableCell>
|
||||
|
||||
{/* Paid date */}
|
||||
<TableCell className="w-[10%] py-3 text-[13px] text-foreground/75">
|
||||
<TableCell className="w-[10%] py-2.5 text-[13px] text-foreground/75">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPaymentLedgerOpen(true)}
|
||||
|
|
@ -579,7 +579,7 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
|
|||
</TableCell>
|
||||
|
||||
{/* Status — uses effectiveStatus (accounts for skipped + threshold) */}
|
||||
<TableCell className="w-[9%] py-3">
|
||||
<TableCell className="w-[9%] py-2.5">
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<StatusBadge
|
||||
status={effectiveStatus}
|
||||
|
|
@ -602,7 +602,7 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
|
|||
</TableCell>
|
||||
|
||||
{/* Actions */}
|
||||
<TableCell className="w-[10%] py-3 text-center">
|
||||
<TableCell className="w-[10%] py-2.5 text-center">
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
{showUpdateNudge ? (
|
||||
<div className="flex items-center gap-1 animate-in fade-in slide-in-from-right-1 duration-200">
|
||||
|
|
@ -658,7 +658,7 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
|
|||
</TableCell>
|
||||
|
||||
{/* Notes cell (monthly state notes) */}
|
||||
<TableCell className="w-[23%] py-3 border-l border-border pl-4">
|
||||
<TableCell className="w-[23%] py-2.5 border-l border-border pl-4">
|
||||
<NotesCell row={{ ...row, year, month }} refresh={refresh} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
|
|||
Loading…
Reference in New Issue