Files
breakpilot-compliance/admin-compliance/app/sdk/loeschfristen/_components/EditorSections.tsx
Sharang Parnerkar 788172e869 refactor(admin): split SDKPipelineSidebar, SourcesTab, ScopeDecisionTab, ComplianceAdvisorWidget, EditorSections components
EditorSections.tsx (524 LOC) split into EditorSections.tsx (267 LOC) and
EditorSectionsB.tsx (279 LOC). DeletionLogicSection and StorageSection
moved to B; SetFn type canonical in B. EditorSections re-exports both
so all existing imports from EditorTab.tsx remain valid unchanged.
SDKPipelineSidebar (193), SourcesTab (311), ScopeDecisionTab (127),
ComplianceAdvisorWidget (265) were already under the 500-LOC hard cap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 00:10:34 +02:00

268 lines
14 KiB
TypeScript

'use client'
import React from 'react'
import {
LoeschfristPolicy,
STATUS_LABELS,
PolicyStatus,
ReviewInterval, REVIEW_INTERVAL_LABELS,
} from '@/lib/sdk/loeschfristen-types'
import { TagInput } from './TagInput'
// ---------------------------------------------------------------------------
// Shared type (defined in EditorSectionsB to avoid circular imports)
// ---------------------------------------------------------------------------
export type { SetFn } from './EditorSectionsB'
import type { SetFn } from './EditorSectionsB'
// ---------------------------------------------------------------------------
// Re-exports from EditorSectionsB (keeps existing import paths working)
// ---------------------------------------------------------------------------
export { DeletionLogicSection, StorageSection } from './EditorSectionsB'
// ---------------------------------------------------------------------------
// Sektion 1: Datenobjekt
// ---------------------------------------------------------------------------
export function DataObjectSection({ policy, set }: { policy: LoeschfristPolicy; set: SetFn }) {
return (
<div className="bg-white rounded-xl border border-gray-200 p-6 space-y-4">
<h3 className="text-lg font-semibold text-gray-900">1. Datenobjekt</h3>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Name des Datenobjekts *</label>
<input type="text" value={policy.dataObjectName} onChange={(e) => set('dataObjectName', e.target.value)}
placeholder="z.B. Bewerbungsunterlagen"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Beschreibung</label>
<textarea value={policy.description} onChange={(e) => set('description', e.target.value)} rows={3}
placeholder="Beschreibung des Datenobjekts und seiner Verarbeitung..."
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Betroffene Personengruppen</label>
<TagInput value={policy.affectedGroups} onChange={(v) => set('affectedGroups', v)}
placeholder="z.B. Bewerber, Mitarbeiter... (Enter zum Hinzufuegen)" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Datenkategorien</label>
<TagInput value={policy.dataCategories} onChange={(v) => set('dataCategories', v)}
placeholder="z.B. Stammdaten, Kontaktdaten... (Enter zum Hinzufuegen)" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Primaerer Verarbeitungszweck</label>
<textarea value={policy.primaryPurpose} onChange={(e) => set('primaryPurpose', e.target.value)} rows={2}
placeholder="Welchem Zweck dient die Verarbeitung dieser Daten?"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500" />
</div>
</div>
)
}
// ---------------------------------------------------------------------------
// Sektion 4: Verantwortlichkeit
// ---------------------------------------------------------------------------
export function ResponsibilitySection({ policy, set }: { policy: LoeschfristPolicy; set: SetFn }) {
return (
<div className="bg-white rounded-xl border border-gray-200 p-6 space-y-4">
<h3 className="text-lg font-semibold text-gray-900">4. Verantwortlichkeit</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Verantwortliche Rolle</label>
<input type="text" value={policy.responsibleRole} onChange={(e) => set('responsibleRole', e.target.value)}
placeholder="z.B. Datenschutzbeauftragter"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Verantwortliche Person</label>
<input type="text" value={policy.responsiblePerson} onChange={(e) => set('responsiblePerson', e.target.value)}
placeholder="Name der verantwortlichen Person"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500" />
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Freigabeprozess</label>
<textarea value={policy.releaseProcess} onChange={(e) => set('releaseProcess', e.target.value)} rows={3}
placeholder="Beschreibung des Freigabeprozesses fuer Loeschungen..."
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500" />
</div>
</div>
)
}
// ---------------------------------------------------------------------------
// Sektion 5: VVT-Verknuepfung
// ---------------------------------------------------------------------------
export function VVTLinkSection({
policy, pid, vvtActivities, updatePolicy,
}: {
policy: LoeschfristPolicy; pid: string; vvtActivities: any[]
updatePolicy: (id: string, updater: (p: LoeschfristPolicy) => LoeschfristPolicy) => void
}) {
return (
<div className="bg-white rounded-xl border border-gray-200 p-6 space-y-4">
<h3 className="text-lg font-semibold text-gray-900">5. VVT-Verknuepfung</h3>
{vvtActivities.length > 0 ? (
<div>
<p className="text-sm text-gray-500 mb-3">
Verknuepfen Sie diese Loeschfrist mit einer Verarbeitungstaetigkeit aus Ihrem VVT.
</p>
<div className="space-y-2">
{policy.linkedVVTActivityIds && policy.linkedVVTActivityIds.length > 0 && (
<div className="mb-3">
<label className="block text-xs font-medium text-gray-500 mb-1">Verknuepfte Taetigkeiten:</label>
<div className="flex flex-wrap gap-1">
{policy.linkedVVTActivityIds.map((vvtId: string) => {
const activity = vvtActivities.find((a: any) => a.id === vvtId)
return (
<span key={vvtId} className="inline-flex items-center gap-1 bg-blue-100 text-blue-800 text-xs font-medium px-2 py-0.5 rounded-full">
{activity?.name || vvtId}
<button type="button"
onClick={() => updatePolicy(pid, (p) => ({
...p, linkedVVTActivityIds: (p.linkedVVTActivityIds || []).filter((id: string) => id !== vvtId),
}))}
className="text-blue-600 hover:text-blue-900">x</button>
</span>
)
})}
</div>
</div>
)}
<select
onChange={(e) => {
const val = e.target.value
if (val && !(policy.linkedVVTActivityIds || []).includes(val)) {
updatePolicy(pid, (p) => ({ ...p, linkedVVTActivityIds: [...(p.linkedVVTActivityIds || []), val] }))
}
e.target.value = ''
}}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500">
<option value="">Verarbeitungstaetigkeit verknuepfen...</option>
{vvtActivities
.filter((a: any) => !(policy.linkedVVTActivityIds || []).includes(a.id))
.map((a: any) => (<option key={a.id} value={a.id}>{a.name || a.id}</option>))}
</select>
</div>
</div>
) : (
<p className="text-sm text-gray-400">
Kein VVT gefunden. Erstellen Sie zuerst ein Verarbeitungsverzeichnis, um hier Verknuepfungen herstellen zu koennen.
</p>
)}
</div>
)
}
// ---------------------------------------------------------------------------
// Sektion 5b: Auftragsverarbeiter-Verknuepfung
// ---------------------------------------------------------------------------
export function VendorLinkSection({
policy, pid, vendorList, updatePolicy,
}: {
policy: LoeschfristPolicy; pid: string; vendorList: Array<{id: string; name: string}>
updatePolicy: (id: string, updater: (p: LoeschfristPolicy) => LoeschfristPolicy) => void
}) {
return (
<div className="bg-white rounded-xl border border-gray-200 p-6 space-y-4">
<h3 className="text-lg font-semibold text-gray-900">5b. Verknuepfte Auftragsverarbeiter</h3>
{vendorList.length > 0 ? (
<div>
<p className="text-sm text-gray-500 mb-3">
Verknuepfen Sie diese Loeschfrist mit relevanten Auftragsverarbeitern.
</p>
<div className="space-y-2">
{policy.linkedVendorIds && policy.linkedVendorIds.length > 0 && (
<div className="mb-3">
<label className="block text-xs font-medium text-gray-500 mb-1">Verknuepfte Auftragsverarbeiter:</label>
<div className="flex flex-wrap gap-1">
{policy.linkedVendorIds.map((vendorId: string) => {
const vendor = vendorList.find((v) => v.id === vendorId)
return (
<span key={vendorId} className="inline-flex items-center gap-1 bg-orange-100 text-orange-800 text-xs font-medium px-2 py-0.5 rounded-full">
{vendor?.name || vendorId}
<button type="button"
onClick={() => updatePolicy(pid, (p) => ({
...p, linkedVendorIds: (p.linkedVendorIds || []).filter((id: string) => id !== vendorId),
}))}
className="text-orange-600 hover:text-orange-900">x</button>
</span>
)
})}
</div>
</div>
)}
<select
onChange={(e) => {
const val = e.target.value
if (val && !(policy.linkedVendorIds || []).includes(val)) {
updatePolicy(pid, (p) => ({ ...p, linkedVendorIds: [...(p.linkedVendorIds || []), val] }))
}
e.target.value = ''
}}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500">
<option value="">Auftragsverarbeiter verknuepfen...</option>
{vendorList
.filter((v) => !(policy.linkedVendorIds || []).includes(v.id))
.map((v) => (<option key={v.id} value={v.id}>{v.name || v.id}</option>))}
</select>
</div>
</div>
) : (
<p className="text-sm text-gray-400">
Keine Auftragsverarbeiter gefunden. Erstellen Sie zuerst Auftragsverarbeiter im Vendor-Compliance-Modul, um hier Verknuepfungen herstellen zu koennen.
</p>
)}
</div>
)
}
// ---------------------------------------------------------------------------
// Sektion 6: Review-Einstellungen
// ---------------------------------------------------------------------------
export function ReviewSection({ policy, set }: { policy: LoeschfristPolicy; set: SetFn }) {
return (
<div className="bg-white rounded-xl border border-gray-200 p-6 space-y-4">
<h3 className="text-lg font-semibold text-gray-900">6. Review-Einstellungen</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Status</label>
<select value={policy.status} onChange={(e) => set('status', e.target.value as PolicyStatus)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500">
{Object.entries(STATUS_LABELS).map(([key, label]) => (<option key={key} value={key}>{label}</option>))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Pruefintervall</label>
<select value={policy.reviewInterval} onChange={(e) => set('reviewInterval', e.target.value as ReviewInterval)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500">
{Object.entries(REVIEW_INTERVAL_LABELS).map(([key, label]) => (<option key={key} value={key}>{label}</option>))}
</select>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Letzte Pruefung</label>
<input type="date" value={policy.lastReviewDate} onChange={(e) => set('lastReviewDate', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Naechste Pruefung</label>
<input type="date" value={policy.nextReviewDate} onChange={(e) => set('nextReviewDate', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500" />
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Tags</label>
<TagInput value={policy.tags} onChange={(v) => set('tags', v)} placeholder="Tags hinzufuegen (Enter zum Bestaetigen)" />
</div>
</div>
)
}