Files
breakpilot-compliance/admin-compliance/components/sdk/tom-generator/TOMGeneratorWizard.tsx
Benjamin Boenisch 4435e7ea0a Initial commit: breakpilot-compliance - Compliance SDK Platform
Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:28 +01:00

287 lines
8.7 KiB
TypeScript

'use client'
// =============================================================================
// TOM Generator Wizard Component
// Main wizard container with step navigation
// =============================================================================
import React from 'react'
import { useTOMGenerator } from '@/lib/sdk/tom-generator'
import { TOM_GENERATOR_STEPS, TOMGeneratorStepId } from '@/lib/sdk/tom-generator/types'
// =============================================================================
// STEP INDICATOR
// =============================================================================
interface StepIndicatorProps {
stepId: TOMGeneratorStepId
stepNumber: number
title: string
isActive: boolean
isCompleted: boolean
onClick: () => void
}
function StepIndicator({
stepNumber,
title,
isActive,
isCompleted,
onClick,
}: StepIndicatorProps) {
return (
<button
onClick={onClick}
className={`flex items-center gap-3 p-3 rounded-lg transition-all w-full text-left ${
isActive
? 'bg-blue-50 border-2 border-blue-500'
: isCompleted
? 'bg-green-50 border border-green-300 hover:bg-green-100'
: 'bg-gray-50 border border-gray-200 hover:bg-gray-100'
}`}
>
<div
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium ${
isActive
? 'bg-blue-500 text-white'
: isCompleted
? 'bg-green-500 text-white'
: 'bg-gray-300 text-gray-600'
}`}
>
{isCompleted ? (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
) : (
stepNumber
)}
</div>
<span
className={`text-sm font-medium ${
isActive ? 'text-blue-700' : isCompleted ? 'text-green-700' : 'text-gray-600'
}`}
>
{title}
</span>
</button>
)
}
// =============================================================================
// WIZARD NAVIGATION
// =============================================================================
interface WizardNavigationProps {
onPrevious: () => void
onNext: () => void
onSave: () => void
canGoPrevious: boolean
canGoNext: boolean
isLastStep: boolean
isSaving: boolean
}
function WizardNavigation({
onPrevious,
onNext,
onSave,
canGoPrevious,
canGoNext,
isLastStep,
isSaving,
}: WizardNavigationProps) {
return (
<div className="flex justify-between items-center mt-8 pt-6 border-t">
<button
onClick={onPrevious}
disabled={!canGoPrevious}
className={`px-4 py-2 rounded-lg font-medium flex items-center gap-2 ${
canGoPrevious
? 'bg-gray-100 text-gray-700 hover:bg-gray-200'
: 'bg-gray-50 text-gray-400 cursor-not-allowed'
}`}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
Zurück
</button>
<div className="flex gap-3">
<button
onClick={onSave}
disabled={isSaving}
className="px-4 py-2 rounded-lg font-medium bg-gray-100 text-gray-700 hover:bg-gray-200 flex items-center gap-2"
>
{isSaving ? (
<svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
) : (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4" />
</svg>
)}
Speichern
</button>
<button
onClick={onNext}
disabled={!canGoNext && !isLastStep}
className={`px-4 py-2 rounded-lg font-medium flex items-center gap-2 ${
canGoNext || isLastStep
? 'bg-blue-600 text-white hover:bg-blue-700'
: 'bg-blue-300 text-white cursor-not-allowed'
}`}
>
{isLastStep ? 'Abschließen' : 'Weiter'}
{!isLastStep && (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
)}
</button>
</div>
</div>
)
}
// =============================================================================
// PROGRESS BAR
// =============================================================================
interface ProgressBarProps {
percentage: number
}
function ProgressBar({ percentage }: ProgressBarProps) {
return (
<div className="mb-6">
<div className="flex justify-between text-sm text-gray-600 mb-2">
<span>Fortschritt</span>
<span>{percentage}%</span>
</div>
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
<div
className="h-full bg-blue-500 transition-all duration-300"
style={{ width: `${percentage}%` }}
/>
</div>
</div>
)
}
// =============================================================================
// MAIN WIZARD COMPONENT
// =============================================================================
interface TOMGeneratorWizardProps {
children: React.ReactNode
showSidebar?: boolean
showProgress?: boolean
}
export function TOMGeneratorWizard({
children,
showSidebar = true,
showProgress = true,
}: TOMGeneratorWizardProps) {
const {
state,
currentStepIndex,
canGoNext,
canGoPrevious,
goToStep,
goToNextStep,
goToPreviousStep,
isStepCompleted,
getCompletionPercentage,
saveState,
isLoading,
} = useTOMGenerator()
const [isSaving, setIsSaving] = React.useState(false)
const handleSave = async () => {
setIsSaving(true)
try {
await saveState()
} catch (error) {
console.error('Failed to save:', error)
} finally {
setIsSaving(false)
}
}
const isLastStep = currentStepIndex === TOM_GENERATOR_STEPS.length - 1
return (
<div className="flex gap-8">
{/* Sidebar */}
{showSidebar && (
<div className="w-72 flex-shrink-0">
<div className="bg-white rounded-lg shadow-sm border p-4 sticky top-4">
<h3 className="font-semibold text-gray-900 mb-4">Wizard-Schritte</h3>
{showProgress && <ProgressBar percentage={getCompletionPercentage()} />}
<div className="space-y-2">
{TOM_GENERATOR_STEPS.map((step, index) => (
<StepIndicator
key={step.id}
stepId={step.id}
stepNumber={index + 1}
title={step.title.de}
isActive={state.currentStep === step.id}
isCompleted={isStepCompleted(step.id)}
onClick={() => goToStep(step.id)}
/>
))}
</div>
</div>
</div>
)}
{/* Main Content */}
<div className="flex-1 min-w-0">
<div className="bg-white rounded-lg shadow-sm border p-6">
{/* Step Header */}
<div className="mb-6">
<div className="text-sm text-blue-600 font-medium mb-1">
Schritt {currentStepIndex + 1} von {TOM_GENERATOR_STEPS.length}
</div>
<h2 className="text-2xl font-bold text-gray-900">
{TOM_GENERATOR_STEPS[currentStepIndex].title.de}
</h2>
<p className="text-gray-600 mt-1">
{TOM_GENERATOR_STEPS[currentStepIndex].description.de}
</p>
</div>
{/* Step Content */}
<div className="min-h-[400px]">{children}</div>
{/* Navigation */}
<WizardNavigation
onPrevious={goToPreviousStep}
onNext={goToNextStep}
onSave={handleSave}
canGoPrevious={canGoPrevious}
canGoNext={canGoNext}
isLastStep={isLastStep}
isSaving={isSaving}
/>
</div>
</div>
</div>
)
}
// =============================================================================
// EXPORTS
// =============================================================================
export { StepIndicator, WizardNavigation, ProgressBar }