Files
breakpilot-compliance/admin-compliance/components/sdk/StepHeader/StepHeader.tsx

304 lines
11 KiB
TypeScript

'use client'
import React, { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useSDK, getStepById, getNextStep, getPreviousStep, SDK_STEPS } from '@/lib/sdk'
import { STEP_EXPLANATIONS } from './StepExplanations'
// =============================================================================
// TYPES
// =============================================================================
export interface StepTip {
icon: 'info' | 'warning' | 'success' | 'lightbulb'
title: string
description: string
}
interface StepHeaderProps {
stepId: string
title?: string
description?: string
explanation?: string
tips?: StepTip[]
showNavigation?: boolean
showProgress?: boolean
onComplete?: () => void
isCompleted?: boolean
children?: React.ReactNode
}
// =============================================================================
// ICONS
// =============================================================================
const icons = {
info: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
warning: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
),
success: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
lightbulb: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} 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>
),
arrowLeft: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
),
arrowRight: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
),
check: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
),
help: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
}
const tipColors = {
info: 'bg-blue-50 border-blue-200 text-blue-800',
warning: 'bg-amber-50 border-amber-200 text-amber-800',
success: 'bg-green-50 border-green-200 text-green-800',
lightbulb: 'bg-purple-50 border-purple-200 text-purple-800',
}
const tipIconColors = {
info: 'text-blue-500',
warning: 'text-amber-500',
success: 'text-green-500',
lightbulb: 'text-purple-500',
}
// =============================================================================
// STEP HEADER COMPONENT
// =============================================================================
export function StepHeader({
stepId,
title: titleProp,
description: descriptionProp,
explanation: explanationProp,
tips: tipsProp,
showNavigation = true,
showProgress = true,
onComplete,
isCompleted = false,
children,
}: StepHeaderProps) {
const router = useRouter()
const { state, dispatch } = useSDK()
const [showHelp, setShowHelp] = useState(false)
// Look up defaults from STEP_EXPLANATIONS when props are not provided
const preset = STEP_EXPLANATIONS[stepId as keyof typeof STEP_EXPLANATIONS]
const title = titleProp ?? preset?.title ?? stepId
const description = descriptionProp ?? preset?.description ?? ''
const explanation = explanationProp ?? preset?.explanation ?? ''
const tips = tipsProp ?? preset?.tips ?? []
const currentStep = getStepById(stepId)
const prevStep = getPreviousStep(stepId)
const nextStep = getNextStep(stepId)
const stepCompleted = state.completedSteps.includes(stepId)
const handleComplete = () => {
if (onComplete) {
onComplete()
}
dispatch({ type: 'COMPLETE_STEP', payload: stepId })
if (nextStep) {
router.push(nextStep.url)
}
}
const handleSkip = () => {
if (nextStep) {
router.push(nextStep.url)
}
}
// Calculate step progress within phase
const phaseSteps = currentStep ?
SDK_STEPS.filter(s => s.phase === currentStep.phase).length : 0
const stepNumber = currentStep?.order || 0
return (
<div className="space-y-6">
{/* Breadcrumb & Progress */}
{showProgress && currentStep && (
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-sm text-gray-500">
<Link href="/sdk" className="hover:text-purple-600 transition-colors">
SDK
</Link>
<span>/</span>
<span className="text-gray-700">
Phase {currentStep.phase}: {currentStep.phase === 1 ? 'Assessment' : 'Dokumente'}
</span>
<span>/</span>
<span className="text-purple-600 font-medium">{currentStep.nameShort}</span>
</div>
<div className="flex items-center gap-2 text-sm">
<span className="text-gray-500">Schritt {stepNumber} von {phaseSteps}</span>
<div className="w-24 h-2 bg-gray-200 rounded-full overflow-hidden">
<div
className="h-full bg-purple-600 rounded-full transition-all"
style={{ width: `${(stepNumber / phaseSteps) * 100}%` }}
/>
</div>
</div>
</div>
)}
{/* Main Header Card */}
<div className="bg-white rounded-xl border border-gray-200 overflow-hidden">
{/* Header */}
<div className="p-6 border-b border-gray-100">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3">
<h1 className="text-2xl font-bold text-gray-900">{title}</h1>
{stepCompleted && (
<span className="flex items-center gap-1 px-2 py-1 text-xs bg-green-100 text-green-700 rounded-full">
{icons.check}
Abgeschlossen
</span>
)}
</div>
<p className="mt-2 text-gray-500">{description}</p>
</div>
<button
onClick={() => setShowHelp(!showHelp)}
className={`p-2 rounded-lg transition-colors ${
showHelp ? 'bg-purple-100 text-purple-600' : 'text-gray-400 hover:bg-gray-100 hover:text-gray-600'
}`}
title="Hilfe anzeigen"
>
{icons.help}
</button>
</div>
</div>
{/* Explanation Panel (collapsible) */}
{showHelp && (
<div className="px-6 py-4 bg-gradient-to-r from-purple-50 to-indigo-50 border-b border-purple-100">
<div className="flex items-start gap-3">
<div className="p-2 bg-purple-100 rounded-lg text-purple-600">
{icons.lightbulb}
</div>
<div>
<h3 className="font-medium text-purple-900">Was ist das?</h3>
<p className="mt-1 text-sm text-purple-800">{explanation}</p>
</div>
</div>
</div>
)}
{/* Tips */}
{tips.length > 0 && (
<div className="px-6 py-4 space-y-3 bg-gray-50 border-b border-gray-100">
{tips.map((tip, index) => (
<div
key={index}
className={`flex items-start gap-3 p-3 rounded-lg border ${tipColors[tip.icon]}`}
>
<div className={tipIconColors[tip.icon]}>
{icons[tip.icon]}
</div>
<div>
<h4 className="font-medium text-sm">{tip.title}</h4>
<p className="text-sm opacity-80">{tip.description}</p>
</div>
</div>
))}
</div>
)}
{/* Action Buttons */}
{children && (
<div className="px-6 py-4 bg-gray-50">
{children}
</div>
)}
</div>
{/* Navigation */}
{showNavigation && (
<div className="flex items-center justify-between">
<div>
{prevStep ? (
<Link
href={prevStep.url}
className="flex items-center gap-2 px-4 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
>
{icons.arrowLeft}
<span>Zurueck: {prevStep.nameShort}</span>
</Link>
) : (
<Link
href="/sdk"
className="flex items-center gap-2 px-4 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
>
{icons.arrowLeft}
<span>Zur Uebersicht</span>
</Link>
)}
</div>
<div className="flex items-center gap-3">
{nextStep && !stepCompleted && (
<button
onClick={handleSkip}
className="px-4 py-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg transition-colors"
>
Ueberspringen
</button>
)}
{nextStep ? (
<button
onClick={handleComplete}
className="flex items-center gap-2 px-6 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
>
<span>{stepCompleted ? 'Weiter' : 'Abschliessen & Weiter'}</span>
{icons.arrowRight}
</button>
) : (
<button
onClick={handleComplete}
className="flex items-center gap-2 px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
>
{icons.check}
<span>Phase abschliessen</span>
</button>
)}
</div>
</div>
)}
</div>
)
}
export default StepHeader