──────
Build and Push Docker Images / detect-changes (push) Successful in 11s
Build and Push Docker Images / build-auth-api (push) Successful in 1m0s
Build and Push Docker Images / build-employee-api (push) Successful in 1m2s
Build and Push Docker Images / build-transit-api (push) Successful in 1m1s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m5s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m13s
Build and Push Docker Images / build-monitoring-ui (push) Successful in 1m15s
Build and Push Docker Images / deploy-test-node (push) Successful in 3s
Build and Push Docker Images / detect-changes (push) Successful in 11s
Build and Push Docker Images / build-auth-api (push) Successful in 1m0s
Build and Push Docker Images / build-employee-api (push) Successful in 1m2s
Build and Push Docker Images / build-transit-api (push) Successful in 1m1s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m5s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m13s
Build and Push Docker Images / build-monitoring-ui (push) Successful in 1m15s
Build and Push Docker Images / deploy-test-node (push) Successful in 3s
### Summary of Changes
1. Documentation Consolidation:
• Relocated the docs/ folder and all repository .md files (README.md, DEPLOYMENT.md, DOCKER_README.md,
DOMAINS_README.md, GETTING_STARTED.md, CI_CD_PORTAINER_GUIDE.md, ADMIN_DEPLOYMENT_SUMMARY.md,
SECURITY_PRIVATE_FILES.md, handoff.md, azure-project-plan.md, plus service-specific READMEs) into .
• Updated document links in README.md.
2. AI Agent Project Map:
• Created PROJECT_MAP.md in the project root containing complete architecture diagrams, service specs, ports, DB
schemas, environment variables, and developer workflows.
• Added PROJECT_MAP.md to .gitignore.
3. Monitoring UI & Infrastructure Integration:
• Added monitoring_ui service to docker-compose.yml (port 5175).
• Configured Gitea workflow docker-build.yml for monitoring_ui builds.
• Added CORS middleware headers in index.js.
• Added nginx.conf copy step in Dockerfile.
──────
### Recommended Commit Message
docs: consolidate project documentation into monitoring_ui and add agent project map
- Move all markdown documentation files and docs/ folder into monitoring_ui/docs/
- Update documentation hub index (monitoring_ui/docs/README.md) with relative paths
- Generate comprehensive AI agent reference map (PROJECT_MAP.md) in repository root
- Add PROJECT_MAP.md and monitoring_ui build output/env overrides to .gitignore
- Integrate monitoring_ui service into docker-compose.yml and Gitea CI workflow
- Configure CORS handling in employee_api for monitoring dashboard access
──────
### Alternative Short Commit Message
refactor(docs): move docs to monitoring_ui and create AI agent PROJECT_MAP.md
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
## Weekly schedules and shifts
|
||||
|
||||
The schedule manager uses the weekly template model. Administrators create a
|
||||
departure for a bus line in one of three categories: weekdays, Saturday, or
|
||||
Sunday. The UI creates the corresponding `day_of_week` rows and assigns drivers
|
||||
through `transit.shifts`, rather than storing a driver on `transit.schedules`.
|
||||
|
||||
The active UI uses these transit API endpoints:
|
||||
|
||||
- `GET /admin/weekly-schedules`
|
||||
- `GET /admin/shift-drivers`
|
||||
- `POST /admin/weekly-schedules`
|
||||
- `PUT /admin/weekly-schedules/:scheduleId/shift`
|
||||
- `DELETE /admin/weekly-schedules/:scheduleId`
|
||||
|
||||
# Transit Admin Control Center
|
||||
|
||||
Production admin panel: `https://admin.xtransit.testinstance.nl`
|
||||
|
||||
For the separate service monitoring dashboard, see
|
||||
[`../docs/MONITORING_UI.md`](../docs/MONITORING_UI.md). The monitoring dashboard
|
||||
is deployed independently on host port `5175` and checks this admin UI through
|
||||
`GET /health`.
|
||||
|
||||
Modern React + Vite web application for comprehensive transit system administration.
|
||||
|
||||
## Features
|
||||
|
||||
✅ **Stop Management** - Create and manage transit stops with geolocation
|
||||
✅ **Bus Lines** - Define routes with start/end stops and estimated durations
|
||||
✅ **Fleet Management** - Monitor and view fleet vehicles
|
||||
✅ **Stop Linking** - Assign stops to bus lines with order and ETA
|
||||
✅ **JWT Authentication** - Secure authentication with role-based access
|
||||
✅ **Production Ready** - Docker containerized with runtime env configuration
|
||||
✅ **Error Log Viewer** - Inspect centralized error logs from all services
|
||||
|
||||
## Local Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 18+
|
||||
- npm or yarn
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
cd admin_ui
|
||||
npm install
|
||||
```
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
Create a `.env` file in the `admin_ui` directory:
|
||||
|
||||
```env
|
||||
VITE_AUTH_API=https://auth.xtransit.testinstance.nl
|
||||
VITE_TRANSIT_API=https://transit.xtransit.testinstance.nl
|
||||
VITE_ERROR_LOG_API=https://error.xtransit.testinstance.nl
|
||||
```
|
||||
|
||||
### Run Development Server
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The app will be available at `http://localhost:5173`
|
||||
|
||||
### Build for Production
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
This creates a production bundle in the `dist/` directory.
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
### Build Docker Image
|
||||
|
||||
```bash
|
||||
docker build -t xtransit/admin_ui:latest .
|
||||
```
|
||||
|
||||
### Run with Docker
|
||||
|
||||
```bash
|
||||
docker run -p 5174:80 \
|
||||
-e VITE_AUTH_API=/api/auth \
|
||||
-e VITE_TRANSIT_API=/api/transit \
|
||||
xtransit/admin_ui:latest
|
||||
```
|
||||
|
||||
### Using Docker Compose
|
||||
|
||||
The admin UI is integrated into the main `docker-compose.yml` at the project root.
|
||||
|
||||
From the project root:
|
||||
|
||||
```bash
|
||||
# Start all services including admin UI
|
||||
docker-compose up -d
|
||||
|
||||
# Access the admin UI
|
||||
http://localhost:5174
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ------------------ | --------------- | ---------------------------------------- |
|
||||
| `VITE_AUTH_API` | Auth API URL | https://auth.xtransit.testinstance.nl |
|
||||
| `VITE_TRANSIT_API` | Transit API URL | https://transit.xtransit.testinstance.nl |
|
||||
|
||||
### Runtime Configuration
|
||||
|
||||
When running in Docker, environment variables are injected at runtime via `docker-entrypoint.sh` which creates `/usr/share/nginx/html/env-config.js`. This allows configuration without rebuilding the image.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Technology Stack
|
||||
|
||||
- **Frontend**: React 18.2 + Vite 5.0
|
||||
- **HTTP Client**: Axios 1.4
|
||||
- **Server**: Nginx (Alpine Linux)
|
||||
- **Build**: Multi-stage Docker build for optimized image size
|
||||
|
||||
### Component Structure
|
||||
|
||||
- `App.jsx` - Main app router, session management
|
||||
- `Login.jsx` - Authentication with dev token helper
|
||||
- `StopsManager.jsx` - Create/list transit stops
|
||||
- `BusLinesManager.jsx` - Create/list bus routes
|
||||
- `FleetManager.jsx` - View fleet vehicles
|
||||
- `LinkStop.jsx` - Link stops to bus lines
|
||||
- `ErrorLogViewer.jsx` - Central error log viewer
|
||||
|
||||
### API Integration
|
||||
|
||||
The app communicates with:
|
||||
|
||||
- **Auth API** (/login, /me)
|
||||
- **Transit API** (/admin/stops, /admin/bus-lines, /admin/fleet/buses)
|
||||
- **Error Log API** (/error_log)
|
||||
|
||||
All requests include JWT Bearer token for authentication.
|
||||
|
||||
## Authentication
|
||||
|
||||
### Login Flow
|
||||
|
||||
1. User enters user code and password
|
||||
2. Client calls POST `/login`
|
||||
3. Receives JWT token with `userId` and `userCode`
|
||||
4. JWT is stored in localStorage
|
||||
5. All API requests include `Authorization: Bearer <token>`
|
||||
|
||||
### Admin Authorization
|
||||
|
||||
Users must have a `userCode` in their JWT and, for admin access, the `isAdmin` flag set by the `auth_api` based on the `ADMIN_USER_CODES` environment variable.
|
||||
|
||||
### Production Setup
|
||||
|
||||
1. Remove dev token button from Login.jsx
|
||||
2. Implement real authentication system (OAuth/SAML)
|
||||
3. Configure proper JWT generation in auth_api
|
||||
4. Set strong `JWT_SECRET` environment variable
|
||||
5. Configure `ADMIN_USER_CODES` with actual admin user IDs
|
||||
6. Configure `VITE_ERROR_LOG_API` if you use a custom logging endpoint
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: "Admin UI not loading"
|
||||
|
||||
- Verify container is running: `docker ps`
|
||||
- Check logs: `docker-compose logs admin_ui`
|
||||
- Clear browser cache (Ctrl+Shift+R)
|
||||
|
||||
### Issue: "API connection refused"
|
||||
|
||||
- Ensure auth_api and transit_api are running
|
||||
- Check environment variables are set correctly
|
||||
- Verify network connectivity between containers
|
||||
|
||||
### Issue: "Permission denied" on admin operations
|
||||
|
||||
- Verify JWT token includes valid `userCode`
|
||||
- Check `ADMIN_USER_CODES` environment variable
|
||||
- Ensure database user has appropriate permissions
|
||||
|
||||
## Deployment
|
||||
|
||||
See [DEPLOYMENT.md](../DEPLOYMENT.md) for comprehensive deployment guide.
|
||||
|
||||
### Production Checklist
|
||||
|
||||
- [ ] Remove development authentication helpers
|
||||
- [ ] Set strong JWT_SECRET
|
||||
- [ ] Configure proper ADMIN_USER_CODES
|
||||
- [ ] Enable HTTPS/SSL
|
||||
- [ ] Configure CORS for production domain
|
||||
- [ ] Set up monitoring and logging
|
||||
- [ ] Configure database backups
|
||||
- [ ] Document admin procedures
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
|
||||
1. Check service logs: `docker-compose logs admin_ui`
|
||||
2. Verify API connectivity: Visit API URLs directly
|
||||
3. Check browser console for client-side errors
|
||||
4. Review DEPLOYMENT.md for troubleshooting
|
||||
|
||||
## License
|
||||
|
||||
Part of the XTransit project
|
||||
@@ -0,0 +1,281 @@
|
||||
# Schedule Maker & Public Timetable API
|
||||
|
||||
## Features
|
||||
|
||||
The Transit API now includes a comprehensive schedule management system with both admin and public endpoints.
|
||||
|
||||
### Weekly Admin Endpoints (requires authentication + admin privileges)
|
||||
|
||||
The active admin UI uses weekly schedule templates. `dayOfWeek` follows the
|
||||
JavaScript/PostgreSQL convention: `0` Sunday, `1` through `5` weekdays, and `6`
|
||||
Saturday. Driver assignment is stored in `transit.shifts` and references
|
||||
`workers.users(id)`.
|
||||
|
||||
#### Get weekly schedules
|
||||
|
||||
```
|
||||
GET /admin/weekly-schedules
|
||||
Response: { schedules: [...] }
|
||||
```
|
||||
|
||||
#### Get shift drivers
|
||||
|
||||
```
|
||||
GET /admin/shift-drivers
|
||||
Response: { drivers: [{ id, name }] }
|
||||
```
|
||||
|
||||
#### Create a weekly schedule
|
||||
|
||||
````
|
||||
|
||||
#### Create multiple departures
|
||||
|
||||
```text
|
||||
POST /admin/weekly-schedules/bulk
|
||||
Body: {
|
||||
busLineId: 1,
|
||||
dayOfWeeks: [1, 2, 3, 4, 5],
|
||||
startTimes: ["06:00", "06:30", "07:00"],
|
||||
driverId: 12
|
||||
}
|
||||
````
|
||||
|
||||
The API creates one schedule row for every day/time combination and derives the
|
||||
end time from the bus line's configured duration.
|
||||
POST /admin/weekly-schedules
|
||||
Body: {
|
||||
busLineId: 1,
|
||||
dayOfWeek: 1,
|
||||
startTime: "08:00",
|
||||
endTime: "09:00",
|
||||
driverId: 12
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Replace a schedule's driver
|
||||
|
||||
```
|
||||
|
||||
PUT /admin/weekly-schedules/:scheduleId/shift
|
||||
Body: { driverId: 12 }
|
||||
|
||||
```
|
||||
|
||||
Use `{ "driverId": null }` to unassign the driver. The current `shifts` table
|
||||
does not contain a week/date column, so this is an active assignment rather than
|
||||
historical per-week assignment. Add `week_start` to support that later.
|
||||
|
||||
### Legacy Admin Endpoints
|
||||
|
||||
#### Get all schedules
|
||||
|
||||
```
|
||||
|
||||
GET /admin/schedules
|
||||
Response: { schedules: [...] }
|
||||
|
||||
```
|
||||
|
||||
#### Create a schedule
|
||||
|
||||
```
|
||||
|
||||
POST /admin/schedules
|
||||
Body: {
|
||||
busLineId: number,
|
||||
busId: number,
|
||||
driverId: number (optional),
|
||||
startTime: "2026-05-06T08:00:00",
|
||||
endTime: "2026-05-06T17:00:00"
|
||||
}
|
||||
Response: { schedule: {...} }
|
||||
|
||||
```
|
||||
|
||||
#### Delete a schedule
|
||||
|
||||
```
|
||||
|
||||
DELETE /admin/schedules/:scheduleId
|
||||
Response: { success: true, schedule: {...} }
|
||||
|
||||
```
|
||||
|
||||
### Public Endpoints (no authentication required)
|
||||
|
||||
#### Get schedules (with optional filters)
|
||||
|
||||
```
|
||||
|
||||
GET /schedules?date=2026-05-06&lineId=1
|
||||
Query params:
|
||||
|
||||
- date: YYYY-MM-DD (optional)
|
||||
- lineId: number (optional)
|
||||
Response: { schedules: [...] }
|
||||
|
||||
```
|
||||
|
||||
#### Get schedule detail
|
||||
|
||||
```
|
||||
|
||||
GET /schedules/:scheduleId
|
||||
Response: { schedule: {...} }
|
||||
|
||||
```
|
||||
|
||||
#### Get stops for a schedule's line
|
||||
|
||||
```
|
||||
|
||||
GET /schedules/:scheduleId/stops
|
||||
Response: { stops: [...] }
|
||||
Shows all stops on the bus line with order, location, and ETA.
|
||||
|
||||
````
|
||||
|
||||
## Admin UI Features
|
||||
|
||||
The ScheduleMaker component provides:
|
||||
|
||||
1. **Weekly schedule editor**
|
||||
|
||||
- Select a bus line and one of weekdays, Saturday, or Sunday
|
||||
- Define start and end times
|
||||
- Assign drivers through shifts
|
||||
- Admin-only access
|
||||
|
||||
2. **Timetable View**
|
||||
- 24-hour time slots (hourly grouping)
|
||||
- Services displayed in corresponding time slots
|
||||
- Shows line number, vehicle name, and license plate
|
||||
- Delete button for each service (admin only)
|
||||
- Color-coded schedule cards with gradient styling
|
||||
|
||||
3. **Drag-and-Drop Ready**
|
||||
- Schedule cards are draggable (extensible for drag-to-reorder)
|
||||
- Follows existing UI/UX patterns from LinkStop component
|
||||
|
||||
4. **Confirmation Modals**
|
||||
- Delete confirmation before removing schedules
|
||||
- Shows service details in confirmation
|
||||
- Prevents accidental deletions
|
||||
|
||||
## Public Timetable Viewer
|
||||
|
||||
The TimetableViewer component provides a public-facing schedule display:
|
||||
|
||||
```jsx
|
||||
import TimetableViewer from "./components/TimetableViewer";
|
||||
|
||||
<TimetableViewer apiBaseUrl="http://api.transit.local:5001" />;
|
||||
````
|
||||
|
||||
Features:
|
||||
|
||||
- Filter by date and bus line
|
||||
- Real-time loading state
|
||||
- Error handling
|
||||
- Status badges (planned/active)
|
||||
- Responsive table layout
|
||||
- No authentication required
|
||||
|
||||
## Development Mode
|
||||
|
||||
With `DEV_MOCK=1`, all schedule operations work in-memory without a database.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
ADMIN_USER_CODES=42 DEV_MOCK=1 JWT_SECRET=devsecret npm --prefix ./transit_api start
|
||||
```
|
||||
|
||||
## Database Schema (Production)
|
||||
|
||||
```sql
|
||||
CREATE TABLE transit.schedules (
|
||||
id BIGINT PRIMARY KEY,
|
||||
bus_line_id BIGINT REFERENCES transit.bus_lines(id),
|
||||
start_time TIME,
|
||||
end_time TIME,
|
||||
status VARCHAR(20) DEFAULT 'planned',
|
||||
day_of_week SMALLINT NOT NULL CHECK (day_of_week BETWEEN 0 AND 6),
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE transit.shifts (
|
||||
id BIGINT PRIMARY KEY,
|
||||
schedule_id BIGINT REFERENCES transit.schedules(id) ON DELETE CASCADE,
|
||||
driver_id BIGINT REFERENCES workers.users(id)
|
||||
);
|
||||
```
|
||||
|
||||
## Styling & UX
|
||||
|
||||
The schedule manager uses:
|
||||
|
||||
### Admin Timetable Styling
|
||||
|
||||
- Responsive table with hourly time slots
|
||||
- Gradient schedule cards (blue/indigo theme)
|
||||
- Hover effects and smooth transitions
|
||||
- Grab cursor on draggable items
|
||||
- Danger buttons for delete actions (red)
|
||||
- Confirmation modals with semi-transparent overlay
|
||||
|
||||
### Public Timetable Styling
|
||||
|
||||
- Clean, simple table layout
|
||||
- Status badges (green for active, gray for planned)
|
||||
- Mobile-responsive with scrolling
|
||||
- Consistent with existing design language
|
||||
|
||||
## Integration Points
|
||||
|
||||
1. **Admin Dashboard**: ScheduleMaker appears at the bottom of Admin Control Center
|
||||
2. **Public API**: Available via public `/schedules` endpoints without authentication
|
||||
3. **Flutter App**: TimetableViewer can be embedded or used as reference for mobile implementation
|
||||
4. **Frontend Flexibility**: TimetableViewer component is reusable for any public-facing website
|
||||
|
||||
## Example Usage
|
||||
|
||||
### Create a schedule (Admin)
|
||||
|
||||
```bash
|
||||
curl -X POST https://transit.xtransit.testinstance.nl/admin/schedules \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"busLineId": 1,
|
||||
"busId": 5,
|
||||
"driverId": 101,
|
||||
"startTime": "2026-05-06T08:00:00",
|
||||
"endTime": "2026-05-06T17:00:00"
|
||||
}'
|
||||
```
|
||||
|
||||
### Get today's schedules (Public)
|
||||
|
||||
```bash
|
||||
curl https://transit.xtransit.testinstance.nl/schedules?date=$(date +%Y-%m-%d)
|
||||
```
|
||||
|
||||
### Get schedules for specific line (Public)
|
||||
|
||||
```bash
|
||||
curl https://transit.xtransit.testinstance.nl/schedules?lineId=3
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Bulk schedule import from CSV
|
||||
- Recurring schedule templates (weekly, daily patterns)
|
||||
- Driver shift assignment with time tracking
|
||||
- Real-time schedule updates and notifications
|
||||
- Schedule conflict detection
|
||||
- Integration with GPS tracking for actual vs scheduled times
|
||||
- Historical schedule analytics
|
||||
@@ -0,0 +1,461 @@
|
||||
# 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:**
|
||||
```json
|
||||
{
|
||||
"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.weekdays` array met dagwaarden (0-6, waarbij 0=Zondag, 1=Maandag, etc.)
|
||||
- **Weergave**: In schedule card getoond als "Maandag, Woensdag, Vrijdag"
|
||||
|
||||
**Request voorbeeld:**
|
||||
```json
|
||||
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/drivers` endpoint
|
||||
- **Opslag**: `schedules.driver_id` veld
|
||||
- **Weergave**:
|
||||
- In schedule card: "Bestuurdernaam"
|
||||
- Optioneel veld (NULL als niet ingesteld)
|
||||
|
||||
**Response voorbeeld:**
|
||||
```json
|
||||
{
|
||||
"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):**
|
||||
```json
|
||||
{
|
||||
"drivers": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Jan Peterse",
|
||||
"email": "jan@transit.local",
|
||||
"phone": "06-12345678",
|
||||
"status": "active"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Response (Production):**
|
||||
- Query uit `workers.employees` tabel
|
||||
- Filtert op `status = 'active'`
|
||||
|
||||
---
|
||||
|
||||
### POST /admin/schedules
|
||||
**Beschrijving**: Maakt nieuwe dienst aan met automatische eindtijd en vertrektijden
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"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`, `startTime` zijn verplicht
|
||||
- `driverId` is optioneel (NULL als niet gegeven)
|
||||
- `weekdays` is optioneel (lege array = eenmalige dienst)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"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.schedules` array
|
||||
|
||||
---
|
||||
|
||||
### GET /admin/schedules
|
||||
**Beschrijving**: Haalt alle geplande diensten met details op
|
||||
|
||||
**Response (bevat nu):**
|
||||
```json
|
||||
{
|
||||
"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:**
|
||||
```jsx
|
||||
const [drivers, setDrivers] = useState([]) // Bestuurders lijst
|
||||
const [selectedWeekdays, setSelectedWeekdays] = useState([]) // Geselecteerde weekdagen
|
||||
const [expandedSchedule, setExpandedSchedule] = useState(null) // Uitgebreide schedule ID
|
||||
```
|
||||
|
||||
**Nieuwe Functions:**
|
||||
|
||||
#### loadDrivers()
|
||||
```jsx
|
||||
const loadDrivers = async () => {
|
||||
const res = await client.get('/admin/drivers')
|
||||
setDrivers(res.data.drivers || [])
|
||||
}
|
||||
```
|
||||
|
||||
#### toggleWeekday(dayValue)
|
||||
```jsx
|
||||
const toggleWeekday = (dayValue) => {
|
||||
setSelectedWeekdays(prev =>
|
||||
prev.includes(dayValue)
|
||||
? prev.filter(d => d !== dayValue)
|
||||
: [...prev, dayValue]
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### getWeekdayLabel(weekdayValues)
|
||||
```jsx
|
||||
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:**
|
||||
|
||||
1. **Bestuurders Dropdown:**
|
||||
```jsx
|
||||
<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>
|
||||
```
|
||||
|
||||
2. **Weekdagknoppen:**
|
||||
```jsx
|
||||
<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>
|
||||
```
|
||||
|
||||
3. **Uitklapbare Haltes Tabel:**
|
||||
```jsx
|
||||
<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()
|
||||
|
||||
```javascript
|
||||
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
|
||||
|
||||
```javascript
|
||||
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.schedules`:
|
||||
|
||||
```sql
|
||||
ALTER TABLE transit.schedules ADD COLUMN IF NOT EXISTS weekdays JSONB;
|
||||
```
|
||||
|
||||
Queries gebruiken:
|
||||
```sql
|
||||
SELECT ... FROM transit.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
|
||||
1. Selecteer Lijn 101, Bus A5
|
||||
2. Selecteer 2026-05-08, 08:00
|
||||
3. Geen weekdagen selecteren
|
||||
4. Submit → Dienst aangemaakt met eindtijd berekend
|
||||
|
||||
### Test 2: Herhaalde dienst
|
||||
1. Selecteer Lijn 101, Bus A5
|
||||
2. Selecteer Ma, Di, Wo, Do, Vr
|
||||
3. Selecteer bestuurder
|
||||
4. Submit → Dienst gepland voor alle werkdagen
|
||||
|
||||
### Test 3: Haltes weergeven
|
||||
1. Klik op "Haltes" knop in schedule card
|
||||
2. Tabel opent met haltes en vertrektijden
|
||||
3. Klik opnieuw om in te klappen
|
||||
|
||||
---
|
||||
|
||||
## Bekende Beperkingen & TODOs
|
||||
|
||||
1. **Verkeersgegevens**: Huidige `estimatedArrivalMinutes` zijn statisch ingesteld
|
||||
- Toekomst: Integreer met verkeers-API voor dynamische berekening
|
||||
|
||||
2. **Tijdzones**: Alles in UTC
|
||||
- Toekomst: Voeg timezone support toe voor meerdere regio's
|
||||
|
||||
3. **Database Migrations**: `transit.schedules` moet `weekdays` kolom hebben
|
||||
- Status: Handmatig SQL nodig voor productie
|
||||
|
||||
4. **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
|
||||
Reference in New Issue
Block a user