feat: Fertigstellungsgrad für Betrieb-Module im SDK Flow
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 35s
CI / test-python-backend-compliance (push) Successful in 30s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 17s

flow-data.ts:
- completion?: number Feld im SDKFlowStep Interface
- Completion-Werte für 7 Betrieb-Module (Academy/Training ausgenommen):
  consent-management: 75%, dsr: 65%, whistleblower: 60%,
  incidents: 55%, notfallplan: 50%, vendor-compliance: 35%, escalations: 30%
- Bewertungsbasis: Frontend-Reifegrad + API-Abdeckung + Backend-Persistenz

page.tsx:
- completionColor() + completionLabel() Hilfsfunktionen (4-stufige Farbskala)
- BetriebOverviewPanel: Übersicht mit Balken + Ø-Wert (erscheint bei Filter=Betrieb)
- DetailPanel: Fertigstellungsgrad-Abschnitt ganz oben (Balken + Label)
- ReactFlow-Nodes: % + Mini-Fortschrittsbalken im Node-Label
- Step-Tabelle: 24px-Fortschrittsbalken-Spalte für alle Steps mit completion

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-03 12:08:27 +01:00
parent 393eab6acd
commit 79b423e549
2 changed files with 186 additions and 0 deletions

View File

@@ -39,6 +39,13 @@ export interface SDKFlowStep {
generates?: string[]
isOptional?: boolean
url: string
/**
* Fertigstellungsgrad in Prozent (0100).
* Nur für Betrieb-Module gesetzt (außer academy/training).
* Berechnung: Frontend-Reifegrad + Backend-Persistenz + API-Abdeckung.
*/
completion?: number
}
// =============================================================================
@@ -632,6 +639,7 @@ export const SDK_FLOW_STEPS: SDKFlowStep[] = [
ragPurpose: 'Art. 15-21 DSGVO Betroffenenrechte',
isOptional: false,
url: '/sdk/dsr',
completion: 65,
},
{
id: 'escalations',
@@ -653,6 +661,7 @@ export const SDK_FLOW_STEPS: SDKFlowStep[] = [
ragCollections: [],
isOptional: false,
url: '/sdk/escalations',
completion: 30,
},
{
id: 'vendor-compliance',
@@ -675,6 +684,7 @@ export const SDK_FLOW_STEPS: SDKFlowStep[] = [
ragPurpose: 'AVV-Vorlagen und Pruefkataloge',
isOptional: false,
url: '/sdk/vendor-compliance',
completion: 35,
},
{
id: 'consent-management',
@@ -696,6 +706,7 @@ export const SDK_FLOW_STEPS: SDKFlowStep[] = [
ragCollections: [],
isOptional: false,
url: '/sdk/consent-management',
completion: 75,
},
{
id: 'notfallplan',
@@ -718,6 +729,7 @@ export const SDK_FLOW_STEPS: SDKFlowStep[] = [
generates: ['Notfallplan (PDF)'],
isOptional: false,
url: '/sdk/notfallplan',
completion: 50,
},
{
id: 'incidents',
@@ -739,6 +751,7 @@ export const SDK_FLOW_STEPS: SDKFlowStep[] = [
ragCollections: [],
isOptional: false,
url: '/sdk/incidents',
completion: 55,
},
{
id: 'whistleblower',
@@ -760,6 +773,7 @@ export const SDK_FLOW_STEPS: SDKFlowStep[] = [
ragCollections: [],
isOptional: false,
url: '/sdk/whistleblower',
completion: 60,
},
{
id: 'academy',

View File

@@ -77,6 +77,98 @@ function getStepPosition(step: SDKFlowStep): { x: number; y: number } {
}
}
// =============================================================================
// HELPERS — FERTIGSTELLUNGSGRAD
// =============================================================================
function completionColor(pct: number): string {
if (pct >= 70) return '#22c55e' // green
if (pct >= 50) return '#84cc16' // lime
if (pct >= 35) return '#f59e0b' // amber
return '#ef4444' // red
}
function completionLabel(pct: number): string {
if (pct >= 70) return 'Fortgeschritten'
if (pct >= 50) return 'In Entwicklung'
if (pct >= 35) return 'Begonnen'
return 'Frühe Phase'
}
// =============================================================================
// BETRIEB OVERVIEW PANEL
// =============================================================================
function BetriebOverviewPanel() {
const betriebWithCompletion = SDK_FLOW_STEPS.filter(
s => s.package === 'betrieb' && s.completion !== undefined
).sort((a, b) => (b.completion ?? 0) - (a.completion ?? 0))
const avg = Math.round(
betriebWithCompletion.reduce((sum, s) => sum + (s.completion ?? 0), 0) /
betriebWithCompletion.length
)
return (
<div className="bg-white rounded-xl border border-slate-200 shadow-sm p-5">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="font-semibold text-slate-800">
Betrieb Fertigstellungsgrad
</h3>
<p className="text-xs text-slate-400 mt-0.5">
{betriebWithCompletion.length} Module bewertet · Academy &amp; Training ausgeschlossen
</p>
</div>
<div className="text-right">
<div className="text-2xl font-bold" style={{ color: completionColor(avg) }}>
{avg}%
</div>
<div className="text-xs text-slate-400">Ø Fertigstellung</div>
</div>
</div>
<div className="space-y-3">
{betriebWithCompletion.map(step => {
const pct = step.completion ?? 0
const color = completionColor(pct)
return (
<div key={step.id}>
<div className="flex items-center justify-between mb-1">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-slate-700">{step.name}</span>
<span
className="px-1.5 py-0.5 rounded text-[10px] font-medium"
style={{ background: color + '22', color }}
>
{completionLabel(pct)}
</span>
</div>
<span className="text-sm font-bold" style={{ color }}>{pct}%</span>
</div>
<div className="h-2 bg-slate-100 rounded-full overflow-hidden">
<div
className="h-full rounded-full transition-all"
style={{ width: `${pct}%`, background: color }}
/>
</div>
<div className="flex items-center gap-3 mt-1 text-[10px] text-slate-400">
{step.checkpointId && (
<span className="bg-red-50 text-red-500 px-1 rounded">{step.checkpointId}</span>
)}
<span>{step.dbTables.length > 0 ? `DB: ${step.dbTables.length} Tabellen` : 'Kein DB-Backend'}</span>
{step.ragCollections.length > 0 && (
<span className="text-green-600">RAG: {step.ragCollections.length}</span>
)}
</div>
</div>
)
})}
</div>
</div>
)
}
// =============================================================================
// DETAIL PANEL
// =============================================================================
@@ -120,6 +212,38 @@ function DetailPanel({
</div>
<div className="p-4 space-y-4">
{/* Fertigstellungsgrad */}
{step.completion !== undefined && (
<div className="bg-slate-50 rounded-lg p-3">
<div className="flex items-center justify-between mb-1.5">
<span className="text-xs font-semibold text-slate-500 uppercase">
Fertigstellungsgrad
</span>
<span
className="text-lg font-bold"
style={{ color: completionColor(step.completion) }}
>
{step.completion}%
</span>
</div>
<div className="h-2.5 bg-slate-200 rounded-full overflow-hidden">
<div
className="h-full rounded-full"
style={{
width: `${step.completion}%`,
background: completionColor(step.completion),
}}
/>
</div>
<div
className="text-xs mt-1.5 font-medium"
style={{ color: completionColor(step.completion) }}
>
{completionLabel(step.completion)}
</div>
</div>
)}
{/* Beschreibung */}
<div>
<p className="text-sm text-slate-700 leading-relaxed">{step.description}</p>
@@ -359,6 +483,28 @@ export default function SDKFlowPage() {
{step.checkpointId || ''}
{badge}
</div>
{step.completion !== undefined && (
<div className="mt-1.5 px-1">
<div
className="flex items-center justify-center gap-1 text-[9px] font-bold mb-0.5"
style={{ color: isSelected ? 'rgba(255,255,255,0.9)' : completionColor(step.completion) }}
>
{step.completion}%
</div>
<div
className="h-1 rounded-full overflow-hidden"
style={{ background: isSelected ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.1)' }}
>
<div
className="h-full rounded-full"
style={{
width: `${step.completion}%`,
background: isSelected ? 'rgba(255,255,255,0.8)' : completionColor(step.completion),
}}
/>
</div>
</div>
)}
</div>
),
},
@@ -771,6 +917,9 @@ export default function SDKFlowPage() {
)}
</div>
{/* Betrieb Fertigstellungsgrad Übersicht */}
{packageFilter === 'betrieb' && <BetriebOverviewPanel />}
{/* Step Table (below flow) */}
<div className="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden">
<div className="px-4 py-3 bg-slate-50 border-b">
@@ -831,6 +980,29 @@ export default function SDKFlowPage() {
)}
</div>
</div>
{step.completion !== undefined ? (
<div className="shrink-0 w-24 mr-2">
<div className="flex items-center justify-between mb-0.5">
<span
className="text-[10px] font-bold"
style={{ color: completionColor(step.completion) }}
>
{step.completion}%
</span>
</div>
<div className="h-1.5 bg-slate-100 rounded-full overflow-hidden">
<div
className="h-full rounded-full"
style={{
width: `${step.completion}%`,
background: completionColor(step.completion),
}}
/>
</div>
</div>
) : (
<div className="shrink-0 w-24 mr-2" />
)}
<span
className="px-2 py-0.5 rounded text-[10px] font-medium shrink-0"
style={{ background: pkg.color.bg, color: pkg.color.text }}