diff --git a/client/pages/HealthPage.jsx b/client/pages/HealthPage.jsx index e9ce80e..3b36a87 100644 --- a/client/pages/HealthPage.jsx +++ b/client/pages/HealthPage.jsx @@ -1,5 +1,4 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Link } from 'react-router-dom'; import { toast } from 'sonner'; import { AlertTriangle, @@ -12,6 +11,7 @@ import { import { api } from '@/api'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import BillModal from '@/components/BillModal'; import { cn } from '@/lib/utils'; const FIELD_LABELS = { @@ -66,8 +66,9 @@ function IssuePill({ issue }) { ); } -function BillIssueCard({ bill }) { +function BillIssueCard({ bill, onOpenBill, openingBillId }) { const sortedIssues = [...bill.issues].sort((a, b) => severityWeight(a.severity) - severityWeight(b.severity)); + const opening = openingBillId === bill.id; return ( @@ -86,8 +87,16 @@ function BillIssueCard({ bill }) { {!bill.active && ' - Inactive'} - @@ -112,6 +121,9 @@ export default function HealthPage() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [includeInactive, setIncludeInactive] = useState(false); + const [categories, setCategories] = useState([]); + const [modalBill, setModalBill] = useState(null); + const [openingBillId, setOpeningBillId] = useState(null); const load = useCallback(async () => { setLoading(true); @@ -126,6 +138,27 @@ export default function HealthPage() { useEffect(() => { load(); }, [load]); + const openBill = useCallback(async (billId) => { + setOpeningBillId(billId); + try { + const [bill, cats] = await Promise.all([ + api.bill(billId), + categories.length ? Promise.resolve(categories) : api.categories(), + ]); + if (!categories.length) setCategories(cats); + setModalBill(bill); + } catch (err) { + toast.error(err.message || 'Could not open bill.'); + } finally { + setOpeningBillId(null); + } + }, [categories]); + + const handleBillSaved = useCallback(() => { + setModalBill(null); + load(); + }, [load]); + const summary = data?.summary || {}; const bills = data?.bills || []; const sortedBills = useMemo(() => [...bills].sort((a, b) => { @@ -206,10 +239,26 @@ export default function HealthPage() {

Fix errors first; warnings are cleanup items that improve confidence and projections.

- {sortedBills.map(bill => )} + {sortedBills.map(bill => ( + + ))}
)} + + {modalBill && ( + setModalBill(null)} + onSave={handleBillSaved} + /> + )} ); } diff --git a/routes/user.js b/routes/user.js index f1d348e..058f7b8 100644 --- a/routes/user.js +++ b/routes/user.js @@ -64,7 +64,7 @@ router.post('/clear-demo-data', demoDataLimiter, (req, res) => { } }); -// POST /api/user/seed-demo-data — seeds 20 demo bills for the requesting user +// POST /api/user/seed-demo-data — seeds demo bills for the requesting user router.post('/seed-demo-data', (req, res) => { try { const result = seedDemoData(req.user.id); diff --git a/scripts/seedDemoData.js b/scripts/seedDemoData.js index 83686fa..dfd9b5f 100644 --- a/scripts/seedDemoData.js +++ b/scripts/seedDemoData.js @@ -1,7 +1,7 @@ #!/usr/bin/env node /** * Seed Demo Data Script - * Creates 20 realistic bills across 8 categories for demo purposes. + * Creates realistic bills across common categories for demo purposes. * Idempotent: can be run multiple times safely. */ @@ -36,12 +36,14 @@ const BILLS = [ { name: 'Internet Provider', category: 'Utilities', amount: 70, dueDay: 18, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Cell Phone', category: 'Utilities', amount: 65, dueDay: 25, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Health Insurance', category: 'Healthcare', amount: 200, dueDay: 1, cycle: 'quarterly', autopay: true, interestRate: 0 }, - { name: 'Credit Card', category: 'Credit Cards', amount: 150, dueDay: 28, cycle: 'monthly', autopay: true, interestRate: 19.99, currentBalance: 2800, minPayment: 75, snowballOrder: 0, snowballInclude: 1 }, - { name: 'Student Loan', category: 'Loans', amount: 250, dueDay: 15, cycle: 'monthly', autopay: true, interestRate: 5.5, currentBalance: 12500, minPayment: 150, snowballOrder: 1, snowballInclude: 1 }, + { name: 'Discover It Card', category: 'Credit Cards', amount: 65, dueDay: 26, cycle: 'monthly', autopay: true, interestRate: 22.99, currentBalance: 920, minPayment: 35, snowballOrder: 0, snowballInclude: 1 }, + { name: 'Capital One Quicksilver', category: 'Credit Cards', amount: 95, dueDay: 28, cycle: 'monthly', autopay: true, interestRate: 24.49, currentBalance: 1850, minPayment: 55, snowballOrder: 1, snowballInclude: 1 }, + { name: 'Chase Freedom', category: 'Credit Cards', amount: 140, dueDay: 12, cycle: 'monthly', autopay: true, interestRate: 21.49, currentBalance: 3200, minPayment: 90, snowballOrder: 2, snowballInclude: 1 }, + { name: 'Student Loan', category: 'Loans', amount: 250, dueDay: 15, cycle: 'monthly', autopay: true, interestRate: 5.5, currentBalance: 12500, minPayment: 150, snowballOrder: 4, snowballInclude: 1 }, { name: 'Gas Utility', category: 'Utilities', amount: 35, dueDay: 12, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Trash Service', category: 'Utilities', amount: 25, dueDay: 28, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Homeowners Insurance', category: 'Insurance', amount: 300, dueDay: 10, cycle: 'annually', autopay: false, interestRate: 0 }, - { name: 'Car Payment', category: 'Loans', amount: 350, dueDay: 22, cycle: 'monthly', autopay: true, interestRate: 4.5, currentBalance: 8400, minPayment: 350, snowballOrder: 2, snowballInclude: 1 }, + { name: 'Car Payment', category: 'Loans', amount: 350, dueDay: 22, cycle: 'monthly', autopay: true, interestRate: 4.5, currentBalance: 8400, minPayment: 350, snowballOrder: 3, snowballInclude: 1 }, { name: 'Spotify', category: 'Entertainment', amount: 9.99, dueDay: 14, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Adobe Creative Cloud', category: 'Subscriptions', amount: 54.99, dueDay: 8, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Amazon Prime', category: 'Subscriptions', amount: 14.99, dueDay: 1, cycle: 'annually', autopay: true, interestRate: 0 }, @@ -148,7 +150,7 @@ function seedDemoData(userId = null) { billData.cycle || 'monthly', amount, billData.autopay !== undefined ? (billData.autopay ? 1 : 0) : Math.random() > 0.5 ? 1 : 0, - billData.interestRate || (Math.random() > 0.7 ? Math.round(Math.random() * 15 * 100) / 100 : 0), + billData.interestRate ?? (Math.random() > 0.7 ? Math.round(Math.random() * 15 * 100) / 100 : 0), billData.currentBalance ?? null, billData.minPayment ?? null, billData.snowballOrder ?? null,