fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
* SDK Pipeline Sidebar
|
||||
*
|
||||
* Floating Action Button mit Drawer zur Visualisierung der SDK-Pipeline.
|
||||
* Zeigt die 5 Pakete mit Fortschritt und ermoeglicht schnelle Navigation.
|
||||
* Zeigt die zwei Phasen (Compliance Assessment & Dokumentengenerierung)
|
||||
* mit Fortschritt und ermöglicht schnelle Navigation.
|
||||
*
|
||||
* Features:
|
||||
* - Desktop (xl+): Fixierte Sidebar rechts
|
||||
@@ -14,7 +15,7 @@
|
||||
import Link from 'next/link'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useSDK, SDK_STEPS, SDK_PACKAGES, getStepsForPackage, type SDKStep, type SDKPackageId } from '@/lib/sdk'
|
||||
import { useSDK, SDK_STEPS, getStepsForPhase, type SDKStep } from '@/lib/sdk'
|
||||
|
||||
// =============================================================================
|
||||
// ICONS
|
||||
@@ -26,12 +27,6 @@ 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" />
|
||||
@@ -50,6 +45,31 @@ 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
|
||||
// =============================================================================
|
||||
@@ -62,6 +82,8 @@ interface StepItemProps {
|
||||
}
|
||||
|
||||
function StepItem({ step, isActive, isCompleted, onNavigate }: StepItemProps) {
|
||||
const icon = STEP_ICONS[step.id] || '•'
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={step.url}
|
||||
@@ -74,6 +96,7 @@ 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">
|
||||
@@ -88,91 +111,78 @@ function StepItem({ step, isActive, isCompleted, onNavigate }: StepItemProps) {
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PACKAGE SECTION
|
||||
// PHASE SECTION
|
||||
// =============================================================================
|
||||
|
||||
interface PackageSectionProps {
|
||||
pkg: (typeof SDK_PACKAGES)[number]
|
||||
interface PhaseSectionProps {
|
||||
phase: 1 | 2
|
||||
title: string
|
||||
steps: SDKStep[]
|
||||
completion: number
|
||||
currentStepId: string
|
||||
completedSteps: string[]
|
||||
isLocked: boolean
|
||||
onNavigate: () => void
|
||||
isExpanded: boolean
|
||||
onToggle: () => void
|
||||
}
|
||||
|
||||
function PackageSection({
|
||||
pkg,
|
||||
function PhaseSection({
|
||||
phase,
|
||||
title,
|
||||
steps,
|
||||
completion,
|
||||
currentStepId,
|
||||
completedSteps,
|
||||
isLocked,
|
||||
onNavigate,
|
||||
isExpanded,
|
||||
onToggle,
|
||||
}: PackageSectionProps) {
|
||||
}: PhaseSectionProps) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{/* Package Header */}
|
||||
{/* Phase Header */}
|
||||
<button
|
||||
onClick={onToggle}
|
||||
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'
|
||||
}`}
|
||||
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"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`w-7 h-7 rounded-full flex items-center justify-center text-sm ${
|
||||
isLocked
|
||||
? 'bg-gray-200 text-gray-400'
|
||||
: completion === 100
|
||||
className={`w-7 h-7 rounded-full flex items-center justify-center text-sm font-bold ${
|
||||
completion === 100
|
||||
? 'bg-green-500 text-white'
|
||||
: 'bg-purple-600 text-white'
|
||||
}`}
|
||||
>
|
||||
{isLocked ? <LockIcon /> : completion === 100 ? <CheckIcon /> : pkg.icon}
|
||||
{completion === 100 ? <CheckIcon /> : phase}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<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 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>
|
||||
</div>
|
||||
{!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>
|
||||
)}
|
||||
<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 */}
|
||||
{!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 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>
|
||||
|
||||
{/* Steps List */}
|
||||
{isExpanded && !isLocked && (
|
||||
{isExpanded && (
|
||||
<div className="space-y-1 pl-2">
|
||||
{steps.map(step => (
|
||||
<StepItem
|
||||
@@ -200,16 +210,39 @@ function PipelineFlow() {
|
||||
Datenfluss
|
||||
</div>
|
||||
<div className="p-3 bg-slate-50 dark:bg-gray-900 rounded-lg">
|
||||
<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 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>
|
||||
))}
|
||||
</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>
|
||||
@@ -226,72 +259,60 @@ interface SidebarContentProps {
|
||||
|
||||
function SidebarContent({ onNavigate }: SidebarContentProps) {
|
||||
const pathname = usePathname()
|
||||
const { state, packageCompletion } = useSDK()
|
||||
const [expandedPackages, setExpandedPackages] = useState<Record<SDKPackageId, boolean>>({
|
||||
'vorbereitung': true,
|
||||
'analyse': false,
|
||||
'dokumentation': false,
|
||||
'rechtliche-texte': false,
|
||||
'betrieb': false,
|
||||
const { state, phase1Completion, phase2Completion } = useSDK()
|
||||
const [expandedPhases, setExpandedPhases] = useState<Record<number, boolean>>({
|
||||
1: true,
|
||||
2: 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 package
|
||||
// Auto-expand current phase
|
||||
useEffect(() => {
|
||||
if (currentStep) {
|
||||
setExpandedPackages(prev => ({
|
||||
setExpandedPhases(prev => ({
|
||||
...prev,
|
||||
[currentStep.package]: true,
|
||||
[currentStep.phase]: true,
|
||||
}))
|
||||
}
|
||||
}, [currentStep])
|
||||
|
||||
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
|
||||
})
|
||||
const togglePhase = (phase: number) => {
|
||||
setExpandedPhases(prev => ({ ...prev, [phase]: !prev[phase] }))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* 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)}
|
||||
/>
|
||||
))}
|
||||
{/* 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)}
|
||||
/>
|
||||
|
||||
{/* Pipeline Flow */}
|
||||
<PipelineFlow />
|
||||
|
||||
Reference in New Issue
Block a user