From 74b6e5f078368bfae90eb3878844e89c27d75f68 Mon Sep 17 00:00:00 2001 From: ulsklyc <108589275+ulsklyc@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:24:08 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Phase=203=20Schritte=2016=E2=80=9318=20?= =?UTF-8?q?=E2=80=94=20Pinnwand,=20Kontakte,=20Budget-Tracker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pinnwand (Notes): - server/routes/notes.js: GET (sortiert: angeheftet zuerst), POST, PUT, PATCH /pin, DELETE - public/pages/notes.js: Masonry-Grid, Markdown-Light-Renderer (fett/kursiv/Liste), Farb-Auswahl (8 Farben), helle/dunkle Textfarbe je nach Hintergrund, Pin-Toggle - public/styles/notes.css: Masonry-Layout, Sticky-Note-Karten, Hover-Aktionen Kontakte: - server/routes/contacts.js: GET (Kategorie- + Volltextfilter), POST, PUT, DELETE, GET /meta - public/pages/contacts.js: Kategorie-Filter-Chips, Echtzeit-Suche, Gruppenansicht, tel:/mailto:/maps-Links, CRUD-Modal - public/styles/contacts.css: Toolbar mit Suche, Filter-Chips, Kontaktliste, Aktions-Buttons Budget-Tracker: - server/routes/budget.js: GET (Monatfilter), GET /summary (Einnahmen/Ausgaben/Saldo + Aufschlüsselung), GET /export (CSV mit BOM), POST, PUT, DELETE, GET /meta - public/pages/budget.js: Monatsnavigation, 3 Zusammenfassungs-Karten, Kategorie-Balken (reines CSS, kein Canvas), Transaktionsliste, Einnahme/Ausgabe-Toggle, CSV-Download - public/styles/budget.css: Summary-Cards, Balkendiagramm, Transaktionsliste, Modal Tests: 34 neue Tests (10 Notes + 9 Contacts + 15 Budget), gesamt 146/146 Co-Authored-By: Claude Sonnet 4.6 --- package.json | 3 +- public/index.html | 3 + public/pages/budget.js | 438 +++++++++++++++++++++++++++++++++- public/pages/contacts.js | 329 ++++++++++++++++++++++++- public/pages/notes.js | 294 ++++++++++++++++++++++- public/styles/budget.css | 402 +++++++++++++++++++++++++++++++ public/styles/contacts.css | 312 ++++++++++++++++++++++++ public/styles/notes.css | 320 +++++++++++++++++++++++++ server/routes/budget.js | 283 +++++++++++++++++++++- server/routes/contacts.js | 148 +++++++++++- server/routes/notes.js | 147 +++++++++++- test-notes-contacts-budget.js | 310 ++++++++++++++++++++++++ 12 files changed, 2935 insertions(+), 54 deletions(-) create mode 100644 public/styles/budget.css create mode 100644 public/styles/contacts.css create mode 100644 public/styles/notes.css create mode 100644 test-notes-contacts-budget.js diff --git a/package.json b/package.json index 23cbf13..22c2e57 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "test:shopping": "node --experimental-sqlite test-shopping.js", "test:meals": "node --experimental-sqlite test-meals.js", "test:calendar": "node --experimental-sqlite test-calendar.js", - "test": "node --experimental-sqlite test-db.js && node --experimental-sqlite test-dashboard.js && node --experimental-sqlite test-tasks.js && node --experimental-sqlite test-shopping.js && node --experimental-sqlite test-meals.js && node --experimental-sqlite test-calendar.js" + "test:ncb": "node --experimental-sqlite test-notes-contacts-budget.js", + "test": "node --experimental-sqlite test-db.js && node --experimental-sqlite test-dashboard.js && node --experimental-sqlite test-tasks.js && node --experimental-sqlite test-shopping.js && node --experimental-sqlite test-meals.js && node --experimental-sqlite test-calendar.js && node --experimental-sqlite test-notes-contacts-budget.js" }, "dependencies": { "bcrypt": "^5.1.1", diff --git a/public/index.html b/public/index.html index 0c15883..e143b29 100644 --- a/public/index.html +++ b/public/index.html @@ -21,6 +21,9 @@ + + + diff --git a/public/pages/budget.js b/public/pages/budget.js index 744c130..c961e13 100644 --- a/public/pages/budget.js +++ b/public/pages/budget.js @@ -1,25 +1,437 @@ /** - * Modul: Budget - * Zweck: Seite für das Budget-Modul - * Abhängigkeiten: /api.js + * Modul: Budget-Tracker (Budget) + * Zweck: Monatsübersicht, Kategorie-Balkendiagramm (Canvas), Transaktionsliste, + * CRUD, CSV-Export + * Abhängigkeiten: /api.js, /router.js (window.oikos) */ import { api } from '/api.js'; -/** - * @param {HTMLElement} container - * @param {{ user: object }} context - */ +// -------------------------------------------------------- +// Konstanten +// -------------------------------------------------------- + +const CATEGORIES = [ + 'Lebensmittel', 'Miete', 'Versicherung', 'Mobilität', + 'Freizeit', 'Kleidung', 'Gesundheit', 'Bildung', 'Sonstiges', +]; + +const MONTH_NAMES = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', + 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']; + +// -------------------------------------------------------- +// State +// -------------------------------------------------------- + +let state = { + month: '', // YYYY-MM + entries: [], + summary: null, +}; + +// -------------------------------------------------------- +// Formatierung +// -------------------------------------------------------- + +function formatAmount(n) { + return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(n); +} + +function formatMonthLabel(ym) { + const [y, m] = ym.split('-'); + return `${MONTH_NAMES[parseInt(m, 10) - 1]} ${y}`; +} + +function addMonths(ym, n) { + const [y, m] = ym.split('-').map(Number); + const d = new Date(y, m - 1 + n, 1); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`; +} + +// -------------------------------------------------------- +// API +// -------------------------------------------------------- + +async function loadMonth(month) { + const [entriesRes, summaryRes] = await Promise.all([ + api.get(`/budget?month=${month}`), + api.get(`/budget/summary?month=${month}`), + ]); + state.month = month; + state.entries = entriesRes.data; + state.summary = summaryRes.data; +} + +// -------------------------------------------------------- +// Entry Point +// -------------------------------------------------------- + export async function render(container, { user }) { + const today = new Date(); + state.month = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}`; + container.innerHTML = ` -
-