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:
2026-10-04 20:51:00 +02:00
parent 9e74813e22
commit 9d14d367ee
22 changed files with 1840 additions and 107 deletions
+13 -12
View File
@@ -59,11 +59,12 @@ function StatCard({
}
function TenantDashboard() {
const { meineEinheit: demoUnit, me, transactions, tickets, threads, announcements } = useStore()
const { meineEinheit: demoUnit, me, transactions, tickets, threads, announcements, mieter } = useStore()
const myTx = transactions.filter((t) => t.unitId === demoUnit.id)
const openTx = myTx.filter((t) => t.status !== 'bezahlt')
const myTickets = tickets.filter((t) => t.unitId === demoUnit.id)
const unread = threads.filter((t) => t.unreadByTenant)
const meinMieter = mieter.find((m) => m.unitId === demoUnit.id)
const chartData = monthShort.slice(0, 10).map((m, i) => ({
m,
@@ -89,8 +90,8 @@ function TenantDashboard() {
<StatCard
icon={Wallet}
label="Monatliche Warmmiete"
value={fmtEUR.format(demoUnit.warmRent)}
sub={`Kaltmiete ${fmtEUR.format(demoUnit.coldRent)} + NK`}
value={meinMieter ? fmtEUR.format(meinMieter.warmmiete) : '–'}
sub={meinMieter ? `Kaltmiete ${fmtEUR.format(meinMieter.kaltmiete)} + NK` : 'Mieterdaten folgen'}
/>
<StatCard
icon={Euro}
@@ -109,8 +110,8 @@ function TenantDashboard() {
<StatCard
icon={TicketCheck}
label="Mietverhältnis seit"
value="März 2024"
sub="Kaution: 1.500,00 € hinterlegt"
value={meinMieter?.mietbeginnAm ? fmtDatum(meinMieter.mietbeginnAm) : '–'}
sub={meinMieter ? `Kaution: ${fmtEUR.format(meinMieter.kautionHoehe)} hinterlegt` : 'Mieterdaten folgen'}
/>
</div>
@@ -216,10 +217,10 @@ function TenantDashboard() {
}
function AdminDashboard() {
const { units, transactions, tickets, announcements, buildings: gebaeude } = useStore()
const { units, transactions, tickets, announcements, buildings: gebaeude, mieter } = useStore()
const rented = units.filter((u) => u.status === 'vermietet')
const occupancy = Math.round((rented.length / units.length) * 100)
const monthlyIncome = rented.reduce((s, u) => s + u.warmRent, 0)
const occupancy = units.length ? Math.round((rented.length / units.length) * 100) : 0
const monthlyIncome = mieter.reduce((s, m) => s + m.warmmiete, 0)
const openTickets = tickets.filter((t) => t.status === 'offen')
const overdue = transactions.filter((t) => t.status === 'überfällig')
@@ -235,7 +236,7 @@ function AdminDashboard() {
return {
name: g.name,
Vermietet: us.filter((u) => u.status === 'vermietet').length,
Frei: us.filter((u) => u.status === 'frei').length,
Leerstand: us.filter((u) => u.status === 'leerstand').length,
}
})
@@ -249,7 +250,7 @@ function AdminDashboard() {
</div>
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
<StatCard icon={Building2} label="Wohneinheiten gesamt" value={String(units.length)} sub={`${units.filter((u) => u.status === 'frei').length} aktuell frei`} />
<StatCard icon={Building2} label="Wohneinheiten gesamt" value={String(units.length)} sub={`${units.filter((u) => u.status === 'leerstand').length} aktuell im Leerstand`} />
<StatCard icon={Users} label="Vermietungsquote" value={`${occupancy} %`} sub={`${rented.length} von ${units.length} Wohneinheiten`} />
<StatCard icon={Euro} label="Monatliche Warmmiete (Soll)" value={fmtEUR.format(monthlyIncome)} sub="Summe aller vermieteten Einheiten" />
<StatCard
@@ -293,13 +294,13 @@ function AdminDashboard() {
<YAxis type="category" dataKey="name" tick={{ fontSize: 11 }} stroke="#94a3b8" width={95} />
<Tooltip />
<Bar dataKey="Vermietet" stackId="a" fill="#0d9488" radius={[0, 0, 0, 0]} isAnimationActive={false} />
<Bar dataKey="Frei" stackId="a" fill="#f59e0b" radius={[0, 6, 6, 0]} isAnimationActive={false} />
<Bar dataKey="Leerstand" stackId="a" fill="#f59e0b" radius={[0, 6, 6, 0]} isAnimationActive={false} />
</BarChart>
</ResponsiveContainer>
</div>
<div className="mt-2 flex gap-4 text-xs text-slate-500">
<span className="flex items-center gap-1.5"><span className="h-2.5 w-2.5 rounded-sm bg-teal-600" /> vermietet</span>
<span className="flex items-center gap-1.5"><span className="h-2.5 w-2.5 rounded-sm bg-amber-500" /> frei</span>
<span className="flex items-center gap-1.5"><span className="h-2.5 w-2.5 rounded-sm bg-amber-500" /> Leerstand</span>
</div>
</div>
</div>