Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
156 lines
5.5 KiB
TypeScript
156 lines
5.5 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useState } from 'react'
|
|
import { navigation, metaModules } from '@/lib/navigation'
|
|
import { getStoredRole, isCategoryVisibleForRole, RoleId } from '@/lib/roles'
|
|
import { CategoryCard } from '@/components/common/ModuleCard'
|
|
import { InfoNote } from '@/components/common/InfoBox'
|
|
import { ServiceStatus } from '@/components/common/ServiceStatus'
|
|
import { NightModeWidget } from '@/components/dashboard/NightModeWidget'
|
|
import Link from 'next/link'
|
|
|
|
interface Stats {
|
|
activeDocuments: number
|
|
openDSR: number
|
|
registeredUsers: number
|
|
totalConsents: number
|
|
gpuInstances: number
|
|
}
|
|
|
|
export default function DashboardPage() {
|
|
const [stats, setStats] = useState<Stats>({
|
|
activeDocuments: 0,
|
|
openDSR: 0,
|
|
registeredUsers: 0,
|
|
totalConsents: 0,
|
|
gpuInstances: 0,
|
|
})
|
|
const [loading, setLoading] = useState(true)
|
|
const [currentRole, setCurrentRole] = useState<RoleId | null>(null)
|
|
|
|
useEffect(() => {
|
|
const role = getStoredRole()
|
|
setCurrentRole(role)
|
|
|
|
// Load stats
|
|
const loadStats = async () => {
|
|
try {
|
|
const response = await fetch('http://localhost:8081/api/v1/admin/stats')
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
setStats({
|
|
activeDocuments: data.documents_count || 0,
|
|
openDSR: data.open_dsr_count || 0,
|
|
registeredUsers: data.users_count || 0,
|
|
totalConsents: data.consents_count || 0,
|
|
gpuInstances: 0,
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.log('Stats not available')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
loadStats()
|
|
}, [])
|
|
|
|
const statCards = [
|
|
{ label: 'Aktive Dokumente', value: stats.activeDocuments, color: 'text-green-600' },
|
|
{ label: 'Offene DSR', value: stats.openDSR, color: stats.openDSR > 0 ? 'text-orange-600' : 'text-slate-600' },
|
|
{ label: 'Registrierte Nutzer', value: stats.registeredUsers, color: 'text-blue-600' },
|
|
{ label: 'Zustimmungen', value: stats.totalConsents, color: 'text-purple-600' },
|
|
{ label: 'GPU Instanzen', value: stats.gpuInstances, color: 'text-pink-600' },
|
|
]
|
|
|
|
const visibleCategories = currentRole
|
|
? navigation.filter(cat => isCategoryVisibleForRole(cat.id, currentRole))
|
|
: navigation
|
|
|
|
return (
|
|
<div>
|
|
{/* Stats Grid */}
|
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 mb-8">
|
|
{statCards.map((stat) => (
|
|
<div key={stat.label} className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
|
|
<div className={`text-3xl font-bold ${stat.color}`}>
|
|
{loading ? '-' : stat.value}
|
|
</div>
|
|
<div className="text-sm text-slate-500 mt-1">{stat.label}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Categories */}
|
|
<h2 className="text-lg font-semibold text-slate-900 mb-4">Bereiche</h2>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-8">
|
|
{visibleCategories.map((category) => (
|
|
<CategoryCard key={category.id} category={category} />
|
|
))}
|
|
</div>
|
|
|
|
{/* Quick Links */}
|
|
<h2 className="text-lg font-semibold text-slate-900 mb-4">Schnellzugriff</h2>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
|
{metaModules.filter(m => m.id !== 'dashboard').map((module) => (
|
|
<Link
|
|
key={module.id}
|
|
href={module.href}
|
|
className="flex items-center gap-3 p-4 bg-white rounded-xl border border-slate-200 hover:border-primary-300 hover:shadow-md transition-all"
|
|
>
|
|
<div className="w-10 h-10 bg-slate-100 rounded-lg flex items-center justify-center">
|
|
{module.id === 'onboarding' && '📖'}
|
|
{module.id === 'backlog' && '📋'}
|
|
{module.id === 'rbac' && '👥'}
|
|
</div>
|
|
<div>
|
|
<h3 className="font-medium text-slate-900">{module.name}</h3>
|
|
<p className="text-sm text-slate-500">{module.description}</p>
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
|
|
{/* Infrastructure & System Status */}
|
|
<h2 className="text-lg font-semibold text-slate-900 mb-4">Infrastruktur</h2>
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
|
{/* Night Mode Widget */}
|
|
<NightModeWidget />
|
|
|
|
{/* System Status */}
|
|
<ServiceStatus />
|
|
</div>
|
|
|
|
{/* Recent Activity */}
|
|
<h2 className="text-lg font-semibold text-slate-900 mb-4">Aktivitaet</h2>
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* Recent DSR */}
|
|
<div className="bg-white rounded-xl border border-slate-200 shadow-sm">
|
|
<div className="px-4 py-3 border-b border-slate-200 flex items-center justify-between">
|
|
<h3 className="font-semibold text-slate-900">Neueste Datenschutzanfragen</h3>
|
|
<Link href="/sdk/dsr" className="text-sm text-primary-600 hover:text-primary-700">
|
|
Alle anzeigen
|
|
</Link>
|
|
</div>
|
|
<div className="p-4">
|
|
<p className="text-sm text-slate-500 text-center py-4">
|
|
Keine offenen Anfragen
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Info Box */}
|
|
<div className="mt-8">
|
|
<InfoNote title="Admin v2 - Neues Frontend">
|
|
<p>
|
|
Dieses neue Admin-Frontend bietet eine verbesserte Navigation mit Kategorien und Rollen-basiertem Zugriff.
|
|
Das alte Admin-Frontend ist weiterhin unter Port 3000 verfuegbar.
|
|
</p>
|
|
</InfoNote>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|