fix(admin-v2): Restore HEAD SDK files for compatibility with new pages

Restore the SDK context, types, and component files to the HEAD version
since newer pages (company-profile, import) depend on these API changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-02-09 10:18:39 +01:00
parent 613b36be83
commit bd70b59c5e
15 changed files with 1054 additions and 2921 deletions

View File

@@ -102,11 +102,11 @@ export function CommandBar({ onClose }: CommandBarProps) {
{
id: 'action-new-usecase',
type: 'ACTION',
label: 'Neuen Use Case erstellen',
description: 'Startet den Use Case Workshop Wizard',
label: 'Neuen Anwendungsfall erstellen',
description: 'Startet die Anwendungsfall-Erfassung',
icon: icons.action,
action: () => {
goToStep('use-case-workshop')
goToStep('use-case-assessment')
onClose()
},
},

View File

@@ -165,6 +165,12 @@ function QRCodeModal({ isOpen, onClose, sessionId }: QRModalProps) {
}
})
// Force HTTP for mobile access (SSL cert is for hostname, not IP)
// This is safe because it's only used on the local network
if (baseUrl.startsWith('https://')) {
baseUrl = baseUrl.replace('https://', 'http://')
}
const uploadPath = `/upload/sdk/${sessionId}`
const fullUrl = `${baseUrl}${uploadPath}`
setUploadUrl(fullUrl)

View File

