From 7910636ffaecf4407f0538a764810e1f375746b8 Mon Sep 17 00:00:00 2001 From: Serhiy Bobrov Date: Sun, 19 Apr 2026 09:15:29 +0300 Subject: [PATCH 1/2] feat: add income categories to budget management --- public/locales/ar.json | 5 +++++ public/locales/de.json | 5 +++++ public/locales/el.json | 5 +++++ public/locales/en.json | 5 +++++ public/locales/es.json | 5 +++++ public/locales/fr.json | 5 +++++ public/locales/hi.json | 5 +++++ public/locales/it.json | 5 +++++ public/locales/ja.json | 5 +++++ public/locales/pt.json | 5 +++++ public/locales/ru.json | 5 +++++ public/locales/sv.json | 5 +++++ public/locales/tr.json | 5 +++++ public/locales/uk.json | 8 ++++++-- public/locales/zh.json | 5 +++++ public/pages/budget.js | 34 +++++++++++++++++++++++++++++++--- server/routes/budget.js | 9 ++++++++- 17 files changed, 115 insertions(+), 6 deletions(-) diff --git a/public/locales/ar.json b/public/locales/ar.json index 1b00c2e..0c38056 100644 --- a/public/locales/ar.json +++ b/public/locales/ar.json @@ -451,6 +451,11 @@ "catHealth": "الصحة", "catEducation": "التعليم", "catMisc": "متنوع", + "catEarnedIncome": "دخل العمل", + "catInvestmentIncome": "دخل الاستثمار", + "catTransferGiftIncome": "التحويلات والهدايا", + "catGovernmentBenefits": "المزايا الاجتماعية", + "catOtherIncome": "دخل آخر", "loadingIndicator": "جارٍ التحميل…" }, "settings": { diff --git a/public/locales/de.json b/public/locales/de.json index 3c4c5aa..5a4516e 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -451,6 +451,11 @@ "catHealth": "Gesundheit", "catEducation": "Bildung", "catMisc": "Sonstiges", + "catEarnedIncome": "Erwerbseinkommen", + "catInvestmentIncome": "Kapitalerträge", + "catTransferGiftIncome": "Geschenke & Transfers", + "catGovernmentBenefits": "Sozialleistungen", + "catOtherIncome": "Sonstiges Einkommen", "loadingIndicator": "Lade…" }, "settings": { diff --git a/public/locales/el.json b/public/locales/el.json index bd22908..341d8e0 100644 --- a/public/locales/el.json +++ b/public/locales/el.json @@ -451,6 +451,11 @@ "catHealth": "Υγεία", "catEducation": "Εκπαίδευση", "catMisc": "Διάφορα", + "catEarnedIncome": "Εισόδημα από εργασία", + "catInvestmentIncome": "Επενδυτικό εισόδημα", + "catTransferGiftIncome": "Μεταφορές και δώρα", + "catGovernmentBenefits": "Κοινωνικές παροχές", + "catOtherIncome": "Άλλο εισόδημα", "loadingIndicator": "Φόρτωση…" }, "settings": { diff --git a/public/locales/en.json b/public/locales/en.json index f123d64..c2c3d1c 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -451,6 +451,11 @@ "catHealth": "Health", "catEducation": "Education", "catMisc": "Miscellaneous", + "catEarnedIncome": "Earned Income", + "catInvestmentIncome": "Investment Income", + "catTransferGiftIncome": "Transfer & Gift Income", + "catGovernmentBenefits": "Government & Social Benefits", + "catOtherIncome": "Other Income", "loadingIndicator": "Loading…" }, "settings": { diff --git a/public/locales/es.json b/public/locales/es.json index b4dbe21..0034563 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -451,6 +451,11 @@ "catHealth": "Salud", "catEducation": "Educación", "catMisc": "Otros", + "catEarnedIncome": "Ingresos del Trabajo", + "catInvestmentIncome": "Ingresos de Inversión", + "catTransferGiftIncome": "Transferencias y Regalos", + "catGovernmentBenefits": "Prestaciones Sociales", + "catOtherIncome": "Otros Ingresos", "loadingIndicator": "Cargando…" }, "settings": { diff --git a/public/locales/fr.json b/public/locales/fr.json index efaa4c2..205e26e 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -451,6 +451,11 @@ "catHealth": "Santé", "catEducation": "Éducation", "catMisc": "Divers", + "catEarnedIncome": "Revenus du Travail", + "catInvestmentIncome": "Revenus d'Investissement", + "catTransferGiftIncome": "Transferts et Cadeaux", + "catGovernmentBenefits": "Allocations Sociales", + "catOtherIncome": "Autres Revenus", "loadingIndicator": "Chargement…" }, "settings": { diff --git a/public/locales/hi.json b/public/locales/hi.json index 309e5c9..97316a7 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -451,6 +451,11 @@ "catHealth": "स्वास्थ्य", "catEducation": "शिक्षा", "catMisc": "विविध", + "catEarnedIncome": "कमाई आय", + "catInvestmentIncome": "निवेश आय", + "catTransferGiftIncome": "स्थानांतरण और उपहार", + "catGovernmentBenefits": "सामाजिक लाभ", + "catOtherIncome": "अन्य आय", "loadingIndicator": "लोड हो रहा है…" }, "settings": { diff --git a/public/locales/it.json b/public/locales/it.json index d9aab09..d3d907c 100644 --- a/public/locales/it.json +++ b/public/locales/it.json @@ -451,6 +451,11 @@ "catHealth": "Salute", "catEducation": "Istruzione", "catMisc": "Varie", + "catEarnedIncome": "Reddito da Lavoro", + "catInvestmentIncome": "Reddito da Investimenti", + "catTransferGiftIncome": "Trasferimenti e Regali", + "catGovernmentBenefits": "Prestazioni Sociali", + "catOtherIncome": "Altro Reddito", "loadingIndicator": "Caricamento…" }, "settings": { diff --git a/public/locales/ja.json b/public/locales/ja.json index 2a5c5c1..28f925a 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -451,6 +451,11 @@ "catHealth": "医療", "catEducation": "教育", "catMisc": "その他", + "catEarnedIncome": "給与・報酬", + "catInvestmentIncome": "投資収入", + "catTransferGiftIncome": "譲渡・贈与", + "catGovernmentBenefits": "社会保障給付", + "catOtherIncome": "その他の収入", "loadingIndicator": "読み込み中…" }, "settings": { diff --git a/public/locales/pt.json b/public/locales/pt.json index a0c5960..0f925c4 100644 --- a/public/locales/pt.json +++ b/public/locales/pt.json @@ -451,6 +451,11 @@ "catHealth": "Saúde", "catEducation": "Educação", "catMisc": "Outros", + "catEarnedIncome": "Renda do Trabalho", + "catInvestmentIncome": "Renda de Investimento", + "catTransferGiftIncome": "Transferências e Presentes", + "catGovernmentBenefits": "Benefícios Sociais", + "catOtherIncome": "Outras Rendas", "loadingIndicator": "Carregando…" }, "settings": { diff --git a/public/locales/ru.json b/public/locales/ru.json index 2b4405f..b3f1ef8 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -451,6 +451,11 @@ "catHealth": "Здоровье", "catEducation": "Образование", "catMisc": "Разное", + "catEarnedIncome": "Трудовой доход", + "catInvestmentIncome": "Инвестиционный доход", + "catTransferGiftIncome": "Переводы и подарки", + "catGovernmentBenefits": "Социальные пособия", + "catOtherIncome": "Прочие доходы", "loadingIndicator": "Загрузка…" }, "settings": { diff --git a/public/locales/sv.json b/public/locales/sv.json index 4693c2c..f5bb5e9 100644 --- a/public/locales/sv.json +++ b/public/locales/sv.json @@ -451,6 +451,11 @@ "catHealth": "Hälsa", "catEducation": "Utbildning", "catMisc": "Diverse", + "catEarnedIncome": "Arbetsinkomst", + "catInvestmentIncome": "Investeringsinkomst", + "catTransferGiftIncome": "Överföringar och gåvor", + "catGovernmentBenefits": "Socialförmåner", + "catOtherIncome": "Övrig inkomst", "loadingIndicator": "Laddar…" }, "settings": { diff --git a/public/locales/tr.json b/public/locales/tr.json index c9ce1ac..26e8a10 100644 --- a/public/locales/tr.json +++ b/public/locales/tr.json @@ -451,6 +451,11 @@ "catHealth": "Sağlık", "catEducation": "Eğitim", "catMisc": "Diğer", + "catEarnedIncome": "Kazanç Geliri", + "catInvestmentIncome": "Yatırım Geliri", + "catTransferGiftIncome": "Transferler ve Hediyeler", + "catGovernmentBenefits": "Sosyal Yardımlar", + "catOtherIncome": "Diğer Gelir", "loadingIndicator": "Yükleniyor…" }, "settings": { diff --git a/public/locales/uk.json b/public/locales/uk.json index ece443b..00705fa 100644 --- a/public/locales/uk.json +++ b/public/locales/uk.json @@ -451,6 +451,11 @@ "catHealth": "Здоров'я", "catEducation": "Освіта", "catMisc": "Різне", + "catEarnedIncome": "Трудовий дохід", + "catInvestmentIncome": "Інвестиційний дохід", + "catTransferGiftIncome": "Переводи та подарунки", + "catGovernmentBenefits": "Соціальні виплати", + "catOtherIncome": "Інші доходи", "loadingIndicator": "Завантаження…" }, "settings": { @@ -619,5 +624,4 @@ "pendingBadgeTitle": "{{count}} нагадування", "pendingBadgeTitlePlural": "{{count}} нагадувань" } -} - +} \ No newline at end of file diff --git a/public/locales/zh.json b/public/locales/zh.json index 9a7b717..59cf108 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -451,6 +451,11 @@ "catHealth": "健康", "catEducation": "教育", "catMisc": "其他", + "catEarnedIncome": "劳动收入", + "catInvestmentIncome": "投资收入", + "catTransferGiftIncome": "转账和礼物", + "catGovernmentBenefits": "社会福利", + "catOtherIncome": "其他收入", "loadingIndicator": "加载中…" }, "settings": { diff --git a/public/pages/budget.js b/public/pages/budget.js index f9bc995..fc02863 100644 --- a/public/pages/budget.js +++ b/public/pages/budget.js @@ -15,12 +15,20 @@ import { esc } from '/utils/html.js'; // Konstanten // -------------------------------------------------------- -const CATEGORIES = [ +const EXPENSE_CATEGORIES = [ 'Lebensmittel', 'Miete', 'Versicherung', 'Mobilität', 'Freizeit', 'Kleidung', 'Gesundheit', 'Bildung', 'Sonstiges', ]; +const INCOME_CATEGORIES = [ + 'Erwerbseinkommen', 'Kapitalerträge', 'Geschenke & Transfers', + 'Sozialleistungen', 'Sonstiges Einkommen', +]; + +const CATEGORIES = [...EXPENSE_CATEGORIES, ...INCOME_CATEGORIES]; + const CATEGORY_LABELS = () => ({ + // Expense categories 'Lebensmittel': t('budget.catFood'), 'Miete': t('budget.catRent'), 'Versicherung': t('budget.catInsurance'), @@ -30,6 +38,12 @@ const CATEGORY_LABELS = () => ({ 'Gesundheit': t('budget.catHealth'), 'Bildung': t('budget.catEducation'), 'Sonstiges': t('budget.catMisc'), + // Income categories + 'Erwerbseinkommen': t('budget.catEarnedIncome'), + 'Kapitalerträge': t('budget.catInvestmentIncome'), + 'Geschenke & Transfers': t('budget.catTransferGiftIncome'), + 'Sozialleistungen': t('budget.catGovernmentBenefits'), + 'Sonstiges Einkommen': t('budget.catOtherIncome'), }); function getMonthName(monthIndex) { @@ -345,8 +359,9 @@ function openBudgetModal({ mode, entry = null }) { const isExpense = isEdit ? entry.amount < 0 : true; const absAmount = isEdit ? Math.abs(entry.amount).toFixed(2) : ''; - const catLabels = CATEGORY_LABELS(); - const catOpts = CATEGORIES.map((c) => + const catLabels = CATEGORY_LABELS(); + const initialCats = isExpense ? EXPENSE_CATEGORIES : INCOME_CATEGORIES; + const catOpts = initialCats.map((c) => `` ).join(''); @@ -407,15 +422,28 @@ function openBudgetModal({ mode, entry = null }) { onSave(panel) { let currentType = isExpense ? 'expense' : 'income'; + const updateCategoryOptions = () => { + const catLabels = CATEGORY_LABELS(); + const cats = currentType === 'income' ? INCOME_CATEGORIES : EXPENSE_CATEGORIES; + const catSelect = panel.querySelector('#bm-category'); + const currentValue = catSelect.value; + + catSelect.innerHTML = cats.map((c) => + `` + ).join(''); + }; + panel.querySelector('#type-expense').addEventListener('click', () => { currentType = 'expense'; panel.querySelector('#type-expense').classList.add('amount-type-btn--active'); panel.querySelector('#type-income').classList.remove('amount-type-btn--active'); + updateCategoryOptions(); }); panel.querySelector('#type-income').addEventListener('click', () => { currentType = 'income'; panel.querySelector('#type-income').classList.add('amount-type-btn--active'); panel.querySelector('#type-expense').classList.remove('amount-type-btn--active'); + updateCategoryOptions(); }); panel.querySelector('#bm-cancel').addEventListener('click', closeModal); diff --git a/server/routes/budget.js b/server/routes/budget.js index 0e026e4..c5bfdf4 100644 --- a/server/routes/budget.js +++ b/server/routes/budget.js @@ -63,11 +63,18 @@ function generateRecurringInstances(database, month) { } } -const VALID_CATEGORIES = [ +const EXPENSE_CATEGORIES = [ 'Lebensmittel', 'Miete', 'Versicherung', 'Mobilität', 'Freizeit', 'Kleidung', 'Gesundheit', 'Bildung', 'Sonstiges', ]; +const INCOME_CATEGORIES = [ + 'Erwerbseinkommen', 'Kapitalerträge', 'Geschenke & Transfers', + 'Sozialleistungen', 'Sonstiges Einkommen', +]; + +const VALID_CATEGORIES = [...EXPENSE_CATEGORIES, ...INCOME_CATEGORIES]; + // -------------------------------------------------------- // Statische Routen vor /:id // -------------------------------------------------------- From 52b494241e4e3245879500a00aa0cd6c24964f0b Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Sun, 19 Apr 2026 13:07:32 +0200 Subject: [PATCH 2/2] fix: replace innerHTML with DOM API in updateCategoryOptions; restore uk.json newline innerHTML violates project convention (PostToolUse hook blocks it); use replaceChildren with createElement/textContent instead. Also restore the trailing newline removed from uk.json. Co-Authored-By: Claude Sonnet 4.6 --- public/locales/uk.json | 2 +- public/pages/budget.js | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/public/locales/uk.json b/public/locales/uk.json index 00705fa..600623f 100644 --- a/public/locales/uk.json +++ b/public/locales/uk.json @@ -624,4 +624,4 @@ "pendingBadgeTitle": "{{count}} нагадування", "pendingBadgeTitlePlural": "{{count}} нагадувань" } -} \ No newline at end of file +} diff --git a/public/pages/budget.js b/public/pages/budget.js index fc02863..5eb29d8 100644 --- a/public/pages/budget.js +++ b/public/pages/budget.js @@ -428,9 +428,14 @@ function openBudgetModal({ mode, entry = null }) { const catSelect = panel.querySelector('#bm-category'); const currentValue = catSelect.value; - catSelect.innerHTML = cats.map((c) => - `` - ).join(''); + const options = cats.map((c) => { + const opt = document.createElement('option'); + opt.value = c; + opt.textContent = catLabels[c] || c; + opt.selected = currentValue === c; + return opt; + }); + catSelect.replaceChildren(...options); }; panel.querySelector('#type-expense').addEventListener('click', () => {