215 lines
8.1 KiB
TypeScript
215 lines
8.1 KiB
TypeScript
'use client'
|
|
|
|
import { use, useState, useEffect } from 'react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { trpc } from '@/lib/trpc-client'
|
|
import { getTrpcErrorMessage } from '@/lib/trpc-error'
|
|
import Link from 'next/link'
|
|
import { format } from 'date-fns'
|
|
|
|
const TYPEN = [
|
|
{ value: 'Pruefung', label: 'Prüfung' },
|
|
{ value: 'Versammlung', label: 'Versammlung' },
|
|
{ value: 'Kurs', label: 'Kurs' },
|
|
{ value: 'Event', label: 'Event' },
|
|
{ value: 'Sonstiges', label: 'Sonstiges' },
|
|
]
|
|
|
|
export default function TerminEditPage({ params }: { params: Promise<{ id: string }> }) {
|
|
const { id } = use(params)
|
|
const router = useRouter()
|
|
|
|
const { data: termin, isLoading } = trpc.termine.byId.useQuery({ id })
|
|
const updateMutation = trpc.termine.update.useMutation({
|
|
onSuccess: () => router.push('/dashboard/termine'),
|
|
})
|
|
const deleteMutation = trpc.termine.delete.useMutation({
|
|
onSuccess: () => router.push('/dashboard/termine'),
|
|
})
|
|
|
|
const [form, setForm] = useState({
|
|
titel: '',
|
|
datum: '',
|
|
uhrzeit: '',
|
|
endeDatum: '',
|
|
endeUhrzeit: '',
|
|
ort: '',
|
|
adresse: '',
|
|
typ: 'Versammlung',
|
|
beschreibung: '',
|
|
maxTeilnehmer: '',
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (termin) {
|
|
setForm({
|
|
titel: termin.titel,
|
|
datum: format(new Date(termin.datum), 'yyyy-MM-dd'),
|
|
uhrzeit: termin.uhrzeit ?? '',
|
|
endeDatum: termin.endeDatum ? format(new Date(termin.endeDatum), 'yyyy-MM-dd') : '',
|
|
endeUhrzeit: termin.endeUhrzeit ?? '',
|
|
ort: termin.ort ?? '',
|
|
adresse: termin.adresse ?? '',
|
|
typ: termin.typ,
|
|
beschreibung: termin.beschreibung ?? '',
|
|
maxTeilnehmer: termin.maxTeilnehmer ? String(termin.maxTeilnehmer) : '',
|
|
})
|
|
}
|
|
}, [termin])
|
|
|
|
if (isLoading) return <div className="text-gray-500 text-sm">Wird geladen...</div>
|
|
if (!termin) return <div className="text-gray-500 text-sm">Termin nicht gefunden.</div>
|
|
|
|
const F = (field: string) => (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) =>
|
|
setForm((prev) => ({ ...prev, [field]: e.target.value }))
|
|
|
|
function handleSubmit(e: React.FormEvent) {
|
|
e.preventDefault()
|
|
updateMutation.mutate({
|
|
id,
|
|
data: {
|
|
titel: form.titel,
|
|
datum: form.datum,
|
|
uhrzeit: form.uhrzeit || undefined,
|
|
endeDatum: form.endeDatum || null,
|
|
endeUhrzeit: form.endeUhrzeit || null,
|
|
ort: form.ort || undefined,
|
|
adresse: form.adresse || undefined,
|
|
typ: form.typ as never,
|
|
beschreibung: form.beschreibung || undefined,
|
|
maxTeilnehmer: form.maxTeilnehmer ? Number(form.maxTeilnehmer) : null,
|
|
},
|
|
})
|
|
}
|
|
|
|
const inputClass =
|
|
'w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-brand-500'
|
|
|
|
return (
|
|
<div className="max-w-2xl space-y-6">
|
|
<div className="flex items-center gap-4">
|
|
<Link href="/dashboard/termine" className="text-gray-400 hover:text-gray-600">
|
|
← Zurück
|
|
</Link>
|
|
<h1 className="text-2xl font-bold text-gray-900">Termin bearbeiten</h1>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="bg-white rounded-xl border shadow-sm p-6 space-y-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="col-span-2">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Titel *</label>
|
|
<input required value={form.titel} onChange={F('titel')} className={inputClass} />
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Typ *</label>
|
|
<select value={form.typ} onChange={F('typ')} className={inputClass}>
|
|
{TYPEN.map((t) => <option key={t.value} value={t.value}>{t.label}</option>)}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Max. Teilnehmer</label>
|
|
<input
|
|
type="number"
|
|
value={form.maxTeilnehmer}
|
|
onChange={F('maxTeilnehmer')}
|
|
placeholder="Leer = unbegrenzt"
|
|
className={inputClass}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Datum *</label>
|
|
<input required type="date" value={form.datum} onChange={F('datum')} className={inputClass} />
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Uhrzeit (von)</label>
|
|
<input type="time" value={form.uhrzeit} onChange={F('uhrzeit')} className={inputClass} />
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Ende Datum</label>
|
|
<input type="date" value={form.endeDatum} onChange={F('endeDatum')} className={inputClass} />
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Ende Uhrzeit</label>
|
|
<input type="time" value={form.endeUhrzeit} onChange={F('endeUhrzeit')} className={inputClass} />
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Ort</label>
|
|
<input value={form.ort} onChange={F('ort')} className={inputClass} />
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Adresse</label>
|
|
<input value={form.adresse} onChange={F('adresse')} className={inputClass} />
|
|
</div>
|
|
<div className="col-span-2">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Beschreibung</label>
|
|
<textarea
|
|
value={form.beschreibung}
|
|
onChange={F('beschreibung')}
|
|
rows={4}
|
|
className={inputClass}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{updateMutation.error && (
|
|
<p className="text-sm text-red-600 bg-red-50 px-4 py-2 rounded-lg">
|
|
{getTrpcErrorMessage(updateMutation.error)}
|
|
</p>
|
|
)}
|
|
|
|
<div className="flex items-center justify-between pt-2 border-t">
|
|
<div className="flex gap-3">
|
|
<button
|
|
type="submit"
|
|
disabled={updateMutation.isPending}
|
|
className="bg-brand-500 text-white px-6 py-2 rounded-lg text-sm font-medium hover:bg-brand-600 disabled:opacity-60 transition-colors"
|
|
>
|
|
{updateMutation.isPending ? 'Wird gespeichert...' : 'Speichern'}
|
|
</button>
|
|
<Link href="/dashboard/termine" className="px-6 py-2 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-100 transition-colors">
|
|
Abbrechen
|
|
</Link>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
if (confirm('Termin wirklich löschen?')) deleteMutation.mutate({ id })
|
|
}}
|
|
disabled={deleteMutation.isPending}
|
|
className="text-sm text-red-500 hover:text-red-700 transition-colors"
|
|
>
|
|
Löschen
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
{termin.anmeldungen.length > 0 && (
|
|
<div className="bg-white rounded-xl border shadow-sm p-6">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<h2 className="text-sm font-semibold text-gray-700">
|
|
Anmeldungen ({termin.anmeldungen.length}
|
|
{termin.maxTeilnehmer ? ` / ${termin.maxTeilnehmer}` : ''})
|
|
</h2>
|
|
<a href={`/api/export/termin/${id}`}>
|
|
<button
|
|
type="button"
|
|
className="text-sm border border-gray-300 text-gray-700 px-3 py-1.5 rounded-lg hover:bg-gray-50 transition-colors"
|
|
>
|
|
Teilnehmerliste exportieren
|
|
</button>
|
|
</a>
|
|
</div>
|
|
<ul className="space-y-1">
|
|
{termin.anmeldungen.map((a) => (
|
|
<li key={a.id} className="text-sm text-gray-600">
|
|
{a.member.name}
|
|
{a.member.betrieb && <span className="text-gray-400"> · {a.member.betrieb}</span>}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|