CG-Feedback-Monitor/src/pages/AssignmentEdit.tsx

260 lines
10 KiB
TypeScript
Raw Normal View History

2026-07-02 10:10:06 +02:00
import { useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
2026-07-06 19:08:24 +02:00
import {
type Assignment, type AssignmentType, type Consultant,
getAssignment, updateAssignment, listAssignmentTypes, listConsultants,
} from '../db'
2026-07-02 10:10:06 +02:00
export default function AssignmentEdit() {
const { id } = useParams()
const navigate = useNavigate()
const assignmentId = Number(id)
const [assignment, setAssignment] = useState<Assignment | null>(null)
const [assignmentTypes, setAssignmentTypes] = useState<AssignmentType[]>([])
const [allConsultants, setAllConsultants] = useState<Consultant[]>([])
// Form fields
const [assignmentNumber, setAssignmentNumber] = useState('')
const [title, setTitle] = useState('')
const [client, setClient] = useState('')
const [description, setDescription] = useState('')
const [assignmentTypeId, setAssignmentTypeId] = useState(0)
const [leadName, setLeadName] = useState('')
const [status, setStatus] = useState<'active' | 'done'>('active')
const [instituteeIds, setInstituteeIds] = useState<number[]>([])
const [instituteeEnrollments, setInstituteeEnrollments] = useState<Array<{ instituteeId: number; from?: string; to?: string }>>([])
const [typeChanged, setTypeChanged] = useState(false)
const [addInstituteeId, setAddInstituteeId] = useState(0)
useEffect(() => {
const loadData = async () => {
const [a, types, cons] = await Promise.all([
2026-07-06 19:08:24 +02:00
getAssignment(assignmentId),
listAssignmentTypes(),
listConsultants(),
2026-07-02 10:10:06 +02:00
])
if (!a) return
setAssignment(a)
setAssignmentTypes(types)
setAllConsultants(cons)
setAssignmentNumber(a.assignmentNumber ?? '')
setTitle(a.title)
setClient(a.client)
setDescription(a.description ?? '')
setAssignmentTypeId(a.assignmentTypeId)
setLeadName(a.leadName)
setStatus(a.status)
setInstituteeIds(a.instituteeIds ?? [])
setInstituteeEnrollments(a.instituteeEnrollments ?? [])
}
loadData()
}, [assignmentId])
const handleSave = async () => {
2026-07-06 19:08:24 +02:00
await updateAssignment(assignmentId, {
2026-07-02 10:10:06 +02:00
assignmentNumber: assignmentNumber.trim() || undefined,
title,
client,
description: description.trim() || undefined,
assignmentTypeId,
leadName,
status,
instituteeIds,
instituteeEnrollments,
})
navigate(`/assignment/${assignmentId}`)
}
const handleTypeChange = (newTypeId: number) => {
if (newTypeId !== assignmentTypeId) setTypeChanged(true)
setAssignmentTypeId(newTypeId)
}
const addInstitutee = () => {
if (!addInstituteeId || instituteeIds.includes(addInstituteeId)) return
setInstituteeIds(prev => [...prev, addInstituteeId])
setInstituteeEnrollments(prev => [...prev, { instituteeId: addInstituteeId, from: '', to: '' }])
setAddInstituteeId(0)
}
const removeInstitutee = (cid: number) => {
setInstituteeIds(prev => prev.filter(id => id !== cid))
setInstituteeEnrollments(prev => prev.filter(e => e.instituteeId !== cid))
}
const updateEnrollment = (cid: number, field: 'from' | 'to', value: string) => {
setInstituteeEnrollments(prev =>
prev.map(e => e.instituteeId === cid ? { ...e, [field]: value } : e)
)
}
const availableConsultants = allConsultants.filter(c => !instituteeIds.includes(c.id!))
if (!assignment) return <div className="text-center py-10 text-gray-400">Lädt…</div>
return (
<div className="space-y-4">
{/* Header */}
<div>
<button onClick={() => navigate(`/assignment/${assignmentId}`)}
className="text-sm text-brand mb-2 block">← Zurück</button>
2026-07-02 10:10:06 +02:00
<h1 className="text-xl font-bold text-gray-800">Assignment bearbeiten</h1>
</div>
{/* Formular */}
<div className="bg-white rounded-xl border border-gray-100 shadow-sm p-4 space-y-3">
<div className="space-y-1">
<label className="text-xs font-medium text-gray-500">Assignment-Nummer</label>
<input
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
2026-07-02 10:10:06 +02:00
placeholder="z. B. CGI-2024-001"
value={assignmentNumber}
onChange={e => setAssignmentNumber(e.target.value)}
/>
</div>
<div className="space-y-1">
<label className="text-xs font-medium text-gray-500">Titel</label>
<input
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
2026-07-02 10:10:06 +02:00
value={title}
onChange={e => setTitle(e.target.value)}
/>
</div>
<div className="space-y-1">
<label className="text-xs font-medium text-gray-500">Kunde</label>
<input
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
2026-07-02 10:10:06 +02:00
value={client}
onChange={e => setClient(e.target.value)}
/>
</div>
<div className="space-y-1">
<label className="text-xs font-medium text-gray-500">Beschreibung</label>
<textarea
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
2026-07-02 10:10:06 +02:00
rows={3}
value={description}
onChange={e => setDescription(e.target.value)}
/>
</div>
<div className="space-y-1">
<label className="text-xs font-medium text-gray-500">Assignment-Typ</label>
<select
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
2026-07-02 10:10:06 +02:00
value={assignmentTypeId}
onChange={e => handleTypeChange(Number(e.target.value))}>
{assignmentTypes.map(t => (
<option key={t.id} value={t.id}>{t.name}</option>
))}
</select>
{typeChanged && (
<div className="text-xs text-amber-600 bg-amber-50 border border-amber-200 rounded-lg px-3 py-2 mt-1">
⚠️ Bestehende Meetings bleiben erhalten
</div>
)}
</div>
<div className="space-y-1">
<label className="text-xs font-medium text-gray-500">Lead</label>
<input
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
2026-07-02 10:10:06 +02:00
value={leadName}
onChange={e => setLeadName(e.target.value)}
/>
</div>
<div className="space-y-1">
<label className="text-xs font-medium text-gray-500">Status</label>
<select
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
2026-07-02 10:10:06 +02:00
value={status}
onChange={e => setStatus(e.target.value as 'active' | 'done')}>
<option value="active">Aktiv</option>
<option value="done">Abgeschlossen</option>
</select>
</div>
</div>
{/* Institutees */}
<div className="bg-white rounded-xl border border-gray-100 shadow-sm p-4 space-y-3">
<h2 className="font-semibold text-gray-800">Institutees</h2>
{instituteeIds.length === 0 && (
<p className="text-sm text-gray-400">Noch keine Institutees zugewiesen.</p>
)}
{instituteeIds.map(cid => {
const consultant = allConsultants.find(c => c.id === cid)
const enrollment = instituteeEnrollments.find(e => e.instituteeId === cid)
if (!consultant) return null
return (
<div key={cid} className="flex items-center gap-2 flex-wrap border border-gray-100 rounded-lg px-3 py-2">
<span className="text-sm font-medium text-gray-700 flex-1 min-w-[100px]">
{consultant.firstName} {consultant.lastName}
</span>
<div className="flex items-center gap-1">
<span className="text-xs text-gray-400">Von</span>
<input
type="date"
className="border border-gray-200 rounded px-2 py-1 text-xs focus:outline-none focus:border-brand"
2026-07-02 10:10:06 +02:00
value={enrollment?.from ?? ''}
onChange={e => updateEnrollment(cid, 'from', e.target.value)}
/>
</div>
<div className="flex items-center gap-1">
<span className="text-xs text-gray-400">Bis</span>
<input
type="date"
className="border border-gray-200 rounded px-2 py-1 text-xs focus:outline-none focus:border-brand"
2026-07-02 10:10:06 +02:00
value={enrollment?.to ?? ''}
onChange={e => updateEnrollment(cid, 'to', e.target.value)}
/>
</div>
<button
onClick={() => removeInstitutee(cid)}
className="text-red-300 hover:text-red-500 text-sm px-1">
×
</button>
</div>
)
})}
{/* Institutee hinzufügen */}
{availableConsultants.length > 0 && (
<div className="flex gap-2">
<select
className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
2026-07-02 10:10:06 +02:00
value={addInstituteeId}
onChange={e => setAddInstituteeId(Number(e.target.value))}>
<option value={0}>Institutee auswählen…</option>
{availableConsultants.map(c => (
<option key={c.id} value={c.id}>{c.firstName} {c.lastName}</option>
))}
</select>
<button
onClick={addInstitutee}
disabled={!addInstituteeId}
className="px-4 py-2 bg-brand text-white text-sm rounded-lg font-medium disabled:opacity-40">
2026-07-02 10:10:06 +02:00
Hinzufügen
</button>
</div>
)}
</div>
{/* Aktionen */}
<div className="flex gap-3">
<button
onClick={handleSave}
className="flex-1 py-3 bg-brand text-white rounded-xl text-sm font-semibold">
2026-07-02 10:10:06 +02:00
Speichern
</button>
<button
onClick={() => navigate(`/assignment/${assignmentId}`)}
className="px-5 py-3 border border-gray-200 text-gray-500 rounded-xl text-sm">
Abbrechen
</button>
</div>
</div>
)
}