diff --git a/frontend/src/components/shared/NotificationBell.tsx b/frontend/src/components/shared/NotificationBell.tsx index 75f7928..910a5ce 100644 --- a/frontend/src/components/shared/NotificationBell.tsx +++ b/frontend/src/components/shared/NotificationBell.tsx @@ -102,7 +102,11 @@ const NotificationBell: React.FC = () => { } handleClose(); if (n.link) { - navigate(n.link); + if (n.link.startsWith('http://') || n.link.startsWith('https://')) { + window.open(n.link, '_blank'); + } else { + navigate(n.link); + } } }; diff --git a/frontend/src/pages/Atemschutz.tsx b/frontend/src/pages/Atemschutz.tsx index b29f51d..514a569 100644 --- a/frontend/src/pages/Atemschutz.tsx +++ b/frontend/src/pages/Atemschutz.tsx @@ -45,7 +45,7 @@ import { atemschutzApi } from '../services/atemschutz'; import { membersService } from '../services/members'; import { useNotification } from '../contexts/NotificationContext'; import { useAuth } from '../contexts/AuthContext'; -import { toGermanDate, fromGermanDate } from '../utils/dateInput'; +import { toGermanDate, fromGermanDate, isValidGermanDate } from '../utils/dateInput'; import type { AtemschutzUebersicht, AtemschutzStats, @@ -162,6 +162,7 @@ function Atemschutz() { const [form, setForm] = useState({ ...EMPTY_FORM }); const [dialogLoading, setDialogLoading] = useState(false); const [dialogError, setDialogError] = useState(null); + const [dateErrors, setDateErrors] = useState>>({}); // Delete confirmation const [deleteId, setDeleteId] = useState(null); @@ -248,6 +249,7 @@ function Atemschutz() { setEditingId(null); setForm({ ...EMPTY_FORM }); setDialogError(null); + setDateErrors({}); }; const handleFormChange = ( @@ -266,12 +268,46 @@ function Atemschutz() { const handleSubmit = async () => { setDialogError(null); + setDateErrors({}); if (!editingId && !form.user_id) { setDialogError('Bitte ein Mitglied auswählen.'); return; } + // Validate date fields + const newDateErrors: Partial> = {}; + const dateFields: (keyof AtemschutzFormState)[] = [ + 'lehrgang_datum', 'untersuchung_datum', 'untersuchung_gueltig_bis', + 'leistungstest_datum', 'leistungstest_gueltig_bis', + ]; + for (const field of dateFields) { + const val = form[field] as string; + if (val && !isValidGermanDate(val)) { + newDateErrors[field] = 'Ungültiges Datum (Format: TT.MM.JJJJ)'; + } + } + if (form.untersuchung_datum && form.untersuchung_gueltig_bis && + isValidGermanDate(form.untersuchung_datum) && isValidGermanDate(form.untersuchung_gueltig_bis)) { + const from = new Date(fromGermanDate(form.untersuchung_datum)!); + const to = new Date(fromGermanDate(form.untersuchung_gueltig_bis)!); + if (to < from) { + newDateErrors['untersuchung_gueltig_bis'] = 'Muss nach dem Untersuchungsdatum liegen'; + } + } + if (form.leistungstest_datum && form.leistungstest_gueltig_bis && + isValidGermanDate(form.leistungstest_datum) && isValidGermanDate(form.leistungstest_gueltig_bis)) { + const from = new Date(fromGermanDate(form.leistungstest_datum)!); + const to = new Date(fromGermanDate(form.leistungstest_gueltig_bis)!); + if (to < from) { + newDateErrors['leistungstest_gueltig_bis'] = 'Muss nach dem Leistungstestdatum liegen'; + } + } + if (Object.keys(newDateErrors).length > 0) { + setDateErrors(newDateErrors); + return; + } + setDialogLoading(true); try { if (editingId) { @@ -653,7 +689,8 @@ function Atemschutz() { placeholder="TT.MM.JJJJ" value={form.lehrgang_datum} onChange={(e) => handleFormChange('lehrgang_datum', e.target.value)} - helperText="Format: 01.03.2025" + error={!!dateErrors.lehrgang_datum} + helperText={dateErrors.lehrgang_datum ?? 'Format: 01.03.2025'} InputLabelProps={{ shrink: true }} /> @@ -672,7 +709,8 @@ function Atemschutz() { placeholder="TT.MM.JJJJ" value={form.untersuchung_datum} onChange={(e) => handleFormChange('untersuchung_datum', e.target.value)} - helperText="Format: 08.02.2023" + error={!!dateErrors.untersuchung_datum} + helperText={dateErrors.untersuchung_datum ?? 'Format: 08.02.2023'} InputLabelProps={{ shrink: true }} /> @@ -684,7 +722,8 @@ function Atemschutz() { placeholder="TT.MM.JJJJ" value={form.untersuchung_gueltig_bis} onChange={(e) => handleFormChange('untersuchung_gueltig_bis', e.target.value)} - helperText="Format: 08.02.2028" + error={!!dateErrors.untersuchung_gueltig_bis} + helperText={dateErrors.untersuchung_gueltig_bis ?? 'Format: 08.02.2028'} InputLabelProps={{ shrink: true }} /> @@ -722,7 +761,8 @@ function Atemschutz() { placeholder="TT.MM.JJJJ" value={form.leistungstest_datum} onChange={(e) => handleFormChange('leistungstest_datum', e.target.value)} - helperText="Format: 25.08.2025" + error={!!dateErrors.leistungstest_datum} + helperText={dateErrors.leistungstest_datum ?? 'Format: 25.08.2025'} InputLabelProps={{ shrink: true }} /> @@ -734,7 +774,8 @@ function Atemschutz() { placeholder="TT.MM.JJJJ" value={form.leistungstest_gueltig_bis} onChange={(e) => handleFormChange('leistungstest_gueltig_bis', e.target.value)} - helperText="Format: 25.08.2026" + error={!!dateErrors.leistungstest_gueltig_bis} + helperText={dateErrors.leistungstest_gueltig_bis ?? 'Format: 25.08.2026'} InputLabelProps={{ shrink: true }} /> diff --git a/frontend/src/pages/Kalender.tsx b/frontend/src/pages/Kalender.tsx index 90ae8c3..13ce417 100644 --- a/frontend/src/pages/Kalender.tsx +++ b/frontend/src/pages/Kalender.tsx @@ -1807,6 +1807,20 @@ export default function Kalender() { }, [bookingForm.fahrzeugId, bookingForm.beginn, bookingForm.ende]); const handleBookingSave = async () => { + if (!isValidGermanDateTime(bookingForm.beginn)) { + setBookingDialogError('Ungültiges Beginn-Datum (Format: TT.MM.JJJJ HH:MM)'); + return; + } + if (!isValidGermanDateTime(bookingForm.ende)) { + setBookingDialogError('Ungültiges Ende-Datum (Format: TT.MM.JJJJ HH:MM)'); + return; + } + const beginnIso = fromGermanDateTime(bookingForm.beginn)!; + const endeIso = fromGermanDateTime(bookingForm.ende)!; + if (new Date(endeIso) <= new Date(beginnIso)) { + setBookingDialogError('Ende muss nach dem Beginn liegen'); + return; + } setBookingDialogLoading(true); setBookingDialogError(null); try { @@ -2216,7 +2230,9 @@ export default function Kalender() { @@ -2711,7 +2727,9 @@ export default function Kalender() { diff --git a/frontend/src/pages/Veranstaltungen.tsx b/frontend/src/pages/Veranstaltungen.tsx index bd9fd5a..721559f 100644 --- a/frontend/src/pages/Veranstaltungen.tsx +++ b/frontend/src/pages/Veranstaltungen.tsx @@ -642,6 +642,20 @@ function EventFormDialog({ notification.showError('Titel ist erforderlich'); return; } + const vonDate = new Date(form.datum_von); + const bisDate = new Date(form.datum_bis); + if (isNaN(vonDate.getTime())) { + notification.showError('Ungültiges Von-Datum'); + return; + } + if (isNaN(bisDate.getTime())) { + notification.showError('Ungültiges Bis-Datum'); + return; + } + if (bisDate < vonDate) { + notification.showError('Bis-Datum muss nach dem Von-Datum liegen'); + return; + } setLoading(true); try { if (editingEvent) {