new features

This commit is contained in:
Matthias Hochmeister
2026-03-23 16:47:36 +01:00
parent 8c66492b27
commit 690f260b71
9 changed files with 80 additions and 36 deletions

View File

@@ -287,10 +287,17 @@ function FahrzeugBuchungen() {
setDialogLoading(true);
setDialogError(null);
try {
const beginnDate = new Date(form.beginn);
const endeDate = new Date(form.ende);
if (isNaN(beginnDate.getTime()) || isNaN(endeDate.getTime())) {
setDialogError('Ungültiges Datum. Bitte Beginn und Ende prüfen.');
setDialogLoading(false);
return;
}
const payload: CreateBuchungInput = {
...form,
beginn: new Date(form.beginn).toISOString(),
ende: new Date(form.ende).toISOString(),
beginn: beginnDate.toISOString(),
ende: endeDate.toISOString(),
ganztaegig: form.ganztaegig || false,
};
if (editingBooking) {
@@ -374,6 +381,7 @@ function FahrzeugBuchungen() {
buchungsArt: detailBooking.buchungs_art,
kontaktPerson: '',
kontaktTelefon: '',
ganztaegig: detailBooking.ganztaegig || false,
});
setDialogError(null);
setAvailability(null);
@@ -698,9 +706,10 @@ function FahrzeugBuchungen() {
}}
/>
<Typography variant="body2">
{format(parseISO(detailBooking.beginn), 'dd.MM.yyyy HH:mm')}
{' '}
{format(parseISO(detailBooking.ende), 'dd.MM.yyyy HH:mm')}
{detailBooking.ganztaegig
? `${format(parseISO(detailBooking.beginn), 'dd.MM.yyyy')} ${format(parseISO(detailBooking.ende), 'dd.MM.yyyy')} (Ganztägig)`
: `${format(parseISO(detailBooking.beginn), 'dd.MM.yyyy HH:mm')} ${format(parseISO(detailBooking.ende), 'dd.MM.yyyy HH:mm')}`
}
</Typography>
{detailBooking.gebucht_von_name && (
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>
@@ -778,7 +787,6 @@ function FahrzeugBuchungen() {
{vehicles.map((v) => (
<MenuItem key={v.id} value={v.id}>
{v.bezeichnung}
{v.amtliches_kennzeichen ? ` (${v.amtliches_kennzeichen})` : ''}
</MenuItem>
))}
</Select>

View File

@@ -158,6 +158,7 @@ const EMPTY_BOOKING_FORM: CreateBuchungInput = {
buchungsArt: 'intern',
kontaktPerson: '',
kontaktTelefon: '',
ganztaegig: false,
};
// ──────────────────────────────────────────────────────────────────────────────
@@ -1446,7 +1447,7 @@ function VeranstaltungFormDialog({
try {
const createPayload: CreateVeranstaltungInput = {
...form,
wiederholung: (!editingEvent && wiederholungAktiv && wiederholungBis)
wiederholung: ((!editingEvent || (editingEvent.wiederholung && !editingEvent.wiederholung_parent_id)) && wiederholungAktiv && wiederholungBis)
? {
typ: wiederholungTyp,
bis: wiederholungBis,
@@ -1458,7 +1459,7 @@ function VeranstaltungFormDialog({
: null,
};
if (editingEvent) {
await eventsApi.updateEvent(editingEvent.id, form);
await eventsApi.updateEvent(editingEvent.id, createPayload);
notification.showSuccess('Veranstaltung aktualisiert');
} else {
await eventsApi.createEvent(createPayload);
@@ -1636,10 +1637,10 @@ function VeranstaltungFormDialog({
{(!editingEvent || (editingEvent && editingEvent.wiederholung)) && (
<>
<Divider />
{editingEvent && editingEvent.wiederholung ? (
{editingEvent && editingEvent.wiederholung && editingEvent.wiederholung_parent_id ? (
<>
<Typography variant="body2" color="text.secondary" sx={{ fontStyle: 'italic' }}>
Wiederholung kann nicht bearbeitet werden
Wiederholung kann nicht bearbeitet werden (Einzeltermin einer Serie)
</Typography>
<FormControlLabel
control={<Switch checked disabled />}
@@ -1659,7 +1660,7 @@ function VeranstaltungFormDialog({
)}
{wiederholungAktiv && (
<Stack spacing={2}>
<FormControl fullWidth size="small" disabled={!!editingEvent}>
<FormControl fullWidth size="small" disabled={!!editingEvent?.wiederholung_parent_id}>
<InputLabel id="wiederholung-typ-label">Wiederholung</InputLabel>
<Select
labelId="wiederholung-typ-label"
@@ -1684,12 +1685,12 @@ function VeranstaltungFormDialog({
onChange={(e) => setWiederholungIntervall(Math.max(1, Math.min(52, parseInt(e.target.value) || 1)))}
inputProps={{ min: 1, max: 52 }}
fullWidth
disabled={!!editingEvent}
disabled={!!editingEvent?.wiederholung_parent_id}
/>
)}
{(wiederholungTyp === 'monatlich_erster_wochentag' || wiederholungTyp === 'monatlich_letzter_wochentag') && (
<FormControl fullWidth size="small" disabled={!!editingEvent}>
<FormControl fullWidth size="small" disabled={!!editingEvent?.wiederholung_parent_id}>
<InputLabel id="wiederholung-wochentag-label">Wochentag</InputLabel>
<Select
labelId="wiederholung-wochentag-label"
@@ -1712,7 +1713,7 @@ function VeranstaltungFormDialog({
onChange={(e) => setWiederholungBis(e.target.value)}
InputLabelProps={{ shrink: true }}
fullWidth
disabled={!!editingEvent}
disabled={!!editingEvent?.wiederholung_parent_id}
helperText="Letztes Datum für Wiederholungen"
/>
</Stack>
@@ -2085,6 +2086,7 @@ export default function Kalender() {
buchungsArt: detailBooking.buchungs_art,
kontaktPerson: '',
kontaktTelefon: '',
ganztaegig: (detailBooking as any).ganztaegig || false,
});
setBookingDialogError(null);
setAvailability(null);
@@ -2128,7 +2130,17 @@ export default function Kalender() {
}
const beginnIso = fromGermanDateTime(bookingForm.beginn)!;
const endeIso = fromGermanDateTime(bookingForm.ende)!;
if (new Date(endeIso) <= new Date(beginnIso)) {
const beginnDate = new Date(beginnIso);
const endeDate = new Date(endeIso);
if (isNaN(beginnDate.getTime())) {
setBookingDialogError('Ungültiges Beginn-Datum');
return;
}
if (isNaN(endeDate.getTime())) {
setBookingDialogError('Ungültiges Ende-Datum');
return;
}
if (endeDate <= beginnDate) {
setBookingDialogError('Ende muss nach dem Beginn liegen');
return;
}
@@ -2137,8 +2149,8 @@ export default function Kalender() {
try {
const payload: CreateBuchungInput = {
...bookingForm,
beginn: (() => { const iso = fromGermanDateTime(bookingForm.beginn); return iso ? new Date(iso).toISOString() : new Date(bookingForm.beginn).toISOString(); })(),
ende: (() => { const iso = fromGermanDateTime(bookingForm.ende); return iso ? new Date(iso).toISOString() : new Date(bookingForm.ende).toISOString(); })(),
beginn: beginnDate.toISOString(),
ende: endeDate.toISOString(),
};
if (editingBooking) {
await bookingApi.update(editingBooking.id, payload);
@@ -2874,11 +2886,6 @@ export default function Kalender() {
<Typography variant="body2" fontWeight={600}>
{vehicle.bezeichnung}
</Typography>
{vehicle.amtliches_kennzeichen && (
<Typography variant="caption" color="text.secondary">
{vehicle.amtliches_kennzeichen}
</Typography>
)}
</TableCell>
{weekDays.map((day) => {
const cellBookings = getBookingsForCell(vehicle.id, day);
@@ -3010,9 +3017,10 @@ export default function Kalender() {
}}
/>
<Typography variant="body2">
{fnsFormat(parseISO(detailBooking.beginn), 'dd.MM.yyyy HH:mm')}
{' '}
{fnsFormat(parseISO(detailBooking.ende), 'dd.MM.yyyy HH:mm')}
{(detailBooking as any).ganztaegig
? `${fnsFormat(parseISO(detailBooking.beginn), 'dd.MM.yyyy')} ${fnsFormat(parseISO(detailBooking.ende), 'dd.MM.yyyy')} (Ganztägig)`
: `${fnsFormat(parseISO(detailBooking.beginn), 'dd.MM.yyyy HH:mm')} ${fnsFormat(parseISO(detailBooking.ende), 'dd.MM.yyyy HH:mm')}`
}
</Typography>
{detailBooking.gebucht_von_name && (
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>
@@ -3074,7 +3082,7 @@ export default function Kalender() {
>
{vehicles.map((v) => (
<MenuItem key={v.id} value={v.id}>
{v.bezeichnung}{v.amtliches_kennzeichen ? ` (${v.amtliches_kennzeichen})` : ''}
{v.bezeichnung}
</MenuItem>
))}
</Select>
@@ -3088,6 +3096,19 @@ export default function Kalender() {
}
/>
<FormControlLabel
control={
<Switch
checked={bookingForm.ganztaegig || false}
onChange={(e) => {
const checked = e.target.checked;
setBookingForm((f) => ({ ...f, ganztaegig: checked }));
}}
/>
}
label="Ganztägig"
/>
<TextField
fullWidth size="small" label="Beginn" placeholder="TT.MM.JJJJ HH:MM" required
value={bookingForm.beginn}