Files
breakpilot-compliance/admin-compliance/app/sdk/obligations/_components/ObligationDetail.tsx
Sharang Parnerkar 637eb012f5 refactor(admin): split obligations page.tsx into colocated components
Extract ObligationModal, ObligationDetail, ObligationCard, ObligationsHeader,
StatsGrid, FilterBar and InfoBanners into _components/, plus _types.ts for
shared types/constants. page.tsx drops from 987 to 325 LOC, below the 300
soft target region and well under the 500 hard cap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 22:49:49 +02:00

137 lines
5.5 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.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>
)
}