Summen-Statistik der Kategorien
Eigenes 'Löschen' PopUp
This commit is contained in:
@@ -56,6 +56,21 @@ export async function GET(request: Request) {
|
|||||||
|
|
||||||
const data = rows[0] || {};
|
const data = rows[0] || {};
|
||||||
|
|
||||||
|
// Per-category breakdown
|
||||||
|
const [katRows] = await pool.query<RowDataPacket[]>(
|
||||||
|
`SELECT Kat, SUM(Wieviel) as total
|
||||||
|
FROM Ausgaben
|
||||||
|
WHERE YEAR(Datum) = ? AND MONTH(Datum) = ? AND TYP = ?
|
||||||
|
GROUP BY Kat
|
||||||
|
HAVING total > 0
|
||||||
|
ORDER BY total DESC`,
|
||||||
|
[year, month, parseInt(typ)]
|
||||||
|
);
|
||||||
|
const katStats: Record<string, number> = {};
|
||||||
|
for (const row of katRows) {
|
||||||
|
katStats[row.Kat] = parseFloat(row.total) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Convert string values from MySQL to numbers
|
// Convert string values from MySQL to numbers
|
||||||
const parsedData: any = {
|
const parsedData: any = {
|
||||||
totalAusgaben: parseFloat(data.totalAusgaben) || 0,
|
totalAusgaben: parseFloat(data.totalAusgaben) || 0,
|
||||||
@@ -77,7 +92,7 @@ export async function GET(request: Request) {
|
|||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: parsedData,
|
data: { ...parsedData, katStats },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Database error:', error);
|
console.error('Database error:', error);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import AusgabenForm from '@/components/AusgabenForm';
|
import AusgabenForm from '@/components/AusgabenForm';
|
||||||
import AusgabenList from '@/components/AusgabenList';
|
import AusgabenList from '@/components/AusgabenList';
|
||||||
|
import MonatsStatistik from '@/components/MonatsStatistik';
|
||||||
import LogoutButton from '@/components/LogoutButton';
|
import LogoutButton from '@/components/LogoutButton';
|
||||||
import { AusgabenEntry } from '@/types/ausgaben';
|
import { AusgabenEntry } from '@/types/ausgaben';
|
||||||
import packageJson from '@/package.json';
|
import packageJson from '@/package.json';
|
||||||
@@ -12,6 +13,7 @@ export default function Home() {
|
|||||||
const [entries, setEntries] = useState<AusgabenEntry[]>([]);
|
const [entries, setEntries] = useState<AusgabenEntry[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [selectedEntry, setSelectedEntry] = useState<AusgabenEntry | null>(null);
|
const [selectedEntry, setSelectedEntry] = useState<AusgabenEntry | null>(null);
|
||||||
|
const [statsRefreshKey, setStatsRefreshKey] = useState(0);
|
||||||
|
|
||||||
const version = packageJson.version;
|
const version = packageJson.version;
|
||||||
const buildDate = process.env.NEXT_PUBLIC_BUILD_DATE || new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
const buildDate = process.env.NEXT_PUBLIC_BUILD_DATE || new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||||
@@ -43,6 +45,7 @@ export default function Home() {
|
|||||||
|
|
||||||
const handleSuccess = () => {
|
const handleSuccess = () => {
|
||||||
setSelectedEntry(null);
|
setSelectedEntry(null);
|
||||||
|
setStatsRefreshKey((k) => k + 1);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fetchRecentEntries();
|
fetchRecentEntries();
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -50,6 +53,7 @@ export default function Home() {
|
|||||||
|
|
||||||
const handleDelete = (id: number) => {
|
const handleDelete = (id: number) => {
|
||||||
setEntries(entries.filter(entry => entry.ID !== id));
|
setEntries(entries.filter(entry => entry.ID !== id));
|
||||||
|
setStatsRefreshKey((k) => k + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (entry: AusgabenEntry) => {
|
const handleEdit = (entry: AusgabenEntry) => {
|
||||||
@@ -93,6 +97,8 @@ export default function Home() {
|
|||||||
<h2 className="text-xl font-semibold mb-4">Eingabe</h2>
|
<h2 className="text-xl font-semibold mb-4">Eingabe</h2>
|
||||||
<AusgabenForm onSuccess={handleSuccess} selectedEntry={selectedEntry} typ={activeTab} />
|
<AusgabenForm onSuccess={handleSuccess} selectedEntry={selectedEntry} typ={activeTab} />
|
||||||
|
|
||||||
|
<MonatsStatistik typ={activeTab} refreshKey={statsRefreshKey} />
|
||||||
|
|
||||||
<div className="mt-6 bg-white border border-black rounded-lg shadow-md p-6">
|
<div className="mt-6 bg-white border border-black rounded-lg shadow-md p-6">
|
||||||
<h3 className="text-xl font-semibold mb-4">Letzte 20 Einträge</h3>
|
<h3 className="text-xl font-semibold mb-4">Letzte 20 Einträge</h3>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef } 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 } from '@/types/ausgaben';
|
||||||
import { Category } from '@/app/api/categories/route';
|
import { Category } from '@/app/api/categories/route';
|
||||||
|
|
||||||
interface AusgabenFormProps {
|
interface AusgabenFormProps {
|
||||||
@@ -28,12 +28,6 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
|
|||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [editId, setEditId] = useState<number | null>(null);
|
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);
|
|
||||||
|
|
||||||
// Autocomplete data
|
// Autocomplete data
|
||||||
const [autoCompleteWo, setAutoCompleteWo] = useState<string[]>([]);
|
const [autoCompleteWo, setAutoCompleteWo] = useState<string[]>([]);
|
||||||
const [autoCompleteWas, setAutoCompleteWas] = useState<string[]>([]);
|
const [autoCompleteWas, setAutoCompleteWas] = useState<string[]>([]);
|
||||||
@@ -41,23 +35,6 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
|
|||||||
const [katDropdownOpen, setKatDropdownOpen] = useState(false);
|
const [katDropdownOpen, setKatDropdownOpen] = useState(false);
|
||||||
const katDropdownRef = useRef<HTMLDivElement>(null);
|
const katDropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const fetchStats = useCallback(async (y: string, m: string) => {
|
|
||||||
if (!y || !m) return;
|
|
||||||
|
|
||||||
setIsLoadingStats(true);
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/ausgaben/stats?year=${y}&month=${m}&typ=${typ}`);
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.success) {
|
|
||||||
setStats(data.data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching stats:', error);
|
|
||||||
} finally {
|
|
||||||
setIsLoadingStats(false);
|
|
||||||
}
|
|
||||||
}, [typ]);
|
|
||||||
|
|
||||||
const fetchAutoComplete = useCallback(async () => {
|
const fetchAutoComplete = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/ausgaben/autocomplete?typ=${typ}`);
|
const response = await fetch(`/api/ausgaben/autocomplete?typ=${typ}`);
|
||||||
@@ -71,22 +48,6 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
|
|||||||
}
|
}
|
||||||
}, [typ]);
|
}, [typ]);
|
||||||
|
|
||||||
// Initialize month/year on first load
|
|
||||||
useEffect(() => {
|
|
||||||
const now = new Date();
|
|
||||||
const currentMonth = String(now.getMonth() + 1).padStart(2, '0');
|
|
||||||
const currentYear = String(now.getFullYear());
|
|
||||||
setMonth(currentMonth);
|
|
||||||
setYear(currentYear);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Fetch stats when month, year, or typ changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (month && year) {
|
|
||||||
fetchStats(year, month);
|
|
||||||
}
|
|
||||||
}, [month, year, typ, fetchStats]);
|
|
||||||
|
|
||||||
// Fetch autocomplete data when typ changes
|
// Fetch autocomplete data when typ changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchAutoComplete();
|
fetchAutoComplete();
|
||||||
@@ -111,21 +72,6 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
|
|||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleMonthChange = (newMonth: string) => {
|
|
||||||
setMonth(newMonth);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleYearChange = (newYear: string) => {
|
|
||||||
setYear(newYear);
|
|
||||||
};
|
|
||||||
|
|
||||||
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(() => {
|
useEffect(() => {
|
||||||
if (selectedEntry) {
|
if (selectedEntry) {
|
||||||
@@ -211,7 +157,7 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
|
|||||||
Wo: formData.Wo,
|
Wo: formData.Wo,
|
||||||
Was: formData.Was,
|
Was: formData.Was,
|
||||||
Kat: formData.Kat,
|
Kat: formData.Kat,
|
||||||
Wieviel: formData.Wieviel,
|
Wieviel: String(formData.Wieviel).replace(',', '.'),
|
||||||
Wie: formData.Wie,
|
Wie: formData.Wie,
|
||||||
TYP: formData.TYP,
|
TYP: formData.TYP,
|
||||||
};
|
};
|
||||||
@@ -227,8 +173,6 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
handleReset();
|
handleReset();
|
||||||
onSuccess();
|
onSuccess();
|
||||||
// Refresh stats after successful save
|
|
||||||
fetchStats(year, month);
|
|
||||||
} else {
|
} else {
|
||||||
alert('Fehler beim Speichern!');
|
alert('Fehler beim Speichern!');
|
||||||
}
|
}
|
||||||
@@ -355,11 +299,14 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
|
|||||||
</td>
|
</td>
|
||||||
<td className="p-2 w-24">
|
<td className="p-2 w-24">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="text"
|
||||||
step="0.01"
|
inputMode="decimal"
|
||||||
value={formData.Wieviel}
|
value={formData.Wieviel}
|
||||||
onChange={(e) => setFormData({ ...formData, Wieviel: e.target.value })}
|
onChange={(e) => {
|
||||||
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"
|
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"
|
placeholder="0.00"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@@ -405,53 +352,6 @@ export default function AusgabenForm({ onSuccess, selectedEntry, typ }: Ausgaben
|
|||||||
Löschen
|
Löschen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Monatsstatistiken */}
|
|
||||||
<div className="mt-6 pt-4 pb-6 -mb-6 border-t border-black -mx-6 px-6 bg-[#E0E0FF]">
|
|
||||||
<div className="flex items-center justify-between pt-1">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
import { AusgabenEntry } from '@/types/ausgaben';
|
import { AusgabenEntry } from '@/types/ausgaben';
|
||||||
|
|
||||||
interface AusgabenListProps {
|
interface AusgabenListProps {
|
||||||
@@ -9,14 +10,12 @@ interface AusgabenListProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AusgabenList({ entries, onDelete, onEdit }: AusgabenListProps) {
|
export default function AusgabenList({ entries, onDelete, onEdit }: AusgabenListProps) {
|
||||||
const handleDelete = async (id: number) => {
|
const [confirmId, setConfirmId] = useState<number | null>(null);
|
||||||
if (!confirm('Wirklich löschen?')) return;
|
|
||||||
|
|
||||||
|
const handleDeleteConfirmed = async (id: number) => {
|
||||||
|
setConfirmId(null);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/ausgaben/${id}`, {
|
const response = await fetch(`/api/ausgaben/${id}`, { method: 'DELETE' });
|
||||||
method: 'DELETE',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
onDelete(id);
|
onDelete(id);
|
||||||
} else {
|
} else {
|
||||||
@@ -83,7 +82,7 @@ export default function AusgabenList({ entries, onDelete, onEdit }: AusgabenList
|
|||||||
Bearbeiten
|
Bearbeiten
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDelete(entry.ID)}
|
onClick={() => setConfirmId(entry.ID)}
|
||||||
className="text-red-600 hover:text-red-800 px-3 py-1 rounded text-sm"
|
className="text-red-600 hover:text-red-800 px-3 py-1 rounded text-sm"
|
||||||
>
|
>
|
||||||
Löschen
|
Löschen
|
||||||
@@ -94,6 +93,29 @@ export default function AusgabenList({ entries, onDelete, onEdit }: AusgabenList
|
|||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{/* Bestätigungs-Modal */}
|
||||||
|
{confirmId !== null && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
|
||||||
|
<div className="bg-white border-2 border-black rounded-lg shadow-xl p-6 w-80">
|
||||||
|
<p className="text-lg font-semibold mb-6 text-center">Eintrag wirklich löschen?</p>
|
||||||
|
<div className="flex justify-center gap-4">
|
||||||
|
<button
|
||||||
|
onClick={() => handleDeleteConfirmed(confirmId)}
|
||||||
|
className="bg-red-600 hover:bg-red-700 text-white font-medium py-2 px-6 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
Löschen
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setConfirmId(null)}
|
||||||
|
className="bg-gray-200 hover:bg-gray-300 text-black font-medium py-2 px-6 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
Abbrechen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
120
components/MonatsStatistik.tsx
Normal file
120
components/MonatsStatistik.tsx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { MonthlyStats } from '@/types/ausgaben';
|
||||||
|
import { Category } from '@/app/api/categories/route';
|
||||||
|
|
||||||
|
interface MonatsStatistikProps {
|
||||||
|
typ: number;
|
||||||
|
refreshKey?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MonatsStatistik({ typ, refreshKey }: MonatsStatistikProps) {
|
||||||
|
const [stats, setStats] = useState<MonthlyStats | null>(null);
|
||||||
|
const [month, setMonth] = useState('');
|
||||||
|
const [year, setYear] = useState('');
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [categories, setCategories] = useState<Category[]>([]);
|
||||||
|
|
||||||
|
// Initialize month/year
|
||||||
|
useEffect(() => {
|
||||||
|
const now = new Date();
|
||||||
|
setMonth(String(now.getMonth() + 1).padStart(2, '0'));
|
||||||
|
setYear(String(now.getFullYear()));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Fetch categories once
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/categories')
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((data) => { if (data.success) setCategories(data.data); })
|
||||||
|
.catch(() => {});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchStats = useCallback(async (y: string, m: string) => {
|
||||||
|
if (!y || !m) return;
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/ausgaben/stats?year=${y}&month=${m}&typ=${typ}`);
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.success) setStats(data.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching stats:', error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [typ]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (month && year) fetchStats(year, month);
|
||||||
|
}, [month, year, typ, refreshKey, fetchStats]);
|
||||||
|
|
||||||
|
const formatAmount = (amount: number) =>
|
||||||
|
new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(amount);
|
||||||
|
|
||||||
|
const getCatLabel = (code: string) => {
|
||||||
|
const cat = categories.find((c) => c.value === code);
|
||||||
|
return cat ? `${cat.label}` : code;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-4 bg-[#E0E0FF] border border-black rounded-lg shadow-md p-4">
|
||||||
|
{/* Zeile 1: Monat/Jahr + Gesamtsumme */}
|
||||||
|
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||||
|
<div className="flex gap-4 items-center">
|
||||||
|
<label className="font-semibold">Monat:</label>
|
||||||
|
<select
|
||||||
|
value={month}
|
||||||
|
onChange={(e) => setMonth(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) => setYear(e.target.value)}
|
||||||
|
className="border border-gray-400 rounded px-3 py-1 w-24"
|
||||||
|
min="2013"
|
||||||
|
max="2099"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{isLoading ? (
|
||||||
|
<span>Lade...</span>
|
||||||
|
) : stats ? (
|
||||||
|
<span className="font-bold text-lg">
|
||||||
|
Summe: {formatAmount(stats.totalAusgaben)}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Zeile 2+: Kategorien */}
|
||||||
|
{!isLoading && stats?.katStats && Object.keys(stats.katStats).length > 0 && (
|
||||||
|
<div className="mt-3 pt-3 border-t border-gray-400 flex flex-wrap gap-x-6 gap-y-1">
|
||||||
|
{Object.entries(stats.katStats).map(([code, total]) => (
|
||||||
|
<div key={code} className="flex gap-2 text-sm">
|
||||||
|
<span className="font-medium">{getCatLabel(code)}:</span>
|
||||||
|
<span>{formatAmount(total)}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ export interface MonthlyStats {
|
|||||||
MASTER?: number;
|
MASTER?: number;
|
||||||
Einnahmen: number;
|
Einnahmen: number;
|
||||||
Ueberweisungen: number;
|
Ueberweisungen: number;
|
||||||
|
katStats?: Record<string, number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Haushalt Zahlungsarten (TYP = 0)
|
// Haushalt Zahlungsarten (TYP = 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user