Split two oversized page files into _components/ directories following Next.js 15 conventions and the 500-LOC hard cap: - loeschfristen/page.tsx (2322 LOC -> 412 LOC orchestrator + 6 components) - dsb-portal/page.tsx (2068 LOC -> 135 LOC orchestrator + 9 components) All component files stay under 500 lines. Build verified. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
70 lines
2.5 KiB
TypeScript
70 lines
2.5 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import { AssignmentOverview, ASSIGNMENT_STATUS_LABELS, ASSIGNMENT_STATUS_COLORS, formatDate } from './types'
|
|
import { Badge, ComplianceBar, HoursBar, IconTask, IconCalendar } from './ui-primitives'
|
|
|
|
export function MandantCard({
|
|
assignment,
|
|
onClick,
|
|
}: {
|
|
assignment: AssignmentOverview
|
|
onClick: () => void
|
|
}) {
|
|
return (
|
|
<div
|
|
onClick={onClick}
|
|
className="bg-white rounded-xl border border-gray-200 p-5 hover:border-purple-400 hover:shadow-lg cursor-pointer transition-all group"
|
|
>
|
|
{/* Header */}
|
|
<div className="flex items-start justify-between mb-3">
|
|
<div className="min-w-0">
|
|
<h3 className="font-semibold text-gray-900 truncate group-hover:text-purple-700 transition-colors">
|
|
{assignment.tenant_name}
|
|
</h3>
|
|
<p className="text-xs text-gray-400 font-mono">{assignment.tenant_slug}</p>
|
|
</div>
|
|
<Badge
|
|
label={ASSIGNMENT_STATUS_LABELS[assignment.status] || assignment.status}
|
|
className={ASSIGNMENT_STATUS_COLORS[assignment.status] || 'bg-gray-100 text-gray-600'}
|
|
/>
|
|
</div>
|
|
|
|
{/* Compliance Score */}
|
|
<div className="mb-3">
|
|
<div className="flex items-center justify-between text-xs text-gray-500 mb-1">
|
|
<span>Compliance-Score</span>
|
|
</div>
|
|
<ComplianceBar score={assignment.compliance_score} />
|
|
</div>
|
|
|
|
{/* Hours */}
|
|
<div className="mb-3">
|
|
<div className="flex items-center justify-between text-xs text-gray-500 mb-1">
|
|
<span>Stunden diesen Monat</span>
|
|
</div>
|
|
<HoursBar used={assignment.hours_this_month} budget={assignment.hours_budget} />
|
|
</div>
|
|
|
|
{/* Footer: Tasks */}
|
|
<div className="flex items-center justify-between pt-3 border-t border-gray-100">
|
|
<div className="flex items-center gap-1 text-sm text-gray-600">
|
|
<IconTask className="w-4 h-4" />
|
|
<span>{assignment.open_task_count} offene Aufgaben</span>
|
|
</div>
|
|
{assignment.urgent_task_count > 0 && (
|
|
<Badge label={`${assignment.urgent_task_count} dringend`} className="bg-red-100 text-red-700 border-red-200" />
|
|
)}
|
|
</div>
|
|
|
|
{/* Next deadline */}
|
|
{assignment.next_deadline && (
|
|
<div className="flex items-center gap-1 mt-2 text-xs text-gray-400">
|
|
<IconCalendar className="w-3 h-3" />
|
|
<span>Naechste Frist: {formatDate(assignment.next_deadline)}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|