feat(admin-v2): Major SDK/Compliance overhaul and new modules
SDK modules added/enhanced: - compliance-hub, compliance-scope, consent-management, notfallplan - audit-report, workflow, source-policy, dsms - advisory-board documentation section - TOM dashboard components, TOM generator SDM mapping - DSFA: mitigation library, risk catalog, threshold analysis, source attribution - VVT: baseline catalog, profiling engine, types - Loeschfristen: baseline catalog, compliance engine, export, profiling, types - Compliance scope: engine, profiling, golden tests, types Existing SDK pages updated: - dsfa/[id], tom, vvt, loeschfristen, advisory-board — expanded functionality - SDKSidebar, StepHeader — new navigation items and layout - SDK layout, context, types — expanded type system Other admin-v2 changes: - AI agents page, RAG pipeline DSFA integration - GridOverlay component updates - Companion feature (development + education) - Compliance advisor SOUL definition Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
15
admin-v2/app/(sdk)/sdk/tom/layout.tsx
Normal file
15
admin-v2/app/(sdk)/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>
|
||||
)
|
||||
}
|
||||
@@ -1,377 +1,356 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useCallback } from 'react'
|
||||
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 { DocumentUploadSection, type UploadedDocument } from '@/components/sdk'
|
||||
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
|
||||
// =============================================================================
|
||||
|
||||
interface TOM {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
category: 'confidentiality' | 'integrity' | 'availability' | 'resilience'
|
||||
type: 'technical' | 'organizational'
|
||||
status: 'implemented' | 'partial' | 'planned' | 'not-implemented'
|
||||
article32Reference: string
|
||||
lastReview: Date
|
||||
nextReview: Date
|
||||
responsible: string
|
||||
documentation: string | null
|
||||
type Tab = 'uebersicht' | 'editor' | 'generator' | 'gap-export'
|
||||
|
||||
interface TabDefinition {
|
||||
key: Tab
|
||||
label: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MOCK DATA
|
||||
// =============================================================================
|
||||
|
||||
const mockTOMs: TOM[] = [
|
||||
{
|
||||
id: 'tom-1',
|
||||
title: 'Zutrittskontrolle',
|
||||
description: 'Physische Zugangskontrolle zu Serverraeumen und Rechenzentren',
|
||||
category: 'confidentiality',
|
||||
type: 'technical',
|
||||
status: 'implemented',
|
||||
article32Reference: 'Art. 32 Abs. 1 lit. b',
|
||||
lastReview: new Date('2024-01-01'),
|
||||
nextReview: new Date('2024-07-01'),
|
||||
responsible: 'Facility Management',
|
||||
documentation: 'TOM-001-Zutrittskontrolle.pdf',
|
||||
},
|
||||
{
|
||||
id: 'tom-2',
|
||||
title: 'Zugangskontrolle',
|
||||
description: 'Authentifizierung und Autorisierung fuer IT-Systeme',
|
||||
category: 'confidentiality',
|
||||
type: 'technical',
|
||||
status: 'implemented',
|
||||
article32Reference: 'Art. 32 Abs. 1 lit. b',
|
||||
lastReview: new Date('2024-01-15'),
|
||||
nextReview: new Date('2024-07-15'),
|
||||
responsible: 'IT Security',
|
||||
documentation: 'TOM-002-Zugangskontrolle.pdf',
|
||||
},
|
||||
{
|
||||
id: 'tom-3',
|
||||
title: 'Verschluesselung',
|
||||
description: 'Verschluesselung von Daten bei Speicherung und Uebertragung',
|
||||
category: 'confidentiality',
|
||||
type: 'technical',
|
||||
status: 'implemented',
|
||||
article32Reference: 'Art. 32 Abs. 1 lit. a',
|
||||
lastReview: new Date('2024-01-10'),
|
||||
nextReview: new Date('2024-07-10'),
|
||||
responsible: 'IT Security',
|
||||
documentation: 'TOM-003-Verschluesselung.pdf',
|
||||
},
|
||||
{
|
||||
id: 'tom-4',
|
||||
title: 'Datensicherung',
|
||||
description: 'Regelmaessige Backups und Wiederherstellungstests',
|
||||
category: 'availability',
|
||||
type: 'technical',
|
||||
status: 'implemented',
|
||||
article32Reference: 'Art. 32 Abs. 1 lit. c',
|
||||
lastReview: new Date('2023-12-01'),
|
||||
nextReview: new Date('2024-06-01'),
|
||||
responsible: 'IT Operations',
|
||||
documentation: 'TOM-004-Backup.pdf',
|
||||
},
|
||||
{
|
||||
id: 'tom-5',
|
||||
title: 'Datenschutzschulung',
|
||||
description: 'Regelmaessige Schulungen fuer alle Mitarbeiter',
|
||||
category: 'confidentiality',
|
||||
type: 'organizational',
|
||||
status: 'partial',
|
||||
article32Reference: 'Art. 32 Abs. 1 lit. b',
|
||||
lastReview: new Date('2023-11-01'),
|
||||
nextReview: new Date('2024-02-01'),
|
||||
responsible: 'HR / Datenschutz',
|
||||
documentation: null,
|
||||
},
|
||||
{
|
||||
id: 'tom-6',
|
||||
title: 'Incident Response Plan',
|
||||
description: 'Prozess zur Behandlung von Sicherheitsvorfaellen',
|
||||
category: 'resilience',
|
||||
type: 'organizational',
|
||||
status: 'planned',
|
||||
article32Reference: 'Art. 32 Abs. 1 lit. c',
|
||||
lastReview: new Date('2024-01-20'),
|
||||
nextReview: new Date('2024-04-20'),
|
||||
responsible: 'CISO',
|
||||
documentation: null,
|
||||
},
|
||||
{
|
||||
id: 'tom-7',
|
||||
title: 'Protokollierung',
|
||||
description: 'Logging aller sicherheitsrelevanten Ereignisse',
|
||||
category: 'integrity',
|
||||
type: 'technical',
|
||||
status: 'implemented',
|
||||
article32Reference: 'Art. 32 Abs. 1 lit. b',
|
||||
lastReview: new Date('2024-01-05'),
|
||||
nextReview: new Date('2024-07-05'),
|
||||
responsible: 'IT Security',
|
||||
documentation: 'TOM-007-Logging.pdf',
|
||||
},
|
||||
const TABS: TabDefinition[] = [
|
||||
{ key: 'uebersicht', label: 'Uebersicht' },
|
||||
{ key: 'editor', label: 'Detail-Editor' },
|
||||
{ key: 'generator', label: 'Generator' },
|
||||
{ key: 'gap-export', label: 'Gap-Analyse & Export' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// COMPONENTS
|
||||
// =============================================================================
|
||||
|
||||
function TOMCard({ tom }: { tom: TOM }) {
|
||||
const categoryColors = {
|
||||
confidentiality: 'bg-blue-100 text-blue-700',
|
||||
integrity: 'bg-green-100 text-green-700',
|
||||
availability: 'bg-purple-100 text-purple-700',
|
||||
resilience: 'bg-orange-100 text-orange-700',
|
||||
}
|
||||
|
||||
const categoryLabels = {
|
||||
confidentiality: 'Vertraulichkeit',
|
||||
integrity: 'Integritaet',
|
||||
availability: 'Verfuegbarkeit',
|
||||
resilience: 'Belastbarkeit',
|
||||
}
|
||||
|
||||
const statusColors = {
|
||||
implemented: 'bg-green-100 text-green-700 border-green-200',
|
||||
partial: 'bg-yellow-100 text-yellow-700 border-yellow-200',
|
||||
planned: 'bg-blue-100 text-blue-700 border-blue-200',
|
||||
'not-implemented': 'bg-red-100 text-red-700 border-red-200',
|
||||
}
|
||||
|
||||
const statusLabels = {
|
||||
implemented: 'Implementiert',
|
||||
partial: 'Teilweise',
|
||||
planned: 'Geplant',
|
||||
'not-implemented': 'Nicht implementiert',
|
||||
}
|
||||
|
||||
const isReviewDue = tom.nextReview <= new Date()
|
||||
|
||||
return (
|
||||
<div className={`bg-white rounded-xl border-2 p-6 ${
|
||||
isReviewDue ? 'border-orange-200' :
|
||||
tom.status === 'implemented' ? 'border-green-200' : 'border-gray-200'
|
||||
}`}>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className={`px-2 py-1 text-xs rounded-full ${categoryColors[tom.category]}`}>
|
||||
{categoryLabels[tom.category]}
|
||||
</span>
|
||||
<span className={`px-2 py-1 text-xs rounded-full ${
|
||||
tom.type === 'technical' ? 'bg-gray-100 text-gray-700' : 'bg-purple-50 text-purple-700'
|
||||
}`}>
|
||||
{tom.type === 'technical' ? 'Technisch' : 'Organisatorisch'}
|
||||
</span>
|
||||
<span className={`px-2 py-1 text-xs rounded-full ${statusColors[tom.status]}`}>
|
||||
{statusLabels[tom.status]}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">{tom.title}</h3>
|
||||
<p className="text-sm text-gray-500 mt-1">{tom.description}</p>
|
||||
<p className="text-xs text-gray-400 mt-2">Rechtsgrundlage: {tom.article32Reference}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-500">Verantwortlich: </span>
|
||||
<span className="font-medium text-gray-700">{tom.responsible}</span>
|
||||
</div>
|
||||
<div className={isReviewDue ? 'text-orange-600' : ''}>
|
||||
<span className="text-gray-500">Naechste Pruefung: </span>
|
||||
<span className="font-medium">
|
||||
{tom.nextReview.toLocaleDateString('de-DE')}
|
||||
{isReviewDue && ' (faellig)'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 pt-4 border-t border-gray-100 flex items-center justify-between">
|
||||
{tom.documentation ? (
|
||||
<span className="text-sm text-green-600 flex items-center gap-1">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
Dokumentiert
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-sm text-gray-400">Keine Dokumentation</span>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<button className="px-3 py-1 text-sm text-purple-600 hover:bg-purple-50 rounded-lg transition-colors">
|
||||
Bearbeiten
|
||||
</button>
|
||||
<button className="px-3 py-1 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition-colors">
|
||||
Pruefung starten
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MAIN PAGE
|
||||
// PAGE COMPONENT
|
||||
// =============================================================================
|
||||
|
||||
export default function TOMPage() {
|
||||
const router = useRouter()
|
||||
const { state } = useSDK()
|
||||
const [toms] = useState<TOM[]>(mockTOMs)
|
||||
const [filter, setFilter] = useState<string>('all')
|
||||
const sdk = useSDK()
|
||||
const { state, dispatch, bulkUpdateTOMs, runGapAnalysis } = useTOMGenerator()
|
||||
|
||||
// Handle uploaded document - import into SDK state
|
||||
const handleDocumentProcessed = useCallback((doc: UploadedDocument) => {
|
||||
console.log('[TOM Page] Document processed:', doc)
|
||||
// In production: Parse document content and add to state.toms
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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')
|
||||
}, [])
|
||||
|
||||
// Open document in workflow editor
|
||||
const handleOpenInEditor = useCallback((doc: UploadedDocument) => {
|
||||
router.push(`/compliance/workflow?documentType=tom&documentId=${doc.id}&mode=change`)
|
||||
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 filteredTOMs = filter === 'all'
|
||||
? toms
|
||||
: toms.filter(t => t.category === filter || t.type === filter || t.status === filter)
|
||||
const handleBackToOverview = useCallback(() => {
|
||||
setSelectedTOMId(null)
|
||||
setTab('uebersicht')
|
||||
}, [])
|
||||
|
||||
const implementedCount = toms.filter(t => t.status === 'implemented').length
|
||||
const technicalCount = toms.filter(t => t.type === 'technical').length
|
||||
const organizationalCount = toms.filter(t => t.type === 'organizational').length
|
||||
const reviewDueCount = toms.filter(t => t.nextReview <= new Date()).length
|
||||
const handleRunGapAnalysis = useCallback(() => {
|
||||
if (typeof runGapAnalysis === 'function') {
|
||||
runGapAnalysis()
|
||||
}
|
||||
}, [runGapAnalysis])
|
||||
|
||||
const stepInfo = STEP_EXPLANATIONS['tom']
|
||||
const handleTabChange = useCallback((newTab: Tab) => {
|
||||
setTab(newTab)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Step Header */}
|
||||
<StepHeader
|
||||
stepId="tom"
|
||||
title={stepInfo.title}
|
||||
description={stepInfo.description}
|
||||
explanation={stepInfo.explanation}
|
||||
tips={stepInfo.tips}
|
||||
>
|
||||
<button className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors">
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
TOM hinzufuegen
|
||||
</button>
|
||||
</StepHeader>
|
||||
// ---------------------------------------------------------------------------
|
||||
// Render helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
{/* Document Upload Section */}
|
||||
<DocumentUploadSection
|
||||
documentType="tom"
|
||||
onDocumentProcessed={handleDocumentProcessed}
|
||||
onOpenInEditor={handleOpenInEditor}
|
||||
/>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<div className="text-sm text-gray-500">Gesamt</div>
|
||||
<div className="text-3xl font-bold text-gray-900">{toms.length}</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-green-200 p-6">
|
||||
<div className="text-sm text-green-600">Implementiert</div>
|
||||
<div className="text-3xl font-bold text-green-600">{implementedCount}</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-blue-200 p-6">
|
||||
<div className="text-sm text-blue-600">Technisch / Organisatorisch</div>
|
||||
<div className="text-3xl font-bold text-blue-600">{technicalCount} / {organizationalCount}</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-orange-200 p-6">
|
||||
<div className="text-sm text-orange-600">Pruefung faellig</div>
|
||||
<div className="text-3xl font-bold text-orange-600">{reviewDueCount}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Article 32 Overview */}
|
||||
<div className="bg-gradient-to-r from-blue-50 to-purple-50 rounded-xl border border-blue-200 p-6">
|
||||
<h3 className="font-semibold text-gray-900 mb-4">Art. 32 DSGVO - Schutzziele</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="bg-white rounded-lg p-4 text-center">
|
||||
<div className="text-2xl font-bold text-blue-600">
|
||||
{toms.filter(t => t.category === 'confidentiality').length}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">Vertraulichkeit</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-4 text-center">
|
||||
<div className="text-2xl font-bold text-green-600">
|
||||
{toms.filter(t => t.category === 'integrity').length}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">Integritaet</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-4 text-center">
|
||||
<div className="text-2xl font-bold text-purple-600">
|
||||
{toms.filter(t => t.category === 'availability').length}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">Verfuegbarkeit</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-4 text-center">
|
||||
<div className="text-2xl font-bold text-orange-600">
|
||||
{toms.filter(t => t.category === 'resilience').length}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">Belastbarkeit</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filter */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-sm text-gray-500">Filter:</span>
|
||||
{['all', 'confidentiality', 'integrity', 'availability', 'resilience', 'technical', 'organizational', 'implemented', 'partial'].map(f => (
|
||||
const renderTabBar = () => (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{TABS.map((t) => {
|
||||
const isActive = tab === t.key
|
||||
return (
|
||||
<button
|
||||
key={f}
|
||||
onClick={() => setFilter(f)}
|
||||
className={`px-3 py-1 text-sm rounded-full transition-colors ${
|
||||
filter === f
|
||||
? 'bg-purple-600 text-white'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||
}`}
|
||||
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'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{f === 'all' ? 'Alle' :
|
||||
f === 'confidentiality' ? 'Vertraulichkeit' :
|
||||
f === 'integrity' ? 'Integritaet' :
|
||||
f === 'availability' ? 'Verfuegbarkeit' :
|
||||
f === 'resilience' ? 'Belastbarkeit' :
|
||||
f === 'technical' ? 'Technisch' :
|
||||
f === 'organizational' ? 'Organisatorisch' :
|
||||
f === 'implemented' ? 'Implementiert' : 'Teilweise'}
|
||||
{t.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
|
||||
{/* TOM List */}
|
||||
<div className="space-y-4">
|
||||
{filteredTOMs.map(tom => (
|
||||
<TOMCard key={tom.id} tom={tom} />
|
||||
))}
|
||||
</div>
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tab 1 – Uebersicht
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
{filteredTOMs.length === 0 && (
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-12 text-center">
|
||||
<div className="w-16 h-16 mx-auto bg-gray-100 rounded-full flex items-center justify-center mb-4">
|
||||
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
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>
|
||||
<h3 className="text-lg font-semibold text-gray-900">Keine TOMs gefunden</h3>
|
||||
<p className="mt-2 text-gray-500">Passen Sie den Filter an oder fuegen Sie neue TOMs hinzu.</p>
|
||||
|
||||
<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