From 1e41c05214103c46eff78a7cbf23ae5545c4305f Mon Sep 17 00:00:00 2001 From: Sven laptop Date: Wed, 13 May 2026 17:41:25 +0200 Subject: [PATCH] Enhance schedule management; add recurring schedules screen, implement schedule editing functionality, and improve schedule loading and filtering in ScheduleMaker --- admin_ui/src/components/ScheduleMaker.jsx | 134 ++++++- .../screens/recurring_schedules_screen.dart | 326 ++++++++++++++++ .../lib/screens/schedule_overview_screen.dart | 349 +++++++++--------- transit_api/index.js | 216 ++++++++--- 4 files changed, 804 insertions(+), 221 deletions(-) create mode 100644 bus_terminal/lib/screens/recurring_schedules_screen.dart diff --git a/admin_ui/src/components/ScheduleMaker.jsx b/admin_ui/src/components/ScheduleMaker.jsx index 08111f5..88745a8 100644 --- a/admin_ui/src/components/ScheduleMaker.jsx +++ b/admin_ui/src/components/ScheduleMaker.jsx @@ -4,9 +4,22 @@ import { transitClient } from '../api' const WEEKDAYS = ['Ma', 'Di', 'Wo', 'Do', 'Vr', 'Za', 'Zo'] const WEEKDAY_VALUES = [1, 2, 3, 4, 5, 6, 0] // 0=Sun, 1=Mon... +const toInputDate = (value) => { + const date = new Date(value) + if (Number.isNaN(date.getTime())) return new Date().toISOString().split('T')[0] + return date.toISOString().split('T')[0] +} + +const toInputTime = (value) => { + const date = new Date(value) + if (Number.isNaN(date.getTime())) return '08:00' + return `${String(date.getUTCHours()).padStart(2, '0')}:${String(date.getUTCMinutes()).padStart(2, '0')}` +} + export default function ScheduleMaker({ token, user }) { const client = transitClient(token) const [schedules, setSchedules] = useState([]) + const [scheduleTemplates, setScheduleTemplates] = useState([]) const [overviewSchedules, setOverviewSchedules] = useState([]) const [busLines, setBusLines] = useState([]) const [buses, setBuses] = useState([]) @@ -16,6 +29,7 @@ export default function ScheduleMaker({ token, user }) { const [isAdmin] = useState(Boolean(user?.userCode)) const [deleteConfirm, setDeleteConfirm] = useState(null) const [expandedSchedule, setExpandedSchedule] = useState(null) + const [editingScheduleId, setEditingScheduleId] = useState(null) const [routeStopsByLine, setRouteStopsByLine] = useState({}) // Form state @@ -36,6 +50,15 @@ export default function ScheduleMaker({ token, user }) { } } + const loadScheduleTemplates = async () => { + try { + const res = await client.get('/admin/schedules?templates=1') + setScheduleTemplates((res.data.schedules || []).filter(schedule => Array.isArray(schedule.weekdays) && schedule.weekdays.length > 0)) + } catch (err) { + setMessage('Error loading fixed schedules: ' + (err.response?.data?.error || err.message)) + } + } + const loadOverviewSchedules = async () => { try { const params = new URLSearchParams({ date: overviewDate }) @@ -79,6 +102,7 @@ export default function ScheduleMaker({ token, user }) { useEffect(() => { loadSchedules() + loadScheduleTemplates() loadBusLines() loadBuses() loadDrivers() @@ -88,7 +112,28 @@ export default function ScheduleMaker({ token, user }) { loadOverviewSchedules() }, [overviewDate, selectedDriverFilter]) - const createSchedule = async (e) => { + const resetScheduleForm = () => { + setEditingScheduleId(null) + setSelectedLine('') + setSelectedBus('') + setStartTime('08:00') + setDriverId('') + setSelectedWeekdays([]) + } + + const startEditingSchedule = (schedule) => { + setMessage(null) + setEditingScheduleId(schedule.id) + setSelectedLine(String(schedule.busLineId)) + setSelectedBus(String(schedule.busId)) + setDriverId(schedule.driverId ? String(schedule.driverId) : '') + setSelectedDate(toInputDate(schedule.startTime)) + setStartTime(toInputTime(schedule.startTime)) + setSelectedWeekdays(Array.isArray(schedule.weekdays) ? [...schedule.weekdays] : []) + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + + const submitSchedule = async (e) => { e.preventDefault() setMessage(null) @@ -100,25 +145,27 @@ export default function ScheduleMaker({ token, user }) { try { const startDateTime = `${selectedDate}T${startTime}:00` const normalizedWeekdays = [...selectedWeekdays].sort((a, b) => a - b) - - const res = await client.post('/admin/schedules', { + const payload = { busLineId: Number(selectedLine), busId: Number(selectedBus), driverId: driverId ? Number(driverId) : null, startTime: startDateTime, weekdays: normalizedWeekdays, - }) + } + + const res = editingScheduleId + ? await client.put(`/admin/schedules/${editingScheduleId}`, payload) + : await client.post('/admin/schedules', payload) const dayLabels = normalizedWeekdays.length > 0 ? normalizedWeekdays.map(d => WEEKDAYS[WEEKDAY_VALUES.indexOf(d)]).join(', ') : 'eenmalig' - setMessage(`Dienst aangemaakt: #${res.data.schedule.id} (${dayLabels})`) - setSelectedLine('') - setSelectedBus('') - setStartTime('08:00') - setDriverId('') - setSelectedWeekdays([]) + setMessage(editingScheduleId + ? `Dienst bijgewerkt: #${res.data.schedule.id} (${dayLabels})` + : `Dienst aangemaakt: #${res.data.schedule.id} (${dayLabels})`) + resetScheduleForm() await loadSchedules() + await loadScheduleTemplates() await loadOverviewSchedules() } catch (err) { const errMsg = err.response?.data?.error || err.message @@ -132,7 +179,11 @@ export default function ScheduleMaker({ token, user }) { await client.delete(`/admin/schedules/${scheduleId}`) setMessage(`Dienst verwijderd: Lijn ${lineNumber}, ${busName}`) setDeleteConfirm(null) + if (editingScheduleId === scheduleId) { + resetScheduleForm() + } await loadSchedules() + await loadScheduleTemplates() await loadOverviewSchedules() } catch (err) { const errMsg = err.response?.data?.error || err.message @@ -270,7 +321,21 @@ export default function ScheduleMaker({ token, user }) { {isAdmin && ( -
+ +
+
+

{editingScheduleId ? 'Vaste dienst bewerken' : 'Dienst aanmaken'}

+

+ {editingScheduleId ? 'Werk de bestaande dienst bij en sla de wijzigingen op.' : 'Maak een nieuwe dienst of vaste herhaling aan.'} +

+
+ {editingScheduleId && ( + + )} +
+