import React, { useState, useEffect } from 'react' const TimetableDisplay = ({ client, overviewDate, setOverviewDate, selectedDriverFilter, setSelectedDriverFilter, drivers }) => { const [overviewSchedules, setOverviewSchedules] = useState([]) const [message, setMessage] = useState(null) const [expandedSchedule, setExpandedSchedule] = useState(null) const [routeStopsByLine, setRouteStopsByLine] = useState({}) const loadOverviewSchedules = async () => { try { const params = new URLSearchParams({ date: overviewDate }) if (selectedDriverFilter) { params.set('driverId', selectedDriverFilter) } const res = await client.get(`/schedules/overview?${params.toString()}`) setOverviewSchedules(res.data.schedules || []) } catch (err) { setMessage('Error loading timetable: ' + (err.response?.data?.error || err.message)) } } useEffect(() => { loadOverviewSchedules() }, [overviewDate, selectedDriverFilter]) const formatTime = (timeString) => { const date = new Date(timeString) return date.toLocaleTimeString('nl-NL', { hour: '2-digit', minute: '2-digit' }) } const loadRouteStops = async (busLineId) => { if (!busLineId) return try { const res = await client.get(`/admin/bus-lines/${busLineId}/stops`) const routeStops = res.data.routeStops || [] setRouteStopsByLine(prev => ({ ...prev, [busLineId]: routeStops })) } catch (err) { // ignore silently } } const getWeekdayLabel = (weekdayValues) => { const WEEKDAYS = ['Ma', 'Di', 'Wo', 'Do', 'Vr', 'Za', 'Zo'] const WEEKDAY_VALUES = [1, 2, 3, 4, 5, 6, 0] if (!weekdayValues || weekdayValues.length === 0) return 'Eenmalig' if (weekdayValues.length === 7) return 'Dagelijks' const labels = weekdayValues .slice() .sort((a, b) => a - b) .map(w => WEEKDAYS[WEEKDAY_VALUES.indexOf(w)]) return labels.join(', ') } const timeSlots = (() => { const slots = [] for (let hour = 0; hour < 24; hour++) { slots.push(`${String(hour).padStart(2, '0')}:00-${String((hour + 1) % 24).padStart(2, '0')}:00`) } return slots })() const schedulesBySlot = (() => { const grouped = {} timeSlots.forEach(slot => { grouped[slot] = [] }) overviewSchedules.forEach(schedule => { const startHour = new Date(schedule.startTime).getHours() const slot = `${String(startHour).padStart(2, '0')}:00-${String((startHour + 1) % 24).padStart(2, '0')}:00` if (grouped[slot]) { grouped[slot].push(schedule) } }) return grouped })() return (

Dagelijks Overzicht

Alle geplande diensten voor de geselecteerde dag

setOverviewDate(e.target.value)} className="no-grow" style={{ minWidth: 150 }} />
{timeSlots.map((slot, idx) => ( ))}
Tijdvak Lijnen en Voertuigen
{slot}
{schedulesBySlot[slot]?.map(schedule => (
Lijn {schedule.lineNumber} {formatTime(schedule.startTime)} - {formatTime(schedule.endTime)}
{schedule.busName}
{schedule.licensePlate}
{schedule.driverName && (
{schedule.driverName}
)}
{schedule.recurrenceLabel || getWeekdayLabel(schedule.weekdays)}
{/* Expandable stops list */} {expandedSchedule === schedule.id && (
{(() => { const lineId = schedule.busLineId const cached = routeStopsByLine[lineId] if (!cached) { loadRouteStops(lineId) return

Laden haltes…

} if (cached.length === 0) { return

Geen haltes gekoppeld

} return ( {cached.map(rs => { const departureDate = new Date(schedule.startTime) departureDate.setMinutes(departureDate.getMinutes() + (rs.estimatedArrivalMinutes || 0)) return ( ) })}
{rs.stopOrder}. {rs.name} {formatTime(departureDate.toISOString())}
) })()}
)}
))}
{message &&
{message}
}
) } export default TimetableDisplay