feat(sdk): Profil-Summary + Use-Case-Workshop komplett auf Kachel-UI umstellen
Some checks failed
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) Failing after 38s
CI / test-python-backend-compliance (push) Successful in 41s
CI / test-python-document-crawler (push) Successful in 25s
CI / test-python-dsms-gateway (push) Successful in 22s

- Profil: Summary-Seite (Step 99) nach Abschluss statt direkter Sprung zu Scope
- Profil: "Dokumente generieren" Block entfernt
- Use Cases Step 1: Branche aus Profil auto-abgeleitet, 21 KI-Kategorien als Kacheln
- Use Cases Step 2: ~60 Datenkategorien in 10 Gruppen als Kacheln (Art. 9 orange)
- Use Cases Step 3: Rechtsgrundlage entfernt (SDK ermittelt), 16 Zweck-Kacheln
- Use Cases Step 4: Automatisierungsgrad als Single-Select-Kacheln
- Use Cases Step 5: Hosting/Region/Modellnutzung als Kacheln statt Dropdowns
- Use Cases Step 6: Transfer-Ziele + Mechanismus als Kacheln statt Checkbox/Dropdown
- Use Cases Step 7: Aufbewahrungsdauer als Kacheln statt Zahlenfeld
- Use Cases Step 8: Compliance-Dokumente als Multi-Select-Kacheln

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-10 19:03:47 +01:00
parent 24afed69c1
commit 6e7d0d9b14
2 changed files with 789 additions and 488 deletions

View File

