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>
363 lines
15 KiB
TypeScript
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>
|
|
)
|
|
}
|