fix phone dashboard

This commit is contained in:
root
2026-05-27 03:11:45 -04:00
parent 7ca43ba2f3
commit 1112b04516
48 changed files with 1222 additions and 368 deletions
+3 -5
View File
@@ -8,7 +8,7 @@ class AppConstants {
// Production
static const String productionApiUrl = 'https://api.rentaldrivego.ma/api/v1';
static const String productionDashboardUrl = 'https://rentaldrivego.ma/dashboard';
static const String productionDashboardUrl = 'https://rentaldrivego.ma';
// Development
static const String devAndroidApiUrl = 'http://10.0.2.2:4000/api/v1';
@@ -18,14 +18,12 @@ class AppConstants {
static String get baseUrl {
if (apiBaseUrlOverride.isNotEmpty) return apiBaseUrlOverride;
if (Platform.isAndroid) return devAndroidApiUrl;
return devLocalApiUrl;
return productionApiUrl;
}
static String get dashboardBaseUrl {
if (dashboardUrlOverride.isNotEmpty) return dashboardUrlOverride;
if (Platform.isAndroid) return devAndroidDashboardUrl;
return devLocalDashboardUrl;
return productionDashboardUrl;
}
static String resolveMediaUrl(String url) {
+171 -32
View File
@@ -1,40 +1,179 @@
class DashboardMetrics {
final int totalReservations;
final int activeReservations;
final double totalRevenue;
final int totalVehicles;
final int availableVehicles;
final int totalCustomers;
final double occupancyRate;
final DashboardKpis kpis;
final List<DashboardRecentReservation> recentReservations;
final List<DashboardSourceBreakdown> sourceBreakdown;
final DashboardSubscription? subscription;
const DashboardMetrics({
required this.totalReservations,
required this.activeReservations,
required this.totalRevenue,
required this.totalVehicles,
required this.availableVehicles,
required this.totalCustomers,
required this.occupancyRate,
required this.kpis,
required this.recentReservations,
required this.sourceBreakdown,
required this.subscription,
});
factory DashboardMetrics.fromJson(Map<String, dynamic> json) =>
DashboardMetrics(
totalReservations: json['totalReservations'] as int? ?? 0,
activeReservations: json['activeReservations'] as int? ?? 0,
totalRevenue: (json['totalRevenue'] as num?)?.toDouble() ?? 0,
totalVehicles: json['totalVehicles'] as int? ?? 0,
availableVehicles: json['availableVehicles'] as int? ?? 0,
totalCustomers: json['totalCustomers'] as int? ?? 0,
occupancyRate: (json['occupancyRate'] as num?)?.toDouble() ?? 0,
);
factory DashboardMetrics.fromJson(Map<String, dynamic> json) {
final kpiJson = json['kpis'] as Map<String, dynamic>?;
factory DashboardMetrics.empty() => const DashboardMetrics(
totalReservations: 0,
activeReservations: 0,
totalRevenue: 0,
totalVehicles: 0,
availableVehicles: 0,
totalCustomers: 0,
occupancyRate: 0,
return DashboardMetrics(
kpis: DashboardKpis.fromJson(kpiJson ?? json),
recentReservations: (json['recentReservations'] as List<dynamic>?)
?.map(
(e) => DashboardRecentReservation.fromJson(
e as Map<String, dynamic>,
),
)
.toList() ??
const [],
sourceBreakdown: (json['sourceBreakdown'] as List<dynamic>?)
?.map(
(e) => DashboardSourceBreakdown.fromJson(
e as Map<String, dynamic>,
),
)
.toList() ??
const [],
subscription: json['subscription'] is Map<String, dynamic>
? DashboardSubscription.fromJson(
json['subscription'] as Map<String, dynamic>,
)
: null,
);
}
factory DashboardMetrics.empty() => DashboardMetrics(
kpis: DashboardKpis.empty(),
recentReservations: const [],
sourceBreakdown: const [],
subscription: null,
);
}
class DashboardKpis {
final int totalBookings;
final int activeVehicles;
final double monthlyRevenue;
final int totalCustomers;
final double bookingsChange;
final double vehiclesChange;
final double revenueChange;
final double customersChange;
const DashboardKpis({
required this.totalBookings,
required this.activeVehicles,
required this.monthlyRevenue,
required this.totalCustomers,
required this.bookingsChange,
required this.vehiclesChange,
required this.revenueChange,
required this.customersChange,
});
factory DashboardKpis.fromJson(Map<String, dynamic> json) => DashboardKpis(
totalBookings:
(json['totalBookings'] as num?)?.toInt() ??
(json['totalReservations'] as num?)?.toInt() ??
0,
activeVehicles:
(json['activeVehicles'] as num?)?.toInt() ??
(json['availableVehicles'] as num?)?.toInt() ??
(json['totalVehicles'] as num?)?.toInt() ??
0,
monthlyRevenue:
(json['monthlyRevenue'] as num?)?.toDouble() ??
(json['totalRevenue'] as num?)?.toDouble() ??
0,
totalCustomers: (json['totalCustomers'] as num?)?.toInt() ?? 0,
bookingsChange: (json['bookingsChange'] as num?)?.toDouble() ?? 0,
vehiclesChange: (json['vehiclesChange'] as num?)?.toDouble() ?? 0,
revenueChange: (json['revenueChange'] as num?)?.toDouble() ?? 0,
customersChange: (json['customersChange'] as num?)?.toDouble() ?? 0,
);
factory DashboardKpis.empty() => const DashboardKpis(
totalBookings: 0,
activeVehicles: 0,
monthlyRevenue: 0,
totalCustomers: 0,
bookingsChange: 0,
vehiclesChange: 0,
revenueChange: 0,
customersChange: 0,
);
}
class DashboardRecentReservation {
final String id;
final String bookingRef;
final String customerName;
final String vehicleName;
final DateTime startDate;
final DateTime endDate;
final String status;
final double totalAmount;
const DashboardRecentReservation({
required this.id,
required this.bookingRef,
required this.customerName,
required this.vehicleName,
required this.startDate,
required this.endDate,
required this.status,
required this.totalAmount,
});
factory DashboardRecentReservation.fromJson(Map<String, dynamic> json) =>
DashboardRecentReservation(
id: json['id'] as String,
bookingRef: json['bookingRef'] as String? ?? '',
customerName: json['customerName'] as String? ?? 'Unknown customer',
vehicleName: json['vehicleName'] as String? ?? 'Unknown vehicle',
startDate: DateTime.tryParse(json['startDate'] as String? ?? '') ??
DateTime.now(),
endDate: DateTime.tryParse(json['endDate'] as String? ?? '') ??
DateTime.now(),
status: json['status'] as String? ?? 'UNKNOWN',
totalAmount: (json['totalAmount'] as num?)?.toDouble() ?? 0,
);
}
class DashboardSourceBreakdown {
final String source;
final int count;
final double revenue;
const DashboardSourceBreakdown({
required this.source,
required this.count,
required this.revenue,
});
factory DashboardSourceBreakdown.fromJson(Map<String, dynamic> json) =>
DashboardSourceBreakdown(
source: json['source'] as String? ?? 'UNKNOWN',
count: (json['count'] as num?)?.toInt() ?? 0,
revenue: (json['revenue'] as num?)?.toDouble() ?? 0,
);
}
class DashboardSubscription {
final String status;
final String? planName;
final DateTime? trialEndsAt;
const DashboardSubscription({
required this.status,
required this.planName,
required this.trialEndsAt,
});
factory DashboardSubscription.fromJson(Map<String, dynamic> json) =>
DashboardSubscription(
status: json['status'] as String? ?? 'ACTIVE',
planName: json['planName'] as String?,
trialEndsAt: json['trialEndsAt'] == null
? null
: DateTime.tryParse(json['trialEndsAt'] as String),
);
}
+17 -2
View File
@@ -58,12 +58,27 @@ class CustomerListResponse {
required this.pageSize,
});
factory CustomerListResponse.fromApi(dynamic json) {
if (json is List<dynamic>) {
final customers = json
.map((e) => Customer.fromJson(e as Map<String, dynamic>))
.toList();
return CustomerListResponse(
data: customers,
total: customers.length,
page: 1,
pageSize: customers.length,
);
}
return CustomerListResponse.fromJson(json as Map<String, dynamic>);
}
factory CustomerListResponse.fromJson(Map<String, dynamic> json) =>
CustomerListResponse(
data: (json['data'] as List<dynamic>)
data: ((json['data'] ?? const <dynamic>[]) as List<dynamic>)
.map((e) => Customer.fromJson(e as Map<String, dynamic>))
.toList(),
total: json['total'] as int? ?? 0,
total: json['total'] as int? ?? ((json['data'] as List<dynamic>?)?.length ?? 0),
page: json['page'] as int? ?? 1,
pageSize: json['pageSize'] as int? ?? 20,
);
+17 -2
View File
@@ -131,12 +131,27 @@ class ReservationListResponse {
required this.pageSize,
});
factory ReservationListResponse.fromApi(dynamic json) {
if (json is List<dynamic>) {
final reservations = json
.map((e) => Reservation.fromJson(e as Map<String, dynamic>))
.toList();
return ReservationListResponse(
data: reservations,
total: reservations.length,
page: 1,
pageSize: reservations.length,
);
}
return ReservationListResponse.fromJson(json as Map<String, dynamic>);
}
factory ReservationListResponse.fromJson(Map<String, dynamic> json) =>
ReservationListResponse(
data: (json['data'] as List<dynamic>)
data: ((json['data'] ?? const <dynamic>[]) as List<dynamic>)
.map((e) => Reservation.fromJson(e as Map<String, dynamic>))
.toList(),
total: json['total'] as int? ?? 0,
total: json['total'] as int? ?? ((json['data'] as List<dynamic>?)?.length ?? 0),
page: json['page'] as int? ?? 1,
pageSize: json['pageSize'] as int? ?? 20,
);
+17 -2
View File
@@ -72,12 +72,27 @@ class VehicleListResponse {
required this.pageSize,
});
factory VehicleListResponse.fromApi(dynamic json) {
if (json is List<dynamic>) {
final vehicles = json
.map((e) => Vehicle.fromJson(e as Map<String, dynamic>))
.toList();
return VehicleListResponse(
data: vehicles,
total: vehicles.length,
page: 1,
pageSize: vehicles.length,
);
}
return VehicleListResponse.fromJson(json as Map<String, dynamic>);
}
factory VehicleListResponse.fromJson(Map<String, dynamic> json) =>
VehicleListResponse(
data: (json['data'] as List<dynamic>)
data: ((json['data'] ?? const <dynamic>[]) as List<dynamic>)
.map((e) => Vehicle.fromJson(e as Map<String, dynamic>))
.toList(),
total: json['total'] as int? ?? 0,
total: json['total'] as int? ?? ((json['data'] as List<dynamic>?)?.length ?? 0),
page: json['page'] as int? ?? 1,
pageSize: json['pageSize'] as int? ?? 20,
);
+31 -4
View File
@@ -31,7 +31,6 @@ import '../../features/client/screens/booking_screen.dart';
import '../../features/client/screens/booking_confirmation_screen.dart';
import '../../features/client/screens/my_bookings_screen.dart';
import '../../features/client/models/booking_result.dart';
import '../../features/agency_webview/screens/agency_dashboard_webview_screen.dart';
final routerProvider = Provider<GoRouter>((ref) {
final router = GoRouter(
@@ -43,6 +42,7 @@ final routerProvider = Provider<GoRouter>((ref) {
final isSplash = loc == '/splash';
final isLanding = loc == '/landing';
final isLogin = loc.startsWith('/login');
final isAgency = loc == '/agency';
if (status == AuthStatus.unknown) {
return isSplash ? null : '/splash';
@@ -57,12 +57,20 @@ final routerProvider = Provider<GoRouter>((ref) {
return '/landing';
}
if (isAgency) {
if (status == AuthStatus.authenticated) {
return auth.userType == AppConstants.userTypeEmployee
? '/dashboard'
: '/client';
}
return '/login/employee';
}
if (status == AuthStatus.unauthenticated) {
if (isLanding ||
isLogin ||
loc.startsWith('/client') ||
loc == '/role' ||
loc == '/agency') {
loc == '/role') {
return null;
}
return '/landing';
@@ -90,6 +98,10 @@ final routerProvider = Provider<GoRouter>((ref) {
path: '/dashboard/vehicles',
builder: (context, _) => const VehiclesScreen(),
),
GoRoute(
path: '/dashboard/fleet',
builder: (context, _) => const VehiclesScreen(),
),
GoRoute(
path: '/dashboard/reservations',
builder: (context, _) => const ReservationsScreen(),
@@ -103,6 +115,11 @@ final routerProvider = Provider<GoRouter>((ref) {
builder: (_, state) =>
VehicleDetailScreen(id: state.pathParameters['id']!),
),
GoRoute(
path: '/dashboard/fleet/:id',
builder: (_, state) =>
VehicleDetailScreen(id: state.pathParameters['id']!),
),
GoRoute(
path: '/dashboard/reservations/:id',
builder: (_, state) =>
@@ -178,7 +195,7 @@ final routerProvider = Provider<GoRouter>((ref) {
),
GoRoute(
path: '/agency',
builder: (context, _) => const AgencyDashboardWebViewScreen(),
builder: (context, _) => const EmployeeLoginScreen(),
),
GoRoute(
path: '/dashboard/reservations/new',
@@ -188,12 +205,22 @@ final routerProvider = Provider<GoRouter>((ref) {
path: '/dashboard/vehicles/new',
builder: (context, _) => const VehicleFormScreen(),
),
GoRoute(
path: '/dashboard/fleet/new',
builder: (context, _) => const VehicleFormScreen(),
),
GoRoute(
path: '/dashboard/vehicles/:id/edit',
builder: (_, state) => VehicleFormScreen(
vehicle: state.extra as dynamic,
),
),
GoRoute(
path: '/dashboard/fleet/:id/edit',
builder: (_, state) => VehicleFormScreen(
vehicle: state.extra as dynamic,
),
),
GoRoute(
path: '/dashboard/customers/new',
builder: (context, _) => const CustomerFormScreen(),
+1 -1
View File
@@ -15,7 +15,7 @@ class CustomerService {
'pageSize': pageSize,
if (search != null && search.isNotEmpty) 'search': search,
});
return CustomerListResponse.fromJson(res.data as Map<String, dynamic>);
return CustomerListResponse.fromApi(res.data);
}
Future<Customer> getCustomer(String id) async {
+1 -1
View File
@@ -52,7 +52,7 @@ class ReservationService {
if (search != null && search.isNotEmpty) 'search': search,
'customerId': ?customerId,
});
return ReservationListResponse.fromJson(res.data as Map<String, dynamic>);
return ReservationListResponse.fromApi(res.data);
}
Future<Reservation> getReservation(String id) async {
+6 -7
View File
@@ -71,15 +71,14 @@ class VehicleService {
int page = 1,
int pageSize = 20,
String? status,
String? search,
}) async {
final res = await _client.get('/vehicles', params: {
'page': page,
'pageSize': pageSize,
final params = <String, dynamic>{
if (page != 1) 'page': page,
if (pageSize != 20) 'pageSize': pageSize,
'status': ?status,
if (search != null && search.isNotEmpty) 'search': search,
});
return VehicleListResponse.fromJson(res.data as Map<String, dynamic>);
};
final res = await _client.get('/vehicles', params: params);
return VehicleListResponse.fromApi(res.data);
}
Future<Vehicle> getVehicle(String id) async {