Skip to content

Commit 3606369

Browse files
committed
removed any hardcoded values
1 parent a7b6af4 commit 3606369

6 files changed

Lines changed: 117 additions & 89 deletions

File tree

web/budgeting.html

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,13 @@ <h1>Budgeting</h1>
2727
<p class="subtle">Plan your monthly spend, track actuals, and move unused funds to savings or another category.</p>
2828
</div>
2929
<div class="budget-hero-meta">
30-
<label class="subtle" for="budgetMonthSelect">Month</label>
31-
<select id="budgetMonthSelect" class="month-select" aria-label="Select budget month"></select>
32-
<span class="pill" id="budgetPeriod" aria-live="polite"></span>
30+
<div class="month-controls">
31+
<div class="month-row">
32+
<label class="subtle" for="budgetMonthSelect">Month</label>
33+
<select id="budgetMonthSelect" class="month-select" aria-label="Select budget month"></select>
34+
</div>
35+
<span class="pill" id="budgetPeriod" aria-live="polite"></span>
36+
</div>
3337
<button class="btn" id="btnResetBudgets" type="button">Reset defaults</button>
3438
</div>
3539
</section>

web/home.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ <h2>Net Worth</h2>
8383
</div>
8484
<div class="net-worth-chart">
8585
<canvas id="netWorthChart" aria-label="Net worth trend" role="img"></canvas>
86-
<p class="chart-caption subtle">Last 6 months</p>
86+
<p class="chart-caption subtle">Recent months</p>
8787
</div>
8888
</article>
8989

web/scripts/budgeting.js

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ import { api } from "./api.js";
66
const CURRENCY_FALLBACK = "USD";
77

88
const DEFAULT_CATEGORIES = [
9-
{ name: "Housing", budget: 1500 },
10-
{ name: "Utilities", budget: 220 },
11-
{ name: "Groceries", budget: 450 },
12-
{ name: "Transportation", budget: 180 },
13-
{ name: "Dining", budget: 200 },
14-
{ name: "Health", budget: 160 },
15-
{ name: "Entertainment", budget: 140 },
16-
{ name: "Subscriptions", budget: 65 },
17-
{ name: "Travel", budget: 120 },
18-
{ name: "Education", budget: 90 },
19-
{ name: "Giving", budget: 75 },
20-
{ name: "Savings", budget: 300 },
21-
{ name: "Other", budget: 100 },
9+
{ name: "Housing", budget: null },
10+
{ name: "Utilities", budget: null },
11+
{ name: "Groceries", budget: null },
12+
{ name: "Transportation", budget: null },
13+
{ name: "Dining", budget: null },
14+
{ name: "Health", budget: null },
15+
{ name: "Entertainment", budget: null },
16+
{ name: "Subscriptions", budget: null },
17+
{ name: "Travel", budget: null },
18+
{ name: "Education", budget: null },
19+
{ name: "Giving", budget: null },
20+
{ name: "Savings", budget: null },
21+
{ name: "Other", budget: null },
2222
];
2323

2424
const $ = (sel, root = document) => root.querySelector(sel);
@@ -61,7 +61,12 @@ import { api } from "./api.js";
6161

