import { useMemo } from 'react'; import { Box, List, ListItem, ListItemText, Chip, Typography, Button, Skeleton, Tooltip, } from '@mui/material'; import { CheckCircle as CheckIcon, Cancel as CancelIcon, HelpOutline as UnknownIcon, Star as StarIcon, CalendarMonth as CalendarIcon, ArrowForward as ArrowIcon, } from '@mui/icons-material'; import { useNavigate } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import { trainingApi } from '../../services/training'; import type { UebungListItem, TeilnahmeStatus, UebungTyp } from '../../types/training.types'; // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- const TYP_COLORS: Record = { 'Übungsabend': 'primary', 'Lehrgang': 'secondary', 'Sonderdienst': 'warning', 'Versammlung': 'default', 'Gemeinschaftsübung': 'info', 'Sonstiges': 'default', }; const WEEKDAY_SHORT = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']; const MONTH_SHORT = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']; function formatEventDate(isoString: string): string { const d = new Date(isoString); return `${WEEKDAY_SHORT[d.getDay()]}, ${String(d.getDate()).padStart(2, '0')}. ${MONTH_SHORT[d.getMonth()]}`; } function formatEventTime(vonIso: string, bisIso: string): string { const von = new Date(vonIso); const bis = new Date(bisIso); const pad = (n: number) => String(n).padStart(2, '0'); return `${pad(von.getHours())}:${pad(von.getMinutes())} – ${pad(bis.getHours())}:${pad(bis.getMinutes())} Uhr`; } // --------------------------------------------------------------------------- // RSVP Status badge // --------------------------------------------------------------------------- function RsvpBadge({ status }: { status: TeilnahmeStatus | undefined }) { if (!status || status === 'unbekannt') { return ( ); } if (status === 'zugesagt') { return ( ); } if (status === 'erschienen') { return ( ); } if (status === 'abgesagt' || status === 'entschuldigt') { return ( ); } return null; } // --------------------------------------------------------------------------- // Single event row // --------------------------------------------------------------------------- function EventRow({ event }: { event: UebungListItem }) { const navigate = useNavigate(); return ( navigate(`/training/${event.id}`)} sx={{ cursor: 'pointer', borderRadius: 1, mb: 0.5, px: 1, py: 0.75, transition: 'background 0.15s', '&:hover': { backgroundColor: 'action.hover' }, opacity: event.abgesagt ? 0.55 : 1, }} > {/* Date column */} {formatEventDate(event.datum_von)} {formatEventTime(event.datum_von, event.datum_bis)} {/* Title + chip */} {event.pflichtveranstaltung && ( )} {event.titel} } secondary={ } sx={{ my: 0 }} /> {/* RSVP status */} ); } // --------------------------------------------------------------------------- // Main widget component // --------------------------------------------------------------------------- export default function UpcomingEvents() { const navigate = useNavigate(); const { data, isLoading, isError } = useQuery({ queryKey: ['training', 'upcoming', 3], queryFn: () => trainingApi.getUpcoming(3), staleTime: 5 * 60 * 1000, // 5 min }); const events = useMemo(() => data ?? [], [data]); return ( Nächste Dienste {isLoading && ( {[1, 2, 3].map((n) => ( ))} )} {isError && ( Dienste konnten nicht geladen werden. )} {!isLoading && !isError && events.length === 0 && ( Keine bevorstehenden Veranstaltungen. )} {!isLoading && !isError && events.length > 0 && ( {events.map((event) => ( ))} )} ); }