diff --git a/bus_terminal/lib/screens/schedule_overview_screen.dart b/bus_terminal/lib/screens/schedule_overview_screen.dart index e3df529..3509246 100644 --- a/bus_terminal/lib/screens/schedule_overview_screen.dart +++ b/bus_terminal/lib/screens/schedule_overview_screen.dart @@ -112,43 +112,54 @@ class _ScheduleOverviewScreenState extends State { SizedBox( width: double.infinity, child: FilledButton.icon( - onPressed: () { - // Close the sheet and start navigation with provided stops - Navigator.of(sheetContext).pop(); + onPressed: () async { + try { + await _scheduleService.updateScheduleStatus(svc.id, 'in_progress'); + + if (!mounted) return; + + // Close the sheet and start navigation with provided stops + Navigator.of(sheetContext).pop(); final routeObj = sched.Route( id: svc.id, - busLine: sched.BusLine( - id: svc.busLineId, - lineNumber: svc.lineNumber, - startStop: '', - endStop: '', - estimatedDuration: 0, - ), - busType: sched.BusType( - id: svc.busId, - name: svc.busName, - seatCapacity: 0, - licensePlate: '', - ), - startTime: svc.startTime, - endTime: svc.endTime, - status: svc.status, - ); + busLine: sched.BusLine( + id: svc.busLineId, + lineNumber: svc.lineNumber, + startStop: '', + endStop: '', + estimatedDuration: 0, + ), + busType: sched.BusType( + id: svc.busId, + name: svc.busName, + seatCapacity: 0, + licensePlate: '', + ), + startTime: svc.startTime, + endTime: svc.endTime, + status: 'in_progress', + ); - final navStops = stops - .map((s) => NavigationPoint( - latitude: s.latitude, - longitude: s.longitude, - name: s.name, - )) - .toList(); + final navStops = stops + .map((s) => NavigationPoint( + latitude: s.latitude, + longitude: s.longitude, + name: s.name, + )) + .toList(); - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => NavigationScreen(route: routeObj, initialStops: navStops), - ), - ); + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => NavigationScreen(route: routeObj, initialStops: navStops), + ), + ); + } catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Status bijwerken mislukt: $e')), + ); + } }, icon: const Icon(Icons.navigation), label: const Text('Start route'), diff --git a/bus_terminal/lib/services/navigation_service.dart b/bus_terminal/lib/services/navigation_service.dart index efdaa70..0c4e968 100644 --- a/bus_terminal/lib/services/navigation_service.dart +++ b/bus_terminal/lib/services/navigation_service.dart @@ -7,7 +7,7 @@ import '../models/navigation.dart'; /// Navigation Service using OpenStreetMap class NavigationService { // OSRM (OpenStreetMap Routing Machine) - free public service - static const String _osrmBaseUrl = 'https://router.project-osrm.org/route/v1/car'; + static const String _osrmBaseUrl = 'https://router.project-osrm.org/route/v1/driving'; /// Get current device location Future getCurrentLocation() async { @@ -45,7 +45,7 @@ class NavigationService { if (!validCoord(start.latitude, start.longitude) || !validCoord(end.latitude, end.longitude)) { throw Exception('Invalid coordinates for routing: start=(${start.latitude},${start.longitude}) end=(${end.latitude},${end.longitude})'); } - // OSRM format: /route/v1/car/lon1,lat1;lon2,lat2 + // OSRM format: /route/v1/driving/lon1,lat1;lon2,lat2 final coordinates = '${start.longitude},${start.latitude};${end.longitude},${end.latitude}'; final url = @@ -152,7 +152,7 @@ class NavigationService { .map((stop) => '${stop.longitude},${stop.latitude}') .join(';'); final url = - 'https://router.project-osrm.org/trip/v1/car/$coordinates?overview=full&steps=true&geometries=geojson&source=first&destination=last&roundtrip=false'; + 'https://router.project-osrm.org/trip/v1/driving/$coordinates?overview=full&steps=true&geometries=geojson&source=first&destination=last&roundtrip=false'; final response = await http.get(Uri.parse(url)); diff --git a/bus_terminal/lib/services/schedule_service.dart b/bus_terminal/lib/services/schedule_service.dart index e4a48c6..041925c 100644 --- a/bus_terminal/lib/services/schedule_service.dart +++ b/bus_terminal/lib/services/schedule_service.dart @@ -138,6 +138,29 @@ class ScheduleService { } } + /// Update schedule status for driver-facing schedule workflows. + Future updateScheduleStatus(int scheduleId, String status) async { + try { + final headers = await _authService.getAuthHeaders(); + + final response = await http.patch( + Uri.parse('$_apiBaseUrl/driver/schedules/$scheduleId/status'), + headers: headers, + body: jsonEncode({'status': status}), + ); + + if (response.statusCode == 401 || response.statusCode == 403) { + await _authService.logout(); + throw UnauthorizedException('Session expired - Please login again'); + } else if (response.statusCode != 200) { + throw Exception('Failed to update schedule status: ${response.statusCode}'); + } + } catch (e) { + if (e is UnauthorizedException) rethrow; + throw Exception('Schedule status update error: $e'); + } + } + /// Get stops for a bus line Future> getStops(int busLineId) async { try { diff --git a/bus_terminal/lib/widgets/route_card.dart b/bus_terminal/lib/widgets/route_card.dart index 24232aa..45f1a2d 100644 --- a/bus_terminal/lib/widgets/route_card.dart +++ b/bus_terminal/lib/widgets/route_card.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import '../models/schedule.dart' as schedule; import '../screens/navigation_screen.dart'; +import '../services/schedule_service.dart'; /// Route card widget showing route details class RouteCard extends StatelessWidget { @@ -56,7 +57,14 @@ class RouteCard extends StatelessWidget { return Material( color: Colors.transparent, child: InkWell( - onTap: () { + onTap: () async { + try { + final scheduleService = ScheduleService(); + await scheduleService.updateRouteStatus(route.id, 'in_progress'); + } catch (_) { + // Ignore status update errors here; navigation should still open. + } + Navigator.of(context).push( MaterialPageRoute( builder: (context) => NavigationScreen(route: route), diff --git a/transit_api/index.js b/transit_api/index.js index 8f07c88..b341af9 100644 --- a/transit_api/index.js +++ b/transit_api/index.js @@ -1523,6 +1523,39 @@ app.patch('/routes/:routeId/status', authMiddleware, async (req, res) => { } }); +// ============================================ +// DRIVER - Update schedule status +// ============================================ +app.patch('/driver/schedules/:scheduleId/status', authMiddleware, async (req, res) => { + try { + const { scheduleId } = req.params; + const { status } = req.body; + + const validStatuses = ['planned', 'in_progress', 'completed', 'cancelled', 'active']; + if (!validStatuses.includes(status)) { + return res.status(400).json({ error: 'invalid_status' }); + } + + const normalizedStatus = status === 'active' ? 'in_progress' : status; + + const result = await db.query(` + UPDATE transit.schedules + SET status = $1, updated_at = NOW() + WHERE id = $2 + RETURNING * + `, [normalizedStatus, scheduleId]); + + if (result.rows.length === 0) { + return res.status(404).json({ error: 'schedule_not_found' }); + } + + res.json({ schedule: result.rows[0] }); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'internal_error', details: err.message }); + } +}); + // ============================================ // ADMIN - Get drivers for scheduling // ============================================ @@ -2142,7 +2175,7 @@ app.post('/driver/schedules/:scheduleId/stops/:stopOrder/passed', authMiddleware schedule.status = 'completed'; schedule.endTime = actualPassedAt.toISOString(); } else if (schedule.status === 'planned') { - schedule.status = 'active'; + schedule.status = 'in_progress'; } return res.json({ @@ -2233,7 +2266,7 @@ app.post('/driver/schedules/:scheduleId/stops/:stopOrder/passed', authMiddleware const totalStops = Number(totalsResult.rows[0]?.total_stops || 0); const passedStops = Number(totalsResult.rows[0]?.passed_stops || 0); const isCompleted = totalStops > 0 && passedStops >= totalStops; - const nextStatus = isCompleted ? 'completed' : 'active'; + const nextStatus = isCompleted ? 'completed' : 'in_progress'; await db.query(` UPDATE transit.schedules