From 5f329bb5c1f91fff14ca04aea32accb557c680db Mon Sep 17 00:00:00 2001 From: Matthias Hochmeister Date: Mon, 16 Mar 2026 12:11:32 +0100 Subject: [PATCH] update --- backend/src/routes/booking.routes.ts | 1 + frontend/src/pages/FahrzeugBuchungen.tsx | 3 ++- frontend/src/services/bookings.ts | 2 +- sync/src/scraper.ts | 16 +++++++++++++--- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/backend/src/routes/booking.routes.ts b/backend/src/routes/booking.routes.ts index 9b70acc..2422d2d 100644 --- a/backend/src/routes/booking.routes.ts +++ b/backend/src/routes/booking.routes.ts @@ -23,6 +23,7 @@ router.patch('/:id', authenticate, requirePermission('bookings:write'), bookingC // Soft-cancel (sets abgesagt=TRUE) router.delete('/:id', authenticate, requirePermission('bookings:write'), bookingController.cancel.bind(bookingController)); +router.patch('/:id/cancel', authenticate, requirePermission('bookings:write'), bookingController.cancel.bind(bookingController)); // Hard-delete (admin only) router.delete('/:id/force', authenticate, requirePermission('bookings:delete'), bookingController.hardDelete.bind(bookingController)); diff --git a/frontend/src/pages/FahrzeugBuchungen.tsx b/frontend/src/pages/FahrzeugBuchungen.tsx index 20b5100..554520a 100644 --- a/frontend/src/pages/FahrzeugBuchungen.tsx +++ b/frontend/src/pages/FahrzeugBuchungen.tsx @@ -277,7 +277,8 @@ function FahrzeugBuchungen() { setDetailBooking(null); loadData(); } catch (e: unknown) { - const msg = e instanceof Error ? e.message : 'Fehler beim Stornieren'; + const axiosErr = e as { response?: { data?: { message?: string } }; message?: string }; + const msg = axiosErr?.response?.data?.message || (e instanceof Error ? e.message : 'Fehler beim Stornieren'); notification.showError(msg); } finally { setCancelLoading(false); diff --git a/frontend/src/services/bookings.ts b/frontend/src/services/bookings.ts index 78d07ae..612b7ad 100644 --- a/frontend/src/services/bookings.ts +++ b/frontend/src/services/bookings.ts @@ -90,7 +90,7 @@ export const bookingApi = { cancel(id: string, abgesagt_grund: string): Promise { return api - .delete(`/api/bookings/${id}`, { data: { abgesagt_grund } }) + .patch(`/api/bookings/${id}/cancel`, { abgesagt_grund }) .then(() => undefined); }, diff --git a/sync/src/scraper.ts b/sync/src/scraper.ts index b21a4db..be04dbc 100644 --- a/sync/src/scraper.ts +++ b/sync/src/scraper.ts @@ -905,11 +905,21 @@ async function scrapeMemberFahrgenehmigungen( const pageData = await frame.evaluate(() => { const extractCellValue = (cell: Element): string => { const input = cell.querySelector('input[type="text"], input:not([type])') as HTMLInputElement | null; - if (input) return input.value?.trim() ?? ''; + if (input && input.value?.trim()) return input.value.trim(); const sel = cell.querySelector('select') as HTMLSelectElement | null; if (sel) { - const opt = sel.options[sel.selectedIndex]; - return (opt?.text || opt?.value || '').trim(); + const idx = sel.selectedIndex; + if (idx >= 0 && sel.options[idx]) { + const t = (sel.options[idx].text || sel.options[idx].value || '').trim(); + if (t) return t; + } + if (sel.value?.trim()) return sel.value.trim(); + const selectedOpt = sel.querySelector('option[selected]') as HTMLOptionElement | null; + if (selectedOpt) { + const t = (selectedOpt.text || selectedOpt.value || '').trim(); + if (t) return t; + } + // fall through to textContent if select is empty } const anchor = cell.querySelector('a'); const atitle = anchor?.getAttribute('title')?.trim();