'use client' import React, { useCallback, useEffect, useState } from 'react' import { AlertTriangle, BarChart3, Building2, CheckCircle2, Plus, RefreshCw, Search, X, } from 'lucide-react' import type { OverviewResponse, SortField, StatusFilter, TenantOverview } from './_types' import { FILTER_OPTIONS, RISK_ORDER, SORT_OPTIONS } from './_components/constants' import { apiFetch, formatDateTime } from './_components/helpers' import { LoadingSkeleton } from './_components/LoadingSkeleton' import { EmptyState } from './_components/EmptyState' import { StatCard } from './_components/StatCard' import { TenantCard } from './_components/TenantCard' import { CreateTenantModal } from './_components/CreateTenantModal' import { EditTenantModal } from './_components/EditTenantModal' import { TenantDetailModal } from './_components/TenantDetailModal' export default function MultiTenantPage() { // Data state const [tenants, setTenants] = useState([]) const [totalTenants, setTotalTenants] = useState(0) const [averageScore, setAverageScore] = useState(0) const [generatedAt, setGeneratedAt] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) // UI state const [searchQuery, setSearchQuery] = useState('') const [statusFilter, setStatusFilter] = useState('all') const [sortField, setSortField] = useState('name') const [refreshing, setRefreshing] = useState(false) // Modal state const [showCreate, setShowCreate] = useState(false) const [editTenant, setEditTenant] = useState(null) const [detailTenant, setDetailTenant] = useState(null) // Switch tenant notification const [switchNotification, setSwitchNotification] = useState(null) // --------------------------------------------------------------------------- // DATA LOADING // --------------------------------------------------------------------------- const loadOverview = useCallback(async (showRefresh = false) => { if (showRefresh) setRefreshing(true) else setLoading(true) setError(null) try { const data = await apiFetch('/overview') setTenants(data.tenants || []) setTotalTenants(data.total || 0) setAverageScore(data.average_score || 0) setGeneratedAt(data.generated_at || null) } catch (err) { setError(err instanceof Error ? err.message : 'Fehler beim Laden der Mandanten') } finally { setLoading(false) setRefreshing(false) } }, []) useEffect(() => { loadOverview() }, [loadOverview]) // --------------------------------------------------------------------------- // FILTERING, SEARCHING, SORTING // --------------------------------------------------------------------------- const filteredTenants = React.useMemo(() => { let result = [...tenants] // Filter by status if (statusFilter !== 'all') { result = result.filter((t) => t.status === statusFilter) } // Filter by search query if (searchQuery.trim()) { const q = searchQuery.toLowerCase().trim() result = result.filter( (t) => t.name.toLowerCase().includes(q) || t.slug.toLowerCase().includes(q) ) } // Sort result.sort((a, b) => { switch (sortField) { case 'name': return a.name.localeCompare(b.name, 'de-DE') case 'score': return b.compliance_score - a.compliance_score case 'risk': return (RISK_ORDER[b.risk_level] || 0) - (RISK_ORDER[a.risk_level] || 0) default: return 0 } }) return result }, [tenants, statusFilter, searchQuery, sortField]) // --------------------------------------------------------------------------- // DERIVED STATS // --------------------------------------------------------------------------- const activeTenants = React.useMemo( () => tenants.filter((t) => t.status === 'active').length, [tenants] ) const criticalRisks = React.useMemo( () => tenants.filter((t) => t.risk_level === 'HIGH' || t.risk_level === 'CRITICAL').length, [tenants] ) // --------------------------------------------------------------------------- // HANDLERS // --------------------------------------------------------------------------- const handleRefresh = () => loadOverview(true) const handleSwitchTenant = async (tenant: TenantOverview) => { try { await apiFetch<{ tenant: { id: string; name: string } }>('/switch', { method: 'POST', body: JSON.stringify({ tenant_id: tenant.id }), }) if (typeof window !== 'undefined') { localStorage.setItem('sdk-tenant-id', tenant.id) } setSwitchNotification(`Mandant gewechselt zu: ${tenant.name}`) setTimeout(() => setSwitchNotification(null), 4000) } catch (err) { // Fallback: just set in localStorage even if API call fails if (typeof window !== 'undefined') { localStorage.setItem('sdk-tenant-id', tenant.id) } setSwitchNotification(`Mandant gewechselt zu: ${tenant.name}`) setTimeout(() => setSwitchNotification(null), 4000) } } const handleCreated = () => { loadOverview(true) } const handleUpdated = () => { loadOverview(true) } // --------------------------------------------------------------------------- // RENDER: LOADING STATE // --------------------------------------------------------------------------- if (loading) { return (
) } // --------------------------------------------------------------------------- // RENDER: ERROR STATE // --------------------------------------------------------------------------- if (error && tenants.length === 0) { return (

Fehler beim Laden

{error}

) } // --------------------------------------------------------------------------- // RENDER: MAIN PAGE // --------------------------------------------------------------------------- return (
{/* Switch Notification */} {switchNotification && (
{switchNotification}
)} {/* HEADER */}

Multi-Tenant Verwaltung

Mandanten verwalten und Compliance-Status ueberwachen

{/* Generated At */} {generatedAt && (

Zuletzt aktualisiert: {formatDateTime(generatedAt)}

)} {/* STATS OVERVIEW */}
} color="indigo" /> } color="green" /> } color="blue" /> } color="red" />
{/* SEARCH & FILTER BAR */}
{/* Search Input */}
setSearchQuery(e.target.value)} placeholder="Mandant suchen (Name oder Slug)..." className="w-full pl-9 pr-9 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" /> {searchQuery && ( )}
{/* Status Filter */}
{FILTER_OPTIONS.map((opt) => ( ))}
{/* Sort */}
Sortierung:
{/* REFRESH ERROR BANNER */} {error && tenants.length > 0 && (
Fehler beim Aktualisieren: {error}
)} {/* TENANT CARDS GRID */} {filteredTenants.length === 0 ? ( tenants.length === 0 ? ( } title="Keine Mandanten vorhanden" description="Erstellen Sie Ihren ersten Mandanten, um die Multi-Tenant-Verwaltung zu nutzen." action={ } /> ) : ( } title="Keine Ergebnisse" description={`Kein Mandant gefunden fuer "${searchQuery}"${statusFilter !== 'all' ? ` mit Status "${FILTER_OPTIONS.find((f) => f.value === statusFilter)?.label}"` : ''}.`} action={ } /> ) ) : ( <>

{filteredTenants.length} von {tenants.length} Mandant{tenants.length !== 1 ? 'en' : ''}

{filteredTenants.map((tenant) => ( ))}
)} {/* MODALS */} setShowCreate(false)} onCreated={handleCreated} /> setEditTenant(null)} tenant={editTenant} onUpdated={handleUpdated} /> setDetailTenant(null)} tenant={detailTenant} onSwitchTenant={handleSwitchTenant} />
) }