feat: summary overdue highlighting, tracker row visual polish, bill table cleanup

This commit is contained in:
null 2026-06-06 23:29:34 -05:00
parent bb04966bbc
commit 88cb9d5340
4 changed files with 133 additions and 57 deletions

View File

@ -2,6 +2,7 @@ import { ArrowDown, ArrowUp, Copy, GripVertical, PenLine, EyeOff, Eye, Clock, Tr
import { cn } from '@/lib/utils';
import { scheduleLabel } from '@/lib/billingSchedule';
import { MobileBillRow } from '@/components/MobileBillRow';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
function ordinal(n) {
const d = Number(n);
@ -104,37 +105,58 @@ function BillCard({ bill, prefs = ALL_ON, onEdit, onToggle, onDelete, onHistory,
</span>
)}
{prefs.showAutopay && !!bill.autopay_enabled && (
<span className="shrink-0 rounded bg-emerald-500/20 px-1.5 py-0.5 text-[11px] font-semibold text-emerald-300">
Autopay
</span>
)}
{prefs.show2fa && !!bill.has_2fa && (
<span className="shrink-0 rounded bg-violet-500/20 px-1.5 py-0.5 text-[11px] font-semibold text-violet-300">
2FA
</span>
)}
{prefs.showSubscription && !!bill.is_subscription && (
<span className="shrink-0 rounded border border-indigo-500/25 bg-indigo-500/10 px-1.5 py-0.5 text-[11px] font-semibold text-indigo-600 dark:text-indigo-300">
S
</span>
)}
{(!!bill.has_merchant_rule || !!bill.has_linked_transactions) && (
<span
className="shrink-0 rounded border border-emerald-500/25 bg-emerald-500/10 px-1.5 py-0.5 text-[11px] font-semibold text-emerald-600 dark:text-emerald-400"
title="Linked to bank transactions"
>
L
</span>
)}
{hasHistory && (
<span
className="inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full border border-sky-500/25 bg-sky-500/10 text-sky-500"
title="Historical visibility configured"
>
<Clock className="h-2.5 w-2.5" />
</span>
)}
<TooltipProvider delayDuration={300}>
{prefs.showAutopay && !!bill.autopay_enabled && (
<Tooltip>
<TooltipTrigger asChild>
<span className="shrink-0 rounded border border-sky-500/25 bg-sky-500/10 px-1.5 py-0.5 text-[11px] font-semibold text-sky-600 dark:text-sky-300 cursor-default">
AP
</span>
</TooltipTrigger>
<TooltipContent>Autopay enabled</TooltipContent>
</Tooltip>
)}
{prefs.show2fa && !!bill.has_2fa && (
<Tooltip>
<TooltipTrigger asChild>
<span className="shrink-0 rounded bg-violet-500/20 px-1.5 py-0.5 text-[11px] font-semibold text-violet-300 cursor-default">
2FA
</span>
</TooltipTrigger>
<TooltipContent>Two-factor authentication configured</TooltipContent>
</Tooltip>
)}
{prefs.showSubscription && !!bill.is_subscription && (
<Tooltip>
<TooltipTrigger asChild>
<span className="shrink-0 rounded border border-indigo-500/25 bg-indigo-500/10 px-1.5 py-0.5 text-[11px] font-semibold text-indigo-600 dark:text-indigo-300 cursor-default">
S
</span>
</TooltipTrigger>
<TooltipContent>Subscription</TooltipContent>
</Tooltip>
)}
{(!!bill.has_merchant_rule || !!bill.has_linked_transactions) && (
<Tooltip>
<TooltipTrigger asChild>
<span className="shrink-0 rounded border border-emerald-500/25 bg-emerald-500/10 px-1.5 py-0.5 text-[11px] font-semibold text-emerald-600 dark:text-emerald-400 cursor-default">
L
</span>
</TooltipTrigger>
<TooltipContent>Linked to bank transactions</TooltipContent>
</Tooltip>
)}
{hasHistory && (
<Tooltip>
<TooltipTrigger asChild>
<span className="inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full border border-sky-500/25 bg-sky-500/10 text-sky-500 cursor-default">
<Clock className="h-2.5 w-2.5" />
</span>
</TooltipTrigger>
<TooltipContent>Historical visibility configured</TooltipContent>
</Tooltip>
)}
</TooltipProvider>
</div>
{/* Meta row */}

View File

@ -1,5 +1,6 @@
import React, { useState, useRef, useTransition } from 'react';
import { ArrowDown, ArrowUp, GripVertical, Pencil, X } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { toast } from 'sonner';
import { api } from '@/api.js';
import { cn, fmt, fmtDate } from '@/lib/utils';
@ -356,30 +357,38 @@ export function TrackerRow({ row, year, month, refresh, index, onEditBill, moveC
{row.name}
</span>
)}
{row.autopay_enabled && (
<span
className="inline-flex shrink-0 rounded border border-sky-500/25 bg-sky-500/10 px-1.5 py-0.5 text-[10px] font-bold leading-none text-sky-600 dark:text-sky-300"
title="Autopay"
>
AP
</span>
)}
{(row.has_merchant_rule || row.has_linked_transactions) && (
<span
className="inline-flex shrink-0 rounded border border-emerald-500/25 bg-emerald-500/10 px-1.5 py-0.5 text-[10px] font-bold leading-none text-emerald-600 dark:text-emerald-400"
title="Linked to bank transactions"
>
L
</span>
)}
{row.is_subscription && (
<span
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
</span>
)}
<TooltipProvider delayDuration={300}>
{row.autopay_enabled && (
<Tooltip>
<TooltipTrigger asChild>
<span className="inline-flex shrink-0 rounded border border-sky-500/25 bg-sky-500/10 px-1.5 py-0.5 text-[10px] font-bold leading-none text-sky-600 dark:text-sky-300 cursor-default">
AP
</span>
</TooltipTrigger>
<TooltipContent>Autopay enabled</TooltipContent>
</Tooltip>
)}
{(row.has_merchant_rule || row.has_linked_transactions) && (
<Tooltip>
<TooltipTrigger asChild>
<span className="inline-flex shrink-0 rounded border border-emerald-500/25 bg-emerald-500/10 px-1.5 py-0.5 text-[10px] font-bold leading-none text-emerald-600 dark:text-emerald-400 cursor-default">
L
</span>
</TooltipTrigger>
<TooltipContent>Linked to bank transactions</TooltipContent>
</Tooltip>
)}
{row.is_subscription && (
<Tooltip>
<TooltipTrigger asChild>
<span 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 cursor-default">
S
</span>
</TooltipTrigger>
<TooltipContent>Subscription</TooltipContent>
</Tooltip>
)}
</TooltipProvider>
<Button
size="icon" variant="ghost"
className="h-7 w-7 opacity-100 transition-opacity text-muted-foreground hover:text-foreground hover:bg-accent lg:opacity-0 lg:group-hover:opacity-100"

View File

@ -19,6 +19,7 @@ import { api } from '@/api.js';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { cn, fmt } from '@/lib/utils';
import { moveInArray, reorderPayload } from '@/lib/reorder';
@ -183,7 +184,41 @@ function ExpenseRow({ expense, moveControls, dragProps }) {
</div>
</div>
<div className="min-w-0">
<div className="truncate text-sm font-semibold text-foreground">{expense.name}</div>
<div className="flex flex-wrap items-center gap-1.5">
<span className="truncate text-sm font-semibold text-foreground">{expense.name}</span>
<TooltipProvider delayDuration={300}>
{expense.autopay_enabled && (
<Tooltip>
<TooltipTrigger asChild>
<span className="inline-flex shrink-0 rounded border border-sky-500/25 bg-sky-500/10 px-1.5 py-0.5 text-[10px] font-bold leading-none text-sky-600 dark:text-sky-300 cursor-default">
AP
</span>
</TooltipTrigger>
<TooltipContent>Autopay enabled</TooltipContent>
</Tooltip>
)}
{expense.is_subscription && (
<Tooltip>
<TooltipTrigger asChild>
<span 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 cursor-default">
S
</span>
</TooltipTrigger>
<TooltipContent>Subscription</TooltipContent>
</Tooltip>
)}
{(expense.has_merchant_rule || expense.has_linked_transactions) && (
<Tooltip>
<TooltipTrigger asChild>
<span className="inline-flex shrink-0 rounded border border-emerald-500/25 bg-emerald-500/10 px-1.5 py-0.5 text-[10px] font-bold leading-none text-emerald-600 dark:text-emerald-400 cursor-default">
L
</span>
</TooltipTrigger>
<TooltipContent>Linked to bank transactions</TooltipContent>
</Tooltip>
)}
</TooltipProvider>
</div>
<div className="mt-1 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
{expense.category_name && <span>{expense.category_name}</span>}
<span>Due day {expense.due_day}</span>

View File

@ -238,10 +238,16 @@ function buildSummary(db, userId, year, month) {
c.name AS category_name,
m.actual_amount,
m.is_skipped,
b.sort_order
b.sort_order,
b.autopay_enabled,
b.is_subscription,
CASE WHEN mr.bill_id IS NOT NULL THEN 1 ELSE 0 END AS has_merchant_rule,
CASE WHEN lt.matched_bill_id IS NOT NULL THEN 1 ELSE 0 END AS has_linked_transactions
FROM bills b
LEFT JOIN categories c ON c.id = b.category_id AND c.user_id = b.user_id AND c.deleted_at IS NULL
LEFT JOIN monthly_bill_state m ON m.bill_id = b.id AND m.year = ? AND m.month = ?
LEFT JOIN (SELECT DISTINCT bill_id FROM bill_merchant_rules) mr ON mr.bill_id = b.id
LEFT JOIN (SELECT DISTINCT matched_bill_id FROM transactions WHERE match_status = 'matched') lt ON lt.matched_bill_id = b.id
WHERE b.user_id = ? AND b.active = 1 AND b.deleted_at IS NULL
ORDER BY CASE WHEN b.sort_order IS NULL THEN 1 ELSE 0 END,
b.sort_order ASC,
@ -292,6 +298,10 @@ function buildSummary(db, userId, year, month) {
is_skipped: !!row.is_skipped,
due_day: row.due_day,
category_name: row.category_name || null,
autopay_enabled: !!row.autopay_enabled,
is_subscription: !!row.is_subscription,
has_merchant_rule: !!row.has_merchant_rule,
has_linked_transactions: !!row.has_linked_transactions,
};
});