A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
348 lines
14 KiB
TypeScript
348 lines
14 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* Admin Layout with Sidebar Navigation
|
|
*
|
|
* Shared layout for all admin pages with collapsible sidebar
|
|
*/
|
|
|
|
import Link from 'next/link'
|
|
import { usePathname } from 'next/navigation'
|
|
import { useState } from 'react'
|
|
|
|
interface NavItem {
|
|
name: string
|
|
href: string
|
|
icon: React.ReactNode
|
|
description?: string
|
|
}
|
|
|
|
const navigation: NavItem[] = [
|
|
{
|
|
name: 'Dashboard',
|
|
href: '/admin',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
|
</svg>
|
|
),
|
|
description: 'Übersicht & Statistiken',
|
|
},
|
|
{
|
|
name: 'GPU Infrastruktur',
|
|
href: '/admin/gpu',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
|
</svg>
|
|
),
|
|
description: 'vast.ai GPU Management',
|
|
},
|
|
{
|
|
name: 'Consent Verwaltung',
|
|
href: '/admin/consent',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
),
|
|
description: 'Rechtliche Dokumente & Versionen',
|
|
},
|
|
{
|
|
name: 'Datenschutzanfragen',
|
|
href: '/admin/dsr',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
|
</svg>
|
|
),
|
|
description: 'DSGVO Art. 15-21 Anfragen',
|
|
},
|
|
{
|
|
name: 'DSMS',
|
|
href: '/admin/dsms',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
</svg>
|
|
),
|
|
description: 'Datenschutz-Management-System',
|
|
},
|
|
{
|
|
name: 'Übersetzungen',
|
|
href: '/admin/content',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129" />
|
|
</svg>
|
|
),
|
|
description: 'Website Content & Sprachen',
|
|
},
|
|
{
|
|
name: 'Education Search',
|
|
href: '/admin/edu-search',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
|
</svg>
|
|
),
|
|
description: 'Bildungsquellen & Crawler',
|
|
},
|
|
{
|
|
name: 'Personensuche',
|
|
href: '/admin/staff-search',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
|
</svg>
|
|
),
|
|
description: 'Uni-Mitarbeiter & Publikationen',
|
|
},
|
|
{
|
|
name: 'Uni-Crawler',
|
|
href: '/admin/uni-crawler',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
),
|
|
description: 'Universitaets-Crawling Orchestrator',
|
|
},
|
|
{
|
|
name: 'LLM Vergleich',
|
|
href: '/admin/llm-compare',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
|
</svg>
|
|
),
|
|
description: 'KI-Provider Vergleich',
|
|
},
|
|
{
|
|
name: 'Daten & RAG',
|
|
href: '/admin/rag',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
|
|
</svg>
|
|
),
|
|
description: 'Training Data & RAG Management',
|
|
},
|
|
{
|
|
name: 'PCA Platform',
|
|
href: '/admin/pca-platform',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
),
|
|
description: 'Bot-Erkennung & Monetarisierung',
|
|
},
|
|
{
|
|
name: 'Production Backlog',
|
|
href: '/admin/backlog',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
|
</svg>
|
|
),
|
|
description: 'Go-Live Checkliste',
|
|
},
|
|
{
|
|
name: 'Developer Docs',
|
|
href: '/admin/docs',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
|
</svg>
|
|
),
|
|
description: 'API & Architektur Dokumentation',
|
|
},
|
|
{
|
|
name: 'Kommunikation',
|
|
href: '/admin/communication',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
|
</svg>
|
|
),
|
|
description: 'Matrix & Jitsi Monitoring',
|
|
},
|
|
{
|
|
name: 'Unified Inbox',
|
|
href: '/admin/mail',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
|
</svg>
|
|
),
|
|
description: 'E-Mail-Konten & KI-Analyse',
|
|
},
|
|
{
|
|
name: 'Security',
|
|
href: '/admin/security',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
|
</svg>
|
|
),
|
|
description: 'DevSecOps Dashboard & Scans',
|
|
},
|
|
{
|
|
name: 'SBOM',
|
|
href: '/admin/sbom',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
|
|
</svg>
|
|
),
|
|
description: 'Software Bill of Materials',
|
|
},
|
|
{
|
|
name: 'Brandbook',
|
|
href: '/admin/brandbook',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
|
|
</svg>
|
|
),
|
|
description: 'Corporate Design & Styleguide',
|
|
},
|
|
{
|
|
name: 'Unity Bridge',
|
|
href: '/admin/unity-bridge',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
</svg>
|
|
),
|
|
description: 'Unity Editor Steuerung',
|
|
},
|
|
{
|
|
name: 'Qualitaet',
|
|
href: '/admin/quality',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
),
|
|
description: 'BQAS Quality Dashboard',
|
|
},
|
|
]
|
|
|
|
interface AdminLayoutProps {
|
|
children: React.ReactNode
|
|
title?: string
|
|
description?: string
|
|
}
|
|
|
|
export default function AdminLayout({ children, title, description }: AdminLayoutProps) {
|
|
const pathname = usePathname()
|
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
|
|
|
|
const isActive = (href: string) => {
|
|
if (href === '/admin') {
|
|
return pathname === '/admin'
|
|
}
|
|
return pathname.startsWith(href)
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-50 flex">
|
|
{/* Sidebar */}
|
|
<aside
|
|
className={`${
|
|
sidebarCollapsed ? 'w-16' : 'w-64'
|
|
} bg-slate-900 text-white flex flex-col transition-all duration-300 fixed h-full z-20`}
|
|
>
|
|
{/* Logo/Header */}
|
|
<div className="h-16 flex items-center justify-between px-4 border-b border-slate-700">
|
|
{!sidebarCollapsed && (
|
|
<Link href="/admin" className="font-bold text-lg">
|
|
BreakPilot Admin
|
|
</Link>
|
|
)}
|
|
<button
|
|
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
|
|
className="p-2 rounded-lg hover:bg-slate-800 transition-colors"
|
|
title={sidebarCollapsed ? 'Sidebar erweitern' : 'Sidebar einklappen'}
|
|
>
|
|
<svg
|
|
className={`w-5 h-5 transition-transform ${sidebarCollapsed ? 'rotate-180' : ''}`}
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Navigation */}
|
|
<nav className="flex-1 py-4 overflow-y-auto">
|
|
<ul className="space-y-1 px-2">
|
|
{navigation.map((item) => (
|
|
<li key={item.href}>
|
|
<Link
|
|
href={item.href}
|
|
className={`flex items-center gap-3 px-3 py-2.5 rounded-lg transition-colors ${
|
|
isActive(item.href)
|
|
? 'bg-primary-600 text-white'
|
|
: 'text-slate-300 hover:bg-slate-800 hover:text-white'
|
|
}`}
|
|
title={sidebarCollapsed ? item.name : undefined}
|
|
>
|
|
<span className="flex-shrink-0">{item.icon}</span>
|
|
{!sidebarCollapsed && (
|
|
<span className="truncate">{item.name}</span>
|
|
)}
|
|
</Link>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</nav>
|
|
|
|
{/* Footer */}
|
|
<div className="p-4 border-t border-slate-700">
|
|
<Link
|
|
href="/"
|
|
className={`flex items-center gap-3 px-3 py-2 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 transition-colors ${
|
|
sidebarCollapsed ? 'justify-center' : ''
|
|
}`}
|
|
title="Zur Website"
|
|
>
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
</svg>
|
|
{!sidebarCollapsed && <span>Zur Website</span>}
|
|
</Link>
|
|
</div>
|
|
</aside>
|
|
|
|
{/* Main Content */}
|
|
<div className={`flex-1 ${sidebarCollapsed ? 'ml-16' : 'ml-64'} transition-all duration-300`}>
|
|
{/* Top Header */}
|
|
<header className="h-16 bg-white border-b border-slate-200 flex items-center px-6 sticky top-0 z-10">
|
|
<div className="flex-1">
|
|
{title && <h1 className="text-xl font-semibold text-slate-900">{title}</h1>}
|
|
{description && <p className="text-sm text-slate-500">{description}</p>}
|
|
</div>
|
|
|
|
{/* User/Settings Area */}
|
|
<div className="flex items-center gap-4">
|
|
<span className="text-sm text-slate-500">Developer Admin</span>
|
|
<div className="w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center text-white text-sm font-medium">
|
|
A
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Page Content */}
|
|
<main className="p-6">
|
|
{children}
|
|
</main>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|