Kategorien dazu

This commit is contained in:
2026-03-01 11:26:44 +00:00
parent 319ac8699e
commit ed6bc21248
8 changed files with 126 additions and 14 deletions

5
add_kategorie.sql Normal file
View File

@@ -0,0 +1,5 @@
-- Migration: Kategorie-Spalte zur Ausgaben-Tabelle hinzufügen
-- Ausführen: mysql -u <user> -p <database> < add_kategorie.sql
ALTER TABLE Ausgaben
ADD COLUMN Kat VARCHAR(4) NOT NULL DEFAULT 'L' AFTER Was;

View File

@@ -10,7 +10,7 @@ export async function PUT(
try { try {
const { id } = await context.params; const { id } = await context.params;
const body = await request.json(); 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) { if (!Datum || !Wo || !Was || !Wieviel || !Wie || TYP === undefined) {
return NextResponse.json( return NextResponse.json(
@@ -23,7 +23,7 @@ export async function PUT(
const query = ` const query = `
UPDATE Ausgaben UPDATE Ausgaben
SET Datum = ?, Wo = ?, Was = ?, Wieviel = ?, Wie = ?, TYP = ? SET Datum = ?, Wo = ?, Was = ?, Kat = ?, Wieviel = ?, Wie = ?, TYP = ?
WHERE ID = ? WHERE ID = ?
`; `;
@@ -31,6 +31,7 @@ export async function PUT(
Datum, Datum,
Wo, Wo,
Was, Was,
Kat || 'L',
parseFloat(Wieviel), parseFloat(Wieviel),
Wie, Wie,
TYP, TYP,

View File

@@ -15,7 +15,7 @@ export async function GET(request: Request) {
const pool = getDbPool(); const pool = getDbPool();
let query = `SELECT let query = `SELECT
ID, Datum, Wo, Was, Wieviel, Wie, TYP, ID, Datum, Wo, Was, Kat, Wieviel, Wie, TYP,
CASE DAYOFWEEK(Datum) CASE DAYOFWEEK(Datum)
WHEN 1 THEN 'Sonntag' WHEN 1 THEN 'Sonntag'
WHEN 2 THEN 'Montag' WHEN 2 THEN 'Montag'
@@ -68,7 +68,7 @@ export async function GET(request: Request) {
export async function POST(request: Request) { export async function POST(request: Request) {
try { try {
const body = await request.json(); 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) { if (!Datum || !Wo || !Was || !Wieviel || !Wie || TYP === undefined) {
return NextResponse.json( return NextResponse.json(
@@ -80,14 +80,15 @@ export async function POST(request: Request) {
const pool = getDbPool(); const pool = getDbPool();
const query = ` const query = `
INSERT INTO Ausgaben (Datum, Wo, Was, Wieviel, Wie, TYP) INSERT INTO Ausgaben (Datum, Wo, Was, Kat, Wieviel, Wie, TYP)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
`; `;
const [result] = await pool.query<ResultSetHeader>(query, [ const [result] = await pool.query<ResultSetHeader>(query, [
Datum, Datum,
Wo, Wo,
Was, Was,
Kat || 'L',
parseFloat(Wieviel), parseFloat(Wieviel),
Wie, Wie,
TYP, TYP,

View File

@@ -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 }
);
}
}

15
categories.txt Normal file
View File

@@ -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

View File

@@ -1,7 +1,8 @@
'use client'; '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 { CreateAusgabenEntry, AusgabenEntry, ZAHLUNGSARTEN_HAUSHALT, ZAHLUNGSARTEN_PRIVAT, MonthlyStats } from '@/types/ausgaben';
import { Category } from '@/app/api/categories/route';
interface AusgabenFormProps { interface AusgabenFormProps {
onSuccess: () => void; onSuccess: () => void;
@@ -18,6 +19,7 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
WochTag: '', WochTag: '',
Wo: '', Wo: '',
Was: '', Was: '',
Kat: 'L',
Wieviel: '', Wieviel: '',
Wie: defaultZahlungsart, Wie: defaultZahlungsart,
TYP: typ, TYP: typ,
@@ -35,6 +37,9 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
// Autocomplete data // Autocomplete data
const [autoCompleteWo, setAutoCompleteWo] = useState<string[]>([]); const [autoCompleteWo, setAutoCompleteWo] = useState<string[]>([]);
const [autoCompleteWas, setAutoCompleteWas] = useState<string[]>([]); const [autoCompleteWas, setAutoCompleteWas] = useState<string[]>([]);
const [categories, setCategories] = useState<Category[]>([]);
const [katDropdownOpen, setKatDropdownOpen] = useState(false);
const katDropdownRef = useRef<HTMLDivElement>(null);
const fetchStats = useCallback(async (y: string, m: string) => { const fetchStats = useCallback(async (y: string, m: string) => {
if (!y || !m) return; if (!y || !m) return;
@@ -87,6 +92,25 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
fetchAutoComplete(); fetchAutoComplete();
}, [typ, 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) => { const handleMonthChange = (newMonth: string) => {
setMonth(newMonth); setMonth(newMonth);
}; };
@@ -113,6 +137,7 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
WochTag: selectedEntry.WochTag, WochTag: selectedEntry.WochTag,
Wo: selectedEntry.Wo, Wo: selectedEntry.Wo,
Was: selectedEntry.Was, Was: selectedEntry.Was,
Kat: selectedEntry.Kat || 'L',
Wieviel: selectedEntry.Wieviel.toString(), Wieviel: selectedEntry.Wieviel.toString(),
Wie: selectedEntry.Wie, Wie: selectedEntry.Wie,
TYP: selectedEntry.TYP, TYP: selectedEntry.TYP,
@@ -132,6 +157,7 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
WochTag: weekday, WochTag: weekday,
Wo: '', Wo: '',
Was: '', Was: '',
Kat: 'L',
Wieviel: '', Wieviel: '',
Wie: defaultZahlungsart, Wie: defaultZahlungsart,
TYP: typ, TYP: typ,
@@ -184,6 +210,7 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
Datum: formData.Datum, Datum: formData.Datum,
Wo: formData.Wo, Wo: formData.Wo,
Was: formData.Was, Was: formData.Was,
Kat: formData.Kat,
Wieviel: formData.Wieviel, Wieviel: formData.Wieviel,
Wie: formData.Wie, Wie: formData.Wie,
TYP: formData.TYP, TYP: formData.TYP,
@@ -223,6 +250,7 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
WochTag: weekday, WochTag: weekday,
Wo: '', Wo: '',
Was: '', Was: '',
Kat: 'L',
Wieviel: '', Wieviel: '',
Wie: defaultZahlungsart, Wie: defaultZahlungsart,
TYP: typ, TYP: typ,
@@ -247,6 +275,7 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
<th className="p-2 w-32">Datum</th> <th className="p-2 w-32">Datum</th>
<th className="p-2">Wo</th> <th className="p-2">Wo</th>
<th className="p-2">Was</th> <th className="p-2">Was</th>
<th className="p-2 w-12">Kat.</th>
<th className="p-2 w-24">Wieviel</th> <th className="p-2 w-24">Wieviel</th>
<th className="p-2 w-4"></th> <th className="p-2 w-4"></th>
<th className="p-2 w-38 text-left">Wie</th> <th className="p-2 w-38 text-left">Wie</th>
@@ -295,6 +324,35 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
))} ))}
</datalist> </datalist>
</td> </td>
<td className="p-2 w-12">
<div ref={katDropdownRef} className="relative w-full">
<button
type="button"
onClick={() => setKatDropdownOpen((o) => !o)}
className="w-full px-2 py-1 text-base text-left rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none"
>
{formData.Kat}
</button>
{katDropdownOpen && (
<ul className="absolute z-50 left-0 mt-1 w-48 bg-white border-2 border-gray-400 rounded shadow-lg max-h-60 overflow-y-auto text-left">
{categories.map((cat) => (
<li
key={cat.value}
className={`px-3 py-1 cursor-pointer hover:bg-blue-100 text-sm ${
formData.Kat === cat.value ? 'bg-blue-50 font-semibold' : ''
}`}
onMouseDown={() => {
setFormData({ ...formData, Kat: cat.value });
setKatDropdownOpen(false);
}}
>
{cat.value} - {cat.label}
</li>
))}
</ul>
)}
</div>
</td>
<td className="p-2 w-24"> <td className="p-2 w-24">
<input <input
type="number" type="number"

View File

@@ -29,12 +29,7 @@ export default function AusgabenList({ entries, onDelete, onEdit }: AusgabenList
}; };
const formatDate = (dateStr: string) => { const formatDate = (dateStr: string) => {
const date = new Date(dateStr); return dateStr.toString().split('T')[0];
return date.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
});
}; };
const formatAmount = (amount: number) => { const formatAmount = (amount: number) => {
@@ -53,6 +48,7 @@ export default function AusgabenList({ entries, onDelete, onEdit }: AusgabenList
<th className="border-b-2 border-black p-2 w-12">Tag</th> <th className="border-b-2 border-black p-2 w-12">Tag</th>
<th className="border-b-2 border-black p-2 w-36">Wo</th> <th className="border-b-2 border-black p-2 w-36">Wo</th>
<th className="border-b-2 border-black p-2 w-48">Was</th> <th className="border-b-2 border-black p-2 w-48">Was</th>
<th className="border-b-2 border-black p-2 w-12">Kat.</th>
<th className="border-b-2 border-black p-2 w-8">Betrag</th> <th className="border-b-2 border-black p-2 w-8">Betrag</th>
<th className="border-b-2 border-black p-2 w-16">Wie</th> <th className="border-b-2 border-black p-2 w-16">Wie</th>
<th className="border-b-2 border-black p-2 w-38">Aktion</th> <th className="border-b-2 border-black p-2 w-38">Aktion</th>
@@ -61,7 +57,7 @@ export default function AusgabenList({ entries, onDelete, onEdit }: AusgabenList
<tbody> <tbody>
{entries.length === 0 ? ( {entries.length === 0 ? (
<tr> <tr>
<td colSpan={7} className="text-center p-4 text-gray-500"> <td colSpan={8} className="text-center p-4 text-gray-500">
Keine Einträge vorhanden Keine Einträge vorhanden
</td> </td>
</tr> </tr>
@@ -74,6 +70,7 @@ export default function AusgabenList({ entries, onDelete, onEdit }: AusgabenList
<td className="border-y border-black p-2 text-center">{entry.WochTag.slice(0, 2)}</td> <td className="border-y border-black p-2 text-center">{entry.WochTag.slice(0, 2)}</td>
<td className="border-y border-black p-2">{entry.Wo}</td> <td className="border-y border-black p-2">{entry.Wo}</td>
<td className="border-y border-black p-2">{entry.Was}</td> <td className="border-y border-black p-2">{entry.Was}</td>
<td className="border-y border-black p-2 text-center">{entry.Kat}</td>
<td className="border-y border-black p-2 text-right"> <td className="border-y border-black p-2 text-right">
{formatAmount(entry.Wieviel)} {formatAmount(entry.Wieviel)}
</td> </td>

View File

@@ -6,6 +6,7 @@ export interface AusgabenEntry {
WochTag: string; WochTag: string;
Wo: string; Wo: string;
Was: string; Was: string;
Kat: string;
Wieviel: number; Wieviel: number;
Wie: string; Wie: string;
TYP: number; TYP: number;
@@ -16,6 +17,7 @@ export interface CreateAusgabenEntry {
WochTag: string; WochTag: string;
Wo: string; Wo: string;
Was: string; Was: string;
Kat: string;
Wieviel: string | number; Wieviel: string | number;
Wie: string; Wie: string;
TYP: number; TYP: number;