The admin-v2 application was incomplete in the repository. This commit restores all missing components: - Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education, infrastructure, communication, development, onboarding, rbac - SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen, vendor-compliance, tom-generator, dsr, and more - Developer portal (25 pages): API docs, SDK guides, frameworks - All components, lib files, hooks, and types - Updated package.json with all dependencies The issue was caused by incomplete initial repository state - the full admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2 but was never fully synced to the main admin-v2 directory. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
340 lines
14 KiB
TypeScript
340 lines
14 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* ArchitectureView - Shows backend modules and their connection status
|
|
*
|
|
* This component helps track which backend modules are connected to the frontend
|
|
* during migration and ensures no modules get lost.
|
|
*/
|
|
|
|
import { useState } from 'react'
|
|
import Link from 'next/link'
|
|
import {
|
|
MODULE_REGISTRY,
|
|
getModulesByCategory,
|
|
getModuleStats,
|
|
getCategoryStats,
|
|
type BackendModule
|
|
} from '@/lib/module-registry'
|
|
|
|
interface ArchitectureViewProps {
|
|
category?: BackendModule['category']
|
|
showAllCategories?: boolean
|
|
}
|
|
|
|
const STATUS_CONFIG = {
|
|
connected: {
|
|
label: 'Verbunden',
|
|
color: 'bg-green-100 text-green-700 border-green-200',
|
|
dot: 'bg-green-500'
|
|
},
|
|
partial: {
|
|
label: 'Teilweise',
|
|
color: 'bg-yellow-100 text-yellow-700 border-yellow-200',
|
|
dot: 'bg-yellow-500'
|
|
},
|
|
'not-connected': {
|
|
label: 'Nicht verbunden',
|
|
color: 'bg-red-100 text-red-700 border-red-200',
|
|
dot: 'bg-red-500'
|
|
},
|
|
deprecated: {
|
|
label: 'Veraltet',
|
|
color: 'bg-slate-100 text-slate-700 border-slate-200',
|
|
dot: 'bg-slate-500'
|
|
}
|
|
}
|
|
|
|
const PRIORITY_CONFIG = {
|
|
critical: { label: 'Kritisch', color: 'text-red-600' },
|
|
high: { label: 'Hoch', color: 'text-orange-600' },
|
|
medium: { label: 'Mittel', color: 'text-yellow-600' },
|
|
low: { label: 'Niedrig', color: 'text-slate-600' }
|
|
}
|
|
|
|
const CATEGORY_CONFIG: Record<BackendModule['category'], { name: string; icon: string; color: string }> = {
|
|
compliance: { name: 'DSGVO & Compliance', icon: 'shield', color: 'purple' },
|
|
ai: { name: 'KI & Automatisierung', icon: 'brain', color: 'teal' },
|
|
infrastructure: { name: 'Infrastruktur & DevOps', icon: 'server', color: 'orange' },
|
|
education: { name: 'Bildung & Schule', icon: 'graduation', color: 'blue' },
|
|
communication: { name: 'Kommunikation & Alerts', icon: 'mail', color: 'green' },
|
|
development: { name: 'Entwicklung & Produkte', icon: 'code', color: 'slate' }
|
|
}
|
|
|
|
export function ArchitectureView({ category, showAllCategories = false }: ArchitectureViewProps) {
|
|
const [expandedModule, setExpandedModule] = useState<string | null>(null)
|
|
const [filterStatus, setFilterStatus] = useState<string>('all')
|
|
|
|
const modules = category && !showAllCategories
|
|
? getModulesByCategory(category)
|
|
: MODULE_REGISTRY
|
|
|
|
const filteredModules = filterStatus === 'all'
|
|
? modules
|
|
: modules.filter(m => m.frontend.status === filterStatus)
|
|
|
|
const stats = category && !showAllCategories
|
|
? getCategoryStats(category)
|
|
: getModuleStats()
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Stats Overview */}
|
|
<div className="bg-white rounded-xl shadow-sm border p-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h2 className="text-lg font-semibold text-slate-900">
|
|
Migrations-Fortschritt
|
|
{category && !showAllCategories && (
|
|
<span className="ml-2 text-slate-500 font-normal">
|
|
- {CATEGORY_CONFIG[category].name}
|
|
</span>
|
|
)}
|
|
</h2>
|
|
<span className="text-2xl font-bold text-purple-600">
|
|
{stats.percentComplete}%
|
|
</span>
|
|
</div>
|
|
|
|
{/* Progress Bar */}
|
|
<div className="h-4 bg-slate-200 rounded-full overflow-hidden mb-4">
|
|
<div
|
|
className="h-full bg-gradient-to-r from-green-500 to-emerald-500 transition-all duration-500"
|
|
style={{ width: `${stats.percentComplete}%` }}
|
|
/>
|
|
</div>
|
|
|
|
{/* Status Counts */}
|
|
<div className="grid grid-cols-4 gap-4">
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-green-600">{stats.connected}</div>
|
|
<div className="text-sm text-slate-500">Verbunden</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-yellow-600">{stats.partial}</div>
|
|
<div className="text-sm text-slate-500">Teilweise</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-red-600">{stats.notConnected}</div>
|
|
<div className="text-sm text-slate-500">Nicht verbunden</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-slate-600">{stats.total}</div>
|
|
<div className="text-sm text-slate-500">Gesamt</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Filter */}
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-sm text-slate-500">Filter:</span>
|
|
<button
|
|
onClick={() => setFilterStatus('all')}
|
|
className={`px-3 py-1 rounded-full text-sm ${
|
|
filterStatus === 'all' ? 'bg-purple-100 text-purple-700' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'
|
|
}`}
|
|
>
|
|
Alle ({modules.length})
|
|
</button>
|
|
<button
|
|
onClick={() => setFilterStatus('connected')}
|
|
className={`px-3 py-1 rounded-full text-sm ${
|
|
filterStatus === 'connected' ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'
|
|
}`}
|
|
>
|
|
Verbunden
|
|
</button>
|
|
<button
|
|
onClick={() => setFilterStatus('partial')}
|
|
className={`px-3 py-1 rounded-full text-sm ${
|
|
filterStatus === 'partial' ? 'bg-yellow-100 text-yellow-700' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'
|
|
}`}
|
|
>
|
|
Teilweise
|
|
</button>
|
|
<button
|
|
onClick={() => setFilterStatus('not-connected')}
|
|
className={`px-3 py-1 rounded-full text-sm ${
|
|
filterStatus === 'not-connected' ? 'bg-red-100 text-red-700' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'
|
|
}`}
|
|
>
|
|
Nicht verbunden
|
|
</button>
|
|
</div>
|
|
|
|
{/* Module List */}
|
|
<div className="space-y-3">
|
|
{filteredModules.map((module) => (
|
|
<div
|
|
key={module.id}
|
|
className="bg-white rounded-xl shadow-sm border overflow-hidden"
|
|
>
|
|
{/* Module Header */}
|
|
<button
|
|
onClick={() => setExpandedModule(expandedModule === module.id ? null : module.id)}
|
|
className="w-full px-4 py-3 flex items-center justify-between hover:bg-slate-50 transition-colors"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div className={`w-2 h-2 rounded-full ${STATUS_CONFIG[module.frontend.status].dot}`} />
|
|
<div className="text-left">
|
|
<div className="font-medium text-slate-900">{module.name}</div>
|
|
<div className="text-sm text-slate-500">{module.description}</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<span className={`px-2 py-1 rounded text-xs border ${STATUS_CONFIG[module.frontend.status].color}`}>
|
|
{STATUS_CONFIG[module.frontend.status].label}
|
|
</span>
|
|
<span className={`text-xs ${PRIORITY_CONFIG[module.priority].color}`}>
|
|
{PRIORITY_CONFIG[module.priority].label}
|
|
</span>
|
|
<svg
|
|
className={`w-5 h-5 text-slate-400 transition-transform ${expandedModule === module.id ? 'rotate-180' : ''}`}
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
</svg>
|
|
</div>
|
|
</button>
|
|
|
|
{/* Module Details */}
|
|
{expandedModule === module.id && (
|
|
<div className="px-4 py-4 border-t border-slate-200 bg-slate-50">
|
|
<div className="grid grid-cols-2 gap-6">
|
|
{/* Backend Info */}
|
|
<div>
|
|
<h4 className="font-medium text-slate-900 mb-2">Backend</h4>
|
|
<div className="space-y-2 text-sm">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-slate-500">Service:</span>
|
|
<code className="px-2 py-0.5 bg-slate-200 rounded text-slate-700">
|
|
{module.backend.service}
|
|
</code>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-slate-500">Port:</span>
|
|
<code className="px-2 py-0.5 bg-slate-200 rounded text-slate-700">
|
|
{module.backend.port}
|
|
</code>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-slate-500">Base Path:</span>
|
|
<code className="px-2 py-0.5 bg-slate-200 rounded text-slate-700">
|
|
{module.backend.basePath}
|
|
</code>
|
|
</div>
|
|
</div>
|
|
|
|
<h5 className="font-medium text-slate-700 mt-4 mb-2">Endpoints</h5>
|
|
<div className="space-y-1 max-h-48 overflow-y-auto">
|
|
{module.backend.endpoints.map((ep, idx) => (
|
|
<div key={idx} className="flex items-center gap-2 text-sm">
|
|
<span className={`px-1.5 py-0.5 rounded text-xs font-mono ${
|
|
ep.method === 'GET' ? 'bg-blue-100 text-blue-700' :
|
|
ep.method === 'POST' ? 'bg-green-100 text-green-700' :
|
|
ep.method === 'PUT' ? 'bg-yellow-100 text-yellow-700' :
|
|
'bg-red-100 text-red-700'
|
|
}`}>
|
|
{ep.method}
|
|
</span>
|
|
<code className="text-slate-600 text-xs">{ep.path}</code>
|
|
<span className="text-slate-400 text-xs">- {ep.description}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Frontend Info */}
|
|
<div>
|
|
<h4 className="font-medium text-slate-900 mb-2">Frontend</h4>
|
|
<div className="space-y-3 text-sm">
|
|
<div>
|
|
<span className="text-slate-500 block mb-1">Admin v2 Seite:</span>
|
|
{module.frontend.adminV2Page ? (
|
|
<Link
|
|
href={module.frontend.adminV2Page}
|
|
className="text-purple-600 hover:text-purple-800 hover:underline"
|
|
>
|
|
{module.frontend.adminV2Page}
|
|
</Link>
|
|
) : (
|
|
<span className="text-red-500 italic">Noch nicht angelegt</span>
|
|
)}
|
|
</div>
|
|
<div>
|
|
<span className="text-slate-500 block mb-1">Altes Admin (Referenz):</span>
|
|
{module.frontend.oldAdminPage ? (
|
|
<code className="px-2 py-0.5 bg-slate-200 rounded text-slate-700">
|
|
{module.frontend.oldAdminPage}
|
|
</code>
|
|
) : (
|
|
<span className="text-slate-400 italic">-</span>
|
|
)}
|
|
</div>
|
|
{module.notes && (
|
|
<div className="mt-3 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
|
<span className="text-yellow-700 text-sm">{module.notes}</span>
|
|
</div>
|
|
)}
|
|
{module.dependencies && module.dependencies.length > 0 && (
|
|
<div>
|
|
<span className="text-slate-500 block mb-1">Abhaengigkeiten:</span>
|
|
<div className="flex flex-wrap gap-1">
|
|
{module.dependencies.map((dep) => (
|
|
<span key={dep} className="px-2 py-0.5 bg-purple-100 text-purple-700 rounded text-xs">
|
|
{dep}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Category Summary (if showing all) */}
|
|
{showAllCategories && (
|
|
<div className="bg-white rounded-xl shadow-sm border p-6 mt-8">
|
|
<h3 className="text-lg font-semibold text-slate-900 mb-4">Kategorie-Uebersicht</h3>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{(Object.keys(CATEGORY_CONFIG) as BackendModule['category'][]).map((cat) => {
|
|
const catStats = getCategoryStats(cat)
|
|
return (
|
|
<div key={cat} className="p-4 border border-slate-200 rounded-lg">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<span className="font-medium text-slate-900">{CATEGORY_CONFIG[cat].name}</span>
|
|
<span className={`text-sm ${catStats.percentComplete === 100 ? 'text-green-600' : 'text-slate-500'}`}>
|
|
{catStats.percentComplete}%
|
|
</span>
|
|
</div>
|
|
<div className="h-2 bg-slate-200 rounded-full overflow-hidden">
|
|
<div
|
|
className={`h-full transition-all duration-500 ${
|
|
catStats.percentComplete === 100 ? 'bg-green-500' :
|
|
catStats.percentComplete > 50 ? 'bg-yellow-500' : 'bg-red-500'
|
|
}`}
|
|
style={{ width: `${catStats.percentComplete}%` }}
|
|
/>
|
|
</div>
|
|
<div className="flex justify-between mt-2 text-xs text-slate-500">
|
|
<span>{catStats.connected}/{catStats.total} verbunden</span>
|
|
{catStats.notConnected > 0 && (
|
|
<span className="text-red-500">{catStats.notConnected} offen</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|