@@ -4,8 +4,7 @@
* SDK Pipeline Sidebar
*
* Floating Action Button mit Drawer zur Visualisierung der SDK-Pipeline.
* Zeigt die zwei Phasen (Compliance Assessment & Dokumentengenerierung)
* mit Fortschritt und ermöglicht schnelle Navigation.
* Zeigt die 5 Pakete mit Fortschritt und ermoeglicht schnelle Navigation.
*
* Features:
* - Desktop (xl+): Fixierte Sidebar rechts
@@ -15,7 +14,7 @@
import Link from 'next/link'
import { useState, useEffect } from 'react'
import { usePathname } from 'next/navigation'
import { useSDK, SDK_STEPS, getStepsForPhase, type SDKStep } from '@/lib/sdk'
import { useSDK, SDK_STEPS, SDK_PACKAGES, getStepsForPackage, type SDKStep, type SDKPackageId } from '@/lib/sdk'
// =============================================================================
// ICONS
@@ -27,6 +26,12 @@ const CheckIcon = () => (
</svg>
)
const LockIcon = () => (
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
)
const ArrowIcon = () => (
<svg className="w-3 h-3 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
@@ -45,31 +50,6 @@ const PipelineIcon = () => (
</svg>
)
// Step Icons als Emojis
const STEP_ICONS: Record<string, string> = {
// Phase 1
'use-case-workshop': '📋',
'screening': '🔍',
'modules': '📦',
'requirements': '📜',
'controls': '🛡️',
'evidence': '📎',
'audit-checklist': '✅',
'risks': '⚠️',
// Phase 2
'ai-act': '🤖',
'obligations': '📑',
'dsfa': '📄',
'tom': '🔒',
'einwilligungen': '✍️',
'loeschfristen': '🗑️',
'vvt': '📊',
'consent': '📝',
'cookie-banner': '🍪',
'dsr': '👤',
'escalations': '🚨',
}
// =============================================================================
// STEP ITEM
// =============================================================================
@@ -82,8 +62,6 @@ interface StepItemProps {
}
function StepItem({ step, isActive, isCompleted, onNavigate }: StepItemProps) {
const icon = STEP_ICONS[step.id] || '•'
return (
<Link
href={step.url}
@@ -96,7 +74,6 @@ function StepItem({ step, isActive, isCompleted, onNavigate }: StepItemProps) {
: 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-gray-800'
}`}
>
<span className="text-lg flex-shrink-0">{icon}</span>
<span className="flex-1 text-sm truncate">{step.nameShort}</span>
{isCompleted && !isActive && (
<span className="flex-shrink-0 w-4 h-4 bg-green-500 text-white rounded-full flex items-center justify-center">
@@ -111,78 +88,91 @@ function StepItem({ step, isActive, isCompleted, onNavigate }: StepItemProps) {
}
// =============================================================================
// PHASE SECTION
// PACKAGE SECTION
// =============================================================================
interface PhaseSectionProps {
phase: 1 | 2
title: string
interface PackageSectionProps {
pkg: (typeof SDK_PACKAGES)[number]
steps: SDKStep[]
completion: number
currentStepId: string
completedSteps: string[]
isLocked: boolean
onNavigate: () => void
isExpanded: boolean
onToggle: () => void
}
function PhaseSection({
phase,
title,
function PackageSection({
pkg,
steps,
completion,
currentStepId,
completedSteps,
isLocked,
onNavigate,
isExpanded,
onToggle,
}: PhaseSectionProps) {
}: PackageSectionProps) {
return (
<div className="space-y-2">
{/* Phase Header */}
{/* Package Header */}
<button
onClick={onToggle}
className="w-full flex items-center justify-between px-3 py-2 rounded-lg bg-slate-50 dark:bg-gray-800 hover:bg-slate-100 dark:hover:bg-gray-700 transition-colors"
disabled={isLocked}
className={`w-full flex items-center justify-between px-3 py-2 rounded-lg transition-colors ${
isLocked
? 'bg-slate-100 dark:bg-gray-800 opacity-50 cursor-not-allowed'
: 'bg-slate-50 dark:bg-gray-800 hover:bg-slate-100 dark:hover:bg-gray-700'
}`}
>
<div className="flex items-center gap-2">
<div
className={`w-7 h-7 rounded-full flex items-center justify-center text-sm font-bold ${
completion === 100
className={`w-7 h-7 rounded-full flex items-center justify-center text-sm ${
isLocked
? 'bg-gray-200 text-gray-400'
: completion === 100
? 'bg-green-500 text-white'
: 'bg-purple-600 text-white'
}`}
>
{completion === 100 ? <CheckIcon /> : phase}
{isLocked ? <LockIcon /> : completion === 100 ? <CheckIcon /> : pkg.icon}
</div>
<div className="text-left">
<div className="text-sm font-medium text-slate-700 dark:text-slate-200">{title}</div>
<div className="text-xs text-slate-500 dark:text-slate-400">{completion}% abgeschlossen</div>
<div className={`text-sm font-medium ${isLocked ? 'text-slate-400' : 'text-slate-700 dark:text-slate-200'}`}>
{pkg.order}. {pkg.nameShort}
</div>
<div className="text-xs text-slate-500 dark:text-slate-400">{completion}%</div>
</div>
</div>
<svg
className={`w-4 h-4 text-slate-400 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
{!isLocked && (
<svg
className={`w-4 h-4 text-slate-400 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
)}
</button>
{/* Progress Bar */}
<div className="px-3">
<div className="h-1.5 bg-slate-200 dark:bg-gray-700 rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-500 ${
completion === 100 ? 'bg-green-500' : 'bg-purple-600'
}`}
style={{ width: `${completion}%` }}
/>
{!isLocked && (
<div className="px-3">
<div className="h-1.5 bg-slate-200 dark:bg-gray-700 rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-500 ${
completion === 100 ? 'bg-green-500' : 'bg-purple-600'
}`}
style={{ width: `${completion}%` }}
/>
</div>
</div>
</div>
)}
{/* Steps List */}
{isExpanded && (
{isExpanded && !isLocked && (
<div className="space-y-1 pl-2">
{steps.map(step => (
<StepItem
@@ -210,39 +200,16 @@ function PipelineFlow() {
Datenfluss
</div>
<div className="p-3 bg-slate-50 dark:bg-gray-900 rounded-lg">
<div className="flex flex-col gap-2">
{/* Phase 1 Flow */}
<div className="flex items-center justify-between text-xs">
<span className="text-purple-600 dark:text-purple-400 font-medium">Phase 1</span>
<div className="flex items-center gap-1">
<span title="Use Case">📋</span>
<ArrowIcon />
<span title="Screening">🔍</span>
<ArrowIcon />
<span title="Risiken"></span>
<ArrowIcon />
<span title="Controls">🛡</span>
<div className="flex flex-col gap-1.5">
{SDK_PACKAGES.map((pkg, idx) => (
<div key={pkg.id} className="flex items-center gap-2 text-xs">
<span className="w-5 h-5 rounded-full bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center">
{pkg.icon}
</span>
<span className="text-slate-600 dark:text-slate-400 flex-1">{pkg.nameShort}</span>
{idx < SDK_PACKAGES.length - 1 && <ArrowIcon />}
</div>
</div>
{/* Arrow Down */}
<div className="flex justify-center">
<svg className="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 14l-7 7m0 0l-7-7m7 7V3" />
</svg>
</div>
{/* Phase 2 Flow */}
<div className="flex items-center justify-between text-xs">
<span className="text-indigo-600 dark:text-indigo-400 font-medium">Phase 2</span>
<div className="flex items-center gap-1">
<span title="DSFA">📄</span>
<ArrowIcon />
<span title="TOM">🔒</span>
<ArrowIcon />
<span title="VVT">📊</span>
<ArrowIcon />
<span title="Export"></span>
</div>
</div>
))}
</div>
</div>
</div>
@@ -259,60 +226,72 @@ interface SidebarContentProps {
function SidebarContent({ onNavigate }: SidebarContentProps) {
const pathname = usePathname()
const { state, phase1Completion, phase2Completion } = useSDK()
const [expandedPhases, setExpandedPhases] = useState<Record<number, boolean>>({
1: true,
2: false,
const { state, packageCompletion } = useSDK()
const [expandedPackages, setExpandedPackages] = useState<Record<SDKPackageId, boolean>>({
'vorbereitung': true,
'analyse': false,
'dokumentation': false,
'rechtliche-texte': false,
'betrieb': false,
})
const phase1Steps = getStepsForPhase(1)
const phase2Steps = getStepsForPhase(2)
// Find current step
const currentStep = SDK_STEPS.find(s => s.url === pathname)
const currentStepId = currentStep?.id || ''
// Auto-expand current phase
// Auto-expand current package
useEffect(() => {
if (currentStep) {
setExpandedPhases(prev => ({
setExpandedPackages(prev => ({
...prev,
[currentStep.phase]: true,
[currentStep.package]: true,
}))
}
}, [currentStep])
const togglePhase = (phase: number) => {
setExpandedPhases(prev => ({ ...prev, [phase]: !prev[phase] }))
const togglePackage = (packageId: SDKPackageId) => {
setExpandedPackages(prev => ({ ...prev, [packageId]: !prev[packageId] }))
}
const isPackageLocked = (packageId: SDKPackageId): boolean => {
if (state.preferences?.allowParallelWork) return false
const pkg = SDK_PACKAGES.find(p => p.id === packageId)
if (!pkg || pkg.order === 1) return false
const prevPkg = SDK_PACKAGES.find(p => p.order === pkg.order - 1)
if (!prevPkg) return false
return packageCompletion[prevPkg.id] < 100
}
// Get visible steps based on customer type
const getVisibleSteps = (packageId: SDKPackageId): SDKStep[] => {
const steps = getStepsForPackage(packageId)
return steps.filter(step => {
if (step.id === 'import' && state.customerType === 'new') {
return false
}
return true
})
}
return (
<div className="space-y-4">
{/* Phase 1 */}
<PhaseSection
phase={1}
title="Compliance Assessment"
steps={phase1Steps}
completion={phase1Completion}
currentStepId={currentStepId}
completedSteps={state.completedSteps}
onNavigate={onNavigate}
isExpanded={expandedPhases[1]}
onToggle={() => togglePhase(1)}
/>
{/* Phase 2 */}
<PhaseSection
phase={2}
title="Dokumentengenerierung"
steps={phase2Steps}
completion={phase2Completion}
currentStepId={currentStepId}
completedSteps={state.completedSteps}
onNavigate={onNavigate}
isExpanded={expandedPhases[2]}
onToggle={() => togglePhase(2)}
/>
{/* Packages */}
{SDK_PACKAGES.map(pkg => (
<PackageSection
key={pkg.id}
pkg={pkg}
steps={getVisibleSteps(pkg.id)}
completion={packageCompletion[pkg.id]}
currentStepId={currentStepId}
completedSteps={state.completedSteps}
isLocked={isPackageLocked(pkg.id)}
onNavigate={onNavigate}
isExpanded={expandedPackages[pkg.id]}
onToggle={() => togglePackage(pkg.id)}
/>
))}
{/* Pipeline Flow */}
<PipelineFlow />

View File

@@ -3,7 +3,14 @@
import React from 'react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { useSDK, SDK_STEPS, getStepsForPhase, SDKPhase } from '@/lib/sdk'
import {
useSDK,
SDK_STEPS,
SDK_PACKAGES,
getStepsForPackage,
type SDKPackageId,
type SDKStep,
} from '@/lib/sdk'
// =============================================================================
// ICONS
@@ -44,80 +51,6 @@ const CollapseIcon = ({ collapsed }: { collapsed: boolean }) => (
</svg>
)
// =============================================================================
// PHASE INDICATOR
// =============================================================================
interface PhaseIndicatorProps {
phase: SDKPhase
title: string
completion: number
isActive: boolean
isExpanded: boolean
onToggle: () => void
collapsed: boolean
}
function PhaseIndicator({ phase, title, completion, isActive, isExpanded, onToggle, collapsed }: PhaseIndicatorProps) {
if (collapsed) {
return (
<button
onClick={onToggle}
className={`w-full flex items-center justify-center py-3 transition-colors ${
isActive
? 'bg-purple-50 border-l-4 border-purple-600'
: 'hover:bg-gray-50 border-l-4 border-transparent'
}`}
title={`${title} (${completion}%)`}
>
<div
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold ${
isActive
? 'bg-purple-600 text-white'
: completion === 100
? 'bg-green-500 text-white'
: 'bg-gray-200 text-gray-600'
}`}
>
{completion === 100 ? <CheckIcon /> : phase}
</div>
</button>
)
}
return (
<button
onClick={onToggle}
className={`w-full flex items-center justify-between px-4 py-3 text-left transition-colors ${
isActive
? 'bg-purple-50 border-l-4 border-purple-600'
: 'hover:bg-gray-50 border-l-4 border-transparent'
}`}
>
<div className="flex items-center gap-3">
<div
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold ${
isActive
? 'bg-purple-600 text-white'
: completion === 100
? 'bg-green-500 text-white'
: 'bg-gray-200 text-gray-600'
}`}
>
{completion === 100 ? <CheckIcon /> : phase}
</div>
<div>
<div className={`font-medium ${isActive ? 'text-purple-900' : 'text-gray-700'}`}>
{title}
</div>
<div className="text-xs text-gray-500">{completion}% abgeschlossen</div>
</div>
</div>
<ChevronDownIcon className={`transition-transform ${isExpanded ? 'rotate-180' : ''}`} />
</button>
)
}
// =============================================================================
// PROGRESS BAR
// =============================================================================
@@ -138,12 +71,108 @@ function ProgressBar({ value, className = '' }: ProgressBarProps) {
)
}
// =============================================================================
// PACKAGE INDICATOR
// =============================================================================
interface PackageIndicatorProps {
packageId: SDKPackageId
order: number
name: string
icon: string
completion: number
isActive: boolean
isExpanded: boolean
isLocked: boolean
onToggle: () => void
collapsed: boolean
}
function PackageIndicator({
order,
name,
icon,
completion,
isActive,
isExpanded,
isLocked,
onToggle,
collapsed,
}: PackageIndicatorProps) {
if (collapsed) {
return (
<button
onClick={onToggle}
className={`w-full flex items-center justify-center py-3 transition-colors ${
isActive
? 'bg-purple-50 border-l-4 border-purple-600'
: isLocked
? 'border-l-4 border-transparent opacity-50'
: 'hover:bg-gray-50 border-l-4 border-transparent'
}`}
title={`${order}. ${name} (${completion}%)`}
>
<div
className={`w-8 h-8 rounded-full flex items-center justify-center text-lg ${
isLocked
? 'bg-gray-200 text-gray-400'
: isActive
? 'bg-purple-600 text-white'
: completion === 100
? 'bg-green-500 text-white'
: 'bg-gray-200 text-gray-600'
}`}
>
{isLocked ? <LockIcon /> : completion === 100 ? <CheckIcon /> : icon}
</div>
</button>
)
}
return (
<button
onClick={onToggle}
disabled={isLocked}
className={`w-full flex items-center justify-between px-4 py-3 text-left transition-colors ${
isLocked
? 'opacity-50 cursor-not-allowed'
: isActive
? 'bg-purple-50 border-l-4 border-purple-600'
: 'hover:bg-gray-50 border-l-4 border-transparent'
}`}
>
<div className="flex items-center gap-3">
<div
className={`w-8 h-8 rounded-full flex items-center justify-center text-lg ${
isLocked
? 'bg-gray-200 text-gray-400'
: isActive
? 'bg-purple-600 text-white'
: completion === 100
? 'bg-green-500 text-white'
: 'bg-gray-200 text-gray-600'
}`}
>
{isLocked ? <LockIcon /> : completion === 100 ? <CheckIcon /> : icon}
</div>
<div>
<div className={`font-medium text-sm ${isActive ? 'text-purple-900' : isLocked ? 'text-gray-400' : 'text-gray-700'}`}>
{order}. {name}
</div>
<div className="text-xs text-gray-500">{completion}%</div>
</div>
</div>
{!isLocked && <ChevronDownIcon className={`transition-transform ${isExpanded ? 'rotate-180' : ''}`} />}
</button>
)
}
// =============================================================================
// STEP ITEM
// =============================================================================
interface StepItemProps {
step: (typeof SDK_STEPS)[number]
step: SDKStep
isActive: boolean
isCompleted: boolean
isLocked: boolean
@@ -261,25 +290,48 @@ interface SDKSidebarProps {
export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarProps) {
const pathname = usePathname()
const { state, phase1Completion, phase2Completion, getCheckpointStatus } = useSDK()
const [expandedPhases, setExpandedPhases] = React.useState<Record<number, boolean>>({
1: true,
2: true,
const { state, packageCompletion, completionPercentage, getCheckpointStatus } = useSDK()
const [expandedPackages, setExpandedPackages] = React.useState<Record<SDKPackageId, boolean>>({
'vorbereitung': true,
'analyse': false,
'dokumentation': false,
'rechtliche-texte': false,
'betrieb': false,
})
const togglePhase = (phase: number) => {
setExpandedPhases(prev => ({ ...prev, [phase]: !prev[phase] }))
// Auto-expand current package
React.useEffect(() => {
const currentStep = SDK_STEPS.find(s => s.url === pathname)
if (currentStep) {
setExpandedPackages(prev => ({
...prev,
[currentStep.package]: true,
}))
}
}, [pathname])
const togglePackage = (packageId: SDKPackageId) => {
setExpandedPackages(prev => ({ ...prev, [packageId]: !prev[packageId] }))
}
const phase1Steps = getStepsForPhase(1)
const phase2Steps = getStepsForPhase(2)
const isStepLocked = (step: (typeof SDK_STEPS)[number]): boolean => {
const isStepLocked = (step: SDKStep): boolean => {
if (state.preferences?.allowParallelWork) return false
return step.prerequisiteSteps.some(prereq => !state.completedSteps.includes(prereq))
}
const getStepCheckpointStatus = (step: (typeof SDK_STEPS)[number]): 'passed' | 'failed' | 'warning' | 'pending' => {
const isPackageLocked = (packageId: SDKPackageId): boolean => {
if (state.preferences?.allowParallelWork) return false
const pkg = SDK_PACKAGES.find(p => p.id === packageId)
if (!pkg || pkg.order === 1) return false
// Check if previous package is complete
const prevPkg = SDK_PACKAGES.find(p => p.order === pkg.order - 1)
if (!prevPkg) return false
return packageCompletion[prevPkg.id] < 100
}
const getStepCheckpointStatus = (step: SDKStep): 'passed' | 'failed' | 'warning' | 'pending' => {
const status = getCheckpointStatus(step.checkpointId)
if (!status) return 'pending'
if (status.passed) return 'passed'
@@ -288,6 +340,25 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
return 'pending'
}
const isStepActive = (stepUrl: string) => pathname === stepUrl
const isPackageActive = (packageId: SDKPackageId) => {
const steps = getStepsForPackage(packageId)
return steps.some(s => s.url === pathname)
}
// Filter steps based on customer type
const getVisibleSteps = (packageId: SDKPackageId): SDKStep[] => {
const steps = getStepsForPackage(packageId)
return steps.filter(step => {
// Hide import step for new customers
if (step.id === 'import' && state.customerType === 'new') {
return false
}
return true
})
}
return (
<aside className={`fixed left-0 top-0 h-screen ${collapsed ? 'w-16' : 'w-64'} bg-white border-r border-gray-200 flex flex-col z-40 transition-all duration-300`}>
{/* Header */}
@@ -317,71 +388,51 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
<div className="px-4 py-3 border-b border-gray-100">
<div className="flex items-center justify-between text-sm mb-2">
<span className="text-gray-600">Gesamtfortschritt</span>
<span className="font-medium text-purple-600">
{Math.round((phase1Completion + phase2Completion) / 2)}%
</span>
<span className="font-medium text-purple-600">{completionPercentage}%</span>
</div>
<ProgressBar value={(phase1Completion + phase2Completion) / 2} />
<ProgressBar value={completionPercentage} />
</div>
)}
{/* Navigation */}
{/* Navigation - 5 Packages */}
<nav className="flex-1 overflow-y-auto">
{/* Phase 1 */}
<div>
<PhaseIndicator
phase={1}
title="Compliance Assessment"
completion={phase1Completion}
isActive={state.currentPhase === 1}
isExpanded={expandedPhases[1]}
onToggle={() => togglePhase(1)}
collapsed={collapsed}
/>
{expandedPhases[1] && (
<div className="py-1">
{phase1Steps.map(step => (
<StepItem
key={step.id}
step={step}
isActive={pathname === step.url}
isCompleted={state.completedSteps.includes(step.id)}
isLocked={isStepLocked(step)}
checkpointStatus={getStepCheckpointStatus(step)}
collapsed={collapsed}
/>
))}
</div>
)}
</div>
{SDK_PACKAGES.map(pkg => {
const steps = getVisibleSteps(pkg.id)
const isLocked = isPackageLocked(pkg.id)
const isActive = isPackageActive(pkg.id)
{/* Phase 2 */}
<div className="border-t border-gray-100">
<PhaseIndicator
phase={2}
title="Dokumentengenerierung"
completion={phase2Completion}
isActive={state.currentPhase === 2}
isExpanded={expandedPhases[2]}
onToggle={() => togglePhase(2)}
collapsed={collapsed}
/>
{expandedPhases[2] && (
<div className="py-1">
{phase2Steps.map(step => (
<StepItem
key={step.id}
step={step}
isActive={pathname === step.url}
isCompleted={state.completedSteps.includes(step.id)}
isLocked={isStepLocked(step)}
checkpointStatus={getStepCheckpointStatus(step)}
collapsed={collapsed}
/>
))}
return (
<div key={pkg.id} className={pkg.order > 1 ? 'border-t border-gray-100' : ''}>
<PackageIndicator
packageId={pkg.id}
order={pkg.order}
name={pkg.name}
icon={pkg.icon}
completion={packageCompletion[pkg.id]}
isActive={isActive}
isExpanded={expandedPackages[pkg.id]}
isLocked={isLocked}
onToggle={() => togglePackage(pkg.id)}
collapsed={collapsed}
/>
{expandedPackages[pkg.id] && !isLocked && (
<div className="py-1">
{steps.map(step => (
<StepItem
key={step.id}
step={step}
isActive={isStepActive(step.url)}
isCompleted={state.completedSteps.includes(step.id)}
isLocked={isStepLocked(step)}
checkpointStatus={getStepCheckpointStatus(step)}
collapsed={collapsed}
/>
))}
</div>
)}
</div>
)}
</div>
)
})}
{/* Additional Modules */}
<div className="border-t border-gray-100 py-2">