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>
This commit is contained in:
@@ -2,21 +2,25 @@
|
||||
|
||||
import React from 'react'
|
||||
import {
|
||||
LoeschfristPolicy, LegalHold, StorageLocation,
|
||||
RETENTION_DRIVER_META, RetentionDriverType, DeletionMethodType,
|
||||
DELETION_METHOD_LABELS, STATUS_LABELS,
|
||||
STORAGE_LOCATION_LABELS, StorageLocationType, PolicyStatus,
|
||||
ReviewInterval, DeletionTriggerLevel, RetentionUnit,
|
||||
LegalHoldStatus, REVIEW_INTERVAL_LABELS,
|
||||
LoeschfristPolicy,
|
||||
STATUS_LABELS,
|
||||
PolicyStatus,
|
||||
ReviewInterval, REVIEW_INTERVAL_LABELS,
|
||||
} from '@/lib/sdk/loeschfristen-types'
|
||||
import { TagInput } from './TagInput'
|
||||
import { renderTriggerBadge } from './UebersichtTab'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared type
|
||||
// Shared type (defined in EditorSectionsB to avoid circular imports)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type SetFn = <K extends keyof LoeschfristPolicy>(key: K, val: LoeschfristPolicy[K]) => void
|
||||
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
|
||||
@@ -58,267 +62,6 @@ export function DataObjectSection({ policy, set }: { policy: LoeschfristPolicy;
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sektion 2: 3-stufige Loeschlogik
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function DeletionLogicSection({
|
||||
policy, pid, set, updateLegalHoldItem, addLegalHold, removeLegalHold,
|
||||
}: {
|
||||
policy: LoeschfristPolicy; pid: string; set: SetFn
|
||||
updateLegalHoldItem: (idx: number, updater: (h: LegalHold) => LegalHold) => void
|
||||
addLegalHold: (policyId: string) => void
|
||||
removeLegalHold: (policyId: string, idx: number) => 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">2. 3-stufige Loeschlogik</h3>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Loeschausloeser (Trigger-Stufe)</label>
|
||||
<div className="space-y-2">
|
||||
{(['PURPOSE_END', 'RETENTION_DRIVER', 'LEGAL_HOLD'] as DeletionTriggerLevel[]).map((trigger) => (
|
||||
<label key={trigger} className="flex items-start gap-3 p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer">
|
||||
<input type="radio" name={`trigger-${pid}`} checked={policy.deletionTrigger === trigger}
|
||||
onChange={() => set('deletionTrigger', trigger)} className="mt-0.5 text-purple-600 focus:ring-purple-500" />
|
||||
<div>
|
||||
<div className="flex items-center gap-2">{renderTriggerBadge(trigger)}</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
{trigger === 'PURPOSE_END' && 'Loeschung nach Wegfall des Verarbeitungszwecks'}
|
||||
{trigger === 'RETENTION_DRIVER' && 'Loeschung nach Ablauf gesetzlicher oder vertraglicher Aufbewahrungsfrist'}
|
||||
{trigger === 'LEGAL_HOLD' && 'Loeschung durch aktiven Legal Hold blockiert'}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{policy.deletionTrigger === 'RETENTION_DRIVER' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Aufbewahrungstreiber</label>
|
||||
<select value={policy.retentionDriver}
|
||||
onChange={(e) => {
|
||||
const driver = e.target.value as RetentionDriverType
|
||||
const meta = RETENTION_DRIVER_META[driver]
|
||||
set('retentionDriver', driver)
|
||||
if (meta) {
|
||||
set('retentionDuration', meta.defaultDuration)
|
||||
set('retentionUnit', meta.defaultUnit as RetentionUnit)
|
||||
set('retentionDescription', meta.description)
|
||||
}
|
||||
}}
|
||||
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="">Bitte waehlen...</option>
|
||||
{Object.entries(RETENTION_DRIVER_META).map(([key, meta]) => (
|
||||
<option key={key} value={key}>{meta.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Aufbewahrungsdauer</label>
|
||||
<input type="number" min={0} value={policy.retentionDuration}
|
||||
onChange={(e) => set('retentionDuration', parseInt(e.target.value) || 0)}
|
||||
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">Einheit</label>
|
||||
<select value={policy.retentionUnit} onChange={(e) => set('retentionUnit', e.target.value as RetentionUnit)}
|
||||
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="DAYS">Tage</option>
|
||||
<option value="MONTHS">Monate</option>
|
||||
<option value="YEARS">Jahre</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Beschreibung der Aufbewahrungspflicht</label>
|
||||
<input type="text" value={policy.retentionDescription} onChange={(e) => set('retentionDescription', e.target.value)}
|
||||
placeholder="z.B. Handelsrechtliche Aufbewahrungspflicht gem. HGB"
|
||||
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">Startereignis (Fristbeginn)</label>
|
||||
<input type="text" value={policy.startEvent} onChange={(e) => set('startEvent', e.target.value)}
|
||||
placeholder="z.B. Ende des Geschaeftsjahres, Vertragsende..."
|
||||
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>
|
||||
|
||||
{/* Legal Holds */}
|
||||
<div className="border-t border-gray-200 pt-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="text-sm font-semibold text-gray-800">Legal Holds</h4>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" checked={policy.hasActiveLegalHold}
|
||||
onChange={(e) => set('hasActiveLegalHold', e.target.checked)}
|
||||
className="text-purple-600 focus:ring-purple-500 rounded" />
|
||||
Aktiver Legal Hold
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{policy.legalHolds.length > 0 && (
|
||||
<div className="overflow-x-auto mb-3">
|
||||
<table className="w-full text-sm border border-gray-200 rounded-lg">
|
||||
<thead>
|
||||
<tr className="bg-gray-50">
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Bezeichnung</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Grund</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Status</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Erstellt am</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{policy.legalHolds.map((hold, idx) => (
|
||||
<tr key={idx} className="border-t border-gray-100">
|
||||
<td className="px-3 py-2">
|
||||
<input type="text" value={hold.name}
|
||||
onChange={(e) => updateLegalHoldItem(idx, (h) => ({ ...h, name: e.target.value }))}
|
||||
placeholder="Bezeichnung"
|
||||
className="w-full px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500" />
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<input type="text" value={hold.reason}
|
||||
onChange={(e) => updateLegalHoldItem(idx, (h) => ({ ...h, reason: e.target.value }))}
|
||||
placeholder="Grund"
|
||||
className="w-full px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500" />
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<select value={hold.status}
|
||||
onChange={(e) => updateLegalHoldItem(idx, (h) => ({ ...h, status: e.target.value as LegalHoldStatus }))}
|
||||
className="px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500">
|
||||
<option value="ACTIVE">Aktiv</option>
|
||||
<option value="RELEASED">Aufgehoben</option>
|
||||
<option value="EXPIRED">Abgelaufen</option>
|
||||
</select>
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<input type="date" value={hold.createdAt}
|
||||
onChange={(e) => updateLegalHoldItem(idx, (h) => ({ ...h, createdAt: e.target.value }))}
|
||||
className="px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500" />
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<button onClick={() => removeLegalHold(pid, idx)}
|
||||
className="text-red-500 hover:text-red-700 text-sm font-medium">Entfernen</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button onClick={() => addLegalHold(pid)} className="text-sm text-purple-600 hover:text-purple-800 font-medium">
|
||||
+ Legal Hold hinzufuegen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sektion 3: Speicherorte & Loeschmethode
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function StorageSection({
|
||||
policy, pid, set, updateStorageLocationItem, addStorageLocation, removeStorageLocation,
|
||||
}: {
|
||||
policy: LoeschfristPolicy; pid: string; set: SetFn
|
||||
updateStorageLocationItem: (idx: number, updater: (s: StorageLocation) => StorageLocation) => void
|
||||
addStorageLocation: (policyId: string) => void
|
||||
removeStorageLocation: (policyId: string, idx: number) => 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">3. Speicherorte & Loeschmethode</h3>
|
||||
|
||||
{policy.storageLocations.length > 0 && (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm border border-gray-200 rounded-lg">
|
||||
<thead>
|
||||
<tr className="bg-gray-50">
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Name</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Typ</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Backup</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Anbieter</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Loeschfaehig</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{policy.storageLocations.map((loc, idx) => (
|
||||
<tr key={idx} className="border-t border-gray-100">
|
||||
<td className="px-3 py-2">
|
||||
<input type="text" value={loc.name}
|
||||
onChange={(e) => updateStorageLocationItem(idx, (s) => ({ ...s, name: e.target.value }))}
|
||||
placeholder="Name"
|
||||
className="w-full px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500" />
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<select value={loc.type}
|
||||
onChange={(e) => updateStorageLocationItem(idx, (s) => ({ ...s, type: e.target.value as StorageLocationType }))}
|
||||
className="px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500">
|
||||
{Object.entries(STORAGE_LOCATION_LABELS).map(([key, label]) => (
|
||||
<option key={key} value={key}>{label}</option>
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-center">
|
||||
<input type="checkbox" checked={loc.isBackup}
|
||||
onChange={(e) => updateStorageLocationItem(idx, (s) => ({ ...s, isBackup: e.target.checked }))}
|
||||
className="text-purple-600 focus:ring-purple-500 rounded" />
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<input type="text" value={loc.provider}
|
||||
onChange={(e) => updateStorageLocationItem(idx, (s) => ({ ...s, provider: e.target.value }))}
|
||||
placeholder="Anbieter"
|
||||
className="w-full px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500" />
|
||||
</td>
|
||||
<td className="px-3 py-2 text-center">
|
||||
<input type="checkbox" checked={loc.deletionCapable}
|
||||
onChange={(e) => updateStorageLocationItem(idx, (s) => ({ ...s, deletionCapable: e.target.checked }))}
|
||||
className="text-purple-600 focus:ring-purple-500 rounded" />
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<button onClick={() => removeStorageLocation(pid, idx)}
|
||||
className="text-red-500 hover:text-red-700 text-sm font-medium">Entfernen</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button onClick={() => addStorageLocation(pid)} className="text-sm text-purple-600 hover:text-purple-800 font-medium">
|
||||
+ Speicherort hinzufuegen
|
||||
</button>
|
||||
|
||||
<div className="border-t border-gray-200 pt-4 grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Loeschmethode</label>
|
||||
<select value={policy.deletionMethod} onChange={(e) => set('deletionMethod', e.target.value as DeletionMethodType)}
|
||||
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(DELETION_METHOD_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">Details zur Loeschmethode</label>
|
||||
<textarea value={policy.deletionMethodDetail} onChange={(e) => set('deletionMethodDetail', e.target.value)} rows={2}
|
||||
placeholder="Weitere Details zum Loeschverfahren..."
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sektion 4: Verantwortlichkeit
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import {
|
||||
LoeschfristPolicy, LegalHold, StorageLocation,
|
||||
RETENTION_DRIVER_META, RetentionDriverType, DeletionMethodType,
|
||||
DELETION_METHOD_LABELS,
|
||||
STORAGE_LOCATION_LABELS, StorageLocationType,
|
||||
RetentionUnit,
|
||||
LegalHoldStatus,
|
||||
} from '@/lib/sdk/loeschfristen-types'
|
||||
import { renderTriggerBadge } from './UebersichtTab'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared type (canonical home; EditorSections re-exports this)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type SetFn = <K extends keyof LoeschfristPolicy>(key: K, val: LoeschfristPolicy[K]) => void
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sektion 2: 3-stufige Loeschlogik
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function DeletionLogicSection({
|
||||
policy, pid, set, updateLegalHoldItem, addLegalHold, removeLegalHold,
|
||||
}: {
|
||||
policy: LoeschfristPolicy; pid: string; set: SetFn
|
||||
updateLegalHoldItem: (idx: number, updater: (h: LegalHold) => LegalHold) => void
|
||||
addLegalHold: (policyId: string) => void
|
||||
removeLegalHold: (policyId: string, idx: number) => 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">2. 3-stufige Loeschlogik</h3>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Loeschausloeser (Trigger-Stufe)</label>
|
||||
<div className="space-y-2">
|
||||
{(['PURPOSE_END', 'RETENTION_DRIVER', 'LEGAL_HOLD'] as const).map((trigger) => (
|
||||
<label key={trigger} className="flex items-start gap-3 p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer">
|
||||
<input type="radio" name={`trigger-${pid}`} checked={policy.deletionTrigger === trigger}
|
||||
onChange={() => set('deletionTrigger', trigger)} className="mt-0.5 text-purple-600 focus:ring-purple-500" />
|
||||
<div>
|
||||
<div className="flex items-center gap-2">{renderTriggerBadge(trigger)}</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
{trigger === 'PURPOSE_END' && 'Loeschung nach Wegfall des Verarbeitungszwecks'}
|
||||
{trigger === 'RETENTION_DRIVER' && 'Loeschung nach Ablauf gesetzlicher oder vertraglicher Aufbewahrungsfrist'}
|
||||
{trigger === 'LEGAL_HOLD' && 'Loeschung durch aktiven Legal Hold blockiert'}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{policy.deletionTrigger === 'RETENTION_DRIVER' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Aufbewahrungstreiber</label>
|
||||
<select value={policy.retentionDriver}
|
||||
onChange={(e) => {
|
||||
const driver = e.target.value as RetentionDriverType
|
||||
const meta = RETENTION_DRIVER_META[driver]
|
||||
set('retentionDriver', driver)
|
||||
if (meta) {
|
||||
set('retentionDuration', meta.defaultDuration)
|
||||
set('retentionUnit', meta.defaultUnit as RetentionUnit)
|
||||
set('retentionDescription', meta.description)
|
||||
}
|
||||
}}
|
||||
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="">Bitte waehlen...</option>
|
||||
{Object.entries(RETENTION_DRIVER_META).map(([key, meta]) => (
|
||||
<option key={key} value={key}>{meta.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Aufbewahrungsdauer</label>
|
||||
<input type="number" min={0} value={policy.retentionDuration}
|
||||
onChange={(e) => set('retentionDuration', parseInt(e.target.value) || 0)}
|
||||
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">Einheit</label>
|
||||
<select value={policy.retentionUnit} onChange={(e) => set('retentionUnit', e.target.value as RetentionUnit)}
|
||||
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="DAYS">Tage</option>
|
||||
<option value="MONTHS">Monate</option>
|
||||
<option value="YEARS">Jahre</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Beschreibung der Aufbewahrungspflicht</label>
|
||||
<input type="text" value={policy.retentionDescription} onChange={(e) => set('retentionDescription', e.target.value)}
|
||||
placeholder="z.B. Handelsrechtliche Aufbewahrungspflicht gem. HGB"
|
||||
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">Startereignis (Fristbeginn)</label>
|
||||
<input type="text" value={policy.startEvent} onChange={(e) => set('startEvent', e.target.value)}
|
||||
placeholder="z.B. Ende des Geschaeftsjahres, Vertragsende..."
|
||||
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>
|
||||
|
||||
{/* Legal Holds */}
|
||||
<div className="border-t border-gray-200 pt-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="text-sm font-semibold text-gray-800">Legal Holds</h4>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" checked={policy.hasActiveLegalHold}
|
||||
onChange={(e) => set('hasActiveLegalHold', e.target.checked)}
|
||||
className="text-purple-600 focus:ring-purple-500 rounded" />
|
||||
Aktiver Legal Hold
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{policy.legalHolds.length > 0 && (
|
||||
<div className="overflow-x-auto mb-3">
|
||||
<table className="w-full text-sm border border-gray-200 rounded-lg">
|
||||
<thead>
|
||||
<tr className="bg-gray-50">
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Bezeichnung</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Grund</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Status</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Erstellt am</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{policy.legalHolds.map((hold, idx) => (
|
||||
<tr key={idx} className="border-t border-gray-100">
|
||||
<td className="px-3 py-2">
|
||||
<input type="text" value={hold.name}
|
||||
onChange={(e) => updateLegalHoldItem(idx, (h) => ({ ...h, name: e.target.value }))}
|
||||
placeholder="Bezeichnung"
|
||||
className="w-full px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500" />
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<input type="text" value={hold.reason}
|
||||
onChange={(e) => updateLegalHoldItem(idx, (h) => ({ ...h, reason: e.target.value }))}
|
||||
placeholder="Grund"
|
||||
className="w-full px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500" />
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<select value={hold.status}
|
||||
onChange={(e) => updateLegalHoldItem(idx, (h) => ({ ...h, status: e.target.value as LegalHoldStatus }))}
|
||||
className="px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500">
|
||||
<option value="ACTIVE">Aktiv</option>
|
||||
<option value="RELEASED">Aufgehoben</option>
|
||||
<option value="EXPIRED">Abgelaufen</option>
|
||||
</select>
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<input type="date" value={hold.createdAt}
|
||||
onChange={(e) => updateLegalHoldItem(idx, (h) => ({ ...h, createdAt: e.target.value }))}
|
||||
className="px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500" />
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<button onClick={() => removeLegalHold(pid, idx)}
|
||||
className="text-red-500 hover:text-red-700 text-sm font-medium">Entfernen</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button onClick={() => addLegalHold(pid)} className="text-sm text-purple-600 hover:text-purple-800 font-medium">
|
||||
+ Legal Hold hinzufuegen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sektion 3: Speicherorte & Loeschmethode
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function StorageSection({
|
||||
policy, pid, set, updateStorageLocationItem, addStorageLocation, removeStorageLocation,
|
||||
}: {
|
||||
policy: LoeschfristPolicy; pid: string; set: SetFn
|
||||
updateStorageLocationItem: (idx: number, updater: (s: StorageLocation) => StorageLocation) => void
|
||||
addStorageLocation: (policyId: string) => void
|
||||
removeStorageLocation: (policyId: string, idx: number) => 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">3. Speicherorte & Loeschmethode</h3>
|
||||
|
||||
{policy.storageLocations.length > 0 && (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm border border-gray-200 rounded-lg">
|
||||
<thead>
|
||||
<tr className="bg-gray-50">
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Name</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Typ</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Backup</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Anbieter</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Loeschfaehig</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{policy.storageLocations.map((loc, idx) => (
|
||||
<tr key={idx} className="border-t border-gray-100">
|
||||
<td className="px-3 py-2">
|
||||
<input type="text" value={loc.name}
|
||||
onChange={(e) => updateStorageLocationItem(idx, (s) => ({ ...s, name: e.target.value }))}
|
||||
placeholder="Name"
|
||||
className="w-full px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500" />
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<select value={loc.type}
|
||||
onChange={(e) => updateStorageLocationItem(idx, (s) => ({ ...s, type: e.target.value as StorageLocationType }))}
|
||||
className="px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500">
|
||||
{Object.entries(STORAGE_LOCATION_LABELS).map(([key, label]) => (
|
||||
<option key={key} value={key}>{label}</option>
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-center">
|
||||
<input type="checkbox" checked={loc.isBackup}
|
||||
onChange={(e) => updateStorageLocationItem(idx, (s) => ({ ...s, isBackup: e.target.checked }))}
|
||||
className="text-purple-600 focus:ring-purple-500 rounded" />
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<input type="text" value={loc.provider}
|
||||
onChange={(e) => updateStorageLocationItem(idx, (s) => ({ ...s, provider: e.target.value }))}
|
||||
placeholder="Anbieter"
|
||||
className="w-full px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500" />
|
||||
</td>
|
||||
<td className="px-3 py-2 text-center">
|
||||
<input type="checkbox" checked={loc.deletionCapable}
|
||||
onChange={(e) => updateStorageLocationItem(idx, (s) => ({ ...s, deletionCapable: e.target.checked }))}
|
||||
className="text-purple-600 focus:ring-purple-500 rounded" />
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<button onClick={() => removeStorageLocation(pid, idx)}
|
||||
className="text-red-500 hover:text-red-700 text-sm font-medium">Entfernen</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button onClick={() => addStorageLocation(pid)} className="text-sm text-purple-600 hover:text-purple-800 font-medium">
|
||||
+ Speicherort hinzufuegen
|
||||
</button>
|
||||
|
||||
<div className="border-t border-gray-200 pt-4 grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Loeschmethode</label>
|
||||
<select value={policy.deletionMethod} onChange={(e) => set('deletionMethod', e.target.value as DeletionMethodType)}
|
||||
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(DELETION_METHOD_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">Details zur Loeschmethode</label>
|
||||
<textarea value={policy.deletionMethodDetail} onChange={(e) => set('deletionMethodDetail', e.target.value)} rows={2}
|
||||
placeholder="Weitere Details zum Loeschverfahren..."
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user