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>
112 lines
4.1 KiB
TypeScript
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>
|
|
)
|
|
}
|