calendar download, date input validate, nc talk notification
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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<AtemschutzFormState>({ ...EMPTY_FORM });
|
||||
const [dialogLoading, setDialogLoading] = useState(false);
|
||||
const [dialogError, setDialogError] = useState<string | null>(null);
|
||||
const [dateErrors, setDateErrors] = useState<Partial<Record<keyof AtemschutzFormState, string>>>({});
|
||||
|
||||
// Delete confirmation
|
||||
const [deleteId, setDeleteId] = useState<string | null>(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<Record<keyof AtemschutzFormState, string>> = {};
|
||||
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 }}
|
||||
/>
|
||||
</Grid>
|
||||
@@ -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 }}
|
||||
/>
|
||||
</Grid>
|
||||
@@ -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 }}
|
||||
/>
|
||||
</Grid>
|
||||
@@ -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 }}
|
||||
/>
|
||||
</Grid>
|
||||
@@ -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 }}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -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() {
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<FileDownloadIcon />}
|
||||
onClick={() => window.open(icalEventUrl, '_blank')}
|
||||
component="a"
|
||||
href={icalEventUrl}
|
||||
download="veranstaltungen.ics"
|
||||
>
|
||||
Herunterladen
|
||||
</Button>
|
||||
@@ -2711,7 +2727,9 @@ export default function Kalender() {
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<FileDownloadIcon />}
|
||||
onClick={() => window.open(icalBookingUrl, '_blank')}
|
||||
component="a"
|
||||
href={icalBookingUrl}
|
||||
download="buchungen.ics"
|
||||
>
|
||||
Herunterladen
|
||||
</Button>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user