'use client'; import { useState, useEffect, useCallback, useMemo } from 'react'; import { useRouter } from 'next/navigation'; import { createClient } from '@/lib/supabase/client'; import Header from '@/components/Header'; import { Buchung, Wasserzaehler, Setting } from '@/types'; import ConfirmationModal from '@/components/ConfirmationModal'; type Tab = 'pool' | 'wasserzaehler' | 'einstellungen'; export default function AdminDashboardPage() { const router = useRouter(); const supabase = createClient(); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState('pool'); const [buchungen, setBuchungen] = useState([]); const [zaehler, setZaehler] = useState([]); const [settings, setSettings] = useState([]); const [filterDate, setFilterDate] = useState(''); const [filterStatus, setFilterStatus] = useState('alle'); // Manual booking const [showManualBooking, setShowManualBooking] = useState(false); const [manualForm, setManualForm] = useState({ name: '', strasse: '', telefon: '', email: '', wasserquelle: 'brunnen' as 'brunnen' | 'ortswasserleitung', wassermenge_m3: '', wunschdatum: '', }); const [manualError, setManualError] = useState(''); const [showConfirmation, setShowConfirmation] = useState(false); const [confirmMsg, setConfirmMsg] = useState(''); // Wasserzähler edit/delete const [editZaehler, setEditZaehler] = useState(null); const [editForm, setEditForm] = useState({ haushalt_name: '', adresse: '', kundennummer: '', zaehlernummer: '', alter_stand: '', neuer_stand: '', ablesedatum: '', }); const [editError, setEditError] = useState(''); const [editSaving, setEditSaving] = useState(false); const [deleteConfirm, setDeleteConfirm] = useState(null); useEffect(() => { supabase.auth.getUser().then(({ data: { user } }) => { if (!user) { router.push('/admin/login'); } else { setLoading(false); } }); }, [supabase, router]); const loadBuchungen = useCallback(async () => { let query = supabase.from('buchungen').select('*').order('wunschdatum', { ascending: true }); if (filterDate) query = query.eq('wunschdatum', filterDate); if (filterStatus !== 'alle') query = query.eq('status', filterStatus); const { data, error } = await query; if (error) console.error('loadBuchungen error:', error); setBuchungen(data || []); }, [supabase, filterDate, filterStatus]); const loadZaehler = useCallback(async () => { const { data } = await supabase.from('wasserzaehler').select('*').order('erstellt_am', { ascending: false }); setZaehler(data || []); }, [supabase]); const loadSettings = useCallback(async () => { const { data } = await supabase.from('settings').select('*').order('key'); setSettings(data || []); }, [supabase]); useEffect(() => { if (!loading) { loadBuchungen(); loadZaehler(); loadSettings(); } }, [loading, loadBuchungen, loadZaehler, loadSettings]); const handleLogout = async () => { await supabase.auth.signOut(); router.push('/admin/login'); }; const storniereBuchung = async (id: string) => { await supabase.from('buchungen').update({ status: 'storniert' }).eq('id', id); loadBuchungen(); }; const exportCSV = (data: Record[], filename: string) => { if (!data.length) return; const headers = Object.keys(data[0]); const csv = [ headers.join(';'), ...data.map(row => headers.map(h => `"${row[h] ?? ''}"`).join(';')) ].join('\n'); const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${filename}_${new Date().toISOString().split('T')[0]}.csv`; a.click(); URL.revokeObjectURL(url); }; const handleManualBooking = async (e: React.FormEvent) => { e.preventDefault(); setManualError(''); if (!manualForm.name || !manualForm.strasse || !manualForm.telefon || !manualForm.wunschdatum) { setManualError('Bitte alle Pflichtfelder ausfüllen.'); return; } try { const res = await fetch('/api/pool', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...manualForm, wassermenge_m3: manualForm.wassermenge_m3 ? parseFloat(manualForm.wassermenge_m3) : null, email: manualForm.email || 'telefon@gemeinde.at', }), }); const data = await res.json(); if (!res.ok) { setManualError(data.error || 'Fehler beim Speichern.'); return; } setShowManualBooking(false); setManualForm({ name: '', strasse: '', telefon: '', email: '', wasserquelle: 'brunnen', wassermenge_m3: '', wunschdatum: '', }); setConfirmMsg('Manuelle Buchung erfolgreich eingetragen!'); setShowConfirmation(true); loadBuchungen(); } catch { setManualError('Verbindungsfehler.'); } }; const updateSetting = async (key: string, value: string) => { await supabase.from('settings').update({ value, aktualisiert_am: new Date().toISOString() }).eq('key', key); loadSettings(); }; const openEditZaehler = (z: Wasserzaehler) => { setEditZaehler(z); setEditForm({ haushalt_name: z.haushalt_name || '', adresse: z.adresse || '', kundennummer: z.kundennummer || '', zaehlernummer: z.zaehlernummer || '', alter_stand: z.alter_stand != null ? String(z.alter_stand) : '', neuer_stand: z.neuer_stand != null ? String(z.neuer_stand) : '', ablesedatum: z.ablesedatum || '', }); setEditError(''); }; const handleEditSave = async () => { if (!editZaehler) return; setEditError(''); setEditSaving(true); const alterStand = editForm.alter_stand ? parseFloat(editForm.alter_stand) : null; const neuerStand = editForm.neuer_stand ? parseFloat(editForm.neuer_stand) : null; const verbrauch = neuerStand != null && alterStand != null ? neuerStand - alterStand : null; const { error } = await supabase.from('wasserzaehler').update({ haushalt_name: editForm.haushalt_name, adresse: editForm.adresse, kundennummer: editForm.kundennummer, zaehlernummer: editForm.zaehlernummer, alter_stand: alterStand, neuer_stand: neuerStand, verbrauch, ablesedatum: editForm.ablesedatum || null, }).eq('id', editZaehler.id); setEditSaving(false); if (error) { setEditError(`Fehler: ${error.message}`); return; } setEditZaehler(null); setConfirmMsg('Wasserzähler wurde erfolgreich aktualisiert.'); setShowConfirmation(true); loadZaehler(); }; const handleDeleteZaehler = async (id: string) => { const { error } = await supabase.from('wasserzaehler').delete().eq('id', id); setDeleteConfirm(null); if (error) { setConfirmMsg(`Fehler beim Löschen: ${error.message}`); setShowConfirmation(true); return; } setEditZaehler(null); setConfirmMsg('Wasserzähler wurde gelöscht.'); setShowConfirmation(true); loadZaehler(); }; // Today stats const todayStr = new Date().toISOString().split('T')[0]; const todayStats = useMemo(() => { const todayBuchungen = buchungen.filter(b => b.wunschdatum === todayStr && b.status === 'aktiv'); const todayM3 = todayBuchungen.reduce((sum, b) => sum + (b.wassermenge_m3 || 0), 0); const totalAktiv = buchungen.filter(b => b.status === 'aktiv').length; return { today: todayBuchungen.length, m3: todayM3, total: totalAktiv }; }, [buchungen, todayStr]); if (loading) { return (
{[1, 2, 3].map(i =>
)}
); } const filteredBuchungen = buchungen; return (
{/* Admin Bar */}
Admin
Hilfe
{/* Today Stats */}
{todayStats.today}
Heute
{todayStats.m3}
m³ heute
{todayStats.total}
Gesamt aktiv
{/* Tabs */}
{([ ['pool', 'Pool-Buchungen'], ['wasserzaehler', 'Wasserzähler'], ['einstellungen', 'Einstellungen'], ] as [Tab, string][]).map(([key, label]) => ( ))}
{/* Pool Tab */} {activeTab === 'pool' && (
{/* Actions */}
setFilterDate(e.target.value)} className="border border-border rounded-xl px-3 py-2.5 text-sm" aria-label="Nach Datum filtern" /> {filterDate && ( )}
{/* Table */}
{filteredBuchungen.length === 0 ? ( ) : ( filteredBuchungen.map((b) => ( )) )}
Datum Name Straße Telefon E-Mail Quelle Status Aktion
Keine Buchungen gefunden.
{new Date(b.wunschdatum + 'T00:00:00').toLocaleDateString('de-AT')} {b.name} {b.strasse} {b.telefon} {b.email} {b.wasserquelle === 'brunnen' ? 'Brunnen' : 'Leitung'} {b.wassermenge_m3 || '-'} {b.status} {b.status === 'aktiv' && ( )}
{filteredBuchungen.length} Buchung(en)
)} {/* Wasserzähler Tab */} {activeTab === 'wasserzaehler' && (

Wasserzähler

{zaehler.length > 0 && (

Letzter Import: {new Date( Math.max(...zaehler.map(z => new Date(z.erstellt_am).getTime())) ).toLocaleDateString('de-AT', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })}

)}
{zaehler.length === 0 ? ( ) : ( zaehler.map((z) => ( )) )}
Kundennr. Haushalt Adresse Zählernr. Alter Stand Neuer Stand Verbrauch Ablesedatum Token Aktion
Keine Wasserzähler gefunden.
{z.kundennummer} {z.haushalt_name} {z.adresse} {z.zaehlernummer} {z.alter_stand} m³ {z.neuer_stand !== null ? `${z.neuer_stand} m³` : '-'} {z.verbrauch !== null ? ( {z.verbrauch} m³ ) : '-'} {z.ablesedatum ? new Date(z.ablesedatum).toLocaleDateString('de-AT') : '-'} {z.access_token.slice(0, 8)}...
)} {/* Einstellungen Tab */} {activeTab === 'einstellungen' && (

Einstellungen

{settings.map((s) => { const isDate = s.key === 'saison_start' || s.key === 'saison_ende'; return (
{s.beschreibung || s.key}
{s.key}
{ if (e.target.value !== s.value) { updateSetting(s.key, e.target.value); } }} className="border border-border rounded-xl px-3 py-2.5 text-sm w-48 text-right" aria-label={s.beschreibung || s.key} />
); })}

Änderungen werden beim Verlassen des Feldes gespeichert.

)}
{/* Manual Booking Modal */} {showManualBooking && (

Manuell eintragen

Für telefonische Anmeldungen.

setManualForm(f => ({ ...f, name: e.target.value }))} required className="w-full border border-border rounded-xl px-3 py-2.5 text-sm" />
setManualForm(f => ({ ...f, strasse: e.target.value }))} required className="w-full border border-border rounded-xl px-3 py-2.5 text-sm" />
setManualForm(f => ({ ...f, telefon: e.target.value }))} required className="w-full border border-border rounded-xl px-3 py-2.5 text-sm" />
setManualForm(f => ({ ...f, email: e.target.value }))} className="w-full border border-border rounded-xl px-3 py-2.5 text-sm" placeholder="optional" />
setManualForm(f => ({ ...f, wunschdatum: e.target.value }))} required className="w-full border border-border rounded-xl px-3 py-2.5 text-sm" />
{manualForm.wasserquelle === 'ortswasserleitung' && (
setManualForm(f => ({ ...f, wassermenge_m3: e.target.value }))} min="5" step="0.5" className="w-full border border-border rounded-xl px-3 py-2.5 text-sm" />
)} {manualError && (
{manualError}
)}
)} {/* Wasserzähler Edit Modal */} {editZaehler && (

Wasserzähler bearbeiten

{/* Readonly fields */}
Access Token {editZaehler.access_token}
{editForm.neuer_stand && editForm.alter_stand && (
Verbrauch (berechnet) {(parseFloat(editForm.neuer_stand) - parseFloat(editForm.alter_stand)).toFixed(1)} m³
)}
setEditForm(f => ({ ...f, haushalt_name: e.target.value }))} className="w-full border border-border rounded-xl px-3 py-2.5 text-sm" />
setEditForm(f => ({ ...f, adresse: e.target.value }))} className="w-full border border-border rounded-xl px-3 py-2.5 text-sm" />
setEditForm(f => ({ ...f, kundennummer: e.target.value }))} className="w-full border border-border rounded-xl px-3 py-2.5 text-sm" />
setEditForm(f => ({ ...f, zaehlernummer: e.target.value }))} className="w-full border border-border rounded-xl px-3 py-2.5 text-sm" />
setEditForm(f => ({ ...f, alter_stand: e.target.value }))} className="w-full border border-border rounded-xl px-3 py-2.5 text-sm" />
setEditForm(f => ({ ...f, neuer_stand: e.target.value }))} className="w-full border border-border rounded-xl px-3 py-2.5 text-sm" />
setEditForm(f => ({ ...f, ablesedatum: e.target.value }))} className="w-full border border-border rounded-xl px-3 py-2.5 text-sm" />
{editError && (
{editError}
)}
)} {/* Delete Confirmation */} {deleteConfirm && (

Wirklich löschen?

Der Wasserzähler-Eintrag wird unwiderruflich gelöscht.

)} {showConfirmation && ( setShowConfirmation(false)} /> )}
); }