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

View File

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