refactor(admin): split loeschfristen + dsb-portal page.tsx into colocated components
Split two oversized page files into _components/ directories following Next.js 15 conventions and the 500-LOC hard cap: - loeschfristen/page.tsx (2322 LOC -> 412 LOC orchestrator + 6 components) - dsb-portal/page.tsx (2068 LOC -> 135 LOC orchestrator + 9 components) All component files stay under 500 lines. Build verified. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
170
admin-compliance/app/sdk/loeschfristen/_components/EditorTab.tsx
Normal file
170
admin-compliance/app/sdk/loeschfristen/_components/EditorTab.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import {
|
||||
LoeschfristPolicy, LegalHold, StorageLocation,
|
||||
} from '@/lib/sdk/loeschfristen-types'
|
||||
import { renderStatusBadge } from './UebersichtTab'
|
||||
import {
|
||||
DataObjectSection, DeletionLogicSection, StorageSection,
|
||||
ResponsibilitySection, VVTLinkSection, ReviewSection,
|
||||
} from './EditorSections'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface EditorTabProps {
|
||||
policies: LoeschfristPolicy[]
|
||||
editingId: string | null
|
||||
editingPolicy: LoeschfristPolicy | null
|
||||
vvtActivities: any[]
|
||||
saving: boolean
|
||||
setEditingId: (id: string | null) => void
|
||||
setTab: (tab: 'uebersicht' | 'editor' | 'generator' | 'export') => void
|
||||
updatePolicy: (id: string, updater: (p: LoeschfristPolicy) => LoeschfristPolicy) => void
|
||||
createNewPolicy: () => void
|
||||
deletePolicy: (policyId: string) => void
|
||||
addLegalHold: (policyId: string) => void
|
||||
removeLegalHold: (policyId: string, idx: number) => void
|
||||
addStorageLocation: (policyId: string) => void
|
||||
removeStorageLocation: (policyId: string, idx: number) => void
|
||||
handleSaveAndClose: () => void
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// No-selection view
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function EditorNoSelection({
|
||||
policies, setEditingId, createNewPolicy,
|
||||
}: Pick<EditorTabProps, 'policies' | 'setEditingId' | 'createNewPolicy'>) {
|
||||
return (
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-8">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
Loeschfrist zum Bearbeiten waehlen
|
||||
</h3>
|
||||
{policies.length === 0 ? (
|
||||
<p className="text-gray-500">
|
||||
Noch keine Loeschfristen vorhanden.{' '}
|
||||
<button onClick={createNewPolicy}
|
||||
className="text-purple-600 hover:text-purple-800 font-medium underline">
|
||||
Neue Loeschfrist anlegen
|
||||
</button>
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{policies.map((p) => (
|
||||
<button key={p.policyId} onClick={() => setEditingId(p.policyId)}
|
||||
className="w-full text-left px-4 py-3 border border-gray-200 rounded-lg hover:bg-gray-50 transition flex items-center justify-between">
|
||||
<div>
|
||||
<span className="text-xs text-gray-400 font-mono mr-2">{p.policyId}</span>
|
||||
<span className="font-medium text-gray-900">{p.dataObjectName || 'Ohne Bezeichnung'}</span>
|
||||
</div>
|
||||
{renderStatusBadge(p.status)}
|
||||
</button>
|
||||
))}
|
||||
<button onClick={createNewPolicy}
|
||||
className="w-full text-left px-4 py-3 border border-dashed border-gray-300 rounded-lg hover:bg-gray-50 transition text-purple-600 font-medium">
|
||||
+ Neue Loeschfrist anlegen
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Editor form
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function EditorForm({
|
||||
policy, vvtActivities, saving, setEditingId, setTab,
|
||||
updatePolicy, deletePolicy, addLegalHold, removeLegalHold,
|
||||
addStorageLocation, removeStorageLocation, handleSaveAndClose,
|
||||
}: Omit<EditorTabProps, 'policies' | 'editingId' | 'editingPolicy' | 'createNewPolicy'> & {
|
||||
policy: LoeschfristPolicy
|
||||
}) {
|
||||
const pid = policy.policyId
|
||||
|
||||
const set = <K extends keyof LoeschfristPolicy>(key: K, val: LoeschfristPolicy[K]) => {
|
||||
updatePolicy(pid, (p) => ({ ...p, [key]: val }))
|
||||
}
|
||||
|
||||
const updateLegalHoldItem = (idx: number, updater: (h: LegalHold) => LegalHold) => {
|
||||
updatePolicy(pid, (p) => ({
|
||||
...p, legalHolds: p.legalHolds.map((h, i) => (i === idx ? updater(h) : h)),
|
||||
}))
|
||||
}
|
||||
|
||||
const updateStorageLocationItem = (idx: number, updater: (s: StorageLocation) => StorageLocation) => {
|
||||
updatePolicy(pid, (p) => ({
|
||||
...p, storageLocations: p.storageLocations.map((s, i) => (i === idx ? updater(s) : s)),
|
||||
}))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header with back button */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<button onClick={() => setEditingId(null)} className="text-gray-400 hover:text-gray-600 transition">
|
||||
← Zurueck
|
||||
</button>
|
||||
<h2 className="text-lg font-semibold text-gray-900">
|
||||
{policy.dataObjectName || 'Neue Loeschfrist'}
|
||||
</h2>
|
||||
<span className="text-xs text-gray-400 font-mono">{policy.policyId}</span>
|
||||
</div>
|
||||
{renderStatusBadge(policy.status)}
|
||||
</div>
|
||||
|
||||
<DataObjectSection policy={policy} set={set} />
|
||||
<DeletionLogicSection policy={policy} pid={pid} set={set}
|
||||
updateLegalHoldItem={updateLegalHoldItem} addLegalHold={addLegalHold} removeLegalHold={removeLegalHold} />
|
||||
<StorageSection policy={policy} pid={pid} set={set}
|
||||
updateStorageLocationItem={updateStorageLocationItem}
|
||||
addStorageLocation={addStorageLocation} removeStorageLocation={removeStorageLocation} />
|
||||
<ResponsibilitySection policy={policy} set={set} />
|
||||
<VVTLinkSection policy={policy} pid={pid} vvtActivities={vvtActivities} updatePolicy={updatePolicy} />
|
||||
<ReviewSection policy={policy} set={set} />
|
||||
|
||||
{/* Action buttons */}
|
||||
<div className="flex items-center justify-between bg-white rounded-xl border border-gray-200 p-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (confirm('Moechten Sie diese Loeschfrist wirklich loeschen?')) {
|
||||
deletePolicy(pid); setTab('uebersicht')
|
||||
}
|
||||
}}
|
||||
className="text-red-600 hover:text-red-800 font-medium text-sm">
|
||||
Loeschfrist loeschen
|
||||
</button>
|
||||
<div className="flex gap-3">
|
||||
<button onClick={() => { setEditingId(null); setTab('uebersicht') }}
|
||||
className="bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg px-4 py-2 font-medium transition">
|
||||
Zurueck zur Uebersicht
|
||||
</button>
|
||||
<button onClick={handleSaveAndClose} disabled={saving}
|
||||
className="bg-purple-600 text-white hover:bg-purple-700 disabled:opacity-50 rounded-lg px-4 py-2 font-medium transition">
|
||||
{saving ? 'Speichern...' : 'Speichern & Schliessen'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main export
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function EditorTab(props: EditorTabProps) {
|
||||
if (!props.editingId || !props.editingPolicy) {
|
||||
return (
|
||||
<EditorNoSelection policies={props.policies}
|
||||
setEditingId={props.setEditingId} createNewPolicy={props.createNewPolicy} />
|
||||
)
|
||||
}
|
||||
return <EditorForm {...props} policy={props.editingPolicy} />
|
||||
}
|
||||
Reference in New Issue
Block a user