BillTracker/client/components/SummaryCard.jsx

89 lines
2.8 KiB
React
Raw Permalink Normal View History

2026-05-09 13:03:36 -05:00
import React, { useMemo } from 'react';
import { cn, fmt } from '@/lib/utils';
import { AlertCircle, CheckCircle2, Clock, TrendingUp } from 'lucide-react';
import { Settings2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
const CARD_DEFS = {
starting: {
label: 'Starting',
icon: TrendingUp,
bar: 'from-slate-400 to-slate-300',
glow: '',
valueClass: 'text-foreground',
activateWhen: () => true,
},
paid: {
label: 'Total Paid',
icon: CheckCircle2,
bar: 'from-emerald-500 to-emerald-300',
glow: 'shadow-[0_4px_20px_rgba(16,185,129,0.15)]',
borderActive: 'border-emerald-400/40',
valueClass: 'text-emerald-500',
activateWhen: (v) => v > 0,
},
remaining: {
label: 'Remaining',
icon: Clock,
bar: 'from-blue-400 to-indigo-300',
glow: '',
valueClass: 'text-foreground',
activateWhen: () => true,
},
overdue: {
label: 'Overdue',
icon: AlertCircle,
bar: 'from-rose-500 to-orange-400',
glow: 'shadow-[0_4px_20px_rgba(239,68,68,0.12)]',
borderActive: 'border-red-400/40',
valueClass: 'text-red-500',
activateWhen: (v) => v > 0,
},
};
export const SummaryCard = React.memo(function SummaryCard({ type, value, onEdit, hint }) {
const def = useMemo(() => CARD_DEFS[type], [type]);
const isActive = useMemo(() => def.activateWhen(value || 0), [def, value]);
const Icon = useMemo(() => def.icon, [def]);
return (
<div className={cn(
'flex-1 min-w-0 relative overflow-hidden rounded-xl border border-border',
'bg-card px-5 py-4 transition-all duration-300',
isActive && def.glow,
isActive && def.borderActive,
)}>
<div className={cn(
'absolute top-0 left-0 right-0 h-[3px] bg-gradient-to-r',
def.bar,
!isActive && (type === 'paid' || type === 'overdue') && 'opacity-30',
)} />
<div className="flex items-center gap-2 mb-3">
<Icon className={cn('h-4 w-4', isActive ? def.valueClass : 'text-muted-foreground')} />
<p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
{def.label}
</p>
{type === 'starting' && onEdit && (
<button
onClick={onEdit}
className="ml-auto h-4 w-4 text-muted-foreground hover:text-foreground transition-colors"
title="Edit monthly starting amounts"
aria-label="Edit monthly starting amounts"
>
<Settings2 className="h-4 w-4" />
</button>
)}
</div>
<p className={cn(
'text-[1.75rem] font-bold tracking-tight font-mono leading-none',
isActive ? def.valueClass : 'text-foreground',
)}>
{fmt(value)}
</p>
{hint && <p className="mt-2 text-[11px] text-muted-foreground">{hint}</p>}
</div>
);
});
SummaryCard.displayName = 'SummaryCard';