@@ -2333,67 +2333,6 @@ function StepMachineBuilder({
// GENERATE DOCUMENTS BUTTON
// =============================================================================
const DOC_TYPES = [
{ id: 'dsfa', label: 'DSFA', desc: 'Datenschutz-Folgenabschätzung' },
{ id: 'vvt', label: 'VVT', desc: 'Verarbeitungsverzeichnis' },
{ id: 'tom', label: 'TOM', desc: 'Technisch-Organisatorische Maßnahmen' },
{ id: 'loeschfristen', label: 'Löschfristen', desc: 'Löschfristen-Katalog' },
{ id: 'obligation', label: 'Pflichten', desc: 'Compliance-Pflichten' },
]
function GenerateDocumentsButton() {
const [generating, setGenerating] = useState<string | null>(null)
const [results, setResults] = useState<Record<string, { ok: boolean; count: number }>>({})
const handleGenerate = async (docType: string) => {
setGenerating(docType)
try {
const res = await fetch(`/api/sdk/v1/compliance/generation/apply/${docType}`, { method: 'POST' })
if (res.ok) {
const data = await res.json()
setResults(prev => ({ ...prev, [docType]: { ok: true, count: data.change_requests_created || 0 } }))
} else {
setResults(prev => ({ ...prev, [docType]: { ok: false, count: 0 } }))
}
} catch {
setResults(prev => ({ ...prev, [docType]: { ok: false, count: 0 } }))
} finally {
setGenerating(null)
}
}
return (
<div className="space-y-2">
{DOC_TYPES.map(dt => (
<div key={dt.id} className="flex items-center justify-between">
<div>
<span className="text-sm font-medium text-gray-900">{dt.label}</span>
<span className="text-xs text-gray-500 ml-1">({dt.desc})</span>
</div>
{results[dt.id] ? (
<span className={`text-xs px-2 py-1 rounded ${results[dt.id].ok ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
{results[dt.id].ok ? `${results[dt.id].count} CR erstellt` : 'Fehler'}
</span>
) : (
<button
onClick={() => handleGenerate(dt.id)}
disabled={generating !== null}
className="px-3 py-1 text-xs bg-purple-600 text-white rounded hover:bg-purple-700 disabled:opacity-50"
>
{generating === dt.id ? 'Generiere...' : 'Generieren'}
</button>
)}
</div>
))}
{Object.keys(results).length > 0 && (
<a href="/sdk/change-requests" className="block text-center text-sm text-purple-600 hover:text-purple-800 font-medium mt-3">
Zur Änderungsanfragen-Inbox &rarr;
</a>
)}
</div>
)
}
// =============================================================================
// MAIN COMPONENT
// =============================================================================
@@ -2500,7 +2439,7 @@ export default function CompanyProfilePage() {
} as any
setFormData(backendProfile)
if (backendProfile.isComplete) {
setCurrentStep(6)
setCurrentStep(99)
}
return
}
@@ -2513,7 +2452,7 @@ export default function CompanyProfilePage() {
if (!cancelled && state.companyProfile) {
setFormData(state.companyProfile)
if (state.companyProfile.isComplete) {
setCurrentStep(6)
setCurrentStep(99)
}
}
}
@@ -2661,7 +2600,7 @@ export default function CompanyProfilePage() {
console.error('Failed to save company profile to backend:', err)
}
goToNextStep()
setCurrentStep(99) // Show summary
}
const handleBack = () => {
@@ -2741,6 +2680,114 @@ export default function CompanyProfilePage() {
const isLastStep = currentStep === lastStep || (currentStep === 6 && !showMachineBuilderStep)
// =========================================================================
// SUMMARY VIEW (Step 99) — shown after profile completion
// =========================================================================
if (currentStep === 99) {
const summaryItems = [
{ label: 'Firmenname', value: formData.companyName },
{ label: 'Rechtsform', value: formData.legalForm ? LEGAL_FORM_LABELS[formData.legalForm] : undefined },
{ label: 'Branche', value: formData.industry?.join(', ') },
{ label: 'Geschaeftsmodell', value: formData.businessModel ? BUSINESS_MODEL_LABELS[formData.businessModel]?.short : undefined },
{ label: 'Unternehmensgroesse', value: formData.companySize ? COMPANY_SIZE_LABELS[formData.companySize] : undefined },
{ label: 'Mitarbeiter', value: formData.employeeCount },
{ label: 'Hauptsitz', value: [formData.headquartersZip, formData.headquartersCity, formData.headquartersCountry === 'DE' ? 'Deutschland' : formData.headquartersCountry].filter(Boolean).join(', ') },
{ label: 'Zielmaerkte', value: formData.targetMarkets?.map(m => TARGET_MARKET_LABELS[m] || m).join(', ') },
{ label: 'Verantwortlicher', value: formData.isDataController ? 'Ja' : 'Nein' },
{ label: 'Auftragsverarbeiter', value: formData.isDataProcessor ? 'Ja' : 'Nein' },
{ label: 'DSB', value: formData.dpoName || 'Nicht angegeben' },
].filter(item => item.value && item.value.length > 0)
const missingFields: string[] = []
if (!formData.companyName) missingFields.push('Firmenname')
if (!formData.legalForm) missingFields.push('Rechtsform')
if (!formData.industry || formData.industry.length === 0) missingFields.push('Branche')
if (!formData.businessModel) missingFields.push('Geschaeftsmodell')
if (!formData.companySize) missingFields.push('Unternehmensgroesse')
return (
<div className="min-h-screen bg-gray-50 py-8">
<div className="max-w-4xl mx-auto px-4">
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900">Unternehmensprofil</h1>
</div>
{/* Success Banner */}
<div className={`rounded-xl border-2 p-6 mb-6 ${
formData.isComplete
? 'bg-green-50 border-green-300'
: 'bg-yellow-50 border-yellow-300'
}`}>
<div className="flex items-start gap-4">
<div className={`flex-shrink-0 w-12 h-12 rounded-full flex items-center justify-center ${
formData.isComplete ? 'bg-green-200' : 'bg-yellow-200'
}`}>
<span className="text-2xl">{formData.isComplete ? '\u2713' : '!'}</span>
</div>
<div>
<h2 className={`text-xl font-bold ${formData.isComplete ? 'text-green-800' : 'text-yellow-800'}`}>
{formData.isComplete
? 'Profil erfolgreich abgeschlossen'
: 'Profil unvollstaendig'
}
</h2>
<p className={`mt-1 ${formData.isComplete ? 'text-green-700' : 'text-yellow-700'}`}>
{formData.isComplete
? 'Alle Angaben wurden gespeichert. Sie koennen jetzt mit der Scope-Analyse fortfahren.'
: `Es fehlen noch Angaben: ${missingFields.join(', ')}.`
}
</p>
</div>
</div>
</div>
{/* Profile Summary */}
<div className="bg-white rounded-xl border border-gray-200 p-6 mb-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Zusammenfassung</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{summaryItems.map(item => (
<div key={item.label} className="flex flex-col">
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide">{item.label}</span>
<span className="text-sm text-gray-900 mt-0.5">{item.value}</span>
</div>
))}
</div>
</div>
{/* Actions */}
<div className="flex justify-between items-center">
<button
onClick={() => setCurrentStep(1)}
className="px-6 py-3 text-gray-600 hover:text-gray-900 border border-gray-300 rounded-lg hover:bg-gray-50"
>
Profil bearbeiten
</button>
{formData.isComplete ? (
<button
onClick={() => goToNextStep()}
className="px-8 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 font-medium"
>
Weiter zu Scope
</button>
) : (
<button
onClick={() => setCurrentStep(1)}
className="px-8 py-3 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 font-medium"
>
Fehlende Angaben ergaenzen
</button>
)}
</div>
</div>
</div>
)
}
// =========================================================================
// WIZARD VIEW (Steps 1-7)
// =========================================================================
return (
<div className="min-h-screen bg-gray-50 py-8">
<div className="max-w-6xl mx-auto px-4">
@@ -2855,16 +2902,6 @@ export default function CompanyProfilePage() {
</button>
</div>
{/* Generate Documents CTA (only when profile is complete) */}
{formData.isComplete && (
<div className="mt-6 bg-gradient-to-br from-purple-50 to-indigo-50 rounded-xl border border-purple-200 p-6">
<h3 className="font-semibold text-purple-900 mb-2">Dokumente generieren</h3>
<p className="text-sm text-purple-700 mb-4">
Basierend auf Ihrem Profil können DSFA, VVT, TOM, Löschfristen und Pflichten automatisch als Entwürfe generiert werden.
</p>
<GenerateDocumentsButton />
</div>
)}
</div>
</div>

File diff suppressed because it is too large Load Diff