First commit - es tut schon mal ganz gut

This commit is contained in:
2026-02-27 12:35:29 +00:00
commit 53124c1c78
27 changed files with 8403 additions and 0 deletions

335
components/AusgabenForm.tsx Normal file
View File

@@ -0,0 +1,335 @@
'use client';
import { useState, useEffect } from 'react';
import { CreateAusgabenEntry, AusgabenEntry, ZAHLUNGSARTEN, Zahlungsart, MonthlyStats } from '@/types/ausgaben';
interface AusgabenFormProps {
onSuccess: () => void;
selectedEntry?: AusgabenEntry | null;
}
export default function AusgabenForm({ onSuccess, selectedEntry }: AusgabenFormProps) {
const [formData, setFormData] = useState<CreateAusgabenEntry>({
Datum: '',
WochTag: '',
Wo: '',
Was: '',
Wieviel: '',
Wie: 'EC-R',
OK: 0,
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [editId, setEditId] = useState<number | null>(null);
// Monthly stats
const [stats, setStats] = useState<MonthlyStats | null>(null);
const [month, setMonth] = useState('');
const [year, setYear] = useState('');
const [isLoadingStats, setIsLoadingStats] = useState(false);
// Initialize stats with current month/year
useEffect(() => {
const now = new Date();
const currentMonth = String(now.getMonth() + 1).padStart(2, '0');
const currentYear = String(now.getFullYear());
setMonth(currentMonth);
setYear(currentYear);
fetchStats(currentYear, currentMonth);
}, []);
const fetchStats = async (y: string, m: string) => {
if (!y || !m) return;
setIsLoadingStats(true);
try {
const response = await fetch(`/api/ausgaben/stats?year=${y}&month=${m}`);
const data = await response.json();
if (data.success) {
setStats(data.data);
}
} catch (error) {
console.error('Error fetching stats:', error);
} finally {
setIsLoadingStats(false);
}
};
const handleMonthChange = (newMonth: string) => {
setMonth(newMonth);
fetchStats(year, newMonth);
};
const handleYearChange = (newYear: string) => {
setYear(newYear);
fetchStats(newYear, month);
};
const formatAmount = (amount: number | null) => {
if (amount === null || amount === undefined) return '0,00 €';
return new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
}).format(amount);
};
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,
Wieviel: selectedEntry.Wieviel.toString(),
Wie: selectedEntry.Wie,
OK: selectedEntry.OK || 0,
});
setEditId(selectedEntry.ID);
} else {
// Initialize with current date for new entry
const now = new Date();
const dateStr = now.toISOString().split('T')[0];
const weekday = getWeekday(now);
setFormData(prev => ({
...prev,
Datum: dateStr,
WochTag: weekday,
}));
setEditId(null);
}
}, [selectedEntry]);
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';
const response = await fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
if (response.ok) {
handleReset();
onSuccess();
// Refresh stats after successful save
fetchStats(year, month);
} 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: '',
Wieviel: '',
Wie: 'EC-R',
OK: 0,
});
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-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"
required
/>
</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"
required
/>
</td>
<td className="p-2 w-24">
<input
type="number"
step="0.01"
value={formData.Wieviel}
onChange={(e) => setFormData({ ...formData, Wieviel: 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 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-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 as Zahlungsart })}
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>
<tr>
<th className="p-2 w-32">{formData.WochTag}</th>
<th className="p-2"></th>
<th className="p-2"></th>
<th className="p-2"></th>
<th className="p-2"></th>
<th className="p-2"></th>
</tr>
<tr>
<td colSpan={6} className="p-3">
<div className="flex gap-3 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>
</td>
</tr>
<tr>
<td colSpan={6} className="p-3 pt-6 border-t border-gray-300">
<div className="flex items-center justify-between">
<div className="flex gap-4 items-center">
<label className="font-semibold">Monat:</label>
<select
value={month}
onChange={(e) => handleMonthChange(e.target.value)}
className="border border-gray-400 rounded px-3 py-1"
>
<option value="01">Januar</option>
<option value="02">Februar</option>
<option value="03">März</option>
<option value="04">April</option>
<option value="05">Mai</option>
<option value="06">Juni</option>
<option value="07">Juli</option>
<option value="08">August</option>
<option value="09">September</option>
<option value="10">Oktober</option>
<option value="11">November</option>
<option value="12">Dezember</option>
</select>
<label className="font-semibold">Jahr:</label>
<input
type="number"
value={year}
onChange={(e) => handleYearChange(e.target.value)}
className="border border-gray-400 rounded px-3 py-1 w-24"
min="2013"
max="2099"
/>
</div>
<div>
{isLoadingStats ? (
<span>Lade...</span>
) : stats ? (
<span className="font-bold text-lg">
Summe: {formatAmount(stats.totalAusgaben)}
</span>
) : null}
</div>
</div>
</td>
</tr>
</tbody>
</table>
</form>
</div>
);
}