From ff72daa55ed8de6d47facb42b3749d6e839be0f9 Mon Sep 17 00:00:00 2001 From: Matthias Hochmeister Date: Fri, 13 Mar 2026 15:22:58 +0100 Subject: [PATCH] update --- .../dashboard/VehicleBookingListWidget.tsx | 238 ++++++++++++++++++ frontend/src/components/dashboard/index.ts | 1 + frontend/src/constants/widgets.ts | 1 + frontend/src/pages/Dashboard.tsx | 13 +- 4 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/dashboard/VehicleBookingListWidget.tsx diff --git a/frontend/src/components/dashboard/VehicleBookingListWidget.tsx b/frontend/src/components/dashboard/VehicleBookingListWidget.tsx new file mode 100644 index 0000000..2a1d0bf --- /dev/null +++ b/frontend/src/components/dashboard/VehicleBookingListWidget.tsx @@ -0,0 +1,238 @@ +import React from 'react'; +import { + Box, + Card, + CardContent, + CircularProgress, + Chip, + Divider, + Link, + List, + ListItem, + Typography, +} from '@mui/material'; +import { DirectionsCar as DirectionsCarIcon } from '@mui/icons-material'; +import { Link as RouterLink } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; +import { bookingApi } from '../../services/bookings'; +import type { FahrzeugBuchungListItem } from '../../types/booking.types'; +import { BUCHUNGS_ART_COLORS, BUCHUNGS_ART_LABELS } from '../../types/booking.types'; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +const WEEKDAY_SHORT = ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.']; + +function formatDateShort(isoString: string): string { + const d = new Date(isoString); + const weekday = WEEKDAY_SHORT[d.getDay()]; + const day = String(d.getDate()).padStart(2, '0'); + const month = String(d.getMonth() + 1).padStart(2, '0'); + return `${weekday} ${day}.${month}.`; +} + +function formatTime(isoString: string): string { + const d = new Date(isoString); + return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')} Uhr`; +} + +// --------------------------------------------------------------------------- +// Component +// --------------------------------------------------------------------------- + +const FETCH_LIMIT = 10; +const DISPLAY_LIMIT = 5; + +const VehicleBookingListWidget: React.FC = () => { + const { data: rawItems = [], isLoading, isError } = useQuery({ + queryKey: ['upcoming-vehicle-bookings'], + queryFn: () => bookingApi.getUpcoming(FETCH_LIMIT), + }); + + const items = React.useMemo( + () => rawItems.filter((b) => !b.abgesagt).slice(0, DISPLAY_LIMIT), + [rawItems], + ); + + // ── Loading state ───────────────────────────────────────────────────────── + if (isLoading) { + return ( + + + + + Buchungen werden geladen... + + + + ); + } + + // ── Error state ─────────────────────────────────────────────────────────── + if (isError) { + return ( + + + + + Nächste Fahrzeugbuchungen + + + Buchungen konnten nicht geladen werden. + + + + ); + } + + // ── Main render ─────────────────────────────────────────────────────────── + return ( + + + {/* Header */} + + + + Nächste Fahrzeugbuchungen + + + + + + {/* Empty state */} + {items.length === 0 ? ( + + + Keine bevorstehenden Buchungen + + + ) : ( + + {items.map((booking, index) => { + const color = BUCHUNGS_ART_COLORS[booking.buchungs_art] ?? '#9e9e9e'; + const label = BUCHUNGS_ART_LABELS[booking.buchungs_art] ?? booking.buchungs_art; + return ( + + + {/* Colored type indicator dot */} + + + {/* Date + title block */} + + + + {formatDateShort(booking.beginn)} + {' '}·{' '} + {formatTime(booking.beginn)} + + + + + + + {booking.titel} + + + + {booking.fahrzeug_name} + {booking.fahrzeug_kennzeichen ? ` · ${booking.fahrzeug_kennzeichen}` : ''} + + + + + {index < items.length - 1 && ( + + )} + + ); + })} + + )} + + {/* Footer link */} + + + + Alle Buchungen + + + + + ); +}; + +export default VehicleBookingListWidget; diff --git a/frontend/src/components/dashboard/index.ts b/frontend/src/components/dashboard/index.ts index 3bae2c5..7ed5923 100644 --- a/frontend/src/components/dashboard/index.ts +++ b/frontend/src/components/dashboard/index.ts @@ -10,6 +10,7 @@ export { default as VikunjaMyTasksWidget } from './VikunjaMyTasksWidget'; export { default as VikunjaQuickAddWidget } from './VikunjaQuickAddWidget'; export { default as VikunjaOverdueNotifier } from './VikunjaOverdueNotifier'; export { default as AdminStatusWidget } from './AdminStatusWidget'; +export { default as VehicleBookingListWidget } from './VehicleBookingListWidget'; export { default as VehicleBookingQuickAddWidget } from './VehicleBookingQuickAddWidget'; export { default as EventQuickAddWidget } from './EventQuickAddWidget'; export { default as AnnouncementBanner } from './AnnouncementBanner'; diff --git a/frontend/src/constants/widgets.ts b/frontend/src/constants/widgets.ts index be8397b..c64d3bd 100644 --- a/frontend/src/constants/widgets.ts +++ b/frontend/src/constants/widgets.ts @@ -8,6 +8,7 @@ export const WIDGETS = [ { key: 'bookstackSearch', label: 'Wissen — Suche', defaultVisible: true }, { key: 'vikunjaTasks', label: 'Vikunja Aufgaben', defaultVisible: true }, { key: 'vikunjaQuickAdd', label: 'Vikunja Schnelleingabe', defaultVisible: true }, + { key: 'vehicleBookingList', label: 'Fahrzeugbuchungen', defaultVisible: true }, { key: 'vehicleBooking', label: 'Fahrzeugbuchung', defaultVisible: true }, { key: 'eventQuickAdd', label: 'Termin erstellen', defaultVisible: true }, { key: 'adminStatus', label: 'Admin Status', defaultVisible: true }, diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 5a0c0e7..146d5ab 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -21,6 +21,7 @@ import VikunjaOverdueNotifier from '../components/dashboard/VikunjaOverdueNotifi import AdminStatusWidget from '../components/dashboard/AdminStatusWidget'; import AnnouncementBanner from '../components/dashboard/AnnouncementBanner'; import VehicleBookingQuickAddWidget from '../components/dashboard/VehicleBookingQuickAddWidget'; +import VehicleBookingListWidget from '../components/dashboard/VehicleBookingListWidget'; import EventQuickAddWidget from '../components/dashboard/EventQuickAddWidget'; import LinksWidget from '../components/dashboard/LinksWidget'; import BannerWidget from '../components/dashboard/BannerWidget'; @@ -144,8 +145,16 @@ function Dashboard() { )} - {canWrite && widgetVisible('vehicleBooking') && ( + {widgetVisible('vehicleBookingList') && ( + + + + + )} + + {canWrite && widgetVisible('vehicleBooking') && ( + @@ -153,7 +162,7 @@ function Dashboard() { )} {canWrite && widgetVisible('eventQuickAdd') && ( - +