calendar download, date input validate, nc talk notification
This commit is contained in:
@@ -102,7 +102,11 @@ const NotificationBell: React.FC = () => {
|
|||||||
}
|
}
|
||||||
handleClose();
|
handleClose();
|
||||||
if (n.link) {
|
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 { membersService } from '../services/members';
|
||||||
import { useNotification } from '../contexts/NotificationContext';
|
import { useNotification } from '../contexts/NotificationContext';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
import { toGermanDate, fromGermanDate } from '../utils/dateInput';
|
import { toGermanDate, fromGermanDate, isValidGermanDate } from '../utils/dateInput';
|
||||||
import type {
|
import type {
|
||||||
AtemschutzUebersicht,
|
AtemschutzUebersicht,
|
||||||
AtemschutzStats,
|
AtemschutzStats,
|
||||||
@@ -162,6 +162,7 @@ function Atemschutz() {
|
|||||||
const [form, setForm] = useState<AtemschutzFormState>({ ...EMPTY_FORM });
|
const [form, setForm] = useState<AtemschutzFormState>({ ...EMPTY_FORM });
|
||||||
const [dialogLoading, setDialogLoading] = useState(false);
|
const [dialogLoading, setDialogLoading] = useState(false);
|
||||||
const [dialogError, setDialogError] = useState<string | null>(null);
|
const [dialogError, setDialogError] = useState<string | null>(null);
|
||||||
|
const [dateErrors, setDateErrors] = useState<Partial<Record<keyof AtemschutzFormState, string>>>({});
|
||||||
|
|
||||||
// Delete confirmation
|
// Delete confirmation
|
||||||
const [deleteId, setDeleteId] = useState<string | null>(null);
|
const [deleteId, setDeleteId] = useState<string | null>(null);
|
||||||
@@ -248,6 +249,7 @@ function Atemschutz() {
|
|||||||
setEditingId(null);
|
setEditingId(null);
|
||||||
setForm({ ...EMPTY_FORM });
|
setForm({ ...EMPTY_FORM });
|
||||||
setDialogError(null);
|
setDialogError(null);
|
||||||
|
setDateErrors({});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFormChange = (
|
const handleFormChange = (
|
||||||
@@ -266,12 +268,46 @@ function Atemschutz() {
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
setDialogError(null);
|
setDialogError(null);
|
||||||
|
setDateErrors({});
|
||||||
|
|
||||||
if (!editingId && !form.user_id) {
|
if (!editingId && !form.user_id) {
|
||||||
setDialogError('Bitte ein Mitglied auswählen.');
|
setDialogError('Bitte ein Mitglied auswählen.');
|
||||||
return;
|
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);
|
setDialogLoading(true);
|
||||||
try {
|
try {
|
||||||
if (editingId) {
|
if (editingId) {
|
||||||
@@ -653,7 +689,8 @@ function Atemschutz() {
|
|||||||
placeholder="TT.MM.JJJJ"
|
placeholder="TT.MM.JJJJ"
|
||||||
value={form.lehrgang_datum}
|
value={form.lehrgang_datum}
|
||||||
onChange={(e) => handleFormChange('lehrgang_datum', e.target.value)}
|
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 }}
|
InputLabelProps={{ shrink: true }}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -672,7 +709,8 @@ function Atemschutz() {
|
|||||||
placeholder="TT.MM.JJJJ"
|
placeholder="TT.MM.JJJJ"
|
||||||
value={form.untersuchung_datum}
|
value={form.untersuchung_datum}
|
||||||
onChange={(e) => handleFormChange('untersuchung_datum', e.target.value)}
|
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 }}
|
InputLabelProps={{ shrink: true }}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -684,7 +722,8 @@ function Atemschutz() {
|
|||||||
placeholder="TT.MM.JJJJ"
|
placeholder="TT.MM.JJJJ"
|
||||||
value={form.untersuchung_gueltig_bis}
|
value={form.untersuchung_gueltig_bis}
|
||||||
onChange={(e) => handleFormChange('untersuchung_gueltig_bis', e.target.value)}
|
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 }}
|
InputLabelProps={{ shrink: true }}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -722,7 +761,8 @@ function Atemschutz() {
|
|||||||
placeholder="TT.MM.JJJJ"
|
placeholder="TT.MM.JJJJ"
|
||||||
value={form.leistungstest_datum}
|
value={form.leistungstest_datum}
|
||||||
onChange={(e) => handleFormChange('leistungstest_datum', e.target.value)}
|
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 }}
|
InputLabelProps={{ shrink: true }}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -734,7 +774,8 @@ function Atemschutz() {
|
|||||||
placeholder="TT.MM.JJJJ"
|
placeholder="TT.MM.JJJJ"
|
||||||
value={form.leistungstest_gueltig_bis}
|
value={form.leistungstest_gueltig_bis}
|
||||||
onChange={(e) => handleFormChange('leistungstest_gueltig_bis', e.target.value)}
|
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 }}
|
InputLabelProps={{ shrink: true }}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -1807,6 +1807,20 @@ export default function Kalender() {
|
|||||||
}, [bookingForm.fahrzeugId, bookingForm.beginn, bookingForm.ende]);
|
}, [bookingForm.fahrzeugId, bookingForm.beginn, bookingForm.ende]);
|
||||||
|
|
||||||
const handleBookingSave = async () => {
|
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);
|
setBookingDialogLoading(true);
|
||||||
setBookingDialogError(null);
|
setBookingDialogError(null);
|
||||||
try {
|
try {
|
||||||
@@ -2216,7 +2230,9 @@ export default function Kalender() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
startIcon={<FileDownloadIcon />}
|
startIcon={<FileDownloadIcon />}
|
||||||
onClick={() => window.open(icalEventUrl, '_blank')}
|
component="a"
|
||||||
|
href={icalEventUrl}
|
||||||
|
download="veranstaltungen.ics"
|
||||||
>
|
>
|
||||||
Herunterladen
|
Herunterladen
|
||||||
</Button>
|
</Button>
|
||||||
@@ -2711,7 +2727,9 @@ export default function Kalender() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
startIcon={<FileDownloadIcon />}
|
startIcon={<FileDownloadIcon />}
|
||||||
onClick={() => window.open(icalBookingUrl, '_blank')}
|
component="a"
|
||||||
|
href={icalBookingUrl}
|
||||||
|
download="buchungen.ics"
|
||||||
>
|
>
|
||||||
Herunterladen
|
Herunterladen
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -642,6 +642,20 @@ function EventFormDialog({
|
|||||||
notification.showError('Titel ist erforderlich');
|
notification.showError('Titel ist erforderlich');
|
||||||
return;
|
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);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
if (editingEvent) {
|
if (editingEvent) {
|
||||||
|
|||||||
Reference in New Issue
Block a user