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>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user