The admin-v2 application was incomplete in the repository. This commit restores all missing components: - Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education, infrastructure, communication, development, onboarding, rbac - SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen, vendor-compliance, tom-generator, dsr, and more - Developer portal (25 pages): API docs, SDK guides, frameworks - All components, lib files, hooks, and types - Updated package.json with all dependencies The issue was caused by incomplete initial repository state - the full admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2 but was never fully synced to the main admin-v2 directory. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
287 lines
8.7 KiB
TypeScript
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 }
|