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:
parent
c3c0ab3542
commit
da6a93804b
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue