First commit - es tut schon mal ganz gut
This commit is contained in:
335
components/AusgabenForm.tsx
Normal file
335
components/AusgabenForm.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user