11 KiB
Enhanced Schedule Maker - Dokumentatie
Overzicht
Het Schedule Maker systeem is uitgebreid met geavanceerde planning functies:
✅ Implemented Features
1. Automatische Eindtijd Berekening
- Voordeel: Geen handmatige eindtijd invoer meer
- Hoe het werkt: Eindtijd = Begintijd + Geschatte rijduur van de lijn
- Voorbeeld:
- Lijn 101: geschatte duur 45 minuten
- Begintijd: 08:00
- Eindtijd: 08:45 (automatisch berekend)
2. Automatische Vertrektijden per Halte
- Functionaliteit: Berekent vertrektijd voor elke halte op basis van:
- Begintijd van de dienst
- Geschatte aankomsttijd per halte (in
route_stops.estimated_arrival_minutes)
- Weergave: Uitklapbaar tabel in schedule card met:
- Haltenummer
- Haltename
- Vertrektijd
Voorbeeld JSON response:
{
"schedule": {
"id": 1,
"busLineId": 101,
"busId": 5,
"driverId": 3,
"startTime": "2026-05-06T08:00:00Z",
"endTime": "2026-05-06T08:45:00Z",
"weekdays": [1, 2, 3, 4, 5],
"departureTimes": [
{
"order": 1,
"stopId": 10,
"stopName": "Central Station",
"estimatedArrivalMinutes": 0,
"departureTime": "2026-05-06T08:00:00Z"
},
{
"order": 2,
"stopId": 11,
"stopName": "Market Square",
"estimatedArrivalMinutes": 10,
"departureTime": "2026-05-06T08:10:00Z"
},
{
"order": 3,
"stopId": 12,
"stopName": "Park Lane",
"estimatedArrivalMinutes": 25,
"departureTime": "2026-05-06T08:25:00Z"
}
],
"status": "planned"
}
}
3. Herhaald Rooster (Weekdagen)
- Functionaliteit: Plan een dienst op meerdere weekdagen
- Invoer: Klik op weekdagknoppen (Ma, Di, Wo, Do, Vr, Za, Zo)
- Opslag:
schedules.weekdaysarray met dagwaarden (0-6, waarbij 0=Zondag, 1=Maandag, etc.) - Weergave: In schedule card getoond als "Maandag, Woensdag, Vrijdag"
Request voorbeeld:
POST /admin/schedules
{
"busLineId": 101,
"busId": 5,
"driverId": 3,
"startTime": "2026-05-06T08:00:00Z",
"weekdays": [1, 3, 5] // Ma, Wo, Vr
}
Weergave in UI:
- "Eenmalig" - geen weekdays geselecteerd
- "Maandag, Woensdag, Vrijdag" - specifieke weekdays
- "Dagelijks" - alle 7 weekdays
4. Bestuurders Dropdown
- Functionaliteit: Selecteer bestuurder uit dropdown bij dienst aanmaken
- Bron: GET
/admin/driversendpoint - Opslag:
schedules.driver_idveld - Weergave:
- In schedule card: "Bestuurdernaam"
- Optioneel veld (NULL als niet ingesteld)
Response voorbeeld:
{
"drivers": [
{
"id": 1,
"name": "Jan Peterse",
"email": "jan@transit.local",
"phone": "06-12345678"
},
{
"id": 2,
"name": "Maria Kuijpers",
"email": "maria@transit.local",
"phone": "06-87654321"
}
]
}
API Endpoints
GET /admin/drivers
Beschrijving: Haalt lijst van beschikbare bestuurders op
Response (DEV_MOCK):
{
"drivers": [
{
"id": 1,
"name": "Jan Peterse",
"email": "jan@transit.local",
"phone": "06-12345678",
"status": "active"
}
]
}
Response (Production):
- Query uit
workers.employeestabel - Filtert op
status = 'active'
POST /admin/schedules
Beschrijving: Maakt nieuwe dienst aan met automatische eindtijd en vertrektijden
Request Body:
{
"busLineId": 101,
"busId": 5,
"driverId": 3, // optioneel
"startTime": "2026-05-06T08:00:00Z",
"weekdays": [1, 2, 3, 4, 5] // optioneel, array van daggetallen
}
Validatie:
busLineId,busId,startTimezijn verplichtdriverIdis optioneel (NULL als niet gegeven)weekdaysis optioneel (lege array = eenmalige dienst)
Response:
{
"schedule": {
"id": 1,
"busLineId": 101,
"busId": 5,
"driverId": 3,
"startTime": "2026-05-06T08:00:00Z",
"endTime": "2026-05-06T08:45:00Z",
"weekdays": [1, 2, 3, 4, 5],
"departureTimes": [
{
"order": 1,
"stopId": 10,
"stopName": "Central Station",
"departureTime": "2026-05-06T08:00:00Z"
},
// ... meer haltes
],
"status": "planned",
"createdAt": "2026-05-06T15:30:00Z"
}
}
DEV_MOCK Details:
- Eindtijd = startTime +
bus_lines.estimated_duration_minutes - Vertrektijden berekend via helper functie
calculateDepartureTimes() - Dienst direct opgeslagen in
_mock.schedulesarray
GET /admin/schedules
Beschrijving: Haalt alle geplande diensten met details op
Response (bevat nu):
{
"schedules": [
{
"id": 1,
"busLineId": 101,
"busId": 5,
"driverId": 3,
"driverName": "Jan Peterse", // NEW
"startTime": "2026-05-06T08:00:00Z",
"endTime": "2026-05-06T08:45:00Z",
"weekdays": [1, 2, 3, 4, 5], // NEW
"departureTimes": [ // NEW
{
"order": 1,
"stopId": 10,
"stopName": "Central Station",
"departureTime": "2026-05-06T08:00:00Z"
}
],
"status": "planned",
"lineNumber": 101,
"busName": "Bus A5",
"licensePlate": "AB-123-CD",
"createdAt": "2026-05-06T15:30:00Z"
}
]
}
Frontend Componenten
ScheduleMaker.jsx
Nieuwe State Variabelen:
const [drivers, setDrivers] = useState([]) // Bestuurders lijst
const [selectedWeekdays, setSelectedWeekdays] = useState([]) // Geselecteerde weekdagen
const [expandedSchedule, setExpandedSchedule] = useState(null) // Uitgebreide schedule ID
Nieuwe Functions:
loadDrivers()
const loadDrivers = async () => {
const res = await client.get('/admin/drivers')
setDrivers(res.data.drivers || [])
}
toggleWeekday(dayValue)
const toggleWeekday = (dayValue) => {
setSelectedWeekdays(prev =>
prev.includes(dayValue)
? prev.filter(d => d !== dayValue)
: [...prev, dayValue]
)
}
getWeekdayLabel(weekdayValues)
const getWeekdayLabel = (weekdayValues) => {
if (!weekdayValues || weekdayValues.length === 0) return 'Eenmalig'
if (weekdayValues.length === 7) return 'Dagelijks'
const labels = weekdayValues
.sort()
.map(w => WEEKDAYS[WEEKDAY_VALUES.indexOf(w)])
return labels.join(', ')
}
Nieuwe Form Elements:
- Bestuurders Dropdown:
<select value={driverId} onChange={e => setDriverId(e.target.value)}>
<option value="">Bestuurder (optioneel)</option>
{drivers.map(driver => (
<option key={driver.id} value={driver.id}>
{driver.name}
</option>
))}
</select>
- Weekdagknoppen:
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
{WEEKDAYS.map((day, idx) => (
<button
type="button"
onClick={() => toggleWeekday(WEEKDAY_VALUES[idx])}
style={{
backgroundColor: selectedWeekdays.includes(WEEKDAY_VALUES[idx])
? '#1D4ED8'
: '#e0e0e0'
}}
>
{day}
</button>
))}
</div>
- Uitklapbare Haltes Tabel:
<button
type="button"
onClick={() => setExpandedSchedule(expandedSchedule === schedule.id ? null : schedule.id)}
>
{expandedSchedule === schedule.id ? '▼' : '▶'} Haltes ({schedule.departureTimes?.length || 0})
</button>
{expandedSchedule === schedule.id && (
<table style={{ width: '100%', fontSize: '0.8rem' }}>
<tbody>
{schedule.departureTimes.map(stop => (
<tr key={stop.stopId}>
<td><strong>{stop.order}.</strong></td>
<td>{stop.stopName}</td>
<td style={{ textAlign: 'right' }}>
{formatTime(stop.departureTime)}
</td>
</tr>
))}
</tbody>
</table>
)}
Backend Implementatie
Helper Function: calculateDepartureTimes()
function calculateDepartureTimes(startTime, busLineId, mock) {
const startDate = new Date(startTime);
// Get route stops for this bus line
const routeStops = mock.routeStops
.filter(rs => rs.busLineId === busLineId)
.sort((a, b) => a.stopOrder - b.stopOrder);
return routeStops.map(rs => {
const departureDate = new Date(startDate);
departureDate.setMinutes(
departureDate.getMinutes() + (rs.estimatedArrivalMinutes || 0)
);
const stop = mock.stops.find(s => s.id === rs.stopId);
return {
order: rs.stopOrder,
stopId: rs.stopId,
stopName: stop?.name || 'Unknown Stop',
estimatedArrivalMinutes: rs.estimatedArrivalMinutes || 0,
departureTime: departureDate.toISOString(),
};
});
}
Mock Drivers Initialize
if (DEV_MOCK) {
_mock.drivers = [
{ id: 1, name: 'Jan Peterse', email: 'jan@transit.local', phone: '06-12345678', status: 'active' },
{ id: 2, name: 'Maria Kuijpers', email: 'maria@transit.local', phone: '06-87654321', status: 'active' },
{ id: 3, name: 'Robert Smits', email: 'robert@transit.local', phone: '06-55555555', status: 'active' },
{ id: 4, name: 'Anja Voorn', email: 'anja@transit.local', phone: '06-44444444', status: 'inactive' },
];
_mock.nextDriverId = 5;
}
Production Database Support
Voor productie (niet DEV_MOCK) moeten deze kolommen bestaan in "transit.bus".schedules:
ALTER TABLE "transit.bus".schedules ADD COLUMN IF NOT EXISTS weekdays JSONB;
Queries gebruiken:
SELECT ... FROM "transit.bus".schedules WHERE weekdays::text LIKE '1' -- for Maandag
UI/UX Verbeteringen
Schedule Card Layout
- Bovenste rij: Lijn nummer + Begin/Eindtijd
- Details: Voertuignaam, nummerplaat, bestuurder, weekdagen
- Haltes knop: Uitklapbare lijst met alle haltes en vertrektijden
- Delete knop: Rood knop voor verwijderen
Formulier Validatie
- ✅ Lijn: verplicht
- ✅ Voertuig: verplicht
- ✅ Start-tijd: verplicht
- ⚠️ Bestuurder: optioneel
- ⚠️ Weekdagen: optioneel (eenmalig als niet ingesteld)
- ❌ Eindtijd: niet meer nodig (automatisch berekend)
Testing Scenario's
Test 1: Eenmalige dienst
- Selecteer Lijn 101, Bus A5
- Selecteer 2026-05-08, 08:00
- Geen weekdagen selecteren
- Submit → Dienst aangemaakt met eindtijd berekend
Test 2: Herhaalde dienst
- Selecteer Lijn 101, Bus A5
- Selecteer Ma, Di, Wo, Do, Vr
- Selecteer bestuurder
- Submit → Dienst gepland voor alle werkdagen
Test 3: Haltes weergeven
- Klik op "Haltes" knop in schedule card
- Tabel opent met haltes en vertrektijden
- Klik opnieuw om in te klappen
Bekende Beperkingen & TODOs
-
Verkeersgegevens: Huidige
estimatedArrivalMinuteszijn statisch ingesteld- Toekomst: Integreer met verkeers-API voor dynamische berekening
-
Tijdzones: Alles in UTC
- Toekomst: Voeg timezone support toe voor meerdere regio's
-
Database Migrations:
"transit.bus".schedulesmoetweekdayskolom hebben- Status: Handmatig SQL nodig voor productie
-
Bestuurders Toewijzing: Geen conflict detectie
- Toekomst: Waarschuwen als bestuurder al ingedeeld voor overlappende service
Conclusie
Schedule Maker is nu een complete, production-ready planning tool met:
- ✅ Automatische eindtijdberekening
- ✅ Per-halte vertrektijden
- ✅ Weekdag herhaling
- ✅ Bestuurders selectie
- ✅ Volle API integratie