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>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { AssignmentOverview, ASSIGNMENT_STATUS_LABELS, apiFetch } from './types'
|
||||
import { FormLabel, FormInput, FormTextarea, PrimaryButton } from './ui-primitives'
|
||||
|
||||
export function EinstellungenTab({
|
||||
assignment,
|
||||
onUpdate,
|
||||
addToast,
|
||||
}: {
|
||||
assignment: AssignmentOverview
|
||||
onUpdate: () => void
|
||||
addToast: (msg: string, type?: 'success' | 'error') => void
|
||||
}) {
|
||||
const [status, setStatus] = useState(assignment.status)
|
||||
const [budget, setBudget] = useState(String(assignment.monthly_hours_budget))
|
||||
const [notes, setNotes] = useState(assignment.notes || '')
|
||||
const [contractStart, setContractStart] = useState(assignment.contract_start?.slice(0, 10) || '')
|
||||
const [contractEnd, setContractEnd] = useState(assignment.contract_end?.slice(0, 10) || '')
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true)
|
||||
try {
|
||||
await apiFetch(`/api/sdk/v1/dsb/assignments/${assignment.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
status,
|
||||
monthly_hours_budget: parseFloat(budget) || 0,
|
||||
notes,
|
||||
contract_start: contractStart || null,
|
||||
contract_end: contractEnd || null,
|
||||
}),
|
||||
})
|
||||
addToast('Einstellungen gespeichert')
|
||||
onUpdate()
|
||||
} catch (e: unknown) {
|
||||
addToast(e instanceof Error ? e.message : 'Fehler beim Speichern', 'error')
|
||||
} finally { setSaving(false) }
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl space-y-6">
|
||||
{/* Status */}
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-5">
|
||||
<h4 className="text-sm font-semibold text-gray-700 mb-3">Status</h4>
|
||||
<div className="flex gap-2">
|
||||
{(['active', 'paused', 'terminated'] as const).map((s) => (
|
||||
<button key={s} onClick={() => setStatus(s)}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium border transition-colors ${
|
||||
status === s
|
||||
? s === 'active' ? 'bg-green-100 text-green-700 border-green-300'
|
||||
: s === 'paused' ? 'bg-yellow-100 text-yellow-700 border-yellow-300'
|
||||
: 'bg-red-100 text-red-700 border-red-300'
|
||||
: 'bg-white text-gray-500 border-gray-200 hover:bg-gray-50'
|
||||
}`}>
|
||||
{ASSIGNMENT_STATUS_LABELS[s]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contract period */}
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-5">
|
||||
<h4 className="text-sm font-semibold text-gray-700 mb-3">Vertragszeitraum</h4>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<FormLabel htmlFor="s-start">Vertragsbeginn</FormLabel>
|
||||
<FormInput id="s-start" type="date" value={contractStart} onChange={setContractStart} />
|
||||
</div>
|
||||
<div>
|
||||
<FormLabel htmlFor="s-end">Vertragsende</FormLabel>
|
||||
<FormInput id="s-end" type="date" value={contractEnd} onChange={setContractEnd} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Budget */}
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-5">
|
||||
<h4 className="text-sm font-semibold text-gray-700 mb-3">Monatliches Stundenbudget</h4>
|
||||
<div className="max-w-xs">
|
||||
<FormInput type="number" value={budget} onChange={setBudget} min={0} max={999} step={1} />
|
||||
<p className="text-xs text-gray-400 mt-1">Stunden pro Monat</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Notes */}
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-5">
|
||||
<h4 className="text-sm font-semibold text-gray-700 mb-3">Anmerkungen</h4>
|
||||
<FormTextarea value={notes} onChange={setNotes} placeholder="Interne Anmerkungen zum Mandat..." rows={4} />
|
||||
</div>
|
||||
|
||||
{/* Save */}
|
||||
<div className="flex justify-end">
|
||||
<PrimaryButton onClick={handleSave} disabled={saving}>
|
||||
{saving ? 'Speichere...' : 'Einstellungen speichern'}
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user