Files
breakpilot-compliance/admin-compliance/app/sdk/obligations/_components/ObligationCard.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

112 lines
4.1 KiB
TypeScript

'use client'
import React, { useState } from 'react'
import TOMControlPanel from '@/components/sdk/obligations/TOMControlPanel'
import {
PRIORITY_COLORS,
PRIORITY_LABELS,
STATUS_COLORS,
STATUS_LABELS,
STATUS_NEXT,
type Obligation,
} from '../_types'
export default function ObligationCard({
obligation,
onStatusChange,
onDetails,
}: {
obligation: Obligation
onStatusChange: (id: string, status: string) => Promise<void>
onDetails: () => void
}) {
const [saving, setSaving] = useState(false)
const [showTOM, setShowTOM] = useState(false)
const daysUntil = obligation.deadline
? Math.ceil((new Date(obligation.deadline).getTime() - Date.now()) / 86400000)
: null
const handleMark = async (e: React.MouseEvent) => {
e.stopPropagation()
setSaving(true)
await onStatusChange(obligation.id, STATUS_NEXT[obligation.status])
setSaving(false)
}
return (
<div
className={`bg-white rounded-xl border-2 p-5 cursor-pointer hover:shadow-md transition-all ${
obligation.status === 'overdue' ? 'border-red-200' :
obligation.status === 'completed' ? 'border-green-200' :
obligation.status === 'in-progress' ? 'border-blue-200' : 'border-gray-200'
}`}
onClick={onDetails}
>
<div className="flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1.5 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_article && (
<span className="px-2 py-0.5 text-xs bg-purple-50 text-purple-700 rounded">
{obligation.source} {obligation.source_article}
</span>
)}
</div>
<h3 className="font-semibold text-gray-900 text-sm">{obligation.title}</h3>
{obligation.description && (
<p className="text-xs text-gray-500 mt-1 line-clamp-2">{obligation.description}</p>
)}
</div>
</div>
<div className="mt-3 flex items-center justify-between text-xs text-gray-500">
<div className="flex items-center gap-3">
{obligation.responsible && <span>👤 {obligation.responsible}</span>}
{obligation.deadline && (
<span className={daysUntil !== null && daysUntil < 0 ? 'text-red-600 font-medium' : ''}>
📅 {new Date(obligation.deadline).toLocaleDateString('de-DE')}
{daysUntil !== null && ` (${daysUntil < 0 ? `${Math.abs(daysUntil)}d überfällig` : `${daysUntil}d`})`}
</span>
)}
</div>
<div className="flex items-center gap-2" onClick={e => e.stopPropagation()}>
<button
onClick={(e) => { e.stopPropagation(); setShowTOM(v => !v) }}
className="px-2 py-1 text-purple-600 hover:bg-purple-50 rounded-lg transition-colors"
title="TOM Controls anzeigen"
>
TOM
</button>
<button
onClick={onDetails}
className="text-purple-600 hover:text-purple-700 font-medium"
>
Details
</button>
{obligation.status !== 'completed' && (
<button
onClick={handleMark}
disabled={saving}
className="px-3 py-1 bg-purple-50 text-purple-700 rounded-lg hover:bg-purple-100 transition-colors disabled:opacity-50"
>
{saving ? '...' : STATUS_NEXT[obligation.status] === 'completed' ? 'Erledigt ✓' : `${STATUS_LABELS[STATUS_NEXT[obligation.status]]}`}
</button>
)}
</div>
</div>
{showTOM && (
<div className="mt-3" onClick={e => e.stopPropagation()}>
<TOMControlPanel obligationId={obligation.id} onClose={() => setShowTOM(false)} />
</div>
)}
</div>
)
}