Update readme's, branches and actions
Build and Push Docker Images / detect-changes (push) Successful in 10s
Build and Push Docker Images / build-auth-api (push) Successful in 58s
Build and Push Docker Images / build-employee-api (push) Successful in 58s
Build and Push Docker Images / build-transit-api (push) Successful in 59s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m8s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m14s
Build and Push Docker Images / deploy-test-node (push) Successful in 5s
Build and Push Docker Images / detect-changes (push) Successful in 10s
Build and Push Docker Images / build-auth-api (push) Successful in 58s
Build and Push Docker Images / build-employee-api (push) Successful in 58s
Build and Push Docker Images / build-transit-api (push) Successful in 59s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m8s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m14s
Build and Push Docker Images / deploy-test-node (push) Successful in 5s
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
/// Navigation route point
|
||||
class NavigationPoint {
|
||||
final double latitude;
|
||||
final double longitude;
|
||||
final String? name;
|
||||
|
||||
NavigationPoint({
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
this.name,
|
||||
});
|
||||
|
||||
LatLng toLatLng() => LatLng(latitude, longitude);
|
||||
|
||||
factory NavigationPoint.fromLatLng(LatLng latlng, {String? name}) {
|
||||
return NavigationPoint(
|
||||
latitude: latlng.latitude,
|
||||
longitude: latlng.longitude,
|
||||
name: name,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'latitude': latitude,
|
||||
'longitude': longitude,
|
||||
'name': name,
|
||||
};
|
||||
}
|
||||
|
||||
factory NavigationPoint.fromJson(Map<String, dynamic> json) {
|
||||
return NavigationPoint(
|
||||
latitude: json['latitude'] as double,
|
||||
longitude: json['longitude'] as double,
|
||||
name: json['name'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Route leg with distance and duration
|
||||
class RouteLeg {
|
||||
final List<NavigationPoint> points;
|
||||
final double distance; // in meters
|
||||
final int duration; // in seconds
|
||||
final String summary;
|
||||
|
||||
RouteLeg({
|
||||
required this.points,
|
||||
required this.distance,
|
||||
required this.duration,
|
||||
required this.summary,
|
||||
});
|
||||
|
||||
String getDistanceKm() => (distance / 1000).toStringAsFixed(1);
|
||||
String getDurationMinutes() => (duration ~/ 60).toString();
|
||||
|
||||
factory RouteLeg.fromCoordinates(
|
||||
List<dynamic> coordinates, {
|
||||
double distance = 0.0,
|
||||
int duration = 0,
|
||||
String summary = 'Route',
|
||||
}) {
|
||||
return RouteLeg(
|
||||
points: coordinates
|
||||
.map((coord) {
|
||||
if (coord is List && coord.length >= 2) {
|
||||
return NavigationPoint(
|
||||
latitude: (coord[1] as num).toDouble(),
|
||||
longitude: (coord[0] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.whereType<NavigationPoint>()
|
||||
.toList(),
|
||||
distance: distance,
|
||||
duration: duration,
|
||||
summary: summary,
|
||||
);
|
||||
}
|
||||
|
||||
factory RouteLeg.fromPoints({
|
||||
required List<NavigationPoint> points,
|
||||
required double distance,
|
||||
required int duration,
|
||||
String summary = 'Route',
|
||||
}) {
|
||||
return RouteLeg(
|
||||
points: points,
|
||||
distance: distance,
|
||||
duration: duration,
|
||||
summary: summary,
|
||||
);
|
||||
}
|
||||
|
||||
factory RouteLeg.fromJson(Map<String, dynamic> json) {
|
||||
final geometry = json['geometry'] as Map<String, dynamic>?;
|
||||
final coordinates = geometry?['coordinates'] as List? ?? [];
|
||||
|
||||
return RouteLeg(
|
||||
points: coordinates
|
||||
.map((coord) {
|
||||
if (coord is List && coord.length >= 2) {
|
||||
return NavigationPoint(
|
||||
latitude: (coord[1] as num).toDouble(),
|
||||
longitude: (coord[0] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.whereType<NavigationPoint>()
|
||||
.toList(),
|
||||
distance: (json['distance'] as num?)?.toDouble() ?? 0.0,
|
||||
duration: (json['duration'] as num?)?.toInt() ?? 0,
|
||||
summary: (json['summary'] as String?) ?? 'Route',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Complete navigation route from start to end
|
||||
class NavigationRoute {
|
||||
final NavigationPoint start;
|
||||
final NavigationPoint end;
|
||||
final List<RouteLeg> legs;
|
||||
final double totalDistance; // in meters
|
||||
final int totalDuration; // in seconds
|
||||
|
||||
NavigationRoute({
|
||||
required this.start,
|
||||
required this.end,
|
||||
required this.legs,
|
||||
required this.totalDistance,
|
||||
required this.totalDuration,
|
||||
});
|
||||
|
||||
String getTotalDistanceKm() => (totalDistance / 1000).toStringAsFixed(1);
|
||||
String getTotalDurationMinutes() => (totalDuration ~/ 60).toString();
|
||||
String getTotalDurationHM() {
|
||||
final hours = totalDuration ~/ 3600;
|
||||
final minutes = (totalDuration % 3600) ~/ 60;
|
||||
if (hours > 0) {
|
||||
return '${hours}h ${minutes}m';
|
||||
}
|
||||
return '${minutes}m';
|
||||
}
|
||||
|
||||
List<NavigationPoint> getAllPoints() {
|
||||
final allPoints = <NavigationPoint>[];
|
||||
for (final leg in legs) {
|
||||
allPoints.addAll(leg.points);
|
||||
}
|
||||
return allPoints;
|
||||
}
|
||||
|
||||
factory NavigationRoute.fromJson(Map<String, dynamic> json) {
|
||||
final routes = json['routes'] as List?;
|
||||
if (routes == null || routes.isEmpty) {
|
||||
throw Exception('No route found in response');
|
||||
}
|
||||
|
||||
final routeData = routes[0] as Map<String, dynamic>?;
|
||||
if (routeData == null) {
|
||||
throw Exception('Invalid route data');
|
||||
}
|
||||
|
||||
return NavigationRoute.fromOsrmRouteData(
|
||||
routeData,
|
||||
start: NavigationPoint(latitude: 0, longitude: 0),
|
||||
end: NavigationPoint(latitude: 0, longitude: 0),
|
||||
);
|
||||
}
|
||||
|
||||
factory NavigationRoute.fromOsrmRouteData(
|
||||
Map<String, dynamic> routeData, {
|
||||
required NavigationPoint start,
|
||||
required NavigationPoint end,
|
||||
}) {
|
||||
final geometry = routeData['geometry'] as Map<String, dynamic>?;
|
||||
final coordinates = geometry?['coordinates'] as List? ?? [];
|
||||
final distance = (routeData['distance'] as num?)?.toDouble() ?? 0.0;
|
||||
final duration = (routeData['duration'] as num?)?.toInt() ?? 0;
|
||||
|
||||
final leg = RouteLeg.fromCoordinates(
|
||||
coordinates,
|
||||
distance: distance,
|
||||
duration: duration,
|
||||
summary: (routeData['summary'] as String?) ?? 'Route',
|
||||
);
|
||||
|
||||
return NavigationRoute(
|
||||
start: start,
|
||||
end: end,
|
||||
legs: [leg],
|
||||
totalDistance: distance,
|
||||
totalDuration: duration,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of an optimized trip calculation with reordered stops.
|
||||
class OptimizedRouteResult {
|
||||
final NavigationRoute route;
|
||||
final List<NavigationPoint> orderedStops;
|
||||
|
||||
OptimizedRouteResult({
|
||||
required this.route,
|
||||
required this.orderedStops,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
import 'dart:convert';
|
||||
|
||||
/// Bus Line model
|
||||
class BusLine {
|
||||
final int id;
|
||||
final int lineNumber;
|
||||
final String startStop;
|
||||
final String endStop;
|
||||
final int estimatedDuration; // in minutes
|
||||
final String? description;
|
||||
|
||||
BusLine({
|
||||
required this.id,
|
||||
required this.lineNumber,
|
||||
required this.startStop,
|
||||
required this.endStop,
|
||||
required this.estimatedDuration,
|
||||
this.description,
|
||||
});
|
||||
|
||||
factory BusLine.fromJson(Map<String, dynamic> json) {
|
||||
return BusLine(
|
||||
id: json['id'] as int,
|
||||
lineNumber: json['lineNumber'] as int,
|
||||
startStop: json['startStop'] as String,
|
||||
endStop: json['endStop'] as String,
|
||||
estimatedDuration: json['estimatedDuration'] as int,
|
||||
description: json['description'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'lineNumber': lineNumber,
|
||||
'startStop': startStop,
|
||||
'endStop': endStop,
|
||||
'estimatedDuration': estimatedDuration,
|
||||
'description': description,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Bus type model
|
||||
class BusType {
|
||||
final int id;
|
||||
final String name;
|
||||
final int seatCapacity;
|
||||
final String licensePlate;
|
||||
|
||||
BusType({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.seatCapacity,
|
||||
required this.licensePlate,
|
||||
});
|
||||
|
||||
factory BusType.fromJson(Map<String, dynamic> json) {
|
||||
return BusType(
|
||||
id: json['id'] as int,
|
||||
name: json['name'] as String,
|
||||
seatCapacity: json['seatCapacity'] as int,
|
||||
licensePlate: json['licensePlate'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'seatCapacity': seatCapacity,
|
||||
'licensePlate': licensePlate,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Route model for daily schedule
|
||||
class Route {
|
||||
final int id;
|
||||
final BusLine busLine;
|
||||
final BusType busType;
|
||||
final DateTime startTime;
|
||||
final DateTime? endTime;
|
||||
final String status; // scheduled, in_progress, completed, cancelled
|
||||
|
||||
Route({
|
||||
required this.id,
|
||||
required this.busLine,
|
||||
required this.busType,
|
||||
required this.startTime,
|
||||
this.endTime,
|
||||
required this.status,
|
||||
});
|
||||
|
||||
factory Route.fromJson(Map<String, dynamic> json) {
|
||||
return Route(
|
||||
id: json['id'] as int,
|
||||
busLine: BusLine.fromJson(json['busLine'] as Map<String, dynamic>),
|
||||
busType: BusType.fromJson(json['busType'] as Map<String, dynamic>),
|
||||
startTime: DateTime.parse(json['startTime'] as String),
|
||||
endTime:
|
||||
json['endTime'] != null ? DateTime.parse(json['endTime'] as String) : null,
|
||||
status: json['status'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'busLine': busLine.toJson(),
|
||||
'busType': busType.toJson(),
|
||||
'startTime': startTime.toIso8601String(),
|
||||
'endTime': endTime?.toIso8601String(),
|
||||
'status': status,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Daily schedule model
|
||||
class DailySchedule {
|
||||
final List<Route> routes;
|
||||
final DateTime date;
|
||||
|
||||
DailySchedule({
|
||||
required this.routes,
|
||||
required this.date,
|
||||
});
|
||||
|
||||
factory DailySchedule.fromJson(Map<String, dynamic> json) {
|
||||
return DailySchedule(
|
||||
routes: (json['routes'] as List)
|
||||
.map((r) => Route.fromJson(r as Map<String, dynamic>))
|
||||
.toList(),
|
||||
date: DateTime.parse(json['date'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'routes': routes.map((r) => r.toJson()).toList(),
|
||||
'date': date.toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop model for the new schedule system.
|
||||
class ScheduleStop {
|
||||
final int id;
|
||||
final int order;
|
||||
final String name;
|
||||
final double latitude;
|
||||
final double longitude;
|
||||
final int estimatedArrivalMinutes;
|
||||
|
||||
ScheduleStop({
|
||||
required this.id,
|
||||
required this.order,
|
||||
required this.name,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
required this.estimatedArrivalMinutes,
|
||||
});
|
||||
|
||||
factory ScheduleStop.fromJson(Map<String, dynamic> json) {
|
||||
return ScheduleStop(
|
||||
id: (json['id'] as num).toInt(),
|
||||
order: (json['order'] as num).toInt(),
|
||||
name: (json['name'] ?? json['stopName'] ?? '') as String,
|
||||
latitude: (json['latitude'] as num).toDouble(),
|
||||
longitude: (json['longitude'] as num).toDouble(),
|
||||
estimatedArrivalMinutes: (json['estimatedArrivalMinutes'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedule model for the new public schedule API.
|
||||
class ServiceSchedule {
|
||||
final int id;
|
||||
final int busLineId;
|
||||
final int busId;
|
||||
final DateTime startTime;
|
||||
final DateTime endTime;
|
||||
final String status;
|
||||
final int lineNumber;
|
||||
final String busName;
|
||||
final List<int> weekdays;
|
||||
|
||||
ServiceSchedule({
|
||||
required this.id,
|
||||
required this.busLineId,
|
||||
required this.busId,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.status,
|
||||
required this.lineNumber,
|
||||
required this.busName,
|
||||
required this.weekdays,
|
||||
});
|
||||
|
||||
factory ServiceSchedule.fromJson(Map<String, dynamic> json) {
|
||||
final rawWeekdays = json['weekdays'];
|
||||
final weekdays = rawWeekdays is List
|
||||
? rawWeekdays.map((day) => (day as num).toInt()).toList()
|
||||
: rawWeekdays is String
|
||||
? (jsonDecode(rawWeekdays) as List).map((day) => (day as num).toInt()).toList()
|
||||
: <int>[];
|
||||
|
||||
return ServiceSchedule(
|
||||
id: (json['id'] as num).toInt(),
|
||||
busLineId: (json['busLineId'] as num).toInt(),
|
||||
busId: (json['busId'] as num).toInt(),
|
||||
startTime: DateTime.parse(json['startTime'] as String),
|
||||
endTime: DateTime.parse(json['endTime'] as String),
|
||||
status: (json['status'] ?? 'planned') as String,
|
||||
lineNumber: (json['lineNumber'] as num).toInt(),
|
||||
busName: (json['busName'] ?? '') as String,
|
||||
weekdays: weekdays,
|
||||
);
|
||||
}
|
||||
|
||||
bool get isRecurring => weekdays.isNotEmpty;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/// User model
|
||||
class User {
|
||||
final int id;
|
||||
final int userCode;
|
||||
final String? job;
|
||||
|
||||
User({
|
||||
required this.id,
|
||||
required this.userCode,
|
||||
this.job,
|
||||
});
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) {
|
||||
// userCode may be provided as 'userCode' or 'usercode' and may be int or string
|
||||
final dynamic rawUserCode = json['userCode'] ?? json['usercode'] ?? json['user_code'];
|
||||
int parsedUserCode;
|
||||
if (rawUserCode is int) {
|
||||
parsedUserCode = rawUserCode;
|
||||
} else if (rawUserCode is String) {
|
||||
parsedUserCode = int.tryParse(rawUserCode) ?? (throw FormatException('Invalid userCode'));
|
||||
} else {
|
||||
// If the backend doesn't return a userCode, treat as unknown (-1)
|
||||
parsedUserCode = -1;
|
||||
}
|
||||
|
||||
return User(
|
||||
id: json['id'] as int,
|
||||
userCode: parsedUserCode,
|
||||
job: json['job'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'userCode': userCode,
|
||||
'job': job,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Login response model
|
||||
class AuthResponse {
|
||||
final String token;
|
||||
final User? user;
|
||||
|
||||
AuthResponse({
|
||||
required this.token,
|
||||
this.user,
|
||||
});
|
||||
|
||||
factory AuthResponse.fromJson(Map<String, dynamic> json) {
|
||||
final dynamic rawToken = json['token'] ?? json['Token'] ?? json['authToken'];
|
||||
final token = rawToken?.toString();
|
||||
if (token == null || token.isEmpty) throw FormatException('Missing token in auth response');
|
||||
|
||||
// Determine if a user object exists and appears valid
|
||||
Map<String, dynamic>? userMap;
|
||||
if (json.containsKey('user') && json['user'] is Map<String, dynamic>) {
|
||||
userMap = json['user'] as Map<String, dynamic>;
|
||||
} else {
|
||||
// maybe user fields are top-level (id, userCode, job)
|
||||
final hasUserFields = json.containsKey('id') || json.containsKey('userCode') || json.containsKey('usercode') || json.containsKey('user_code');
|
||||
if (hasUserFields) {
|
||||
// treat whole json as user map
|
||||
userMap = json.cast<String, dynamic>();
|
||||
}
|
||||
}
|
||||
|
||||
User? parsedUser;
|
||||
if (userMap != null) {
|
||||
try {
|
||||
parsedUser = User.fromJson(userMap);
|
||||
} catch (e) {
|
||||
// If parsing user fails, ignore user but keep token
|
||||
parsedUser = null;
|
||||
}
|
||||
}
|
||||
|
||||
return AuthResponse(
|
||||
token: token,
|
||||
user: parsedUser,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user