import { Fragment, useMemo, useState } from 'react' import { Building2, Home, MapPin, CalendarDays, ArrowUpDown, LayoutGrid, Ruler, Pencil, Plus, Users, DoorOpen, Search, List, ChevronDown, ChevronRight, Flame, 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 { Building, HausForm, Unit } from '../types' import { HEIZUNGSARTEN } from '../types' /** Komma oder Punkt als Dezimaltrenner akzeptieren. */ const zahl = (s: string) => Number(s.replace(',', '.')) /** Optischer Abschnitt im einspaltigen Haus-Formular. */ function Abschnitt({ titel, children }: { titel: string; children: React.ReactNode }) { return (

{titel}

{children}
) } function Feld({ id, label, hint, ...input }: { id: string label: string hint?: string } & React.InputHTMLAttributes) { return (
{hint &&

{hint}

}
) } function HausDialog({ haus }: { haus?: Building }) { const { addHaus, updateHaus } = useStore() const bearbeiten = Boolean(haus) const [open, setOpen] = useState(false) const [strasse, setStrasse] = useState(haus?.strasse ?? '') const [hausnummer, setHausnummer] = useState(haus?.hausnummer ?? '') const [plz, setPlz] = useState(haus?.plz ?? '') const [ort, setOrt] = useState(haus?.ort ?? '') const [lage, setLage] = useState(haus?.lage ?? '') const [baujahr, setBaujahr] = useState(String(haus?.baujahr ?? '')) const [stockwerke, setStockwerke] = useState(String(haus?.stockwerke ?? '')) const [etagenhoehe, setEtagenhoehe] = useState(String(haus?.etagenhoehe ?? '').replace('.', ',')) const [wohneinheiten, setWohneinheiten] = useState(String(haus?.wohneinheiten ?? '')) const [wohnflaeche, setWohnflaeche] = useState(String(haus?.wohnflaeche ?? '').replace('.', ',')) const [grundstuecksflaeche, setGrundstuecksflaeche] = useState( haus?.grundstuecksflaeche != null ? String(haus.grundstuecksflaeche).replace('.', ',') : '', ) const [heizungsart, setHeizungsart] = useState(haus?.heizungsart ?? '') const [eigenobjekt, setEigenobjekt] = useState(haus?.eigenobjekt ?? false) const [sende, setSende] = useState(false) const [fehler, setFehler] = useState(null) const vollstaendig = strasse.trim() && hausnummer.trim() && /^\d{5}$/.test(plz.trim()) && ort.trim() && baujahr && stockwerke && etagenhoehe && wohneinheiten && wohnflaeche const submit = async () => { if (sende) return setSende(true) setFehler(null) const payload: HausForm = { strasse: strasse.trim(), hausnummer: hausnummer.trim(), plz: plz.trim(), ort: ort.trim(), lage: lage.trim(), baujahr: zahl(baujahr), stockwerke: zahl(stockwerke), etagenhoehe: zahl(etagenhoehe), wohneinheiten: zahl(wohneinheiten), wohnflaeche: zahl(wohnflaeche), grundstuecksflaeche: grundstuecksflaeche.trim() ? zahl(grundstuecksflaeche) : null, heizungsart, eigenobjekt, } const err = bearbeiten ? await updateHaus(haus!.name, payload) : await addHaus(payload) setSende(false) if (err) return setFehler(err) setOpen(false) } return ( {bearbeiten ? ( ) : ( )} {bearbeiten ? `Haus „${haus!.adresse}" bearbeiten` : 'Neues Haus anlegen'}

Hausname und Adresszeile werden automatisch aus Straße und Hausnummer gebildet.

setStrasse(e.target.value)} /> setHausnummer(e.target.value)} /> setPlz(e.target.value)} /> setOrt(e.target.value)} /> setLage(e.target.value)} /> setBaujahr(e.target.value)} /> setStockwerke(e.target.value)} /> setEtagenhoehe(e.target.value)} /> setWohneinheiten(e.target.value)} />

Optional

setWohnflaeche(e.target.value)} /> setGrundstuecksflaeche(e.target.value)} /> {fehler && (
{fehler}
)}
) } function WohnungDialog({ wohnung, gebaeude, festesHaus }: { wohnung?: Unit; gebaeude: string[]; festesHaus?: string }) { const { addWohnung, updateWohnung, buildings } = useStore() const bearbeiten = Boolean(wohnung) const [open, setOpen] = useState(false) const [building, setBuilding] = useState(wohnung?.building ?? festesHaus ?? gebaeude[0] ?? '') const [number, setNumber] = useState(wohnung?.number ?? '') const [floor, setFloor] = useState(String(wohnung?.floor ?? '0')) const [size, setSize] = useState(wohnung ? String(wohnung.size).replace('.', ',') : '') const [status, setStatus] = useState(wohnung?.status ?? 'leerstand') const [heizungsart, setHeizungsart] = useState(wohnung?.heizungsart ?? '') const [sende, setSende] = useState(false) const [fehler, setFehler] = useState(null) /** Bei Neuanlage: Heizungsart der Wohneinheit aus dem gewählten Haus vorbelegen. */ const waehleHaus = (b: string) => { setBuilding(b) if (!bearbeiten) { const h = buildings.find((x) => x.name === b) if (h?.heizungsart) setHeizungsart(h.heizungsart) } } const submit = async () => { if (sende) return setSende(true) setFehler(null) const payload = { building, number: number.trim(), floor: zahl(floor), size: zahl(size), status, heizungsart, } const err = bearbeiten ? await updateWohnung(wohnung!.id, payload) : await addWohnung(payload) setSende(false) if (err) return setFehler(err) setOpen(false) } return ( {bearbeiten ? ( ) : ( )} {bearbeiten ? `Wohnung ${wohnung!.building}, Whg. ${wohnung!.number} bearbeiten` : festesHaus ? `Neue Wohnung in „${festesHaus}" anlegen` : 'Neue Wohnung anlegen'}
{festesHaus &&

Die Wohnung gehört fest zu diesem Haus.

}
setNumber(e.target.value)} />
setFloor(e.target.value)} /> setSize(e.target.value)} />

Optional{!bearbeiten && ' – wird bei Neuanlage automatisch aus dem Haus übernommen'}.

Vermietet/Leerstand wird bei Anlage oder Löschung eines Mieters in der Mieterverwaltung automatisch gesetzt.

{fehler && (
{fehler}
)}
) } /** Wohnungen eines Hauses: Etage aufsteigend, dann Nummer numerisch. */ function sortiereWohnungen(liste: Unit[]): Unit[] { return [...liste].sort( (a, b) => a.floor - b.floor || a.number.localeCompare(b.number, 'de-DE', { numeric: true }), ) } /** Farben des Wohnungsstatus-Badges. */ function statusBadgeKlasse(status: Unit['status']) { if (status === 'vermietet') return 'bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300' if (status === 'unklar') return 'bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-300' return 'bg-slate-200 text-slate-600 dark:bg-slate-800 dark:text-slate-300' } const STATUS_LABEL: Record = { vermietet: 'Vermietet', leerstand: 'Leerstand', unklar: 'Unklar', } /** Wohnungszeile, in Karten- und Listenansicht gleich. */ function WohnungsZeile({ u, gebaeude, festesHaus }: { u: Unit; gebaeude: string[]; festesHaus: string }) { const { deleteWohnung, role } = useStore() const loesche = async () => { if (!window.confirm(`Wohnung ${u.building}, Whg. ${u.number} wirklich löschen?`)) return const fehler = await deleteWohnung(u.id) if (fehler) window.alert(fehler) } return (
  • Whg. {u.number} Etage {u.floor} · {u.size.toLocaleString('de-DE')} m²
    {STATUS_LABEL[u.status]}
    Mieter verwalten {role === 'admin' && ( )}
  • ) } /** Fortschrittsbalken „angelegte Wohneinheiten gegen Soll". */ function BelegungsFortschritt({ angelegt, soll }: { angelegt: number; soll: number }) { const voll = angelegt === soll const ueberSoll = angelegt > soll return (
    Wohneinheiten {angelegt} von {soll} angelegt
    {ueberSoll && (

    Achtung: mehr Wohnungen angelegt als im Soll vorgesehen.

    )}
    ) } export default function Bestand() { const { buildings, units, deleteHaus, role } = useStore() const [suche, setSuche] = useState('') const [ansicht, setAnsicht] = useState<'karten' | 'liste'>('liste') const [offen, setOffen] = useState(null) const loescheHaus = async (name: string) => { if (!window.confirm(`Haus „${name}" wirklich löschen?`)) return const fehler = await deleteHaus(name) if (fehler) window.alert(fehler) if (offen === name) setOffen(null) } const gebaeudeNamen = useMemo( () => buildings.map((b) => b.name).sort(), [buildings], ) // 1:n-Struktur: jede Wohnung gehört zu genau einem Haus const wohnungenJeHaus = useMemo(() => { const m = new Map() for (const u of units) { const l = m.get(u.building) ?? [] l.push(u) m.set(u.building, l) } for (const [k, l] of m) m.set(k, sortiereWohnungen(l)) return m }, [units]) // Wohnungen ohne Haus (kann bei Altbeständen vorkommen) separat anzeigen const verwaisteWohnungen = useMemo( () => units.filter((u) => !buildings.some((b) => b.name === u.building)), [units, buildings], ) // Suche über Adresse, Einzelfelder, Lage und internem Namen const gefilterteHaeuser = useMemo(() => { const q = suche.trim().toLowerCase() if (!q) return buildings return buildings.filter((h) => [h.adresse, h.strasse, h.hausnummer, h.plz, h.ort, h.lage, h.name] .some((f) => f.toLowerCase().includes(q)), ) }, [buildings, suche]) return (

    Häuser & Wohnungen

    Bestand pflegen – jedes Haus besteht aus mehreren Wohneinheiten, die hier zugeordnet angelegt und bearbeitet werden

    {/* Suche + Ansichtsumschalter */} {buildings.length > 0 && (
    setSuche(e.target.value)} aria-label="Haus suchen" />
    {suche && ( {gefilterteHaeuser.length} von {buildings.length} Häusern )}
    )} {buildings.length === 0 ? (
    Noch keine Häuser angelegt – legen Sie zuerst ein Haus an, dann dessen Wohneinheiten.
    ) : gefilterteHaeuser.length === 0 ? (
    Kein Haus passt zur Suche „{suche}".
    ) : ansicht === 'karten' ? (
    {gefilterteHaeuser.map((h) => { const wohnungen = wohnungenJeHaus.get(h.name) ?? [] const angelegt = wohnungen.length return (
    {/* Haus-Kopf: Überschrift ist die Adresse */}
    {h.adresse}
    {h.lage || '–'} {h.grundstuecksflaeche != null && ( · Grundstück {h.grundstuecksflaeche.toLocaleString('de-DE')} m² )} {h.eigenobjekt && ( Eigenobjekt )}
    {role === 'admin' && ( )}
    Baujahr
    {h.baujahr}
    Stockwerke
    {h.stockwerke}
    Etagenhöhe
    {h.etagenhoehe.toLocaleString('de-DE')} m
    Wohnfläche
    {h.wohnflaeche.toLocaleString('de-DE')} m²
    {h.heizungsart && ( <>
    Heizung
    {h.heizungsart}
    )}
    {/* Wohneinheiten des Hauses (1:n) */}

    Wohneinheiten ({angelegt})

    {angelegt === 0 ? (
    Noch keine Wohnungen – über „+ Wohnung" die erste Einheit anlegen.
    ) : (
      {wohnungen.map((u) => ( ))}
    )}
    ) })}
    ) : ( /* ── Listenansicht ─────────────────────────────── */
    {gefilterteHaeuser.map((h) => { const wohnungen = wohnungenJeHaus.get(h.name) ?? [] const angelegt = wohnungen.length const aufgeklappt = offen === h.name return ( setOffen(aufgeklappt ? null : h.name)} > {aufgeklappt && ( )} ) })}
    Adresse Lage Baujahr Stockwerke Wohnfläche Wohneinheiten Aktion
    {aufgeklappt ? : } {h.adresse} {h.eigenobjekt && ( Eigenobjekt )} {h.lage || '–'} {h.baujahr} {h.stockwerke} {h.wohnflaeche.toLocaleString('de-DE')} m² h.wohneinheiten ? 'font-medium text-amber-600 dark:text-amber-400' : 'text-slate-600 dark:text-slate-300' } > {angelegt} / {h.wohneinheiten} e.stopPropagation()}> {role === 'admin' && ( )}
    Baujahr {h.baujahr} · {h.stockwerke} Stockwerke · Etagenhöhe{' '} {h.etagenhoehe.toLocaleString('de-DE')} m · Wohnfläche{' '} {h.wohnflaeche.toLocaleString('de-DE')} m² {h.grundstuecksflaeche != null && ` · Grundstück ${h.grundstuecksflaeche.toLocaleString('de-DE')} m²`} {h.heizungsart && ` · Heizung: ${h.heizungsart}`}
    {angelegt === 0 ? (

    Noch keine Wohnungen angelegt.

    ) : (
      {wohnungen.map((u) => ( ))}
    )}
    )} {verwaisteWohnungen.length > 0 && (

    Wohnungen ohne Hauszuordnung

    Diese Wohnungen verweisen auf ein nicht angelegtes Haus – bitte Zuordnung über „Bearbeiten" korrigieren.

    {verwaisteWohnungen.map((u) => (
    {u.building}, Whg. {u.number}
    ))}
    )}
    ) }