fix(admin-v2): Restore complete admin-v2 application
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>
This commit is contained in:
339
admin-v2/components/common/ArchitectureView.tsx
Normal file
339
admin-v2/components/common/ArchitectureView.tsx
Normal file
@@ -0,0 +1,339 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user