This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BreakPilot Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +01:00

357 lines
12 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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 &ndash; 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>
)
}