This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/website/components/admin/SystemInfoSection.tsx
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
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>
2026-02-09 09:51:32 +01:00

363 lines
15 KiB
TypeScript

'use client'
/**
* SystemInfoSection Component
*
* Reusable component for displaying system documentation, architecture,
* features, and optimization roadmap in admin pages.
*
* Configurations are now in ./system-info-configs/
*/
import { useState } from 'react'
// Types are now exported from configs
export type {
Feature,
RoadmapPhase,
TechDetail,
AuditInfo,
SystemInfoConfig,
} from './system-info-configs/types'
// Import types for local use
import type { Feature, RoadmapPhase, SystemInfoConfig } from './system-info-configs/types'
// Re-export configs for backwards compatibility
export { SYSTEM_INFO_CONFIGS } from './system-info-configs'
interface SystemInfoSectionProps {
config: SystemInfoConfig
}
export default function SystemInfoSection({ config }: SystemInfoSectionProps) {
const [activeTab, setActiveTab] = useState<'overview' | 'architecture' | 'roadmap' | 'technical' | 'audit' | 'documentation'>('overview')
const [showFullDocs, setShowFullDocs] = useState(false)
const tabs = [
{ id: 'overview' as const, label: 'Uebersicht' },
{ id: 'architecture' as const, label: 'Architektur' },
{ id: 'roadmap' as const, label: 'Roadmap' },
{ id: 'technical' as const, label: 'Technisch' },
...(config.auditInfo ? [{ id: 'audit' as const, label: 'Audit' }] : []),
...(config.fullDocumentation ? [{ id: 'documentation' as const, label: 'Dokumentation' }] : []),
]
const getStatusBadge = (status: Feature['status']) => {
const styles = {
active: 'bg-green-100 text-green-800',
planned: 'bg-amber-100 text-amber-800',
disabled: 'bg-slate-100 text-slate-600',
}
const labels = {
active: 'Aktiv',
planned: 'Geplant',
disabled: 'Deaktiviert',
}
return (
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${styles[status]}`}>
{labels[status]}
</span>
)
}
const getPriorityBadge = (priority: RoadmapPhase['priority']) => {
const styles = {
high: 'bg-red-100 text-red-800',
medium: 'bg-amber-100 text-amber-800',
low: 'bg-blue-100 text-blue-800',
}
return (
<span className={`px-2 py-0.5 rounded text-xs font-semibold uppercase ${styles[priority]}`}>
{priority}
</span>
)
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex justify-between items-start">
<div>
<h2 className="text-lg font-semibold text-slate-900">{config.title}</h2>
<p className="text-sm text-slate-500">{config.description}</p>
</div>
{config.version && (
<span className="px-3 py-1 bg-green-100 text-green-800 text-xs font-medium rounded-full">
Version {config.version}
</span>
)}
</div>
{/* Privacy Notes */}
{config.privacyNotes && config.privacyNotes.length > 0 && (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<h3 className="text-sm font-semibold text-blue-800 mb-2">Datenschutz-Hinweise</h3>
<ul className="space-y-1">
{config.privacyNotes.map((note, i) => (
<li key={i} className="text-sm text-blue-700 flex items-start gap-2">
<svg className="w-4 h-4 mt-0.5 text-blue-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
{note}
</li>
))}
</ul>
</div>
)}
{/* Tab Navigation */}
<div className="border-b border-slate-200">
<nav className="flex gap-4">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`pb-3 px-1 text-sm font-medium border-b-2 transition-colors ${
activeTab === tab.id
? 'border-primary-600 text-primary-600'
: 'border-transparent text-slate-500 hover:text-slate-700'
}`}
>
{tab.label}
</button>
))}
</nav>
</div>
{/* Tab Content */}
<div className="min-h-[400px]">
{/* Overview Tab */}
{activeTab === 'overview' && (
<div className="space-y-6">
<h3 className="text-sm font-semibold text-slate-700 uppercase tracking-wide">Features</h3>
<div className="grid gap-3">
{config.features.map((feature, i) => (
<div key={i} className="bg-white rounded-lg border border-slate-200 p-4">
<div className="flex items-center justify-between mb-2">
<span className="font-medium text-slate-900">{feature.name}</span>
{getStatusBadge(feature.status)}
</div>
<p className="text-sm text-slate-500">{feature.description}</p>
</div>
))}
</div>
</div>
)}
{/* Architecture Tab */}
{activeTab === 'architecture' && (
<div className="space-y-6">
<h3 className="text-sm font-semibold text-slate-700 uppercase tracking-wide">System-Architektur</h3>
<div className="bg-white rounded-lg border border-slate-200 p-6">
<div className="flex flex-col items-center gap-3">
{config.architecture.layers.map((layer, i) => (
<div key={i} className="w-full">
<div
className="w-full p-4 rounded-lg border-2 text-center"
style={{
borderColor: layer.color || '#3b82f6',
backgroundColor: `${layer.color || '#3b82f6'}10`,
}}
>
<div
className="font-semibold text-sm mb-2"
style={{ color: layer.color || '#3b82f6' }}
>
{layer.title}
</div>
<div className="flex flex-wrap gap-2 justify-center">
{layer.components.map((comp, j) => (
<span
key={j}
className="px-3 py-1 rounded text-xs font-medium"
style={{
backgroundColor: `${layer.color || '#3b82f6'}20`,
color: layer.color || '#1e40af',
}}
>
{comp}
</span>
))}
</div>
</div>
{i < config.architecture.layers.length - 1 && (
<div className="flex justify-center py-2">
<svg className="w-6 h-6 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 14l-7 7m0 0l-7-7m7 7V3" />
</svg>
</div>
)}
</div>
))}
</div>
</div>
</div>
)}
{/* Roadmap Tab */}
{activeTab === 'roadmap' && (
<div className="space-y-6">
<h3 className="text-sm font-semibold text-slate-700 uppercase tracking-wide">Optimierungs-Roadmap</h3>
<div className="space-y-4">
{config.roadmap.map((phase, i) => (
<div key={i} className="bg-white rounded-lg border border-slate-200 p-4">
<div className="flex items-center justify-between mb-3">
<span className="font-semibold text-slate-900">{phase.phase}</span>
{getPriorityBadge(phase.priority)}
</div>
<ul className="space-y-2">
{phase.items.map((item, j) => (
<li key={j} className="flex items-start gap-2 text-sm text-slate-600">
<span className="text-slate-400 mt-1"></span>
{item}
</li>
))}
</ul>
</div>
))}
</div>
</div>
)}
{/* Technical Tab */}
{activeTab === 'technical' && (
<div className="space-y-6">
<h3 className="text-sm font-semibold text-slate-700 uppercase tracking-wide">Technische Details</h3>
<div className="bg-white rounded-lg border border-slate-200 overflow-hidden">
<table className="min-w-full divide-y divide-slate-200">
<thead className="bg-slate-50">
<tr>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Komponente</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Technologie</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Version</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Beschreibung</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{config.technicalDetails.map((detail, i) => (
<tr key={i}>
<td className="px-4 py-3 text-sm font-medium text-slate-900">{detail.component}</td>
<td className="px-4 py-3 text-sm text-slate-600 font-mono">{detail.technology}</td>
<td className="px-4 py-3 text-sm text-slate-500">{detail.version || '-'}</td>
<td className="px-4 py-3 text-sm text-slate-500">{detail.description || '-'}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* Audit Tab */}
{activeTab === 'audit' && config.auditInfo && (
<div className="space-y-6">
<h3 className="text-sm font-semibold text-slate-700 uppercase tracking-wide">Audit-relevante Informationen</h3>
<div className="space-y-4">
{config.auditInfo.map((section, i) => (
<div key={i} className="bg-white rounded-lg border border-slate-200 p-4">
<h4 className="font-semibold text-slate-900 mb-3 flex items-center gap-2">
<svg className="w-5 h-5 text-primary-600" 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>
{section.category}
</h4>
<div className="space-y-2">
{section.items.map((item, j) => (
<div key={j} className="flex items-center justify-between py-2 border-b border-slate-100 last:border-0">
<span className="text-sm text-slate-600">{item.label}</span>
<span className={`text-sm font-medium px-2 py-0.5 rounded ${
item.status === 'ok' ? 'bg-green-100 text-green-800' :
item.status === 'warning' ? 'bg-amber-100 text-amber-800' :
item.status === 'critical' ? 'bg-red-100 text-red-800' :
'text-slate-900'
}`}>
{item.value}
</span>
</div>
))}
</div>
</div>
))}
</div>
</div>
)}
{/* Documentation Tab */}
{activeTab === 'documentation' && config.fullDocumentation && (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h3 className="text-sm font-semibold text-slate-700 uppercase tracking-wide">Vollstaendige Dokumentation</h3>
<button
onClick={() => setShowFullDocs(!showFullDocs)}
className="px-3 py-1 text-sm font-medium text-primary-700 bg-primary-50 rounded-lg hover:bg-primary-100"
>
{showFullDocs ? 'Kompakt' : 'Erweitert'}
</button>
</div>
<div
className={`prose prose-sm max-w-none bg-slate-900 text-slate-100 p-6 rounded-lg overflow-auto ${
showFullDocs ? 'max-h-none' : 'max-h-[600px]'
}`}
style={{
// Custom styles for dark background
}}
dangerouslySetInnerHTML={{ __html: config.fullDocumentation }}
/>
{/* Export Button */}
<div className="flex justify-end gap-3">
<button
onClick={() => {
const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `${config.title.toLowerCase().replace(/\s+/g, '-')}-system-info.json`
a.click()
}}
className="px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50"
>
JSON Export
</button>
<button
onClick={() => {
const printWindow = window.open('', '_blank')
if (printWindow) {
printWindow.document.write(`
<html>
<head>
<title>${config.title} - Dokumentation</title>
<style>
body { font-family: system-ui, sans-serif; padding: 40px; max-width: 800px; margin: 0 auto; }
h1, h2, h3 { color: #1e293b; }
pre { background: #f1f5f9; padding: 12px; border-radius: 6px; overflow-x: auto; }
code { background: #f1f5f9; padding: 2px 6px; border-radius: 4px; }
table { border-collapse: collapse; width: 100%; margin: 16px 0; }
th, td { border: 1px solid #e2e8f0; padding: 8px 12px; text-align: left; }
th { background: #f8fafc; }
</style>
</head>
<body>
<h1>${config.title}</h1>
<p><em>${config.description}</em></p>
<hr>
${config.fullDocumentation}
</body>
</html>
`)
printWindow.document.close()
printWindow.print()
}
}}
className="px-4 py-2 text-sm font-medium text-white bg-primary-600 rounded-lg hover:bg-primary-700"
>
Drucken / PDF
</button>
</div>
</div>
)}
</div>
</div>
)
}