From 9a6b9511c8f6d06ad950ae969d5ee7b58ff38748 Mon Sep 17 00:00:00 2001 From: Matthias Hochmeister Date: Mon, 2 Mar 2026 17:26:01 +0100 Subject: [PATCH] featuer change for calendar --- backend/src/services/booking.service.ts | 8 +- frontend/src/pages/FahrzeugBuchungen.tsx | 10 +- frontend/src/pages/Kalender.tsx | 119 ++++++++++++++++++++++- frontend/src/services/bookings.ts | 2 +- frontend/src/types/booking.types.ts | 9 +- 5 files changed, 129 insertions(+), 19 deletions(-) diff --git a/backend/src/services/booking.service.ts b/backend/src/services/booking.service.ts index 42173d1..c209bb6 100644 --- a/backend/src/services/booking.service.ts +++ b/backend/src/services/booking.service.ts @@ -78,7 +78,7 @@ class BookingService { SELECT b.id, b.fahrzeug_id, b.titel, b.buchungs_art::text AS buchungs_art, b.beginn, b.ende, b.abgesagt, - f.name AS fahrzeug_name, f.kennzeichen AS fahrzeug_kennzeichen, + f.bezeichnung AS fahrzeug_name, f.amtliches_kennzeichen AS fahrzeug_kennzeichen, u.name AS gebucht_von_name FROM fahrzeug_buchungen b JOIN fahrzeuge f ON f.id = b.fahrzeug_id @@ -103,7 +103,7 @@ class BookingService { SELECT b.id, b.fahrzeug_id, b.titel, b.buchungs_art::text AS buchungs_art, b.beginn, b.ende, b.abgesagt, - f.name AS fahrzeug_name, f.kennzeichen AS fahrzeug_kennzeichen, + f.bezeichnung AS fahrzeug_name, f.amtliches_kennzeichen AS fahrzeug_kennzeichen, u.name AS gebucht_von_name FROM fahrzeug_buchungen b JOIN fahrzeuge f ON f.id = b.fahrzeug_id @@ -128,7 +128,7 @@ class BookingService { b.gebucht_von, b.kontakt_person, b.kontakt_telefon, b.abgesagt, b.abgesagt_grund, b.erstellt_am, b.aktualisiert_am, - f.name AS fahrzeug_name, f.kennzeichen AS fahrzeug_kennzeichen, + f.bezeichnung AS fahrzeug_name, f.amtliches_kennzeichen AS fahrzeug_kennzeichen, u.name AS gebucht_von_name FROM fahrzeug_buchungen b JOIN fahrzeuge f ON f.id = b.fahrzeug_id @@ -345,7 +345,7 @@ class BookingService { SELECT b.id, b.titel, b.beschreibung, b.buchungs_art::text AS buchungs_art, b.beginn, b.ende, - f.name AS fahrzeug_name + f.bezeichnung AS fahrzeug_name FROM fahrzeug_buchungen b JOIN fahrzeuge f ON f.id = b.fahrzeug_id WHERE b.abgesagt = FALSE diff --git a/frontend/src/pages/FahrzeugBuchungen.tsx b/frontend/src/pages/FahrzeugBuchungen.tsx index 8dbd364..8a23b82 100644 --- a/frontend/src/pages/FahrzeugBuchungen.tsx +++ b/frontend/src/pages/FahrzeugBuchungen.tsx @@ -423,11 +423,11 @@ function FahrzeugBuchungen() { - {vehicle.name} + {vehicle.bezeichnung} - {vehicle.kennzeichen && ( + {vehicle.amtliches_kennzeichen && ( - {vehicle.kennzeichen} + {vehicle.amtliches_kennzeichen} )} @@ -632,8 +632,8 @@ function FahrzeugBuchungen() { > {vehicles.map((v) => ( - {v.name} - {v.kennzeichen ? ` (${v.kennzeichen})` : ''} + {v.bezeichnung} + {v.amtliches_kennzeichen ? ` (${v.amtliches_kennzeichen})` : ''} ))} diff --git a/frontend/src/pages/Kalender.tsx b/frontend/src/pages/Kalender.tsx index 80f2d90..4129fad 100644 --- a/frontend/src/pages/Kalender.tsx +++ b/frontend/src/pages/Kalender.tsx @@ -55,6 +55,7 @@ import { Edit as EditIcon, Event as EventIcon, HelpOutline as UnknownIcon, + IosShare, Star as StarIcon, Today as TodayIcon, Tune, @@ -1085,6 +1086,12 @@ export default function Kalender() { const [cancelBookingGrund, setCancelBookingGrund] = useState(''); const [cancelBookingLoading, setCancelBookingLoading] = useState(false); + // iCal subscription + const [icalEventOpen, setIcalEventOpen] = useState(false); + const [icalEventUrl, setIcalEventUrl] = useState(''); + const [icalBookingOpen, setIcalBookingOpen] = useState(false); + const [icalBookingUrl, setIcalBookingUrl] = useState(''); + // ── Data loading ───────────────────────────────────────────────────────────── const loadCalendarData = useCallback(async () => { @@ -1354,6 +1361,28 @@ export default function Kalender() { } }; + const handleIcalEventOpen = async () => { + try { + const { subscribeUrl } = await eventsApi.getCalendarToken(); + setIcalEventUrl(subscribeUrl); + setIcalEventOpen(true); + } catch (e: unknown) { + const msg = e instanceof Error ? e.message : 'Fehler beim Laden des Tokens'; + notification.showError(msg); + } + }; + + const handleIcalBookingOpen = async () => { + try { + const { subscribeUrl } = await bookingApi.getCalendarToken(); + setIcalBookingUrl(subscribeUrl); + setIcalBookingOpen(true); + } catch (e: unknown) { + const msg = e instanceof Error ? e.message : 'Fehler beim Laden des Tokens'; + notification.showError(msg); + } + }; + // ── Render ─────────────────────────────────────────────────────────────────── return ( @@ -1451,6 +1480,16 @@ export default function Kalender() { )} + + {/* iCal subscribe */} + {/* Month navigation */} @@ -1594,6 +1633,37 @@ export default function Kalender() { + + {/* iCal Event subscription dialog */} + setIcalEventOpen(false)} maxWidth="sm" fullWidth> + Kalender abonnieren + + + Abonniere den Dienste- & Veranstaltungskalender in deiner Kalenderanwendung. Kopiere die URL und füge sie als neuen + Kalender (per URL) in Apple Kalender, Google Kalender oder Outlook ein. + + { + navigator.clipboard.writeText(icalEventUrl); + notification.showSuccess('URL kopiert!'); + }} + > + + + ), + }} + /> + + + + + )} @@ -1649,6 +1719,16 @@ export default function Kalender() { Neue Buchung )} + + {/* iCal subscribe */} + {bookingsLoading && ( @@ -1696,11 +1776,11 @@ export default function Kalender() { - {vehicle.name} + {vehicle.bezeichnung} - {vehicle.kennzeichen && ( + {vehicle.amtliches_kennzeichen && ( - {vehicle.kennzeichen} + {vehicle.amtliches_kennzeichen} )} @@ -1900,7 +1980,7 @@ export default function Kalender() { > {vehicles.map((v) => ( - {v.name}{v.kennzeichen ? ` (${v.kennzeichen})` : ''} + {v.bezeichnung}{v.amtliches_kennzeichen ? ` (${v.amtliches_kennzeichen})` : ''} ))} @@ -2041,6 +2121,37 @@ export default function Kalender() { + + {/* iCal Booking subscription dialog */} + setIcalBookingOpen(false)} maxWidth="sm" fullWidth> + Kalender abonnieren + + + Abonniere den Fahrzeugbuchungskalender in deiner Kalenderanwendung. Kopiere die URL und füge sie als neuen + Kalender (per URL) in Apple Kalender, Google Kalender oder Outlook ein. + + { + navigator.clipboard.writeText(icalBookingUrl); + notification.showSuccess('URL kopiert!'); + }} + > + + + ), + }} + /> + + + + + )} diff --git a/frontend/src/services/bookings.ts b/frontend/src/services/bookings.ts index f47c227..be2c0d0 100644 --- a/frontend/src/services/bookings.ts +++ b/frontend/src/services/bookings.ts @@ -113,5 +113,5 @@ export const bookingApi = { export function fetchVehicles(): Promise { return api .get>('/api/vehicles') - .then((r) => r.data.data.filter((v: Fahrzeug) => !v.archived_at)); + .then((r) => r.data.data); } diff --git a/frontend/src/types/booking.types.ts b/frontend/src/types/booking.types.ts index f399499..a9c053a 100644 --- a/frontend/src/types/booking.types.ts +++ b/frontend/src/types/booking.types.ts @@ -41,11 +41,10 @@ export interface FahrzeugBuchung extends FahrzeugBuchungListItem { export interface Fahrzeug { id: string; - name: string; - kennzeichen?: string | null; - typ: string; - is_active: boolean; - archived_at?: string | null; + bezeichnung: string; + kurzname: string | null; + amtliches_kennzeichen: string | null; + status: string; } export interface CreateBuchungInput {