fix: SimpleFIN recommendation card stays vertical in narrow sidebar

Title and amount in header, badges/reasons below, action buttons
in a clean row at the bottom.
This commit is contained in:
null 2026-05-29 03:55:55 -05:00
parent c3c0ab3542
commit da6a93804b
2 changed files with 58 additions and 56 deletions

View File

@ -195,64 +195,66 @@ function BillPickerDialog({ open, onClose, recommendation, bills, onConfirm, bus
function RecommendationCard({ recommendation, categoryId, onAccept, onDecline, onMatch, busy }) {
return (
<div className="rounded-lg border border-primary/20 bg-primary/[0.05] p-4">
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0">
<div className="flex min-w-0 flex-wrap items-center gap-2">
<p className="min-w-0 max-w-full truncate text-sm font-semibold text-foreground">{recommendation.name}</p>
<Badge variant="outline" className="border-primary/25 bg-primary/10 text-primary">
{recommendation.confidence}% match
</Badge>
<div className="space-y-3">
<div className="flex min-w-0 items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-semibold text-foreground">{recommendation.name}</p>
<p className="mt-1 text-xs font-medium text-muted-foreground">
{TYPE_LABELS[recommendation.subscription_type] || 'Other'} · {recommendation.occurrence_count} charges · last seen {fmtDate(recommendation.last_seen_date)}
</p>
</div>
<p className="mt-1 text-xs font-medium text-muted-foreground">
{TYPE_LABELS[recommendation.subscription_type] || 'Other'} · {recommendation.occurrence_count} charges · last seen {fmtDate(recommendation.last_seen_date)}
</p>
<div className="mt-3 flex flex-wrap gap-2">
{recommendation.reasons?.map(reason => (
<span key={reason} className="rounded-md border border-border/60 bg-background/60 px-2 py-1 text-[11px] font-medium text-muted-foreground">
{reason}
</span>
))}
<div className="shrink-0 text-right">
<p className="tracker-number text-base font-semibold text-foreground">{fmt(recommendation.expected_amount)}</p>
<p className="tracker-number text-xs font-semibold text-emerald-600 dark:text-emerald-300">
{fmt(recommendation.monthly_equivalent)} / mo
</p>
</div>
</div>
<div className="shrink-0 text-left sm:text-right">
<p className="tracker-number text-base font-semibold text-foreground">{fmt(recommendation.expected_amount)}</p>
<p className="tracker-number text-xs font-semibold text-emerald-600 dark:text-emerald-300">
{fmt(recommendation.monthly_equivalent)} / mo
</p>
<div className="mt-3 grid grid-cols-1 gap-2 min-[380px]:grid-cols-3 sm:flex sm:flex-wrap sm:justify-end">
<Button
type="button"
size="sm"
variant="ghost"
className="gap-1.5 text-muted-foreground hover:text-destructive"
disabled={busy}
onClick={() => onDecline(recommendation)}
>
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <X className="h-3.5 w-3.5" />}
Decline
</Button>
<Button
type="button"
size="sm"
variant="outline"
className="gap-1.5"
disabled={busy}
onClick={() => onMatch(recommendation)}
>
<Link2 className="h-3.5 w-3.5" />
Link to bill
</Button>
<Button
type="button"
size="sm"
className="gap-2"
disabled={busy}
onClick={() => onAccept({ ...recommendation, category_id: categoryId })}
>
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <CheckCircle2 className="h-3.5 w-3.5" />}
Track
</Button>
</div>
<div className="flex min-w-0 flex-wrap items-center gap-2">
<Badge variant="outline" className="border-primary/25 bg-primary/10 text-primary">
{recommendation.confidence}% match
</Badge>
{recommendation.reasons?.map(reason => (
<span key={reason} className="max-w-full rounded-md border border-border/60 bg-background/60 px-2 py-1 text-[11px] font-medium text-muted-foreground">
{reason}
</span>
))}
</div>
<div className="grid grid-cols-1 gap-2 min-[380px]:grid-cols-3">
<Button
type="button"
size="sm"
variant="ghost"
className="gap-1.5 text-muted-foreground hover:text-destructive"
disabled={busy}
onClick={() => onDecline(recommendation)}
>
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <X className="h-3.5 w-3.5" />}
Decline
</Button>
<Button
type="button"
size="sm"
variant="outline"
className="gap-1.5"
disabled={busy}
onClick={() => onMatch(recommendation)}
>
<Link2 className="h-3.5 w-3.5" />
Link to bill
</Button>
<Button
type="button"
size="sm"
className="gap-2"
disabled={busy}
onClick={() => onAccept({ ...recommendation, category_id: categoryId })}
>
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <CheckCircle2 className="h-3.5 w-3.5" />}
Track
</Button>
</div>
</div>
</div>

View File

@ -1,6 +1,6 @@
{
"name": "bill-tracker",
"version": "0.33.7",
"version": "0.33.7.1",
"description": "Monthly bill tracking system",
"main": "server.js",
"scripts": {