diff --git a/admin-v2/app/(sdk)/sdk/dsfa/[id]/page.tsx b/admin-v2/app/(sdk)/sdk/dsfa/[id]/page.tsx index 9f4f18a..e9fcdfb 100644 --- a/admin-v2/app/(sdk)/sdk/dsfa/[id]/page.tsx +++ b/admin-v2/app/(sdk)/sdk/dsfa/[id]/page.tsx @@ -21,8 +21,17 @@ import { removeDSFARisk, addDSFAMitigation, updateDSFAMitigationStatus, + updateDSFA, } from '@/lib/sdk/dsfa/api' -import { RiskMatrix, ApprovalPanel } from '@/components/sdk/dsfa' +import { + RiskMatrix, + ApprovalPanel, + DSFASidebar, + ThresholdAnalysisSection, + StakeholderConsultationSection, + Art36Warning, + ReviewScheduleSection, +} from '@/components/sdk/dsfa' // ============================================================================= // SECTION EDITORS @@ -1013,7 +1022,7 @@ export default function DSFAEditorPage() { const [dsfa, setDSFA] = useState(null) const [isLoading, setIsLoading] = useState(true) const [isSaving, setIsSaving] = useState(false) - const [activeSection, setActiveSection] = useState(1) + const [activeSection, setActiveSection] = useState(0) // Start at Section 0: Threshold Analysis const [saveMessage, setSaveMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null) // Load DSFA data @@ -1111,6 +1120,26 @@ export default function DSFAEditorPage() { needs_update: 'bg-orange-100 text-orange-700', } + // Handler for generic DSFA updates (used by new section components) + const handleGenericUpdate = useCallback(async (data: Record) => { + if (!dsfa) return + + setIsSaving(true) + setSaveMessage(null) + + try { + const updated = await updateDSFA(dsfaId, data as Partial) + setDSFA(updated) + setSaveMessage({ type: 'success', text: 'Abschnitt gespeichert' }) + setTimeout(() => setSaveMessage(null), 3000) + } catch (error) { + console.error('Failed to update DSFA:', error) + setSaveMessage({ type: 'error', text: 'Fehler beim Speichern' }) + } finally { + setIsSaving(false) + } + }, [dsfa, dsfaId]) + return (
{/* Header */} @@ -1162,64 +1191,24 @@ export default function DSFAEditorPage() { )}
- {/* Main Content: 2/3 + 1/3 Layout */} -
- {/* Left Column - 2/3 */} -
- {/* Section Tabs */} + {/* Main Content: Sidebar + Content Layout */} +
+ {/* Left Column - Sidebar (1/4) */} +
+ +
+ + {/* Right Column - Content (3/4) */} +
+ {/* Section Content Card */}
-
- -
- {/* Section Header */} {sectionConfig && ( -
+

@@ -1236,6 +1225,16 @@ export default function DSFAEditorPage() { {/* Section Content */}
+ {/* Section 0: Threshold Analysis (NEW) */} + {activeSection === 0 && ( + + )} + + {/* Sections 1-4: Existing */} {activeSection === 1 && ( )} + + {/* Section 5: Stakeholder Consultation (NEW) */} {activeSection === 5 && ( - handleSectionUpdate(5, data)} + onUpdate={handleGenericUpdate} + isSubmitting={isSaving} + /> + )} + + {/* Section 6: DPO & Authority Consultation */} + {activeSection === 6 && ( +
+ {/* Original Section 5 Editor (DPO Opinion) */} + handleSectionUpdate(5, data)} + isSubmitting={isSaving} + /> + + {/* Art. 36 Warning (NEW) */} +
+

+ Art. 36 Behoerdenkonsultation +

+ +
+
+ )} + + {/* Section 7: Review & Maintenance (NEW) */} + {activeSection === 7 && ( + )}

-
- {/* Right Column - 1/3 Sidebar */} -
- {/* Approval Panel */} - - - {/* Quick Info */} -
-

Informationen

-
-
- Erstellt am - - {new Date(dsfa.created_at).toLocaleDateString('de-DE')} - -
-
- Zuletzt aktualisiert - - {new Date(dsfa.updated_at).toLocaleDateString('de-DE')} - -
-
- Risiken - {(dsfa.risks || []).length} -
-
- Massnahmen - {(dsfa.mitigations || []).length} -
+ {/* Bottom Actions Row */} +
+ {/* Navigation */} +
+ {activeSection > 0 && ( + + )} + {activeSection < 7 && ( + + )}
-
- {/* Export Options */} -
-

Export

-
- -
diff --git a/admin-v2/components/sdk/dsfa/Art36Warning.tsx b/admin-v2/components/sdk/dsfa/Art36Warning.tsx new file mode 100644 index 0000000..c831b0f --- /dev/null +++ b/admin-v2/components/sdk/dsfa/Art36Warning.tsx @@ -0,0 +1,372 @@ +'use client' + +import React, { useState } from 'react' +import { + DSFA, + DSFAConsultationRequirement, + DSFA_AUTHORITY_RESOURCES, + getFederalStateOptions, + getAuthorityResource, +} from '@/lib/sdk/dsfa/types' + +interface Art36WarningProps { + dsfa: DSFA + onUpdate: (data: Record) => Promise + isSubmitting: boolean +} + +export function Art36Warning({ dsfa, onUpdate, isSubmitting }: Art36WarningProps) { + const isHighResidualRisk = dsfa.residual_risk_level === 'high' || dsfa.residual_risk_level === 'very_high' + const consultationReq = dsfa.consultation_requirement + + const [federalState, setFederalState] = useState(dsfa.federal_state || '') + const [authorityNotified, setAuthorityNotified] = useState(consultationReq?.authority_notified || false) + const [notificationDate, setNotificationDate] = useState(consultationReq?.notification_date || '') + const [waitingPeriodObserved, setWaitingPeriodObserved] = useState(consultationReq?.waiting_period_observed || false) + const [authorityResponse, setAuthorityResponse] = useState(consultationReq?.authority_response || '') + const [recommendations, setRecommendations] = useState(consultationReq?.authority_recommendations || []) + const [newRecommendation, setNewRecommendation] = useState('') + + const federalStateOptions = getFederalStateOptions() + const selectedAuthority = federalState ? getAuthorityResource(federalState) : null + + const handleSave = async () => { + const requirement: DSFAConsultationRequirement = { + high_residual_risk: isHighResidualRisk, + consultation_required: isHighResidualRisk, + consultation_reason: isHighResidualRisk + ? 'Trotz geplanter Massnahmen verbleibt ein hohes Restrisiko. Gem. Art. 36 Abs. 1 DSGVO ist vor der Verarbeitung die Aufsichtsbehoerde zu konsultieren.' + : undefined, + authority_notified: authorityNotified, + notification_date: notificationDate || undefined, + authority_response: authorityResponse || undefined, + authority_recommendations: recommendations.length > 0 ? recommendations : undefined, + waiting_period_observed: waitingPeriodObserved, + } + + await onUpdate({ + consultation_requirement: requirement, + federal_state: federalState, + authority_resource_id: federalState, + }) + } + + const addRecommendation = () => { + if (newRecommendation.trim()) { + setRecommendations([...recommendations, newRecommendation.trim()]) + setNewRecommendation('') + } + } + + const removeRecommendation = (index: number) => { + setRecommendations(recommendations.filter((_, i) => i !== index)) + } + + // Don't show if residual risk is not high + if (!isHighResidualRisk) { + return ( +
+
+
+ + + +
+
+

Keine Behoerdenkonsultation erforderlich

+

+ Das Restrisiko nach Umsetzung der geplanten Massnahmen ist nicht hoch. + Eine vorherige Konsultation der Aufsichtsbehoerde gem. Art. 36 DSGVO ist nicht erforderlich. +

+
+
+
+ ) + } + + return ( +
+ {/* Warning Banner */} +
+
+
+ + + +
+
+

+ Behoerdenkonsultation erforderlich (Art. 36 DSGVO) +

+

+ Das Restrisiko nach Umsetzung aller geplanten Massnahmen wurde als + {dsfa.residual_risk_level === 'very_high' ? 'SEHR HOCH' : 'HOCH'} + eingestuft. +

+

+ Gemaess Art. 36 Abs. 1 DSGVO muessen Sie vor Beginn der Verarbeitung die + zustaendige Aufsichtsbehoerde konsultieren. Die Behoerde hat eine Frist von 8 Wochen + zur Stellungnahme (Art. 36 Abs. 2 DSGVO). +

+
+
+
+ + {/* Federal State Selection */} +
+

Zustaendige Aufsichtsbehoerde

+ +
+ + +
+ + {/* Authority Details */} + {selectedAuthority && ( +
+
{selectedAuthority.name}
+

({selectedAuthority.shortName})

+ +
+ + + + + DSFA-Informationen + + + {selectedAuthority.publicSectorListUrl && ( + + + + + Muss-Liste (oeffentlich) + + )} + + {selectedAuthority.privateSectorListUrl && ( + + + + + Muss-Liste (nicht-oeffentlich) + + )} + + {selectedAuthority.templateUrl && ( + + + + + DSFA-Vorlage + + )} +
+ + {selectedAuthority.additionalResources && selectedAuthority.additionalResources.length > 0 && ( +
+

Weitere Ressourcen:

+
+ {selectedAuthority.additionalResources.map((resource, idx) => ( + + {resource.title} + + ))} +
+
+ )} +
+ )} +
+ + {/* Consultation Documentation */} +
+

Konsultation dokumentieren

+ +
+ {/* Authority Notified Checkbox */} + + + {authorityNotified && ( + <> + {/* Notification Date */} +
+ + setNotificationDate(e.target.value)} + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500" + /> +
+ + {/* 8-Week Waiting Period */} + + + {/* Authority Response */} +
+ +