'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 */}
{filteredTOMs.map((tom) => ( ))}
ID Maßnahme Anwendbarkeit Status Nachweise
{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