From ed6bc21248dcafe52fa9ebd1f6120f944bfc95db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20X=2E=20F=C3=BCrst?= Date: Sun, 1 Mar 2026 11:26:44 +0000 Subject: [PATCH] Kategorien dazu --- add_kategorie.sql | 5 +++ app/api/ausgaben/[id]/route.ts | 5 +-- app/api/ausgaben/route.ts | 9 ++--- app/api/categories/route.ts | 33 +++++++++++++++++++ categories.txt | 15 +++++++++ components/AusgabenForm.tsx | 60 +++++++++++++++++++++++++++++++++- components/AusgabenList.tsx | 11 +++---- types/ausgaben.ts | 2 ++ 8 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 add_kategorie.sql create mode 100644 app/api/categories/route.ts create mode 100644 categories.txt diff --git a/add_kategorie.sql b/add_kategorie.sql new file mode 100644 index 0000000..46e8b10 --- /dev/null +++ b/add_kategorie.sql @@ -0,0 +1,5 @@ +-- Migration: Kategorie-Spalte zur Ausgaben-Tabelle hinzufügen +-- Ausführen: mysql -u -p < add_kategorie.sql + +ALTER TABLE Ausgaben + ADD COLUMN Kat VARCHAR(4) NOT NULL DEFAULT 'L' AFTER Was; diff --git a/app/api/ausgaben/[id]/route.ts b/app/api/ausgaben/[id]/route.ts index 74fff1f..2c38915 100644 --- a/app/api/ausgaben/[id]/route.ts +++ b/app/api/ausgaben/[id]/route.ts @@ -10,7 +10,7 @@ export async function PUT( try { const { id } = await context.params; const body = await request.json(); - const { Datum, Wo, Was, Wieviel, Wie, TYP } = body; + const { Datum, Wo, Was, Kat, Wieviel, Wie, TYP } = body; if (!Datum || !Wo || !Was || !Wieviel || !Wie || TYP === undefined) { return NextResponse.json( @@ -23,7 +23,7 @@ export async function PUT( const query = ` UPDATE Ausgaben - SET Datum = ?, Wo = ?, Was = ?, Wieviel = ?, Wie = ?, TYP = ? + SET Datum = ?, Wo = ?, Was = ?, Kat = ?, Wieviel = ?, Wie = ?, TYP = ? WHERE ID = ? `; @@ -31,6 +31,7 @@ export async function PUT( Datum, Wo, Was, + Kat || 'L', parseFloat(Wieviel), Wie, TYP, diff --git a/app/api/ausgaben/route.ts b/app/api/ausgaben/route.ts index 5431e96..f0af1dc 100644 --- a/app/api/ausgaben/route.ts +++ b/app/api/ausgaben/route.ts @@ -15,7 +15,7 @@ export async function GET(request: Request) { const pool = getDbPool(); let query = `SELECT - ID, Datum, Wo, Was, Wieviel, Wie, TYP, + ID, Datum, Wo, Was, Kat, Wieviel, Wie, TYP, CASE DAYOFWEEK(Datum) WHEN 1 THEN 'Sonntag' WHEN 2 THEN 'Montag' @@ -68,7 +68,7 @@ export async function GET(request: Request) { export async function POST(request: Request) { try { const body = await request.json(); - const { Datum, Wo, Was, Wieviel, Wie, TYP } = body; + const { Datum, Wo, Was, Kat, Wieviel, Wie, TYP } = body; if (!Datum || !Wo || !Was || !Wieviel || !Wie || TYP === undefined) { return NextResponse.json( @@ -80,14 +80,15 @@ export async function POST(request: Request) { const pool = getDbPool(); const query = ` - INSERT INTO Ausgaben (Datum, Wo, Was, Wieviel, Wie, TYP) - VALUES (?, ?, ?, ?, ?, ?) + INSERT INTO Ausgaben (Datum, Wo, Was, Kat, Wieviel, Wie, TYP) + VALUES (?, ?, ?, ?, ?, ?, ?) `; const [result] = await pool.query(query, [ Datum, Wo, Was, + Kat || 'L', parseFloat(Wieviel), Wie, TYP, diff --git a/app/api/categories/route.ts b/app/api/categories/route.ts new file mode 100644 index 0000000..da96b75 --- /dev/null +++ b/app/api/categories/route.ts @@ -0,0 +1,33 @@ +import { NextResponse } from 'next/server'; +import fs from 'fs'; +import path from 'path'; + +export interface Category { + value: string; + label: string; +} + +// GET /api/categories - Fetch categories from categories.txt +export async function GET() { + try { + const filePath = path.join(process.cwd(), 'categories.txt'); + const content = fs.readFileSync(filePath, 'utf-8'); + + const categories: Category[] = content + .split('\n') + .map((line) => line.trim()) + .filter((line) => line.includes('=')) + .map((line) => { + const [value, label] = line.split('='); + return { value: value.trim(), label: label.trim() }; + }); + + return NextResponse.json({ success: true, data: categories }); + } catch (error) { + console.error('Error reading categories:', error); + return NextResponse.json( + { success: false, error: 'Could not load categories' }, + { status: 500 } + ); + } +} diff --git a/categories.txt b/categories.txt new file mode 100644 index 0000000..f0fe26a --- /dev/null +++ b/categories.txt @@ -0,0 +1,15 @@ +R=Restaurant +L=Lebensmittel +H=Haushalt +Ku=Kultur +Kl=Kleidung +Dr=Drogerie +Ap=Apotheke +Ar=Arzt +Re=Reise +Au=Auto +El=Elektronik +Fr=Freizeit +Ge=Getränke +Ba=Bäckerei +So=Sonstiges diff --git a/components/AusgabenForm.tsx b/components/AusgabenForm.tsx index ca1f368..a1e31ff 100644 --- a/components/AusgabenForm.tsx +++ b/components/AusgabenForm.tsx @@ -1,7 +1,8 @@ 'use client'; -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useRef } from 'react'; import { CreateAusgabenEntry, AusgabenEntry, ZAHLUNGSARTEN_HAUSHALT, ZAHLUNGSARTEN_PRIVAT, MonthlyStats } from '@/types/ausgaben'; +import { Category } from '@/app/api/categories/route'; interface AusgabenFormProps { onSuccess: () => void; @@ -18,6 +19,7 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben WochTag: '', Wo: '', Was: '', + Kat: 'L', Wieviel: '', Wie: defaultZahlungsart, TYP: typ, @@ -35,6 +37,9 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben // Autocomplete data const [autoCompleteWo, setAutoCompleteWo] = useState([]); const [autoCompleteWas, setAutoCompleteWas] = useState([]); + const [categories, setCategories] = useState([]); + const [katDropdownOpen, setKatDropdownOpen] = useState(false); + const katDropdownRef = useRef(null); const fetchStats = useCallback(async (y: string, m: string) => { if (!y || !m) return; @@ -87,6 +92,25 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben fetchAutoComplete(); }, [typ, fetchAutoComplete]); + // Close Kat dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (katDropdownRef.current && !katDropdownRef.current.contains(e.target as Node)) { + setKatDropdownOpen(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + // Fetch categories once on mount + useEffect(() => { + fetch('/api/categories') + .then((r) => r.json()) + .then((data) => { if (data.success) setCategories(data.data); }) + .catch(() => {}); + }, []); + const handleMonthChange = (newMonth: string) => { setMonth(newMonth); }; @@ -113,6 +137,7 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben WochTag: selectedEntry.WochTag, Wo: selectedEntry.Wo, Was: selectedEntry.Was, + Kat: selectedEntry.Kat || 'L', Wieviel: selectedEntry.Wieviel.toString(), Wie: selectedEntry.Wie, TYP: selectedEntry.TYP, @@ -132,6 +157,7 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben WochTag: weekday, Wo: '', Was: '', + Kat: 'L', Wieviel: '', Wie: defaultZahlungsart, TYP: typ, @@ -184,6 +210,7 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben Datum: formData.Datum, Wo: formData.Wo, Was: formData.Was, + Kat: formData.Kat, Wieviel: formData.Wieviel, Wie: formData.Wie, TYP: formData.TYP, @@ -223,6 +250,7 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben WochTag: weekday, Wo: '', Was: '', + Kat: 'L', Wieviel: '', Wie: defaultZahlungsart, TYP: typ, @@ -247,6 +275,7 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben Datum Wo Was + Kat. Wieviel Wie @@ -295,6 +324,35 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben ))} + +
+ + {katDropdownOpen && ( +
    + {categories.map((cat) => ( +
  • { + setFormData({ ...formData, Kat: cat.value }); + setKatDropdownOpen(false); + }} + > + {cat.value} - {cat.label} +
  • + ))} +
+ )} +
+ { - const date = new Date(dateStr); - return date.toLocaleDateString('de-DE', { - day: '2-digit', - month: '2-digit', - year: 'numeric', - }); + return dateStr.toString().split('T')[0]; }; const formatAmount = (amount: number) => { @@ -53,6 +48,7 @@ export default function AusgabenList({ entries, onDelete, onEdit }: AusgabenList Tag Wo Was + Kat. Betrag Wie Aktion @@ -61,7 +57,7 @@ export default function AusgabenList({ entries, onDelete, onEdit }: AusgabenList {entries.length === 0 ? ( - + Keine Einträge vorhanden @@ -74,6 +70,7 @@ export default function AusgabenList({ entries, onDelete, onEdit }: AusgabenList {entry.WochTag.slice(0, 2)} {entry.Wo} {entry.Was} + {entry.Kat} {formatAmount(entry.Wieviel)} diff --git a/types/ausgaben.ts b/types/ausgaben.ts index e5c8f35..1aef7e0 100644 --- a/types/ausgaben.ts +++ b/types/ausgaben.ts @@ -6,6 +6,7 @@ export interface AusgabenEntry { WochTag: string; Wo: string; Was: string; + Kat: string; Wieviel: number; Wie: string; TYP: number; @@ -16,6 +17,7 @@ export interface CreateAusgabenEntry { WochTag: string; Wo: string; Was: string; + Kat: string; Wieviel: string | number; Wie: string; TYP: number;