Implement schedule status update functionality for driver workflows; enhance navigation button behavior to reflect status changes

This commit is contained in:
Sven laptop
2026-05-08 19:19:57 +02:00
parent 3415cfa2b9
commit b00b93e7f0
5 changed files with 113 additions and 38 deletions
@@ -112,7 +112,12 @@ class _ScheduleOverviewScreenState extends State<ScheduleOverviewScreen> {
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: FilledButton.icon( child: FilledButton.icon(
onPressed: () { onPressed: () async {
try {
await _scheduleService.updateScheduleStatus(svc.id, 'in_progress');
if (!mounted) return;
// Close the sheet and start navigation with provided stops // Close the sheet and start navigation with provided stops
Navigator.of(sheetContext).pop(); Navigator.of(sheetContext).pop();
@@ -133,7 +138,7 @@ class _ScheduleOverviewScreenState extends State<ScheduleOverviewScreen> {
), ),
startTime: svc.startTime, startTime: svc.startTime,
endTime: svc.endTime, endTime: svc.endTime,
status: svc.status, status: 'in_progress',
); );
final navStops = stops final navStops = stops
@@ -149,6 +154,12 @@ class _ScheduleOverviewScreenState extends State<ScheduleOverviewScreen> {
builder: (_) => NavigationScreen(route: routeObj, initialStops: navStops), 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), icon: const Icon(Icons.navigation),
label: const Text('Start route'), label: const Text('Start route'),
@@ -7,7 +7,7 @@ import '../models/navigation.dart';
/// Navigation Service using OpenStreetMap /// Navigation Service using OpenStreetMap
class NavigationService { class NavigationService {
// OSRM (OpenStreetMap Routing Machine) - free public service // 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 /// Get current device location
Future<NavigationPoint?> getCurrentLocation() async { Future<NavigationPoint?> getCurrentLocation() async {
@@ -45,7 +45,7 @@ class NavigationService {
if (!validCoord(start.latitude, start.longitude) || !validCoord(end.latitude, end.longitude)) { 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})'); 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 = final coordinates =
'${start.longitude},${start.latitude};${end.longitude},${end.latitude}'; '${start.longitude},${start.latitude};${end.longitude},${end.latitude}';
final url = final url =
@@ -152,7 +152,7 @@ class NavigationService {
.map((stop) => '${stop.longitude},${stop.latitude}') .map((stop) => '${stop.longitude},${stop.latitude}')
.join(';'); .join(';');
final url = 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)); final response = await http.get(Uri.parse(url));
@@ -138,6 +138,29 @@ class ScheduleService {
} }
} }
/// Update schedule status for driver-facing schedule workflows.
Future<void> 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 /// Get stops for a bus line
Future<List<NavigationPoint>> getStops(int busLineId) async { Future<List<NavigationPoint>> getStops(int busLineId) async {
try { try {
+9 -1
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../models/schedule.dart' as schedule; import '../models/schedule.dart' as schedule;
import '../screens/navigation_screen.dart'; import '../screens/navigation_screen.dart';
import '../services/schedule_service.dart';
/// Route card widget showing route details /// Route card widget showing route details
class RouteCard extends StatelessWidget { class RouteCard extends StatelessWidget {
@@ -56,7 +57,14 @@ class RouteCard extends StatelessWidget {
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( 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( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => NavigationScreen(route: route), builder: (context) => NavigationScreen(route: route),
+35 -2
View File
@@ -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 // ADMIN - Get drivers for scheduling
// ============================================ // ============================================
@@ -2142,7 +2175,7 @@ app.post('/driver/schedules/:scheduleId/stops/:stopOrder/passed', authMiddleware
schedule.status = 'completed'; schedule.status = 'completed';
schedule.endTime = actualPassedAt.toISOString(); schedule.endTime = actualPassedAt.toISOString();
} else if (schedule.status === 'planned') { } else if (schedule.status === 'planned') {
schedule.status = 'active'; schedule.status = 'in_progress';
} }
return res.json({ 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 totalStops = Number(totalsResult.rows[0]?.total_stops || 0);
const passedStops = Number(totalsResult.rows[0]?.passed_stops || 0); const passedStops = Number(totalsResult.rows[0]?.passed_stops || 0);
const isCompleted = totalStops > 0 && passedStops >= totalStops; const isCompleted = totalStops > 0 && passedStops >= totalStops;
const nextStatus = isCompleted ? 'completed' : 'active'; const nextStatus = isCompleted ? 'completed' : 'in_progress';
await db.query(` await db.query(`
UPDATE transit.schedules UPDATE transit.schedules