import { useMemo, useState } from 'react' import { Gauge, Zap, Droplets, Flame, Building2, Hash, CalendarDays, Pencil, Plus, History, TriangleAlert, Trash2 } from 'lucide-react' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter, } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { useStore } from '../store/store' import type { Meter, MeterReading, ZaehlerArt } from '../types' const ARTEN: ZaehlerArt[] = ['Strom', 'Wasser', 'Gas', 'Wärme', 'Sonstiges'] const artIcon = (art: ZaehlerArt) => { switch (art) { case 'Strom': return Zap case 'Wasser': return Droplets case 'Gas': return Flame default: return Gauge } } const artStyle: Record = { Strom: 'bg-amber-100 text-amber-800', Wasser: 'bg-blue-100 text-blue-800', Gas: 'bg-orange-100 text-orange-800', Wärme: 'bg-red-100 text-red-700', Sonstiges: 'bg-slate-100 text-slate-600', } /** Deutsche Zahlenanzeige: 48231,4 → „48.231,4" (bis zu 3 Nachkommastellen). */ const fmtStand = (n: number) => n.toLocaleString('de-DE', { maximumFractionDigits: 3 }) function ZaehlerDialog({ meter }: { meter?: Meter }) { const { addZaehler, updateZaehler, units, buildings } = useStore() const bearbeiten = Boolean(meter) const [open, setOpen] = useState(false) const [nummer, setNummer] = useState(meter?.nummer ?? '') const [art, setArt] = useState(meter?.art ?? 'Strom') const [stand, setStand] = useState(String(meter?.stand ?? '').replace('.', ',')) const [standAm, setStandAm] = useState(() => { const d = new Date() return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` }) const [unitId, setUnitId] = useState(meter?.unitId ?? 'haus') const [building, setBuilding] = useState(meter?.building ?? buildings[0]?.name ?? '') const [sende, setSende] = useState(false) const [fehler, setFehler] = useState(null) const submit = async () => { const standZahl = Number(stand.replace(',', '.')) if (!nummer.trim() || !Number.isFinite(standZahl) || standZahl < 0 || sende) return if (!bearbeiten && !standAm) return setSende(true) setFehler(null) let err: string | null if (bearbeiten) { err = await updateZaehler(meter!.id, { nummer: nummer.trim(), art, stand: standZahl, unitId: unitId === 'haus' ? null : unitId, building, }) } else { err = await addZaehler({ nummer: nummer.trim(), art, stand: standZahl, // „YYYY-MM-DD" (Datumseingabe) → „TT.MM.JJJJ" (Serverformat) standAm: standAm.split('-').reverse().join('.'), unitId: unitId === 'haus' ? null : unitId, building, }) } setSende(false) if (err) return setFehler(err) setOpen(false) } return ( {bearbeiten ? ( ) : ( )} {bearbeiten ? `Zähler ${meter!.nummer} bearbeiten` : 'Neuen Zähler anlegen'}
setNummer(e.target.value)} />
setStand(e.target.value)} />
{!bearbeiten && (
setStandAm(e.target.value)} />
)}
{unitId !== 'haus' && (

Das Haus folgt automatisch der gewählten Wohneinheit.

)}
{unitId === 'haus' && (

Nur nötig für Zähler ohne Wohnungsbezug.

)}
{fehler && (
{fehler}
)}
) } /** Verlauf eines Zählers: alle erfassten Stände als Liste (neueste zuerst). */ function VerlaufDialog({ meter }: { meter: Meter }) { const { zaehlerVerlauf, zaehlerEintragLoeschen, role } = useStore() const [open, setOpen] = useState(false) const [liste, setListe] = useState(null) const [lade, setLade] = useState(false) const laden = async () => { setLade(true) try { setListe(await zaehlerVerlauf(meter.id)) } finally { setLade(false) } } const oeffnen = (o: boolean) => { setOpen(o) if (o) { setListe(null); laden() } } const loeschen = async (eintragId: string) => { const err = await zaehlerEintragLoeschen(meter.id, eintragId) if (!err) await laden() } const quelleLabel = (q: MeterReading['quelle']) => q === 'mieter' ? 'Mieter' : q === 'erstbefund' ? 'System' : 'Verwaltung' return ( Verlauf · Zähler {meter.nummer} ({meter.art}, {meter.building}) {lade || liste === null ? (

Verlauf wird geladen …

) : liste.length === 0 ? (

Noch keine Einträge.

) : (
    {[...liste].reverse().map((r) => (
  • {fmtStand(r.stand)} {quelleLabel(r.quelle)} {r.anomalie && ( Auffälligkeit )} {role === 'admin' && ( )}
    Ablesedatum: {r.standAm} {r.erfasstVon && · erfasst von {r.erfasstVon}}
    {r.anomalie && r.anomalieGrund && (

    {r.anomalieGrund}

    )}
  • ))}
)}

Auffälligkeiten (z. B. rückläufige Stände oder ungewöhnlich hoher Verbrauch) markiert das Portal automatisch und benachrichtigt die Verwaltung per E-Mail.

) } /** Neuen Zählerstand erfassen – für Mieter (eigene Zähler) und Verwaltung. */ function StandEintragenDialog({ meter }: { meter: Meter }) { const { zaehlerstandEintragen } = useStore() const [open, setOpen] = useState(false) const [stand, setStand] = useState('') const [datum, setDatum] = useState(() => new Date().toISOString().slice(0, 10)) const [sende, setSende] = useState(false) const [fehler, setFehler] = useState(null) const [warnung, setWarnung] = useState(null) const submit = async () => { const standZahl = Number(stand.replace(',', '.')) if (!Number.isFinite(standZahl) || standZahl < 0 || !datum || sende) return setSende(true) setFehler(null) setWarnung(null) const r = await zaehlerstandEintragen(meter.id, standZahl, datum) setSende(false) if (r.fehler) return setFehler(r.fehler) if (r.anomalie) { // Auffälligkeit: Eintrag bleibt bestehen, Verwaltung wurde per E-Mail informiert setWarnung(`Auffälligkeit erkannt: ${r.grund ?? 'unbekannt'}. Die Verwaltung wurde per E-Mail benachrichtigt.`) } else { setOpen(false) } } return ( Stand eintragen · Zähler {meter.nummer} (akt. Stand: {fmtStand(meter.stand)})
setStand(e.target.value)} />
setDatum(e.target.value)} />
{fehler && (
{fehler}
)} {warnung && (
{warnung}
)}
) } export default function Zaehler() { const { meters, units, role } = useStore() const [filterHaus, setFilterHaus] = useState('alle') const [filterArt, setFilterArt] = useState<'alle' | ZaehlerArt>('alle') const gebaeude = useMemo( () => Array.from(new Set(meters.map((m) => m.building))).sort(), [meters], ) const visible = useMemo( () => meters.filter( (m) => (filterHaus === 'alle' || m.building === filterHaus) && (filterArt === 'alle' || m.art === filterArt), ), [meters, filterHaus, filterArt], ) const einheitLabel = (m: Meter) => { if (!m.unitId) return 'Allgemeinzähler' const u = units.find((x) => x.id === m.unitId) return u ? `Whg. ${u.number}` : m.unitId } return (

Zählerstände

{role === 'tenant' ? 'Ihre Zähler: Strom, Wasser und Gas auf einen Blick – Stände eintragen und Verlauf ansehen' : 'Alle Zähler des Bestands – Verlauf prüfen, Auffälligkeiten erkennen und Wohnungen zuordnen'}

{role === 'admin' && }
{visible.length === 0 ? (
Keine Zähler für diesen Filter.
) : (
{visible.map((m) => { const Icon = artIcon(m.art) return (
{m.art} {m.letzteAnomalie && ( Auffälligkeit )}
{role === 'admin' && }
{fmtStand(m.stand)}
{m.nummer}
{m.building} · {einheitLabel(m)}
Stand: {m.standAm} (gepflegt: {m.geaendertAm})
) })}
)}
) }