diff --git a/client/api.js b/client/api.js index 6fe74f1..2b330d3 100644 --- a/client/api.js +++ b/client/api.js @@ -73,6 +73,7 @@ export const api = { runAdminCleanup: () => post('/admin/cleanup/run'), seedDemoData: () => post('/user/seed-demo-data'), clearDemoData: () => post('/user/clear-demo-data'), + seededStatus: () => get('/user/seeded-status'), downloadAdminBackup: async (id) => { const res = await fetch(`/api/admin/backups/${encodeURIComponent(id)}/download`, { credentials: 'include', diff --git a/client/pages/DataPage.jsx b/client/pages/DataPage.jsx index 2c65549..def7194 100644 --- a/client/pages/DataPage.jsx +++ b/client/pages/DataPage.jsx @@ -1453,6 +1453,25 @@ function SeedDemoDataSection({ onSeeded }) { const [result, setResult] = useState(null); const [clearing, setClearing] = useState(false); const [showClearConfirm, setShowClearConfirm] = useState(false); + const [statusLoading, setStatusLoading] = useState(true); + + // Check seeded status on mount + useEffect(() => { + const checkSeededStatus = async () => { + try { + const data = await api.seededStatus(); + if (data.seeded) { + setSeeded(true); + setResult(data); + } + } catch (err) { + console.error('Failed to check seeded status:', err); + } finally { + setStatusLoading(false); + } + }; + checkSeededStatus(); + }, []); const handleSeed = async () => { setLoading(true); @@ -1541,6 +1560,14 @@ function SeedDemoDataSection({ onSeeded }) { ); } + if (statusLoading) { + return ( + +
Loading…
+
+ ); + } + return (
@@ -1551,7 +1578,7 @@ function SeedDemoDataSection({ onSeeded }) {
-
diff --git a/routes/monthly-starting-amounts.js b/routes/monthly-starting-amounts.js index d00a4f3..9938e5a 100644 --- a/routes/monthly-starting-amounts.js +++ b/routes/monthly-starting-amounts.js @@ -62,6 +62,17 @@ function calculatePaidDeductions(db, userId, year, month) { AND b.due_day BETWEEN 15 AND 31 `).get(userId, start, end); + // Paid from other bucket: bills with due_day outside 1-14 and 15-31 (shouldn't happen with current schema) + const otherPaid = db.prepare(` + SELECT COALESCE(SUM(p.amount), 0) AS paid + FROM payments p + JOIN bills b ON b.id = p.bill_id + WHERE b.user_id = ? + AND p.paid_date BETWEEN ? AND ? + AND p.deleted_at IS NULL + AND (b.due_day < 1 OR b.due_day > 31) + `).get(userId, start, end); + const totalPaid = db.prepare(` SELECT COALESCE(SUM(p.amount), 0) AS paid FROM payments p @@ -74,6 +85,7 @@ function calculatePaidDeductions(db, userId, year, month) { return { paid_from_first: money(firstPaid.paid), paid_from_fifteenth: money(fifteenthPaid.paid), + paid_from_other: money(otherPaid.paid), paid_total: money(totalPaid.paid), }; } @@ -94,10 +106,11 @@ function buildStartingAmountsResponse(db, userId, year, month) { combined_amount, paid_from_first: paid.paid_from_first, paid_from_fifteenth: paid.paid_from_fifteenth, + paid_from_other: paid.paid_from_other, paid_total, first_remaining: amounts.first_amount - paid.paid_from_first, fifteenth_remaining: amounts.fifteenth_amount - paid.paid_from_fifteenth, - other_remaining: amounts.other_amount, + other_remaining: amounts.other_amount - paid.paid_from_other, combined_remaining: combined_amount - paid_total, }; } diff --git a/routes/summary.js b/routes/summary.js index 1a2d316..7987d2e 100644 --- a/routes/summary.js +++ b/routes/summary.js @@ -64,6 +64,17 @@ function calculatePaidDeductions(db, userId, year, month) { AND b.due_day BETWEEN 15 AND 31 `).get(userId, start, end); + // Paid from other bucket: bills with due_day outside 1-14 and 15-31 (shouldn't happen with current schema) + const otherPaid = db.prepare(` + SELECT COALESCE(SUM(p.amount), 0) AS paid + FROM payments p + JOIN bills b ON b.id = p.bill_id + WHERE b.user_id = ? + AND p.paid_date BETWEEN ? AND ? + AND p.deleted_at IS NULL + AND (b.due_day < 1 OR b.due_day > 31) + `).get(userId, start, end); + const totalPaid = db.prepare(` SELECT COALESCE(SUM(p.amount), 0) AS paid FROM payments p @@ -76,6 +87,7 @@ function calculatePaidDeductions(db, userId, year, month) { return { paid_from_first: money(firstPaid.paid), paid_from_fifteenth: money(fifteenthPaid.paid), + paid_from_other: money(otherPaid.paid), paid_total: money(totalPaid.paid), }; } @@ -96,10 +108,11 @@ function buildStartingAmountsSummary(db, userId, year, month) { combined_amount, paid_from_first: paid.paid_from_first, paid_from_fifteenth: paid.paid_from_fifteenth, + paid_from_other: paid.paid_from_other, paid_total, first_remaining: amounts.first_amount - paid.paid_from_first, fifteenth_remaining: amounts.fifteenth_amount - paid.paid_from_fifteenth, - other_remaining: amounts.other_amount, + other_remaining: amounts.other_amount - paid.paid_from_other, combined_remaining: combined_amount - paid_total, }; } diff --git a/routes/user.js b/routes/user.js index 1cef364..f1d348e 100644 --- a/routes/user.js +++ b/routes/user.js @@ -6,6 +6,33 @@ const { getDb } = require('../db/database'); const { seedDemoData } = require('../scripts/seedDemoData'); const { demoDataLimiter } = require('../middleware/rateLimiter'); +// GET /api/user/seeded-status — returns whether the current user has any seeded data +router.get('/seeded-status', (req, res) => { + try { + const db = getDb(); + const userId = req.user.id; + + // Check for seeded bills + const seededBillsResult = db.prepare('SELECT COUNT(*) as count FROM bills WHERE user_id = ? AND is_seeded = 1').get(userId); + const seededBillsCount = seededBillsResult.count; + + // Check for seeded categories + const seededCategoriesResult = db.prepare('SELECT COUNT(*) as count FROM categories WHERE user_id = ? AND is_seeded = 1').get(userId); + const seededCategoriesCount = seededCategoriesResult.count; + + const hasSeededData = seededBillsCount > 0 || seededCategoriesCount > 0; + + res.json({ + seeded: hasSeededData, + seededBills: seededBillsCount, + seededCategories: seededCategoriesCount, + }); + } catch (err) { + const status = err.status || 500; + res.status(status).json({ error: status === 500 ? 'Seeded status check failed' : err.message }); + } +}); + // POST /api/user/clear-demo-data — removes all seeded bills and categories for the requesting user router.post('/clear-demo-data', demoDataLimiter, (req, res) => { try {