Files
breakpilot-compliance/admin-compliance/app/sdk/obligations/_components/ObligationDetail.tsx
Sharang Parnerkar 2ade65431a refactor(admin): split compliance-hub, obligations, document-generator pages
Each page.tsx was >1000 LOC; extract components to _components/ and hooks
to _hooks/ so page files stay under 500 LOC (164 / 255 / 243 respectively).
Zero behavior changes — logic relocated verbatim.

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

150 lines
6.0 KiB
TypeScript

'use client'
import React, { useState } from 'react'
import {
PRIORITY_COLORS,
PRIORITY_LABELS,
STATUS_COLORS,
STATUS_LABELS,
STATUS_NEXT,
type Obligation,
} from '../_types'
export default function ObligationDetail({ obligation, onClose, onStatusChange, onEdit, onDelete }: {
obligation: Obligation
onClose: () => void
onStatusChange: (id: string, status: string) => Promise<void>
onEdit: () => void
onDelete: (id: string) => Promise<void>
}) {
const [saving, setSaving] = useState(false)
const handleStatusCycle = async () => {
setSaving(true)
await onStatusChange(obligation.id, STATUS_NEXT[obligation.status])
setSaving(false)
}
const handleDelete = async () => {
if (!confirm('Pflicht wirklich loeschen?')) return
await onDelete(obligation.id)
onClose()
}
const daysUntil = obligation.deadline
? Math.ceil((new Date(obligation.deadline).getTime() - Date.now()) / 86400000)
: null
return (
<div className="fixed inset-0 bg-black/40 z-50 flex items-end md:items-center justify-center p-4" onClick={e => e.target === e.currentTarget && onClose()}>
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-lg max-h-[85vh] overflow-y-auto">
<div className="flex items-start justify-between p-6 border-b gap-4">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1 flex-wrap">
<span className={`px-2 py-0.5 text-xs rounded-full ${PRIORITY_COLORS[obligation.priority]}`}>{PRIORITY_LABELS[obligation.priority]}</span>
<span className={`px-2 py-0.5 text-xs rounded-full ${STATUS_COLORS[obligation.status]}`}>{STATUS_LABELS[obligation.status]}</span>
{obligation.source && (
<span className="px-2 py-0.5 text-xs bg-purple-50 text-purple-700 rounded">{obligation.source} {obligation.source_article}</span>
)}
</div>
<h2 className="text-base font-semibold text-gray-900">{obligation.title}</h2>
</div>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 flex-shrink-0"></button>
</div>
<div className="p-6 space-y-4 text-sm">
{obligation.description && (
<p className="text-gray-700 whitespace-pre-wrap">{obligation.description}</p>
)}
<div className="grid grid-cols-2 gap-3">
<div>
<span className="text-gray-500">Verantwortlich</span>
<p className="font-medium text-gray-900">{obligation.responsible || '—'}</p>
</div>
<div>
<span className="text-gray-500">Frist</span>
<p className={`font-medium ${daysUntil !== null && daysUntil < 0 ? 'text-red-600' : 'text-gray-900'}`}>
{obligation.deadline
? `${new Date(obligation.deadline).toLocaleDateString('de-DE')}${daysUntil !== null ? ` (${daysUntil < 0 ? `${Math.abs(daysUntil)}d ueberfaellig` : `${daysUntil}d`})` : ''}`
: '—'}
</p>
</div>
</div>
{obligation.linked_systems?.length > 0 && (
<div>
<span className="text-gray-500">Betroffene Systeme</span>
<div className="flex flex-wrap gap-1 mt-1">
{obligation.linked_systems.map(s => (
<span key={s} className="px-2 py-0.5 text-xs bg-gray-100 text-gray-600 rounded">{s}</span>
))}
</div>
</div>
)}
{obligation.linked_vendor_ids && obligation.linked_vendor_ids.length > 0 && (
<div>
<span className="text-gray-500">Verknuepfte Auftragsverarbeiter</span>
<div className="flex flex-wrap gap-1 mt-1">
{obligation.linked_vendor_ids.map(id => (
<a key={id} href="/sdk/vendor-compliance" className="px-2 py-0.5 text-xs bg-indigo-50 text-indigo-700 rounded hover:bg-indigo-100 transition-colors">
{id}
</a>
))}
</div>
</div>
)}
{obligation.notes && (
<div>
<span className="text-gray-500">Notizen</span>
<p className="text-gray-700 mt-1">{obligation.notes}</p>
</div>
)}
{obligation.rule_code && (
<div className="bg-purple-50 rounded-lg p-3">
<span className="text-xs text-purple-600">Aus UCCA Assessment abgeleitet · Regel {obligation.rule_code}</span>
</div>
)}
{obligation.created_at && (
<p className="text-xs text-gray-400">
Erstellt: {new Date(obligation.created_at).toLocaleDateString('de-DE')}
{obligation.updated_at && obligation.updated_at !== obligation.created_at
? ` · Geaendert: ${new Date(obligation.updated_at).toLocaleDateString('de-DE')}`
: ''}
</p>
)}
</div>
<div className="flex items-center justify-between gap-2 p-6 border-t flex-wrap">
<div className="flex gap-2">
<button
onClick={handleDelete}
className="px-3 py-1.5 text-xs text-red-600 hover:bg-red-50 rounded-lg border border-red-200"
>
Loeschen
</button>
</div>
<div className="flex gap-2">
<button onClick={onEdit} className="px-3 py-1.5 text-xs text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg">
Bearbeiten
</button>
{obligation.status !== 'completed' && (
<button
onClick={handleStatusCycle}
disabled={saving}
className="px-4 py-1.5 text-xs bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50"
>
{saving ? '...' : STATUS_NEXT[obligation.status] === 'completed' ? 'Als erledigt markieren' : `${STATUS_LABELS[STATUS_NEXT[obligation.status]]}`}
</button>
)}
</div>
</div>
</div>
</div>
)
}