'use client'
// =============================================================================
// Step 6: Review & Export
// Summary, derived TOMs table, gap analysis, and export
// =============================================================================
import React, { useEffect, useState } from 'react'
import { useTOMGenerator } from '@/lib/sdk/tom-generator'
import { CONTROL_CATEGORIES } from '@/lib/sdk/tom-generator/types'
import { getControlById } from '@/lib/sdk/tom-generator/controls/loader'
import { generateDOCXBlob } from '@/lib/sdk/tom-generator/export/docx'
import { generatePDFBlob } from '@/lib/sdk/tom-generator/export/pdf'
import { generateZIPBlob } from '@/lib/sdk/tom-generator/export/zip'
// =============================================================================
// SUMMARY CARD COMPONENT
// =============================================================================
interface SummaryCardProps {
title: string
value: string | number
description?: string
variant?: 'default' | 'success' | 'warning' | 'danger'
}
function SummaryCard({ title, value, description, variant = 'default' }: SummaryCardProps) {
const colors = {
default: 'bg-gray-100 text-gray-800',
success: 'bg-green-100 text-green-800',
warning: 'bg-yellow-100 text-yellow-800',
danger: 'bg-red-100 text-red-800',
}
return (
{value}
{title}
{description &&
{description}
}
)
}
// =============================================================================
// TOMS TABLE COMPONENT
// =============================================================================
function TOMsTable() {
const { state } = useTOMGenerator()
const [selectedCategory, setSelectedCategory] = useState('all')
const [selectedApplicability, setSelectedApplicability] = useState('all')
const filteredTOMs = state.derivedTOMs.filter((tom) => {
const control = getControlById(tom.controlId)
const categoryMatch = selectedCategory === 'all' || control?.category === selectedCategory
const applicabilityMatch = selectedApplicability === 'all' || tom.applicability === selectedApplicability
return categoryMatch && applicabilityMatch
})
const getStatusBadge = (status: string) => {
const badges: Record = {
IMPLEMENTED: { bg: 'bg-green-100', text: 'text-green-800' },
PARTIAL: { bg: 'bg-yellow-100', text: 'text-yellow-800' },
NOT_IMPLEMENTED: { bg: 'bg-red-100', text: 'text-red-800' },
}
const labels: Record = {
IMPLEMENTED: 'Umgesetzt',
PARTIAL: 'Teilweise',
NOT_IMPLEMENTED: 'Offen',
}
const config = badges[status] || badges.NOT_IMPLEMENTED
return (
{labels[status] || status}
)
}
const getApplicabilityBadge = (applicability: string) => {
const badges: Record = {
REQUIRED: { bg: 'bg-red-100', text: 'text-red-800' },
RECOMMENDED: { bg: 'bg-blue-100', text: 'text-blue-800' },
OPTIONAL: { bg: 'bg-gray-100', text: 'text-gray-800' },
NOT_APPLICABLE: { bg: 'bg-gray-50', text: 'text-gray-500' },
}
const labels: Record = {
REQUIRED: 'Erforderlich',
RECOMMENDED: 'Empfohlen',
OPTIONAL: 'Optional',
NOT_APPLICABLE: 'N/A',
}
const config = badges[applicability] || badges.OPTIONAL
return (
{labels[applicability] || applicability}
)
}
return (
{/* Filters */}
{/* Table */}
|
ID
|
Maßnahme
|
Anwendbarkeit
|
Status
|
Nachweise
|
{filteredTOMs.map((tom) => (
|
{tom.controlId}
|
{tom.name}
{tom.applicabilityReason}
|
{getApplicabilityBadge(tom.applicability)}
|
{getStatusBadge(tom.implementationStatus)}
|
{tom.linkedEvidence.length > 0 ? (
{tom.linkedEvidence.length} Dok.
) : tom.evidenceGaps.length > 0 ? (
{tom.evidenceGaps.length} fehlen
) : (
'-'
)}
|
))}
{filteredTOMs.length} von {state.derivedTOMs.length} Maßnahmen angezeigt
)
}
// =============================================================================
// GAP ANALYSIS PANEL
// =============================================================================
function GapAnalysisPanel() {
const { state, runGapAnalysis } = useTOMGenerator()
useEffect(() => {
if (!state.gapAnalysis && state.derivedTOMs.length > 0) {
runGapAnalysis()
}
}, [state.derivedTOMs, state.gapAnalysis, runGapAnalysis])
if (!state.gapAnalysis) {
return (
Lückenanalyse wird durchgeführt...
)
}
const { overallScore, missingControls, partialControls, recommendations } = state.gapAnalysis
const getScoreColor = (score: number) => {
if (score >= 80) return 'text-green-600'
if (score >= 50) return 'text-yellow-600'
return 'text-red-600'
}
return (
{/* Score */}
{overallScore}%
Compliance Score
{/* Progress bar */}
= 80 ? 'bg-green-500' : overallScore >= 50 ? 'bg-yellow-500' : 'bg-red-500'
}`}
style={{ width: `${overallScore}%` }}
/>
{/* Missing Controls */}
{missingControls.length > 0 && (
Fehlende Maßnahmen ({missingControls.length})
{missingControls.map((mc) => {
const control = getControlById(mc.controlId)
return (
{mc.controlId}
{control?.name.de}
{mc.priority}
)
})}
)}
{/* Partial Controls */}
{partialControls.length > 0 && (
Teilweise umgesetzt ({partialControls.length})
{partialControls.map((pc) => {
const control = getControlById(pc.controlId)
return (
{pc.controlId}
{control?.name.de}
Fehlend: {pc.missingAspects.join(', ')}
)
})}
)}
{/* Recommendations */}
{recommendations.length > 0 && (
Empfehlungen
{recommendations.map((rec, index) => (
-
{rec}
))}
)}
)
}
// =============================================================================
// EXPORT PANEL
// =============================================================================
function ExportPanel() {
const { state, addExport } = useTOMGenerator()
const [isExporting, setIsExporting] = useState
(null)
const handleExport = async (format: 'docx' | 'pdf' | 'json' | 'zip') => {
setIsExporting(format)
try {
let blob: Blob
let filename: string
switch (format) {
case 'docx':
blob = await generateDOCXBlob(state, { language: 'de' })
filename = `TOM-Dokumentation-${new Date().toISOString().split('T')[0]}.docx`
break
case 'pdf':
blob = await generatePDFBlob(state, { language: 'de' })
filename = `TOM-Dokumentation-${new Date().toISOString().split('T')[0]}.pdf`
break
case 'json':
blob = new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' })
filename = `TOM-Export-${new Date().toISOString().split('T')[0]}.json`
break
case 'zip':
blob = await generateZIPBlob(state, { language: 'de' })
filename = `TOM-Package-${new Date().toISOString().split('T')[0]}.zip`
break
default:
return
}
// Download
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
// Record export
addExport({
id: `export-${Date.now()}`,
format: format.toUpperCase() as 'DOCX' | 'PDF' | 'JSON' | 'ZIP',
generatedAt: new Date(),
filename,
})
} catch (error) {
console.error('Export failed:', error)
} finally {
setIsExporting(null)
}
}
const exportFormats = [
{ id: 'docx', label: 'Word (.docx)', icon: '📄', description: 'Bearbeitbares Dokument' },
{ id: 'pdf', label: 'PDF', icon: '📕', description: 'Druckversion' },
{ id: 'json', label: 'JSON', icon: '💾', description: 'Maschinelles Format' },
{ id: 'zip', label: 'ZIP-Paket', icon: '📦', description: 'Vollständiges Paket' },
]
return (
{exportFormats.map((format) => (
))}
{/* Export History */}
{state.exports.length > 0 && (
Letzte Exporte
{state.exports.slice(-5).reverse().map((exp) => (
{exp.filename}
{new Date(exp.generatedAt).toLocaleString('de-DE')}
))}
)}
)
}
// =============================================================================
// MAIN COMPONENT
// =============================================================================
export function ReviewExportStep() {
const { state, deriveTOMs, completeCurrentStep } = useTOMGenerator()
const [activeTab, setActiveTab] = useState<'summary' | 'toms' | 'gaps' | 'export'>('summary')
// Derive TOMs if not already done
useEffect(() => {
if (state.derivedTOMs.length === 0 && state.companyProfile && state.dataProfile) {
deriveTOMs()
}
}, [state, deriveTOMs])
// Mark step as complete when viewing
useEffect(() => {
completeCurrentStep({ reviewed: true })
}, [completeCurrentStep])
// Statistics
const stats = {
totalTOMs: state.derivedTOMs.length,
required: state.derivedTOMs.filter((t) => t.applicability === 'REQUIRED').length,
implemented: state.derivedTOMs.filter((t) => t.implementationStatus === 'IMPLEMENTED').length,
partial: state.derivedTOMs.filter((t) => t.implementationStatus === 'PARTIAL').length,
documents: state.documents.length,
score: state.gapAnalysis?.overallScore ?? 0,
}
const tabs = [
{ id: 'summary', label: 'Zusammenfassung' },
{ id: 'toms', label: 'TOMs-Tabelle' },
{ id: 'gaps', label: 'Lückenanalyse' },
{ id: 'export', label: 'Export' },
]
return (
{/* Tabs */}
{/* Tab Content */}
{activeTab === 'summary' && (
{/* Stats Grid */}
= 80 ? 'success' : stats.score >= 50 ? 'warning' : 'danger'}
/>
{/* Profile Summaries */}
{/* Company */}
{state.companyProfile && (
Unternehmen
- Name:
- {state.companyProfile.name}
- Branche:
- {state.companyProfile.industry}
- Rolle:
- {state.companyProfile.role}
)}
{/* Risk */}
{state.riskProfile && (
Schutzbedarf
- Level:
-
{state.riskProfile.protectionLevel}
- DSFA erforderlich:
-
{state.riskProfile.dsfaRequired ? 'Ja' : 'Nein'}
- CIA (V/I/V):
-
{state.riskProfile.ciaAssessment.confidentiality}/
{state.riskProfile.ciaAssessment.integrity}/
{state.riskProfile.ciaAssessment.availability}
)}
)}
{activeTab === 'toms' &&
}
{activeTab === 'gaps' &&
}
{activeTab === 'export' &&
}
)
}
export default ReviewExportStep