6262
return normalized.map((c) => {
6363
const stored = byName.get(normalizeName(c.name));
64-
return stored ? { ...c, budget: Number(stored.budget) || 0 } : c;
64+
if (!stored) return c;
65+
if (stored.budget === null || stored.budget === undefined || stored.budget === "") {
66+
return { ...c, budget: null };
67+
}
68+
const value = Number(stored.budget);
69+
return { ...c, budget: Number.isFinite(value) ? value : null };
6570
});
6671
} catch {
6772
return DEFAULT_CATEGORIES.map((c) => ({ ...c }));
@@ -112,9 +117,10 @@ import { api } from "./api.js";
112117
function computeTotals(categories, spentMap) {
113118
const totals = categories.reduce(
114119
(acc, c) => {
120+
const budget = Number.isFinite(c.budget) ? c.budget : 0;
115121
const spent = spentMap.get(normalizeName(c.name)) || 0;
116-
const remaining = c.budget - spent;
117-
acc.totalBudget += c.budget;
122+
const remaining = budget - spent;
123+
acc.totalBudget += budget;
118124
acc.totalSpent += spent;
119125
acc.totalRemaining += remaining;
120126
if (normalizeName(c.name) !== "savings" && remaining > 0) acc.unused += remaining;
@@ -153,8 +159,9 @@ import { api } from "./api.js";
153159

154160
categories.forEach((c, idx) => {
155161
const spent = spentMap.get(normalizeName(c.name)) || 0;
156-
const remaining = c.budget - spent;
157-
const progress = c.budget > 0 ? Math.min(spent / c.budget, 1) : 0;
162+
const budget = Number.isFinite(c.budget) ? c.budget : 0;
163+
const remaining = budget - spent;
164+
const progress = budget > 0 ? Math.min(spent / budget, 1) : 0;
158165

159166
const tr = document.createElement("tr");
160167

@@ -167,7 +174,7 @@ import { api } from "./api.js";
167174
input.type = "number";
168175
input.min = "0";
169176
input.step = "1";
170-
input.value = c.budget;
177+
input.value = c.budget ?? "";
171178
input.className = "budget-input";
172179
input.dataset.index = String(idx);
173180
tdBudget.appendChild(input);
@@ -208,7 +215,8 @@ import { api } from "./api.js";
208215
if (isTarget) return { ...c };
209216

210217
const spent = c.spent || 0;
211-
const remaining = c.budget - spent;
218+
const budget = Number.isFinite(c.budget) ? c.budget : 0;
219+
const remaining = budget - spent;
212220
if (remaining > 0) {
213221
unused += remaining;
214222
return { ...c, budget: spent };
@@ -218,7 +226,10 @@ import { api } from "./api.js";
218226

219227
const targetIndex = updated.findIndex((c) => normalizeName(c.name) === targetKey);
220228
if (targetIndex >= 0) {
221-
updated[targetIndex].budget += unused;
229+
const current = Number.isFinite(updated[targetIndex].budget)
230+
? updated[targetIndex].budget
231+
: 0;
232+
updated[targetIndex].budget = current + unused;
222233
}
223234

224235
return { updated, moved: unused };
@@ -298,8 +309,12 @@ import { api } from "./api.js";
298309
if (!target.dataset.index) return;
299310

300311
const idx = Number(target.dataset.index);
301-
const next = Number(target.value || 0);
302-
state.categories[idx].budget = Math.max(0, Number.isFinite(next) ? next : 0);
312+
if (target.value === "") {
313+
state.categories[idx].budget = null;
314+
} else {
315+
const next = Number(target.value || 0);
316+
state.categories[idx].budget = Math.max(0, Number.isFinite(next) ? next : 0);
317+
}
303318
saveCategories(state.categories.map(({ name, budget }) => ({ name, budget })), state.monthKey);
304319

305320
const updatedTotals = computeTotals(state.categories, state.spentMap);

web/scripts/home.js

Lines changed: 47 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,13 @@ import { api } from "./api.js";
173173
const dpr = window.devicePixelRatio || 1;
174174

175175
canvas.width = parentWidth * dpr;
176-
canvas.height = 220 * dpr;
176+
canvas.height = 260 * dpr;
177177

178178
const ctx = canvas.getContext("2d");
179179
ctx.setTransform(1, 0, 0, 1, 0, 0);
180180
ctx.scale(dpr, dpr);
181181

182-
ctx.clearRect(0, 0, parentWidth, 220);
182+
ctx.clearRect(0, 0, parentWidth, 260);
183183

184184
if (!series?.length) return;
185185

@@ -329,33 +329,7 @@ import { api } from "./api.js";
329329
// ============================================================
330330
// NET WORTH DATA + RENDER
331331
// ============================================================
332-
function buildDemoNetWorth(currency) {
333-
return {
334-
currency,
335-
asOf: new Date().toISOString(),
336-
assets: [
337-
{ name: "Checking", amount: 4200 },
338-
{ name: "Savings", amount: 7800 },
339-
{ name: "Investments", amount: 15300 },
340-
{ name: "Property", amount: 62000 },
341-
],
342-
liabilities: [
343-
{ name: "Credit Card", amount: 1800 },
344-
{ name: "Auto Loan", amount: 6200 },
345-
{ name: "Student Loan", amount: 12400 },
346-
],
347-
trend: [
348-
{ label: "Aug", value: 68000 },
349-
{ label: "Sep", value: 69200 },
350-
{ label: "Oct", value: 70100 },
351-
{ label: "Nov", value: 71450 },
352-
{ label: "Dec", value: 70980 },
353-
{ label: "Jan", value: 72620 },
354-
],
355-
};
356-
}
357-
358-
function buildMonthlyNet(records, monthsBack = 6) {
332+
function buildMonthlyNet(records, monthsBack = 12) {
359333
const now = new Date();
360334
const months = [];
361335
for (let i = monthsBack - 1; i >= 0; i -= 1) {
@@ -392,24 +366,30 @@ import { api } from "./api.js";
392366
}
393367
}
394368

395-
const demo = buildDemoNetWorth(currency);
396-
if (!records?.length) return demo;
397-
398-
const assetsTotal = demo.assets.reduce((s, a) => s + a.amount, 0);
399-
const liabilitiesTotal = demo.liabilities.reduce((s, l) => s + l.amount, 0);
400-
const base = assetsTotal - liabilitiesTotal;
401-
const months = buildMonthlyNet(records, 6);
369+
if (!records?.length) {
370+
return {
371+
currency,
372+
asOf: null,
373+
assets: [],
374+
liabilities: [],
375+
trend: [],
376+
};
377+
}
402378

379+
const base = 0;
380+
const months = buildMonthlyNet(records, 12);
403381
let running = base;
404382
const trend = months.map((m) => {
405383
running += m.net;
406384
return { label: m.label, value: Math.max(0, running) };
407385
});
408386

409387
return {
410-
...demo,
411-
trend,
388+
currency,
412389
asOf: new Date().toISOString(),
390+
assets: [],
391+
liabilities: [],
392+
trend,
413393
};
414394
}
415395

@@ -419,21 +399,29 @@ import { api } from "./api.js";
419399
const liabilitiesTotal = (data.liabilities || []).reduce((s, l) => s + l.amount, 0);
420400
const netWorth = assetsTotal - liabilitiesTotal;
421401

422-
setText("#netWorthTotal", fmtMoney(netWorth, data.currency));
423-
setText("#assetsTotal", fmtMoney(assetsTotal, data.currency));
424-
setText("#liabilitiesTotal", fmtMoney(liabilitiesTotal, data.currency));
425-
426-
const deltaBase = data.trend?.length ? data.trend[0].value : netWorth;
427-
const delta = netWorth - deltaBase;
428-
const deltaLabel = delta >= 0 ? "up" : "down";
429-
setText(
430-
"#netWorthDelta",
431-
`${delta >= 0 ? "+" : "-"}${fmtMoney(Math.abs(delta), data.currency)} ${deltaLabel} vs 6 months ago`
432-
);
433-
setText(
434-
"#netWorthUpdated",
435-
`Updated ${new Date(data.asOf).toLocaleDateString()}`
436-
);
402+
if (!data.assets?.length && !data.liabilities?.length && !data.trend?.length) {
403+
setText("#netWorthTotal", "—");
404+
setText("#assetsTotal", "—");
405+
setText("#liabilitiesTotal", "—");
406+
setText("#netWorthDelta", "Connect accounts to see your net worth trend.");
407+
setText("#netWorthUpdated", "No net worth data yet");
408+
} else {
409+
setText("#netWorthTotal", fmtMoney(netWorth, data.currency));
410+
setText("#assetsTotal", fmtMoney(assetsTotal, data.currency));
411+
setText("#liabilitiesTotal", fmtMoney(liabilitiesTotal, data.currency));
412+
413+
const deltaBase = data.trend?.length ? data.trend[0].value : netWorth;
414+
const delta = netWorth - deltaBase;
415+
const deltaLabel = delta >= 0 ? "up" : "down";
416+
setText(
417+
"#netWorthDelta",
418+
`${delta >= 0 ? "+" : "-"}${fmtMoney(Math.abs(delta), data.currency)} ${deltaLabel} vs previous period`
419+
);
420+
setText(
421+
"#netWorthUpdated",
422+
data.asOf ? `Updated ${new Date(data.asOf).toLocaleDateString()}` : "No net worth data yet"
423+
);
424+
}
437425

438426
const assetsList = $("#assetsList");
439427
const liabilitiesList = $("#liabilitiesList");
@@ -464,7 +452,9 @@ import { api } from "./api.js";
464452
renderList(assetsList, data.assets);
465453
renderList(liabilitiesList, data.liabilities);
466454

467-
drawNetWorthChart($("#netWorthChart"), data.trend, data.currency);
455+
if (data.trend?.length) {
456+
drawNetWorthChart($("#netWorthChart"), data.trend, data.currency);
457+
}
468458
}
469459

470460
function renderKpis(comp, viewLabel) {
@@ -697,7 +687,9 @@ import { api } from "./api.js";
697687

698688
const redraw = debounce(() => {
699689
drawBarChart(canvas, computed.categories);
700-
drawNetWorthChart($("#netWorthChart"), netWorthData.trend, netWorthData.currency);
690+
if (netWorthData.trend?.length) {
691+
drawNetWorthChart($("#netWorthChart"), netWorthData.trend, netWorthData.currency);
692+
}
701693
}, 150);
702694

703695
window.addEventListener("resize", redraw);

web/styles/budgeting.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@
2020
justify-content: flex-end;
2121
}
2222

23+
.month-controls {
24+
display: flex;
25+
flex-direction: column;
26+
align-items: flex-end;
27+
gap: 0.35rem;
28+
}
29+
30+
.month-row {
31+
display: inline-flex;
32+
align-items: center;
33+
gap: 0.5rem;
34+
}
35+
2336
.month-select {
2437
min-width: 160px;
2538
padding: 0.45rem 0.6rem;

web/styles/home.css

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,14 @@
7070

7171
.net-worth-grid {
7272
display: grid;
73-
grid-template-columns: minmax(0, 2fr) minmax(0, 1.2fr);
73+
grid-template-columns: minmax(0, 2.4fr) minmax(0, 1.6fr);
7474
gap: 1rem;
7575
}
7676

7777
.net-worth-summary {
7878
display: grid;
79-
grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
80-
gap: 1.5rem;
81-
align-items: center;
79+
grid-template-columns: minmax(0, 1fr);
80+
gap: 1rem;
8281
padding: 1.25rem;
8382
}
8483

@@ -106,15 +105,20 @@
106105

107106
.net-worth-breakdown {
108107
display: grid;
109-
grid-template-columns: repeat(2, minmax(0, 1fr));
108+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
110109
gap: 1rem;
111-
padding: 1.25rem !important;
110+
padding: 1.5rem !important;
111+
align-items: start;
112112
}
113113

114114
.breakdown-col h3 {
115115
margin-bottom: 0.5rem;
116116
}
117117

118+
.breakdown-col {
119+
min-width: 0;
120+
}
121+
118122
.networth-list {
119123
list-style: none;
120124
margin: 0;
@@ -124,7 +128,7 @@
124128
.networth-list li {
125129
display: flex;
126130
justify-content: space-between;
127-
gap: 0.75rem;
131+
gap: 1rem;
128132
padding: 0.4rem 0;
129133
border-bottom: 1px dashed var(--border);
130134
}

0 commit comments

Comments
 (0)