import {
Area,
AreaChart,
Bar,
BarChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts'
import {
ArrowRight,
Building2,
CalendarClock,
CircleAlert,
Euro,
Megaphone,
TicketCheck,
Users,
Wallet,
Wrench,
} from 'lucide-react'
import { Link } from 'react-router'
import { useStore, fmtEUR } from '../store/store'
import { fmtDatum } from '../lib/format'
const monthShort = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
function StatCard({
icon: Icon,
label,
value,
sub,
tone = 'teal',
}: {
icon: React.ElementType
label: string
value: string
sub?: string
tone?: 'teal' | 'amber' | 'red' | 'blue'
}) {
const tones = {
teal: 'bg-teal-50 text-teal-700',
amber: 'bg-amber-50 text-amber-700',
red: 'bg-red-50 text-red-600',
blue: 'bg-blue-50 text-blue-700',
}
return (
{value}
{label}
{sub &&
{sub}
}
)
}
function TenantDashboard() {
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,
Betrag: myTx.filter((t) => t.year === 2026 && myTx.indexOf(t) % 12 === i).reduce((s, t) => s + (t.status === 'bezahlt' ? t.amount : 0), 0),
}))
return (
Hallo, {me?.name}
{demoUnit.building} · Wohnung {demoUnit.number} · {demoUnit.size} m²
Nächste Zahlung: Oktober-Miete bis 03.10.2026
0 ? 'Offene Forderungen' : 'Keine offenen Forderungen'}
value={openTx.length > 0 ? fmtEUR.format(openTx.reduce((s, t) => s + t.amount, 0)) : '0,00 €'}
tone={openTx.length > 0 ? 'amber' : 'teal'}
sub={openTx.length > 0 ? `${openTx.length} Posten zu zahlen` : 'Alles bezahlt – danke!'}
/>
t.status !== 'erledigt').length} in Bearbeitung`}
/>
Gezahlte Miete 2026
Zum Mietkonto
fmtEUR.format(Number(v))} />
Offene Schadensmeldungen
Alle anzeigen
{myTickets.filter((t) => t.status !== 'erledigt').length === 0 ? (
Aktuell keine offenen Meldungen.
) : (
{myTickets
.filter((t) => t.status !== 'erledigt')
.map((t) => (
-
{t.title}
{t.category} · {fmtDatum(t.createdAt)}
{t.status}
))}
)}
{unread.length === 0 ? (
Keine ungelesenen Nachrichten.
) : (
)}
)
}
function AdminDashboard() {
const { units, transactions, tickets, announcements, buildings: gebaeude, mieter } = useStore()
const rented = units.filter((u) => u.status === 'vermietet')
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')
const incomeData = monthShort.slice(0, 10).map((m, i) => ({
m,
Einnahmen: transactions
.filter((t) => t.month === ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober'][i] && t.status === 'bezahlt')
.reduce((s, t) => s + t.amount, 0),
}))
const buildings = gebaeude.map((g) => {
const us = units.filter((u) => u.building === g.name)
return {
name: g.name,
Vermietet: us.filter((u) => u.status === 'vermietet').length,
Leerstand: us.filter((u) => u.status === 'leerstand').length,
}
})
return (
Verwaltungs-Dashboard
Bestand: {buildings.length} {buildings.length === 1 ? 'Haus' : 'Häuser'} · {units.length} Wohneinheiten · {rented.length} vermietet
u.status === 'leerstand').length} aktuell im Leerstand`} />
0 ? 'red' : 'teal'}
sub="Offene Schäden und rückständige Zahlungen"
/>
Eingänge 2026 (bezahlte Mieten)
fmtEUR.format(Number(v))} />
Belegung pro Haus
vermietet
Leerstand
Neueste Schadensmeldungen
Alle anzeigen
{tickets.slice(0, 4).map((t) => (
-
{t.title}
{units.find((u) => u.id === t.unitId)?.building}, Whg. {units.find((u) => u.id === t.unitId)?.number} · {t.tenant}
{t.status}
))}
Letzte Ankündigung
{announcements[0].tag}
{announcements[0].date}
{announcements[0].title}
{announcements[0].text}
Ankündigungen sind im Mieter-Dashboard sowie als Nachricht sichtbar.
)
}
export default function Dashboard() {
const { role } = useStore()
return role === 'tenant' ? :
}