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>
103 lines
4.0 KiB
TypeScript
103 lines
4.0 KiB
TypeScript
'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>
|
|
)
|
|
}
|