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>
177 lines
5.9 KiB
TypeScript
177 lines
5.9 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import { DSRStatus, DSR_STATUS_INFO } from '@/lib/sdk/dsr/types'
|
|
|
|
interface WorkflowStep {
|
|
id: DSRStatus
|
|
label: string
|
|
description?: string
|
|
}
|
|
|
|
const WORKFLOW_STEPS: WorkflowStep[] = [
|
|
{ id: 'intake', label: 'Eingang', description: 'Anfrage dokumentiert' },
|
|
{ id: 'identity_verification', label: 'ID-Pruefung', description: 'Identitaet verifizieren' },
|
|
{ id: 'processing', label: 'Bearbeitung', description: 'Anfrage bearbeiten' },
|
|
{ id: 'completed', label: 'Abschluss', description: 'Antwort versenden' }
|
|
]
|
|
|
|
interface DSRWorkflowStepperProps {
|
|
currentStatus: DSRStatus
|
|
onStepClick?: (status: DSRStatus) => void
|
|
className?: string
|
|
}
|
|
|
|
export function DSRWorkflowStepper({
|
|
currentStatus,
|
|
onStepClick,
|
|
className = ''
|
|
}: DSRWorkflowStepperProps) {
|
|
const currentIndex = WORKFLOW_STEPS.findIndex(s => s.id === currentStatus)
|
|
const isRejectedOrCancelled = currentStatus === 'rejected' || currentStatus === 'cancelled'
|
|
|
|
const getStepState = (index: number): 'completed' | 'current' | 'upcoming' => {
|
|
if (isRejectedOrCancelled) {
|
|
return index <= currentIndex ? 'completed' : 'upcoming'
|
|
}
|
|
if (index < currentIndex) return 'completed'
|
|
if (index === currentIndex) return 'current'
|
|
return 'upcoming'
|
|
}
|
|
|
|
return (
|
|
<div className={`${className}`}>
|
|
<div className="flex items-center justify-between">
|
|
{WORKFLOW_STEPS.map((step, index) => {
|
|
const state = getStepState(index)
|
|
const isLast = index === WORKFLOW_STEPS.length - 1
|
|
|
|
return (
|
|
<React.Fragment key={step.id}>
|
|
{/* Step */}
|
|
<div
|
|
className={`flex flex-col items-center ${
|
|
onStepClick && state !== 'upcoming' ? 'cursor-pointer' : ''
|
|
}`}
|
|
onClick={() => onStepClick && state !== 'upcoming' && onStepClick(step.id)}
|
|
>
|
|
{/* Circle */}
|
|
<div
|
|
className={`
|
|
w-10 h-10 rounded-full flex items-center justify-center font-medium text-sm
|
|
transition-all duration-200
|
|
${state === 'completed'
|
|
? 'bg-green-500 text-white'
|
|
: state === 'current'
|
|
? 'bg-purple-600 text-white ring-4 ring-purple-100'
|
|
: 'bg-gray-200 text-gray-400'
|
|
}
|
|
`}
|
|
>
|
|
{state === 'completed' ? (
|
|
<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>
|
|
) : (
|
|
index + 1
|
|
)}
|
|
</div>
|
|
|
|
{/* Label */}
|
|
<div className="mt-2 text-center">
|
|
<div
|
|
className={`text-sm font-medium ${
|
|
state === 'current' ? 'text-purple-600' :
|
|
state === 'completed' ? 'text-green-600' : 'text-gray-400'
|
|
}`}
|
|
>
|
|
{step.label}
|
|
</div>
|
|
{step.description && (
|
|
<div className="text-xs text-gray-500 mt-0.5 hidden sm:block">
|
|
{step.description}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Connector Line */}
|
|
{!isLast && (
|
|
<div
|
|
className={`
|
|
flex-1 h-1 mx-2 rounded-full
|
|
${state === 'completed' || getStepState(index + 1) === 'completed' || getStepState(index + 1) === 'current'
|
|
? 'bg-green-500'
|
|
: 'bg-gray-200'
|
|
}
|
|
`}
|
|
/>
|
|
)}
|
|
</React.Fragment>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
{/* Rejected/Cancelled Badge */}
|
|
{isRejectedOrCancelled && (
|
|
<div className={`
|
|
mt-4 px-4 py-2 rounded-lg text-center text-sm font-medium
|
|
${currentStatus === 'rejected'
|
|
? 'bg-red-100 text-red-700 border border-red-200'
|
|
: 'bg-gray-100 text-gray-700 border border-gray-200'
|
|
}
|
|
`}>
|
|
{currentStatus === 'rejected' ? 'Anfrage wurde abgelehnt' : 'Anfrage wurde storniert'}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Compact version for list views
|
|
export function DSRWorkflowStepperCompact({
|
|
currentStatus,
|
|
className = ''
|
|
}: {
|
|
currentStatus: DSRStatus
|
|
className?: string
|
|
}) {
|
|
const statusInfo = DSR_STATUS_INFO[currentStatus]
|
|
const currentIndex = WORKFLOW_STEPS.findIndex(s => s.id === currentStatus)
|
|
const totalSteps = WORKFLOW_STEPS.length
|
|
const isTerminal = currentStatus === 'rejected' || currentStatus === 'cancelled' || currentStatus === 'completed'
|
|
|
|
return (
|
|
<div className={`flex items-center gap-2 ${className}`}>
|
|
{/* Mini progress dots */}
|
|
<div className="flex items-center gap-1">
|
|
{WORKFLOW_STEPS.map((step, index) => (
|
|
<div
|
|
key={step.id}
|
|
className={`
|
|
w-2 h-2 rounded-full transition-all
|
|
${index < currentIndex
|
|
? 'bg-green-500'
|
|
: index === currentIndex
|
|
? isTerminal
|
|
? currentStatus === 'completed'
|
|
? 'bg-green-500'
|
|
: currentStatus === 'rejected'
|
|
? 'bg-red-500'
|
|
: 'bg-gray-500'
|
|
: 'bg-purple-500'
|
|
: 'bg-gray-200'
|
|
}
|
|
`}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{/* Status label */}
|
|
<span className={`text-xs font-medium px-2 py-0.5 rounded-full ${statusInfo.bgColor} ${statusInfo.color}`}>
|
|
{statusInfo.label}
|
|
</span>
|
|
</div>
|
|
)
|
|
}
|