Files
breakpilot-compliance/admin-compliance/app/sdk/dsb-portal/_components/DetailView.tsx
Sharang Parnerkar 6c883fb12e refactor(admin): split loeschfristen + dsb-portal page.tsx into colocated components
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>
2026-04-11 18:51:16 +02:00

127 lines
5.2 KiB
TypeScript

'use client'
import React, { useState } from 'react'
import { AssignmentOverview, ASSIGNMENT_STATUS_LABELS, ASSIGNMENT_STATUS_COLORS, formatDate } from './types'
import {
Badge, ComplianceBar, HoursBar,
IconBack, IconTask, IconClock, IconMail, IconSettings, IconShield,
} from './ui-primitives'
import { AufgabenTab } from './AufgabenTab'
import { ZeiterfassungTab } from './ZeiterfassungTab'
import { KommunikationTab } from './KommunikationTab'
import { EinstellungenTab } from './EinstellungenTab'
type DetailTab = 'aufgaben' | 'zeit' | 'kommunikation' | 'einstellungen'
export function DetailView({
assignment,
onBack,
onUpdate,
addToast,
}: {
assignment: AssignmentOverview
onBack: () => void
onUpdate: () => void
addToast: (msg: string, type?: 'success' | 'error') => void
}) {
const [activeTab, setActiveTab] = useState<DetailTab>('aufgaben')
const tabs: { id: DetailTab; label: string; icon: React.ReactNode }[] = [
{ id: 'aufgaben', label: 'Aufgaben', icon: <IconTask className="w-4 h-4" /> },
{ id: 'zeit', label: 'Zeiterfassung', icon: <IconClock className="w-4 h-4" /> },
{ id: 'kommunikation', label: 'Kommunikation', icon: <IconMail className="w-4 h-4" /> },
{ id: 'einstellungen', label: 'Einstellungen', icon: <IconSettings className="w-4 h-4" /> },
]
return (
<div className="space-y-6">
{/* Back + Header */}
<div>
<button onClick={onBack}
className="flex items-center gap-2 text-sm text-purple-600 hover:text-purple-800 font-medium mb-4 transition-colors">
<IconBack className="w-4 h-4" /> Zurueck zur Uebersicht
</button>
<div className="bg-white border border-gray-200 rounded-xl p-6">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
<div>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center text-purple-600">
<IconShield className="w-5 h-5" />
</div>
<div>
<h2 className="text-xl font-bold text-gray-900">{assignment.tenant_name}</h2>
<p className="text-sm text-gray-400 font-mono">{assignment.tenant_slug}</p>
</div>
</div>
</div>
<div className="flex items-center gap-2">
<Badge
label={ASSIGNMENT_STATUS_LABELS[assignment.status] || assignment.status}
className={ASSIGNMENT_STATUS_COLORS[assignment.status] || 'bg-gray-100 text-gray-600'}
/>
</div>
</div>
{/* Meta info */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 mt-5 pt-5 border-t border-gray-100">
<div>
<p className="text-xs text-gray-400">Vertragsbeginn</p>
<p className="text-sm font-medium text-gray-700">{formatDate(assignment.contract_start)}</p>
</div>
<div>
<p className="text-xs text-gray-400">Vertragsende</p>
<p className="text-sm font-medium text-gray-700">
{assignment.contract_end ? formatDate(assignment.contract_end) : 'Unbefristet'}
</p>
</div>
<div>
<p className="text-xs text-gray-400">Compliance-Score</p>
<div className="mt-1"><ComplianceBar score={assignment.compliance_score} /></div>
</div>
<div>
<p className="text-xs text-gray-400">Stunden diesen Monat</p>
<div className="mt-1"><HoursBar used={assignment.hours_this_month} budget={assignment.hours_budget} /></div>
</div>
</div>
{assignment.notes && (
<div className="mt-4 pt-4 border-t border-gray-100">
<p className="text-xs text-gray-400 mb-1">Anmerkungen</p>
<p className="text-sm text-gray-600">{assignment.notes}</p>
</div>
)}
</div>
</div>
{/* Tabs */}
<div className="border-b border-gray-200">
<nav className="flex gap-0 -mb-px overflow-x-auto">
{tabs.map((tab) => (
<button key={tab.id} onClick={() => setActiveTab(tab.id)}
className={`flex items-center gap-2 px-5 py-3 text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
activeTab === tab.id
? 'border-purple-600 text-purple-700'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}>
{tab.icon} {tab.label}
</button>
))}
</nav>
</div>
{/* Tab content */}
<div>
{activeTab === 'aufgaben' && <AufgabenTab assignmentId={assignment.id} addToast={addToast} />}
{activeTab === 'zeit' && (
<ZeiterfassungTab assignmentId={assignment.id} monthlyBudget={assignment.monthly_hours_budget} addToast={addToast} />
)}
{activeTab === 'kommunikation' && <KommunikationTab assignmentId={assignment.id} addToast={addToast} />}
{activeTab === 'einstellungen' && (
<EinstellungenTab assignment={assignment} onUpdate={onUpdate} addToast={addToast} />
)}
</div>
</div>
)
}