refactor: Admin-Layout komplett entfernt — SDK als einziges Layout
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 31s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 19s
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 31s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 19s
Kaputtes (admin) Layout geloescht (Role-Selection, 404-Sidebar, localhost-Dashboard). SDK-Flow nach /sdk/sdk-flow verschoben. Route-Gruppe (sdk) aufgeloest. Root-Seite redirected auf /sdk. ~25 ungenutzte Dateien/Verzeichnisse entfernt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
15
admin-compliance/app/sdk/tom/layout.tsx
Normal file
15
admin-compliance/app/sdk/tom/layout.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
'use client'
|
||||
|
||||
import { TOMGeneratorProvider } from '@/lib/sdk/tom-generator/context'
|
||||
import { useSDK } from '@/lib/sdk'
|
||||
|
||||
export default function TOMLayout({ children }: { children: React.ReactNode }) {
|
||||
const { state } = useSDK()
|
||||
const tenantId = state?.tenantId || 'default'
|
||||
|
||||
return (
|
||||
<TOMGeneratorProvider tenantId={tenantId}>
|
||||
{children}
|
||||
</TOMGeneratorProvider>
|
||||
)
|
||||
}
|
||||
356
admin-compliance/app/sdk/tom/page.tsx
Normal file
356
admin-compliance/app/sdk/tom/page.tsx
Normal file
@@ -0,0 +1,356 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useCallback, useMemo } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useSDK } from '@/lib/sdk'
|
||||
import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader'
|
||||
import { useTOMGenerator } from '@/lib/sdk/tom-generator/context'
|
||||
import { DerivedTOM } from '@/lib/sdk/tom-generator/types'
|
||||
import { TOMOverviewTab, TOMEditorTab, TOMGapExportTab } from '@/components/sdk/tom-dashboard'
|
||||
|
||||
// =============================================================================
|
||||
// TYPES
|
||||
// =============================================================================
|
||||
|
||||
type Tab = 'uebersicht' | 'editor' | 'generator' | 'gap-export'
|
||||
|
||||
interface TabDefinition {
|
||||
key: Tab
|
||||
label: string
|
||||
}
|
||||
|
||||
const TABS: TabDefinition[] = [
|
||||
{ key: 'uebersicht', label: 'Uebersicht' },
|
||||
{ key: 'editor', label: 'Detail-Editor' },
|
||||
{ key: 'generator', label: 'Generator' },
|
||||
{ key: 'gap-export', label: 'Gap-Analyse & Export' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// PAGE COMPONENT
|
||||
// =============================================================================
|
||||
|
||||
export default function TOMPage() {
|
||||
const router = useRouter()
|
||||
const sdk = useSDK()
|
||||
const { state, dispatch, bulkUpdateTOMs, runGapAnalysis } = useTOMGenerator()
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Local state
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const [tab, setTab] = useState<Tab>('uebersicht')
|
||||
const [selectedTOMId, setSelectedTOMId] = useState<string | null>(null)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Computed / memoised values
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const tomCount = useMemo(() => {
|
||||
if (!state?.derivedTOMs) return 0
|
||||
return Array.isArray(state.derivedTOMs)
|
||||
? state.derivedTOMs.length
|
||||
: Object.keys(state.derivedTOMs).length
|
||||
}, [state?.derivedTOMs])
|
||||
|
||||
const lastModifiedFormatted = useMemo(() => {
|
||||
if (!state?.metadata?.lastModified) return null
|
||||
try {
|
||||
const date = new Date(state.metadata.lastModified)
|
||||
return date.toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}, [state?.metadata?.lastModified])
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const handleSelectTOM = useCallback((tomId: string) => {
|
||||
setSelectedTOMId(tomId)
|
||||
setTab('editor')
|
||||
}, [])
|
||||
|
||||
const handleUpdateTOM = useCallback(
|
||||
(tomId: string, updates: Partial<DerivedTOM>) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_DERIVED_TOM',
|
||||
payload: { id: tomId, data: updates },
|
||||
})
|
||||
},
|
||||
[dispatch],
|
||||
)
|
||||
|
||||
const handleStartGenerator = useCallback(() => {
|
||||
router.push('/sdk/tom-generator')
|
||||
}, [router])
|
||||
|
||||
const handleBackToOverview = useCallback(() => {
|
||||
setSelectedTOMId(null)
|
||||
setTab('uebersicht')
|
||||
}, [])
|
||||
|
||||
const handleRunGapAnalysis = useCallback(() => {
|
||||
if (typeof runGapAnalysis === 'function') {
|
||||
runGapAnalysis()
|
||||
}
|
||||
}, [runGapAnalysis])
|
||||
|
||||
const handleTabChange = useCallback((newTab: Tab) => {
|
||||
setTab(newTab)
|
||||
}, [])
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Render helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const renderTabBar = () => (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{TABS.map((t) => {
|
||||
const isActive = tab === t.key
|
||||
return (
|
||||
<button
|
||||
key={t.key}
|
||||
onClick={() => handleTabChange(t.key)}
|
||||
className={`
|
||||
rounded-lg px-4 py-2 text-sm font-medium transition-colors
|
||||
${
|
||||
isActive
|
||||
? 'bg-purple-600 text-white shadow-sm'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{t.label}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tab 1 – Uebersicht
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const renderUebersicht = () => (
|
||||
<TOMOverviewTab
|
||||
state={state}
|
||||
onSelectTOM={handleSelectTOM}
|
||||
onStartGenerator={handleStartGenerator}
|
||||
/>
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tab 2 – Detail-Editor
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const renderEditor = () => (
|
||||
<TOMEditorTab
|
||||
state={state}
|
||||
selectedTOMId={selectedTOMId}
|
||||
onUpdateTOM={handleUpdateTOM}
|
||||
onBack={handleBackToOverview}
|
||||
/>
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tab 3 – Generator
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const renderGenerator = () => (
|
||||
<div className="space-y-6">
|
||||
{/* Info card */}
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<div className="flex items-start gap-4">
|
||||
{/* Icon */}
|
||||
<div className="flex-shrink-0 w-12 h-12 rounded-lg bg-purple-100 flex items-center justify-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-6 w-6 text-purple-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
TOM Generator – 6-Schritte-Assistent
|
||||
</h3>
|
||||
<p className="text-gray-600 leading-relaxed mb-4">
|
||||
Der TOM Generator fuehrt Sie in 6 Schritten durch die systematische
|
||||
Ableitung Ihrer technischen und organisatorischen Massnahmen. Sie
|
||||
beantworten gezielte Fragen zu Ihrem Unternehmen, Ihrer
|
||||
IT-Infrastruktur und Ihren Verarbeitungstaetigkeiten. Daraus werden
|
||||
passende TOMs automatisch abgeleitet und priorisiert.
|
||||
</p>
|
||||
|
||||
<div className="bg-purple-50 rounded-lg p-4 mb-4">
|
||||
<h4 className="text-sm font-semibold text-purple-800 mb-2">
|
||||
Die 6 Schritte im Ueberblick:
|
||||
</h4>
|
||||
<ol className="list-decimal list-inside text-sm text-purple-700 space-y-1">
|
||||
<li>Unternehmenskontext erfassen</li>
|
||||
<li>IT-Infrastruktur beschreiben</li>
|
||||
<li>Verarbeitungstaetigkeiten zuordnen</li>
|
||||
<li>Risikobewertung durchfuehren</li>
|
||||
<li>TOM-Ableitung und Priorisierung</li>
|
||||
<li>Ergebnis pruefen und uebernehmen</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleStartGenerator}
|
||||
className="bg-purple-600 text-white hover:bg-purple-700 rounded-lg px-6 py-3 text-sm font-medium transition-colors shadow-sm"
|
||||
>
|
||||
TOM Generator starten
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick stats – only rendered when derivedTOMs exist */}
|
||||
{tomCount > 0 && (
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-4">
|
||||
Aktueller Stand
|
||||
</h3>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{/* TOM count */}
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<p className="text-sm text-gray-500 mb-1">Abgeleitete TOMs</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{tomCount}</p>
|
||||
</div>
|
||||
|
||||
{/* Last generated date */}
|
||||
{lastModifiedFormatted && (
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<p className="text-sm text-gray-500 mb-1">Zuletzt generiert</p>
|
||||
<p className="text-lg font-semibold text-gray-900">
|
||||
{lastModifiedFormatted}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Status */}
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<p className="text-sm text-gray-500 mb-1">Status</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-2.5 h-2.5 rounded-full bg-green-500" />
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
TOMs vorhanden
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 pt-4 border-t border-gray-100">
|
||||
<p className="text-xs text-gray-400">
|
||||
Sie koennen den Generator jederzeit erneut ausfuehren, um Ihre
|
||||
TOMs zu aktualisieren oder zu erweitern.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Empty state when no TOMs exist yet */}
|
||||
{tomCount === 0 && (
|
||||
<div className="bg-white rounded-xl border border-dashed border-gray-300 p-8 text-center">
|
||||
<div className="mx-auto w-16 h-16 rounded-full bg-gray-100 flex items-center justify-center mb-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-8 w-8 text-gray-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h4 className="text-base font-medium text-gray-700 mb-1">
|
||||
Noch keine TOMs vorhanden
|
||||
</h4>
|
||||
<p className="text-sm text-gray-500 mb-4">
|
||||
Starten Sie den Generator, um Ihre ersten technischen und
|
||||
organisatorischen Massnahmen abzuleiten.
|
||||
</p>
|
||||
<button
|
||||
onClick={handleStartGenerator}
|
||||
className="bg-purple-600 text-white hover:bg-purple-700 rounded-lg px-4 py-2 text-sm font-medium transition-colors"
|
||||
>
|
||||
Jetzt starten
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tab 4 – Gap-Analyse & Export
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const renderGapExport = () => (
|
||||
<TOMGapExportTab
|
||||
state={state}
|
||||
onRunGapAnalysis={handleRunGapAnalysis}
|
||||
/>
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tab content router
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const renderActiveTab = () => {
|
||||
switch (tab) {
|
||||
case 'uebersicht':
|
||||
return renderUebersicht()
|
||||
case 'editor':
|
||||
return renderEditor()
|
||||
case 'generator':
|
||||
return renderGenerator()
|
||||
case 'gap-export':
|
||||
return renderGapExport()
|
||||
default:
|
||||
return renderUebersicht()
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main render
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Step header */}
|
||||
<StepHeader stepId="tom" {...STEP_EXPLANATIONS['tom']} />
|
||||
|
||||
{/* Tab bar */}
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-3">
|
||||
{renderTabBar()}
|
||||
</div>
|
||||
|
||||
{/* Active tab content */}
|
||||
<div>{renderActiveTab()}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user