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
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:
@@ -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 →
|
||||
</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
Reference in New Issue
Block a user