Files
aerzte/components/AppointmentForm.tsx
T
admin 9499011a3a Termine-Tab nur offene Termine, Spalten links, Formular 60% Breite
- 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>
2026-07-01 18:47:02 +02:00

227 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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 &quot;Aktualisieren&quot;, um die Änderungen zu
speichern, oder &quot;Abbrechen&quot;, 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>
);
}