planmymeal/app/components/BudgetMeter.tsx

39 lines
1.5 KiB
TypeScript
Raw Normal View History

import { Meter, type MeterTone } from "@/components/base/meter/meter";
2026-10-31 20:44:29 -05:00
import { formatUsd } from "@/app/lib/units";
/** Success under 85%, warning up to 100%, error over budget. */
function budgetTone(spent: number, budget: number): { tone: MeterTone; text: string; label: string } {
2026-10-31 20:44:29 -05:00
const ratio = budget > 0 ? spent / budget : 0;
if (ratio > 1) return { tone: "error", text: "text-status-danger", label: "Over budget" };
if (ratio > 0.85) return { tone: "warning", text: "text-status-warn", label: "Close to budget" };
return { tone: "success", text: "text-status-ok", label: "Under budget" };
2026-10-31 20:44:29 -05:00
}
interface BudgetMeterProps {
spent: number;
budget: number;
compact?: boolean;
}
export function BudgetMeter({ spent, budget, compact = false }: BudgetMeterProps) {
const tone = budgetTone(spent, budget);
return (
<div className={compact ? "w-full" : "w-full min-w-48"}>
<div className="flex items-baseline justify-between gap-2">
<span className={`text-body-medium tabular-nums ${tone.text}`}>{formatUsd(spent)}</span>
<span className="text-caption-1-medium text-text-secondary tabular-nums">/ {formatUsd(budget)}</span>
2026-10-31 20:44:29 -05:00
</div>
<Meter
className="mt-1.5"
2026-10-31 20:44:29 -05:00
aria-label="Budget used"
tone={tone.tone}
minValue={0}
maxValue={budget > 0 ? budget : 1}
value={Math.min(spent, budget > 0 ? budget : 0)}
valueLabel={`${formatUsd(spent)} of ${formatUsd(budget)}, ${tone.label.toLowerCase()}`}
/>
2026-10-31 20:44:29 -05:00
</div>
);
}