@ -789,7 +789,7 @@ function NotesCell({ row, refresh }) {
}
/ / ─ ─ T a b l e r o w ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
function Row ( { row , year , month , refresh , index , onEditBill } ) {
function Row ( { row , year , month , refresh , index , onEditBill , compact = false } ) {
const amountRef = useRef ( null ) ;
const [ editPayment , setEditPayment ] = useState ( null ) ;
const [ paymentLedgerOpen , setPaymentLedgerOpen ] = useState ( false ) ;
@ -1049,7 +1049,7 @@ function Row({ row, year, month, refresh, index, onEditBill }) {
className = "inline-flex shrink-0 rounded border border-indigo-500/25 bg-indigo-500/10 px-1.5 py-0.5 text-[10px] font-bold leading-none text-indigo-600 dark:text-indigo-300"
title = "Subscription"
>
S ub
S
< / span >
) }
< Button
@ -1155,7 +1155,7 @@ function Row({ row, year, month, refresh, index, onEditBill }) {
< / TableCell >
{ /* Previous month paid */ }
< TableCell className = "tracker-number w-[10%] py-3 text-right text-[13px] font-medium text-muted-foreground/80" >
< TableCell className = { cn ( 'tracker-number w-[10%] py-3 text-right text-[13px] font-medium text-muted-foreground/80' , compact && '2xl:hidden' ) } >
{ row . previous _month _paid > 0 ? fmt ( row . previous _month _paid ) : '—' }
< / TableCell >
@ -1252,7 +1252,7 @@ function Row({ row, year, month, refresh, index, onEditBill }) {
< / TableCell >
{ /* Notes cell (monthly state notes) */ }
< TableCell className = "w-[23%] py-3 border-l border-border pl-4" >
< TableCell className = { cn ( compact ? 'w-[16%]' : 'w-[23%]' , 'py-3 border-l border-border pl-4' ) } >
< NotesCell row = { { ... row , year , month } } refresh = { refresh } / >
< / TableCell >
< / TableRow >
@ -1621,7 +1621,7 @@ function MobileTrackerRow({ row, year, month, refresh, index, onEditBill }) {
}
/ / ─ ─ B u c k e t ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
function Bucket ( { label , rows , year , month , refresh , onEditBill , loading } ) {
function Bucket ( { label , rows , year , month , refresh , onEditBill , loading , compact = false } ) {
/ / U s e a c t u a l _ a m o u n t ( i f s e t ) a s t h e p e r - r o w t h r e s h o l d ; e x c l u d e s k i p p e d r o w s f r o m t o t a l s
const activeRows = rows . filter ( r => ! r . is _skipped ) ;
const totalThreshold = activeRows . reduce ( ( s , r ) => s + ( r . actual _amount ? ? r . expected _amount ? ? 0 ) , 0 ) ;
@ -1631,10 +1631,11 @@ function Bucket({ label, rows, year, month, refresh, onEditBill, loading }) {
const cappedPaid = Number ( r . paid _toward _due ) ;
return s + ( Number . isFinite ( cappedPaid ) ? cappedPaid : Math . min ( Number ( r . total _paid ) || 0 , threshold ) ) ;
} , 0 ) ;
const totalOverpaid = Math . max ( totalPaid - totalPaidTowardDue , 0 ) ;
const skippedCount = rows . length - activeRows . length ;
const pct = totalThreshold > 0 ? Math . min ( ( totalPaidTowardDue / totalThreshold ) * 100 , 100 ) : 0 ;
const allPaid = pct >= 100 ;
const totalOverpaid = Math . max ( totalPaid - totalPaidTowardDue , 0 ) ;
const totalRemaining = Math . max ( totalThreshold - totalPaidTowardDue , 0 ) ;
const skippedCount = rows . length - activeRows . length ;
const pct = totalThreshold > 0 ? Math . min ( ( totalPaidTowardDue / totalThreshold ) * 100 , 100 ) : 0 ;
const allPaid = pct >= 100 ;
return (
< div className = "rounded-xl border border-border/80 overflow-hidden bg-card/95 shadow-sm shadow-black/15" >
@ -1665,16 +1666,24 @@ function Bucket({ label, rows, year, month, refresh, onEditBill, loading }) {
< / span >
< / div >
< / div >
< span className = "text-xs font-mono text-muted-foreground" >
< span className = { cn ( allPaid ? 'text-emerald-500' : 'text-foreground' ) } >
{ fmt ( totalPaidTowardDue ) }
< div className = "flex items-center gap-3 text-xs font-mono text-muted-foreground" >
< span >
< span className = { cn ( allPaid ? 'text-emerald-500' : 'text-foreground' ) } >
{ fmt ( totalPaidTowardDue ) }
< / span >
< span className = "text-muted-foreground/50 mx-1" > / < / span >
{ fmt ( totalThreshold ) }
{ totalOverpaid > 0 && (
< span className = "ml-1 text-emerald-500" > + { fmt ( totalOverpaid ) } < / span >
) }
< / span >
< span className = "text-muted-foreground/50 mx-1" > / < / span >
{ fmt ( totalThreshold ) }
{ totalOverpaid > 0 && (
< span className = "ml-1 text-emerald-500" > + { fmt ( totalOverpaid ) } < / span >
{ ! allPaid && totalRemaining > 0 && (
< span className = "text-[11px] text-muted-foreground/70" > { fmt ( totalRemaining ) } left < / span >
) }
< / span >
{ allPaid && (
< span className = "text-[11px] text-emerald-500" > Done < / span >
) }
< / div >
< / div >
< div className = "grid gap-3 p-3 lg:hidden" aria - busy = { loading ? 'true' : 'false' } >
@ -1723,18 +1732,18 @@ function Bucket({ label, rows, year, month, refresh, onEditBill, loading }) {
< div className = "hidden lg:block" aria - busy = { loading ? 'true' : 'false' } >
< div className = "overflow-x-auto" >
< Table className = "min-w-[1120px]" >
< Table className = { cn ( 'min-w-[1120px]' , compact && '2xl:min-w-[700px]' ) } >
< TableHeader >
< TableRow className = "border-border/80 bg-background/30 hover:bg-background/30" >
< TableHead className = "w-[18%] py-2.5 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/90" > Bill < / TableHead >
< TableHead className = "w-[ 10 %] py-2.5 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/90"> Due < / TableHead >
< TableHead className = "w-[ 9 %] py-2.5 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/90"> Due < / TableHead >
< TableHead className = "w-[10%] py-2.5 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/90 text-right" > Expected < / TableHead >
< TableHead className = " w-[10%] py-2.5 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/80 text-right" > Last Month < / TableHead >
< TableHead className = { cn ( ' w-[10%] py-2.5 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/80 text-right', compact && '2xl:hidden' ) } > Last Month < / TableHead >
< TableHead className = "w-[10%] py-2.5 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/90 text-right" > Paid < / TableHead >
< TableHead className = "w-[10%] py-2.5 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/90" > Paid Date < / TableHead >
< TableHead className = "w-[9%] py-2.5 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/90" > Status < / TableHead >
< TableHead className = "w-[ 10 %] py-2.5" / >
< TableHead className = "w-[23%] py-2.5 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/90 border-l border-border/80 pl-4" >
< TableHead className = "w-[ 8 %] py-2.5" / >
< TableHead className = { cn ( ' py-2.5 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/90 border-l border-border/80 pl-4', compact ? 'w-[16%]' : 'w-[23%]' ) } >
Notes
< / TableHead >
< / TableRow >
@ -1782,6 +1791,7 @@ function Bucket({ label, rows, year, month, refresh, onEditBill, loading }) {
refresh = { refresh }
index = { i }
onEditBill = { onEditBill }
compact = { compact }
/ >
) )
) }
@ -1918,6 +1928,7 @@ export default function TrackerPage() {
} , [ filters , rows , search ] ) ;
const first = filteredRows . filter ( r => r . bucket === '1st' ) ;
const second = filteredRows . filter ( r => r . bucket === '15th' ) ;
const hasBoth = first . length > 0 && second . length > 0 ;
return (
< div className = "space-y-5" >
@ -2038,12 +2049,6 @@ export default function TrackerPage() {
onEdit = { ( ) => setEditStartingOpen ( true ) }
/ >
< SummaryCard type = "paid" value = { summary . total _paid } / >
< SummaryCard
type = "remaining"
value = { summary . remaining }
label = { summary . remaining _label || 'Remaining' }
hint = { summary . remaining _hint }
/ >
< SummaryCard type = "overdue" value = { summary . overdue } / >
< SummaryCard type = "paid" value = { summary . previous _month _total } hint = "Previous month" / >
{ summary . trend && < TrendCard trend = { summary . trend } / > }
@ -2116,8 +2121,12 @@ export default function TrackerPage() {
< / div >
< / div >
) }
{ ! isError && first . length > 0 && < Bucket label = "1st – 14th" rows = { first } year = { year } month = { month } refresh = { refetch } onEditBill = { handleOpenEditBill } loading = { loading } / > }
{ ! isError && second . length > 0 && < Bucket label = "15th – 31st" rows = { second } year = { year } month = { month } refresh = { refetch } onEditBill = { handleOpenEditBill } loading = { loading } / > }
{ ! isError && ( first . length > 0 || second . length > 0 ) && (
< div className = { cn ( 'space-y-5' , hasBoth && '2xl:space-y-0 2xl:grid 2xl:grid-cols-2 2xl:gap-5' ) } >
{ first . length > 0 && < Bucket label = "1st – 14th" rows = { first } year = { year } month = { month } refresh = { refetch } onEditBill = { handleOpenEditBill } loading = { loading } compact = { hasBoth } / > }
{ second . length > 0 && < Bucket label = "15th – 31st" rows = { second } year = { year } month = { month } refresh = { refetch } onEditBill = { handleOpenEditBill } loading = { loading } compact = { hasBoth } / > }
< / div >
) }
{ /* Edit Bill modal — opened by clicking a bill name in any tracker row */ }
{ editBillData && (