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>
357 lines
12 KiB
TypeScript
357 lines
12 KiB
TypeScript
'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>
|
||
)
|
||
}
|