fix: starting amounts paid_from_other calculation + pay badge alignment on tracker
This commit is contained in:
parent
890427c75a
commit
b29d3a0b02
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<SectionCard title="Demo Data" subtitle="Seed your database with demo data for testing" icon={Sparkles}>
|
||||
<div className="px-6 py-4 text-sm text-muted-foreground">Loading…</div>
|
||||
</SectionCard>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionCard title="Demo Data" subtitle="Seed your database with demo data for testing" icon={Sparkles}>
|
||||
<div className="rounded-lg border border-border/60 bg-background/50 p-4">
|
||||
|
|
@ -1551,7 +1578,7 @@ function SeedDemoDataSection({ onSeeded }) {
|
|||
|
||||
<div className="mt-4 space-y-4">
|
||||
<div className="border-t border-border pt-4">
|
||||
<Button size="sm" variant="outline" onClick={handleSeed} disabled={loading}>
|
||||
<Button size="sm" variant="outline" onClick={handleSeed} disabled={loading || seeded}>
|
||||
{loading ? <><Loader2 className="h-3.5 w-3.5 mr-1.5 animate-spin" />Seeding…</> : 'Seed Demo Data'}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue