v1.36: Mieterverwaltung (Mieter 1:1 pro Wohnung, Kaution/Vertrag/Dokumente) + v1.37: KI-Assistent (N8N + Kimi, 6 Portal-Tools)
This commit is contained in:
+35
-27
@@ -22,7 +22,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useStore, fmtEUR } from '../store/store'
|
||||
import { useStore } from '../store/store'
|
||||
import type { Building, HausForm, Unit } from '../types'
|
||||
import { HEIZUNGSARTEN } from '../types'
|
||||
|
||||
@@ -203,10 +203,7 @@ function WohnungDialog({ wohnung, gebaeude, festesHaus }: { wohnung?: Unit; geba
|
||||
const [number, setNumber] = useState(wohnung?.number ?? '')
|
||||
const [floor, setFloor] = useState(String(wohnung?.floor ?? '0'))
|
||||
const [size, setSize] = useState(wohnung ? String(wohnung.size).replace('.', ',') : '')
|
||||
const [coldRent, setColdRent] = useState(wohnung ? String(wohnung.coldRent).replace('.', ',') : '')
|
||||
const [warmRent, setWarmRent] = useState(wohnung ? String(wohnung.warmRent).replace('.', ',') : '')
|
||||
const [tenant, setTenant] = useState(wohnung?.tenant ?? '')
|
||||
const [status, setStatus] = useState<'vermietet' | 'frei'>(wohnung?.status ?? 'frei')
|
||||
const [status, setStatus] = useState<Unit['status']>(wohnung?.status ?? 'leerstand')
|
||||
const [heizungsart, setHeizungsart] = useState(wohnung?.heizungsart ?? '')
|
||||
const [sende, setSende] = useState(false)
|
||||
const [fehler, setFehler] = useState<string | null>(null)
|
||||
@@ -229,9 +226,6 @@ function WohnungDialog({ wohnung, gebaeude, festesHaus }: { wohnung?: Unit; geba
|
||||
number: number.trim(),
|
||||
floor: zahl(floor),
|
||||
size: zahl(size),
|
||||
coldRent: zahl(coldRent),
|
||||
warmRent: zahl(warmRent),
|
||||
tenant: tenant.trim(),
|
||||
status,
|
||||
heizungsart,
|
||||
}
|
||||
@@ -299,21 +293,20 @@ function WohnungDialog({ wohnung, gebaeude, festesHaus }: { wohnung?: Unit; geba
|
||||
</p>
|
||||
</div>
|
||||
</Abschnitt>
|
||||
<Abschnitt titel="Miete">
|
||||
<Feld id="w-kalt" label="Kaltmiete (€)" inputMode="decimal" placeholder="z. B. 520" value={coldRent} onChange={(e) => setColdRent(e.target.value)} />
|
||||
<Feld id="w-warm" label="Warmmiete (€)" inputMode="decimal" placeholder="z. B. 690" value={warmRent} onChange={(e) => setWarmRent(e.target.value)} />
|
||||
</Abschnitt>
|
||||
<Abschnitt titel="Mieter">
|
||||
<Feld id="w-mieter" label="Mieter (Name)" hint="Leer lassen, solange die Wohnung frei ist" placeholder="z. B. Anna Berger" value={tenant} onChange={(e) => setTenant(e.target.value)} />
|
||||
<Abschnitt titel="Status">
|
||||
<div className="space-y-1.5">
|
||||
<Label>Status</Label>
|
||||
<Select value={status} onValueChange={(v) => setStatus(v as 'vermietet' | 'frei')}>
|
||||
<Select value={status} onValueChange={(v) => setStatus(v as Unit['status'])}>
|
||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="vermietet">vermietet</SelectItem>
|
||||
<SelectItem value="frei">frei</SelectItem>
|
||||
<SelectItem value="vermietet">Vermietet</SelectItem>
|
||||
<SelectItem value="leerstand">Leerstand</SelectItem>
|
||||
<SelectItem value="unklar">Unklar</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500">
|
||||
Vermietet/Leerstand wird bei Anlage oder Löschung eines Mieters in der Mieterverwaltung automatisch gesetzt.
|
||||
</p>
|
||||
</div>
|
||||
</Abschnitt>
|
||||
{fehler && (
|
||||
@@ -324,7 +317,7 @@ function WohnungDialog({ wohnung, gebaeude, festesHaus }: { wohnung?: Unit; geba
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setOpen(false)}>Abbrechen</Button>
|
||||
<Button onClick={submit} disabled={sende || !building || !number.trim() || !size || !coldRent || !warmRent}>
|
||||
<Button onClick={submit} disabled={sende || !building || !number.trim() || !size}>
|
||||
{sende ? 'Wird gespeichert …' : bearbeiten ? 'Speichern' : 'Wohnung anlegen'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
@@ -340,6 +333,21 @@ function sortiereWohnungen(liste: Unit[]): Unit[] {
|
||||
)
|
||||
}
|
||||
|
||||
/** 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<Unit['status'], string> = {
|
||||
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()
|
||||
@@ -357,19 +365,19 @@ function WohnungsZeile({ u, gebaeude, festesHaus }: { u: Unit; gebaeude: string[
|
||||
Etage {u.floor} · {u.size.toLocaleString('de-DE')} m²
|
||||
</span>
|
||||
</div>
|
||||
<Badge
|
||||
className={u.status === 'vermietet' ? 'bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300' : 'bg-slate-200 text-slate-600 dark:bg-slate-800 dark:text-slate-300'}
|
||||
variant="secondary"
|
||||
>
|
||||
{u.status}
|
||||
<Badge className={statusBadgeKlasse(u.status)} variant="secondary">
|
||||
{STATUS_LABEL[u.status]}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-1 flex items-center justify-between gap-2 text-xs text-slate-500 dark:text-slate-400">
|
||||
<span className="flex items-center gap-1 truncate">
|
||||
<Users className="h-3.5 w-3.5 shrink-0" /> {u.tenant ?? '–'}
|
||||
</span>
|
||||
<a
|
||||
href="/mieter"
|
||||
className="flex items-center gap-1 truncate text-teal-700 hover:underline dark:text-teal-400"
|
||||
title="Mieterverwaltung öffnen"
|
||||
>
|
||||
<Users className="h-3.5 w-3.5 shrink-0" /> Mieter verwalten
|
||||
</a>
|
||||
<span className="flex shrink-0 items-center gap-2">
|
||||
<span className="font-medium text-slate-700 dark:text-slate-200">{fmtEUR.format(u.warmRent)}</span>
|
||||
<WohnungDialog wohnung={u} gebaeude={gebaeude} festesHaus={festesHaus} />
|
||||
{role === 'admin' && (
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user