9499011a3a
- Termine-Tab (/) zeigt in der Liste nur noch offene (nicht erledigte) Termine; Überschrift entsprechend "Offene Termine" - Spalten Arzt, Fachrichtung und Bemerkungen linksbündig (Header + Zellen) - Eingabe-Panel (AppointmentForm) auf 60% Breite reduziert und zentriert Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
227 lines
6.7 KiB
TypeScript
227 lines
6.7 KiB
TypeScript
'use client';
|
||
|
||
import { useState } from 'react';
|
||
import { Appointment, AppointmentFormData, AppointmentPayload } from '@/types/appointment';
|
||
|
||
interface AppointmentFormProps {
|
||
onAdd: (payload: AppointmentPayload) => void;
|
||
onUpdate: (id: string, payload: AppointmentPayload) => void;
|
||
editingAppointment: Appointment | null;
|
||
onCancelEdit: () => void;
|
||
}
|
||
|
||
const emptyForm: AppointmentFormData = {
|
||
arztName: '',
|
||
arztArt: '',
|
||
terminDatum: '',
|
||
terminUhrzeit: '',
|
||
bemerkungen: '',
|
||
};
|
||
|
||
// Hilfsfunktionen: ISO-String -> Formularfelder
|
||
const toDateInput = (iso: string) => {
|
||
const d = new Date(iso);
|
||
const y = d.getFullYear();
|
||
const m = String(d.getMonth() + 1).padStart(2, '0');
|
||
const day = String(d.getDate()).padStart(2, '0');
|
||
return `${y}-${m}-${day}`;
|
||
};
|
||
const toTimeInput = (iso: string) => {
|
||
const d = new Date(iso);
|
||
const h = String(d.getHours()).padStart(2, '0');
|
||
const min = String(d.getMinutes()).padStart(2, '0');
|
||
return `${h}:${min}`;
|
||
};
|
||
|
||
// Leitet die Formularfelder aus dem zu bearbeitenden Termin ab (oder leer).
|
||
const buildInitialForm = (
|
||
editing: Appointment | null
|
||
): AppointmentFormData =>
|
||
editing
|
||
? {
|
||
arztName: editing.arztName,
|
||
arztArt: editing.arztArt ?? '',
|
||
terminDatum: toDateInput(editing.termin),
|
||
terminUhrzeit: toTimeInput(editing.termin),
|
||
bemerkungen: editing.bemerkungen ?? '',
|
||
}
|
||
: emptyForm;
|
||
|
||
export default function AppointmentForm({
|
||
onAdd,
|
||
onUpdate,
|
||
editingAppointment,
|
||
onCancelEdit,
|
||
}: AppointmentFormProps) {
|
||
// Der Anfangszustand wird aus den Props abgeleitet. Wechselt der zu
|
||
// bearbeitende Termin, wird die Komponente über einen `key` im Parent
|
||
// neu gemountet – dadurch entfällt ein setState im Effect.
|
||
const [formData, setFormData] = useState<AppointmentFormData>(() =>
|
||
buildInitialForm(editingAppointment)
|
||
);
|
||
|
||
const handleChange = (
|
||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||
) => {
|
||
const { name, value } = e.target;
|
||
setFormData((prev) => ({ ...prev, [name]: value }));
|
||
};
|
||
|
||
const handleSubmit = (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
|
||
if (!formData.arztName || !formData.terminDatum || !formData.terminUhrzeit) {
|
||
alert('Bitte füllen Sie Name, Datum und Uhrzeit aus.');
|
||
return;
|
||
}
|
||
|
||
const termin = new Date(
|
||
`${formData.terminDatum}T${formData.terminUhrzeit}`
|
||
).toISOString();
|
||
|
||
const payload: AppointmentPayload = {
|
||
arztName: formData.arztName,
|
||
arztArt: formData.arztArt,
|
||
termin,
|
||
erledigt: editingAppointment ? editingAppointment.erledigt : false,
|
||
bemerkungen: formData.bemerkungen,
|
||
};
|
||
|
||
if (editingAppointment) {
|
||
onUpdate(editingAppointment.id, payload);
|
||
} else {
|
||
onAdd(payload);
|
||
}
|
||
};
|
||
|
||
const inputClass =
|
||
'w-full px-2 py-1 text-sm rounded border-2 border-gray-400 bg-white focus:border-blue-500 focus:outline-none';
|
||
|
||
return (
|
||
<div className="bg-[#CCCCFF] border border-black p-6 rounded-lg mb-6 w-3/5 mx-auto">
|
||
{editingAppointment && (
|
||
<div className="mb-4 p-3 bg-blue-100 border border-blue-400 rounded text-sm text-blue-800">
|
||
ℹ️ <strong>Bearbeitungsmodus:</strong> Sie bearbeiten einen bestehenden
|
||
Termin. Klicken Sie auf "Aktualisieren", um die Änderungen zu
|
||
speichern, oder "Abbrechen", um zur Neuerfassung
|
||
zurückzukehren.
|
||
</div>
|
||
)}
|
||
<form onSubmit={handleSubmit}>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div>
|
||
<label
|
||
htmlFor="arztName"
|
||
className="block text-sm font-medium text-gray-800 mb-1"
|
||
>
|
||
Name des Arztes
|
||
</label>
|
||
<input
|
||
type="text"
|
||
id="arztName"
|
||
name="arztName"
|
||
className={inputClass}
|
||
value={formData.arztName}
|
||
onChange={handleChange}
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label
|
||
htmlFor="arztArt"
|
||
className="block text-sm font-medium text-gray-800 mb-1"
|
||
>
|
||
Art des Arztes
|
||
</label>
|
||
<input
|
||
type="text"
|
||
id="arztArt"
|
||
name="arztArt"
|
||
className={inputClass}
|
||
placeholder="z.B. Zahnarzt, Kardiologe"
|
||
value={formData.arztArt}
|
||
onChange={handleChange}
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label
|
||
htmlFor="terminDatum"
|
||
className="block text-sm font-medium text-gray-800 mb-1"
|
||
>
|
||
Datum
|
||
</label>
|
||
<input
|
||
type="date"
|
||
id="terminDatum"
|
||
name="terminDatum"
|
||
className={inputClass}
|
||
value={formData.terminDatum}
|
||
onChange={handleChange}
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label
|
||
htmlFor="terminUhrzeit"
|
||
className="block text-sm font-medium text-gray-800 mb-1"
|
||
>
|
||
Uhrzeit
|
||
</label>
|
||
<input
|
||
type="time"
|
||
id="terminUhrzeit"
|
||
name="terminUhrzeit"
|
||
className={inputClass}
|
||
value={formData.terminUhrzeit}
|
||
onChange={handleChange}
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div className="md:col-span-2">
|
||
<label
|
||
htmlFor="bemerkungen"
|
||
className="block text-sm font-medium text-gray-800 mb-1"
|
||
>
|
||
Bemerkungen
|
||
</label>
|
||
<textarea
|
||
id="bemerkungen"
|
||
name="bemerkungen"
|
||
rows={3}
|
||
className={`${inputClass} resize-y`}
|
||
value={formData.bemerkungen}
|
||
onChange={handleChange}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex justify-center gap-4 mt-6">
|
||
<button
|
||
type="submit"
|
||
className="bg-[#85B7D7] hover:bg-[#6a9fc5] text-black font-medium py-2 px-8 rounded-lg transition-colors"
|
||
>
|
||
{editingAppointment ? 'Aktualisieren' : 'Speichern'}
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
if (editingAppointment) {
|
||
onCancelEdit();
|
||
} else {
|
||
setFormData(emptyForm);
|
||
}
|
||
}}
|
||
className="bg-[#85B7D7] hover:bg-[#6a9fc5] text-black font-medium py-2 px-8 rounded-lg transition-colors"
|
||
>
|
||
{editingAppointment ? 'Abbrechen' : 'Löschen'}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
);
|
||
}
|