Files
ausgaben-next/components/AusgabenForm.tsx
2026-03-01 11:48:24 +00:00

359 lines
12 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useEffect, useCallback, useRef } from 'react';
import { CreateAusgabenEntry, AusgabenEntry, ZAHLUNGSARTEN_HAUSHALT, ZAHLUNGSARTEN_PRIVAT } from '@/types/ausgaben';
import { Category } from '@/app/api/categories/route';
interface AusgabenFormProps {
onSuccess: () => void;
selectedEntry?: AusgabenEntry | null;
typ: number; // 0 = Haushalt, 1 = Privat
}
export default function AusgabenForm({ onSuccess, selectedEntry, typ }: AusgabenFormProps) {
const zahlungsarten = typ === 0 ? ZAHLUNGSARTEN_HAUSHALT : ZAHLUNGSARTEN_PRIVAT;
const defaultZahlungsart = typ === 0 ? 'ECR' : 'bar';
const [formData, setFormData] = useState<CreateAusgabenEntry>({
Datum: '',
WochTag: '',
Wo: '',
Was: '',
Kat: 'L',
Wieviel: '',
Wie: defaultZahlungsart,
TYP: typ,
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [editId, setEditId] = useState<number | null>(null);
// Autocomplete data
const [autoCompleteWo, setAutoCompleteWo] = useState<string[]>([]);
const [autoCompleteWas, setAutoCompleteWas] = useState<string[]>([]);
const [categories, setCategories] = useState<Category[]>([]);
const [katDropdownOpen, setKatDropdownOpen] = useState(false);
const katDropdownRef = useRef<HTMLDivElement>(null);
const fetchAutoComplete = useCallback(async () => {
try {
const response = await fetch(`/api/ausgaben/autocomplete?typ=${typ}`);
const data = await response.json();
if (data.success) {
setAutoCompleteWo(data.data.wo);
setAutoCompleteWas(data.data.was);
}
} catch (error) {
console.error('Error fetching autocomplete data:', error);
}
}, [typ]);
// Fetch autocomplete data when typ changes
useEffect(() => {
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(() => {});
}, []);
useEffect(() => {
if (selectedEntry) {
// Load selected entry for editing
const dateStr = selectedEntry.Datum.toString().split('T')[0];
setFormData({
Datum: dateStr,
WochTag: selectedEntry.WochTag,
Wo: selectedEntry.Wo,
Was: selectedEntry.Was,
Kat: selectedEntry.Kat || 'L',
Wieviel: selectedEntry.Wieviel.toString(),
Wie: selectedEntry.Wie,
TYP: selectedEntry.TYP,
});
// Handle both uppercase and lowercase ID field names
const entryId = (selectedEntry as any).id || selectedEntry.ID;
setEditId(entryId);
} else {
// Reset form for new entry
const now = new Date();
const dateStr = now.toISOString().split('T')[0];
const weekday = getWeekday(now);
setFormData({
Datum: dateStr,
WochTag: weekday,
Wo: '',
Was: '',
Kat: 'L',
Wieviel: '',
Wie: defaultZahlungsart,
TYP: typ,
});
setEditId(null);
}
}, [selectedEntry]);
// Update TYP when tab changes
useEffect(() => {
if (!selectedEntry) {
setFormData(prev => ({
...prev,
TYP: typ,
Wie: defaultZahlungsart,
}));
}
}, [typ]);
const getWeekday = (date: Date): string => {
const weekdays = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
return weekdays[date.getDay()];
};
const handleDateChange = (dateStr: string) => {
const [year, month, day] = dateStr.split('-');
const date = new Date(Number(year), Number(month) - 1, Number(day));
const weekday = getWeekday(date);
setFormData(prev => ({ ...prev, Datum: dateStr, WochTag: weekday }));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!formData.Wo || !formData.Was || !formData.Wieviel) {
alert('Bitte alle Pflichtfelder ausfüllen!');
return;
}
setIsSubmitting(true);
try {
const url = editId ? `/api/ausgaben/${editId}` : '/api/ausgaben';
const method = editId ? 'PUT' : 'POST';
// Send only the fields we need, excluding any extra fields
const dataToSend = {
Datum: formData.Datum,
Wo: formData.Wo,
Was: formData.Was,
Kat: formData.Kat,
Wieviel: String(formData.Wieviel).replace(',', '.'),
Wie: formData.Wie,
TYP: formData.TYP,
};
const response = await fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(dataToSend),
});
if (response.ok) {
handleReset();
onSuccess();
} else {
alert('Fehler beim Speichern!');
}
} catch (error) {
console.error('Error:', error);
alert('Fehler beim Speichern!');
} finally {
setIsSubmitting(false);
}
};
const handleReset = () => {
const now = new Date();
const dateStr = now.toISOString().split('T')[0];
const weekday = getWeekday(now);
setFormData({
Datum: dateStr,
WochTag: weekday,
Wo: '',
Was: '',
Kat: 'L',
Wieviel: '',
Wie: defaultZahlungsart,
TYP: typ,
});
setEditId(null);
};
return (
<div className="bg-[#CCCCFF] border border-black p-6 rounded-lg mb-6">
{editId && (
<div className="mb-4 p-3 bg-blue-100 border border-blue-400 rounded text-sm text-blue-800">
<strong>Bearbeitungsmodus:</strong> Sie bearbeiten einen bestehenden Eintrag.
</div>
)}
<form onSubmit={handleSubmit}>
<table className="w-full text-center">
<thead>
<tr >
<th className="p-2 w-32">Datum</th>
<th className="p-2">Wo</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-4"></th>
<th className="p-2 w-38 text-left">Wie</th>
</tr>
</thead>
<tbody>
<tr>
<td className="p-2 w-32">
<input
type="date"
value={formData.Datum}
onChange={(e) => handleDateChange(e.target.value)}
className="w-full px-2 py-1 text-base rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none"
required
/>
</td>
<td className="p-2">
<input
type="text"
value={formData.Wo}
onChange={(e) => setFormData({ ...formData, Wo: e.target.value })}
className="w-full px-2 py-1 text-base rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none"
placeholder="Geschäft/Ort"
list="wo-suggestions"
required
/>
<datalist id="wo-suggestions">
{autoCompleteWo.map((wo, index) => (
<option key={index} value={wo} />
))}
</datalist>
</td>
<td className="p-2">
<input
type="text"
value={formData.Was}
onChange={(e) => setFormData({ ...formData, Was: e.target.value })}
className="w-full px-2 py-1 text-base rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none"
placeholder="Beschreibung"
list="was-suggestions"
required
/>
<datalist id="was-suggestions">
{autoCompleteWas.map((was, index) => (
<option key={index} value={was} />
))}
</datalist>
</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">
<input
type="text"
inputMode="decimal"
value={formData.Wieviel}
onChange={(e) => {
const val = e.target.value.replace(/[^0-9.,]/g, '').replace(',', '.');
setFormData({ ...formData, Wieviel: val });
}}
className="w-full px-2 py-1 text-base rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none"
placeholder="0.00"
required
/>
</td>
<td className="p-2 w-4 text-left"></td>
<td className="p-2 w-38">
<select
value={formData.Wie}
onChange={(e) => setFormData({ ...formData, Wie: e.target.value })}
className="w-full px-2 py-1 text-base rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none"
required
>
{zahlungsarten.map((za) => (
<option key={za.value} value={za.value}>
{za.label}
</option>
))}
</select>
</td>
</tr>
</tbody>
</table>
{/* Wochentag */}
<div className="mt-3 text-left pl-3">
<span className="font-semibold">{formData.WochTag}</span>
</div>
{/* Buttons */}
<div className="mb-3 flex gap-10 justify-center">
<button
type="submit"
disabled={isSubmitting}
className="bg-[#85B7D7] hover:bg-[#6a9fc5] text-black font-medium py-2 px-8 rounded-lg transition-colors disabled:opacity-50"
>
{isSubmitting ? 'Speichere...' : editId ? 'Aktualisieren' : 'Speichern'}
</button>
<button
type="button"
onClick={handleReset}
className="bg-[#85B7D7] hover:bg-[#6a9fc5] text-black font-medium py-2 px-8 rounded-lg transition-colors"
>
Löschen
</button>
</div>
</form>
</div>
);
}