add dashboard to phone app
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import '../models/analytics.dart';
|
||||
import '../models/report.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
class AnalyticsService {
|
||||
@@ -8,4 +9,18 @@ class AnalyticsService {
|
||||
final res = await _client.get('/analytics/dashboard');
|
||||
return DashboardMetrics.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<ReportSummary> getReport({
|
||||
required String startDate,
|
||||
required String endDate,
|
||||
String? status,
|
||||
}) async {
|
||||
final res = await _client.get('/analytics/report', params: {
|
||||
'startDate': startDate,
|
||||
'endDate': endDate,
|
||||
'status': ?status,
|
||||
'format': 'JSON',
|
||||
});
|
||||
return ReportSummary.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class ApiClient {
|
||||
_dio.interceptors.add(InterceptorsWrapper(
|
||||
onRequest: (options, handler) async {
|
||||
final token = await TokenStorage.getToken();
|
||||
if (token != null) {
|
||||
if (token != null && !_shouldSkipAuthHeader(options.path)) {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
handler.next(options);
|
||||
@@ -41,6 +41,11 @@ class ApiClient {
|
||||
|
||||
static ApiClient get instance => _instance;
|
||||
|
||||
bool _shouldSkipAuthHeader(String path) {
|
||||
return path.endsWith('/auth/employee/login') ||
|
||||
path.endsWith('/auth/renter/login');
|
||||
}
|
||||
|
||||
// baseUrl is dynamic (platform-aware), so recreate when it may have changed.
|
||||
// In practice this is called once and cached via the singleton.
|
||||
Dio get dio => _dio;
|
||||
@@ -54,5 +59,8 @@ class ApiClient {
|
||||
Future<Response> patch(String path, {dynamic data}) =>
|
||||
_dio.patch(path, data: data);
|
||||
|
||||
Future<Response> put(String path, {dynamic data}) =>
|
||||
_dio.put(path, data: data);
|
||||
|
||||
Future<Response> delete(String path) => _dio.delete(path);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class AuthService {
|
||||
required String password,
|
||||
}) async {
|
||||
final res = await _client.post('/auth/employee/login', data: {
|
||||
'email': email,
|
||||
'email': email.trim().toLowerCase(),
|
||||
'password': password,
|
||||
});
|
||||
final data = res.data as Map<String, dynamic>;
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
import 'api_client.dart';
|
||||
|
||||
class ContractCompany {
|
||||
final String name;
|
||||
final String? email;
|
||||
final String? phone;
|
||||
final String? address;
|
||||
|
||||
const ContractCompany(
|
||||
{required this.name, this.email, this.phone, this.address});
|
||||
|
||||
factory ContractCompany.fromJson(Map<String, dynamic> j) {
|
||||
final parts = <String>[];
|
||||
final addr = j['address'];
|
||||
if (addr is String && addr.trim().isNotEmpty) parts.add(addr.trim());
|
||||
if (addr is Map) {
|
||||
for (final v in addr.values) {
|
||||
if (v is String && v.trim().isNotEmpty) parts.add(v.trim());
|
||||
}
|
||||
}
|
||||
if (j['city'] is String) parts.add(j['city'] as String);
|
||||
if (j['country'] is String) parts.add(j['country'] as String);
|
||||
return ContractCompany(
|
||||
name: (j['legalName'] as String?)?.isNotEmpty == true
|
||||
? j['legalName'] as String
|
||||
: j['name'] as String,
|
||||
email: j['email'] as String?,
|
||||
phone: j['phone'] as String?,
|
||||
address: parts.isEmpty ? null : parts.join(', '),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ContractDriver {
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final String email;
|
||||
final String? phone;
|
||||
final String? driverLicense;
|
||||
final String? licenseCountry;
|
||||
final String? licenseExpiry;
|
||||
final String? nationality;
|
||||
|
||||
const ContractDriver({
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.email,
|
||||
this.phone,
|
||||
this.driverLicense,
|
||||
this.licenseCountry,
|
||||
this.licenseExpiry,
|
||||
this.nationality,
|
||||
});
|
||||
|
||||
String get fullName => '$firstName $lastName';
|
||||
|
||||
factory ContractDriver.fromJson(Map<String, dynamic> j) => ContractDriver(
|
||||
firstName: j['firstName'] as String,
|
||||
lastName: j['lastName'] as String,
|
||||
email: j['email'] as String,
|
||||
phone: j['phone'] as String?,
|
||||
driverLicense: j['driverLicense'] as String?,
|
||||
licenseCountry: j['licenseCountry'] as String?,
|
||||
licenseExpiry: j['licenseExpiry'] as String?,
|
||||
nationality: j['nationality'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
class ContractVehicle {
|
||||
final String make;
|
||||
final String model;
|
||||
final int year;
|
||||
final String? color;
|
||||
final String licensePlate;
|
||||
final String? vin;
|
||||
final String category;
|
||||
|
||||
const ContractVehicle({
|
||||
required this.make,
|
||||
required this.model,
|
||||
required this.year,
|
||||
this.color,
|
||||
required this.licensePlate,
|
||||
this.vin,
|
||||
required this.category,
|
||||
});
|
||||
|
||||
String get displayName => '$year $make $model';
|
||||
|
||||
factory ContractVehicle.fromJson(Map<String, dynamic> j) => ContractVehicle(
|
||||
make: j['make'] as String,
|
||||
model: j['model'] as String,
|
||||
year: j['year'] as int,
|
||||
color: j['color'] as String?,
|
||||
licensePlate: j['licensePlate'] as String,
|
||||
vin: j['vin'] as String?,
|
||||
category: j['category'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
class ContractLineItem {
|
||||
final String description;
|
||||
final int qty;
|
||||
final int unitPrice;
|
||||
final int total;
|
||||
|
||||
const ContractLineItem({
|
||||
required this.description,
|
||||
required this.qty,
|
||||
required this.unitPrice,
|
||||
required this.total,
|
||||
});
|
||||
|
||||
factory ContractLineItem.fromJson(Map<String, dynamic> j) => ContractLineItem(
|
||||
description: j['description'] as String,
|
||||
qty: (j['qty'] as num).toInt(),
|
||||
unitPrice: (j['unitPrice'] as num).toInt(),
|
||||
total: (j['total'] as num).toInt(),
|
||||
);
|
||||
}
|
||||
|
||||
class ContractPayment {
|
||||
final String id;
|
||||
final int amount;
|
||||
final String currency;
|
||||
final String type;
|
||||
final String status;
|
||||
final String? paymentMethod;
|
||||
final DateTime? paidAt;
|
||||
|
||||
const ContractPayment({
|
||||
required this.id,
|
||||
required this.amount,
|
||||
required this.currency,
|
||||
required this.type,
|
||||
required this.status,
|
||||
this.paymentMethod,
|
||||
this.paidAt,
|
||||
});
|
||||
|
||||
factory ContractPayment.fromJson(Map<String, dynamic> j) => ContractPayment(
|
||||
id: j['id'] as String,
|
||||
amount: (j['amount'] as num).toInt(),
|
||||
currency: j['currency'] as String? ?? 'USD',
|
||||
type: j['type'] as String,
|
||||
status: j['status'] as String,
|
||||
paymentMethod: j['paymentMethod'] as String?,
|
||||
paidAt: j['paidAt'] != null
|
||||
? DateTime.tryParse(j['paidAt'] as String)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
class ContractInspection {
|
||||
final int? mileage;
|
||||
final String? fuelLevel;
|
||||
final String? generalCondition;
|
||||
final String? employeeNotes;
|
||||
|
||||
const ContractInspection(
|
||||
{this.mileage, this.fuelLevel, this.generalCondition, this.employeeNotes});
|
||||
|
||||
factory ContractInspection.fromJson(Map<String, dynamic> j) =>
|
||||
ContractInspection(
|
||||
mileage: j['mileage'] as int?,
|
||||
fuelLevel: j['fuelLevel'] as String?,
|
||||
generalCondition: j['generalCondition'] as String?,
|
||||
employeeNotes: j['employeeNotes'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
class ContractPayload {
|
||||
final String reservationId;
|
||||
final String? contractNumber;
|
||||
final String? invoiceNumber;
|
||||
final String status;
|
||||
final String paymentStatus;
|
||||
final String? notes;
|
||||
final DateTime generatedAt;
|
||||
final ContractCompany company;
|
||||
final ContractDriver driver;
|
||||
final ContractVehicle vehicle;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
final int totalDays;
|
||||
final String? pickupLocation;
|
||||
final String? returnLocation;
|
||||
final List<ContractLineItem> lineItems;
|
||||
final int subtotal;
|
||||
final int taxTotal;
|
||||
final int total;
|
||||
final int amountPaid;
|
||||
final int balanceDue;
|
||||
final String currency;
|
||||
final List<ContractPayment> payments;
|
||||
final ContractInspection? checkIn;
|
||||
final ContractInspection? checkOut;
|
||||
final String? terms;
|
||||
final String? fuelPolicy;
|
||||
final String? depositPolicy;
|
||||
|
||||
const ContractPayload({
|
||||
required this.reservationId,
|
||||
this.contractNumber,
|
||||
this.invoiceNumber,
|
||||
required this.status,
|
||||
required this.paymentStatus,
|
||||
this.notes,
|
||||
required this.generatedAt,
|
||||
required this.company,
|
||||
required this.driver,
|
||||
required this.vehicle,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
required this.totalDays,
|
||||
this.pickupLocation,
|
||||
this.returnLocation,
|
||||
required this.lineItems,
|
||||
required this.subtotal,
|
||||
required this.taxTotal,
|
||||
required this.total,
|
||||
required this.amountPaid,
|
||||
required this.balanceDue,
|
||||
required this.currency,
|
||||
required this.payments,
|
||||
this.checkIn,
|
||||
this.checkOut,
|
||||
this.terms,
|
||||
this.fuelPolicy,
|
||||
this.depositPolicy,
|
||||
});
|
||||
|
||||
factory ContractPayload.fromJson(Map<String, dynamic> j) {
|
||||
final period = j['rentalPeriod'] as Map<String, dynamic>;
|
||||
final invoice = j['invoice'] as Map<String, dynamic>;
|
||||
final inspections =
|
||||
j['inspections'] as Map<String, dynamic>? ?? {};
|
||||
final termsData = j['terms'] as Map<String, dynamic>? ?? {};
|
||||
|
||||
return ContractPayload(
|
||||
reservationId: j['reservationId'] as String,
|
||||
contractNumber: j['contractNumber'] as String?,
|
||||
invoiceNumber: j['invoiceNumber'] as String?,
|
||||
status: j['status'] as String,
|
||||
paymentStatus: j['paymentStatus'] as String,
|
||||
notes: j['notes'] as String?,
|
||||
generatedAt: DateTime.parse(j['generatedAt'] as String),
|
||||
company:
|
||||
ContractCompany.fromJson(j['company'] as Map<String, dynamic>),
|
||||
driver:
|
||||
ContractDriver.fromJson(j['driver'] as Map<String, dynamic>),
|
||||
vehicle:
|
||||
ContractVehicle.fromJson(j['vehicle'] as Map<String, dynamic>),
|
||||
startDate: DateTime.parse(period['startDate'] as String),
|
||||
endDate: DateTime.parse(period['endDate'] as String),
|
||||
totalDays: (period['totalDays'] as num).toInt(),
|
||||
pickupLocation: period['pickupLocation'] as String?,
|
||||
returnLocation: period['returnLocation'] as String?,
|
||||
lineItems: (invoice['lineItems'] as List<dynamic>? ?? [])
|
||||
.map((e) => ContractLineItem.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
subtotal: (invoice['subtotal'] as num).toInt(),
|
||||
taxTotal: (invoice['taxTotal'] as num).toInt(),
|
||||
total: (invoice['total'] as num).toInt(),
|
||||
amountPaid: (invoice['amountPaid'] as num).toInt(),
|
||||
balanceDue: (invoice['balanceDue'] as num).toInt(),
|
||||
currency: invoice['currency'] as String? ?? 'USD',
|
||||
payments: (invoice['payments'] as List<dynamic>? ?? [])
|
||||
.map((e) => ContractPayment.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
checkIn: inspections['checkIn'] != null
|
||||
? ContractInspection.fromJson(
|
||||
inspections['checkIn'] as Map<String, dynamic>)
|
||||
: null,
|
||||
checkOut: inspections['checkOut'] != null
|
||||
? ContractInspection.fromJson(
|
||||
inspections['checkOut'] as Map<String, dynamic>)
|
||||
: null,
|
||||
terms: termsData['terms'] as String?,
|
||||
fuelPolicy: termsData['fuelPolicy'] as String?,
|
||||
depositPolicy: termsData['depositPolicy'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ContractService {
|
||||
final _client = ApiClient.instance;
|
||||
|
||||
Future<ContractPayload> getContract(String reservationId) async {
|
||||
final res = await _client.get('/reservations/$reservationId/contract');
|
||||
return ContractPayload.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import '../models/customer.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
@@ -26,4 +27,40 @@ class CustomerService {
|
||||
final res = await _client.post('/customers', data: data);
|
||||
return Customer.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<Customer> updateCustomer(String id, Map<String, dynamic> data) async {
|
||||
final res = await _client.patch('/customers/$id', data: data);
|
||||
return Customer.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<void> flagCustomer(String id, String reason) async {
|
||||
await _client.post('/customers/$id/flag', data: {'reason': reason});
|
||||
}
|
||||
|
||||
Future<void> unflagCustomer(String id) async {
|
||||
await _client.delete('/customers/$id/flag');
|
||||
}
|
||||
|
||||
Future<Customer> approveLicense(
|
||||
String id, String decision, {String? note}) async {
|
||||
final res = await _client.post('/customers/$id/approve-license',
|
||||
data: {'decision': decision, 'note': ?note});
|
||||
return Customer.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<Customer> uploadLicenseImage(String id, String filePath) async {
|
||||
final formData = FormData.fromMap({
|
||||
'file': await MultipartFile.fromFile(filePath),
|
||||
});
|
||||
final res = await _client.dio.post(
|
||||
'/customers/$id/license-image',
|
||||
data: formData,
|
||||
options: Options(headers: {'Content-Type': 'multipart/form-data'}),
|
||||
);
|
||||
final body = res.data;
|
||||
final payload = (body is Map<String, dynamic> && body.containsKey('data'))
|
||||
? body['data']
|
||||
: body;
|
||||
return Customer.fromJson(payload as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +46,10 @@ class MarketplaceService {
|
||||
'startDate': '${startDate.toIso8601String().substring(0, 10)}T00:00:00.000Z',
|
||||
if (endDate != null)
|
||||
'endDate': '${endDate.toIso8601String().substring(0, 10)}T00:00:00.000Z',
|
||||
if (category != null) 'category': category,
|
||||
if (transmission != null) 'transmission': transmission,
|
||||
'category': ?category,
|
||||
'transmission': ?transmission,
|
||||
if (make != null && make.isNotEmpty) 'make': make,
|
||||
if (maxPriceCents != null) 'maxPrice': maxPriceCents,
|
||||
'maxPrice': ?maxPriceCents,
|
||||
});
|
||||
|
||||
final data = res.data;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import '../models/notification.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
class NotificationService {
|
||||
final _client = ApiClient.instance;
|
||||
|
||||
Future<List<AppNotification>> getNotifications({bool? unreadOnly}) async {
|
||||
final res = await _client.get('/notifications/company', params: {
|
||||
if (unreadOnly == true) 'unread': true,
|
||||
});
|
||||
final list = res.data as List<dynamic>;
|
||||
return list
|
||||
.map((e) => AppNotification.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<int> getUnreadCount() async {
|
||||
final res = await _client.get('/notifications/unread-count');
|
||||
return (res.data as Map<String, dynamic>)['unread'] as int? ?? 0;
|
||||
}
|
||||
|
||||
Future<void> markRead(String id) async {
|
||||
await _client.post('/notifications/company/$id/read');
|
||||
}
|
||||
|
||||
Future<void> markAllRead() async {
|
||||
await _client.post('/notifications/company/read-all');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import '../models/offer.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
class OfferService {
|
||||
final _client = ApiClient.instance;
|
||||
|
||||
Future<List<CompanyOffer>> getOffers() async {
|
||||
final res = await _client.get('/offers');
|
||||
final list = res.data as List<dynamic>;
|
||||
return list
|
||||
.map((e) => CompanyOffer.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<CompanyOffer> getOffer(String id) async {
|
||||
final res = await _client.get('/offers/$id');
|
||||
return CompanyOffer.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<CompanyOffer> createOffer(Map<String, dynamic> data) async {
|
||||
final res = await _client.post('/offers', data: data);
|
||||
return CompanyOffer.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<CompanyOffer> updateOffer(String id, Map<String, dynamic> data) async {
|
||||
final res = await _client.patch('/offers/$id', data: data);
|
||||
return CompanyOffer.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<void> deleteOffer(String id) async {
|
||||
await _client.delete('/offers/$id');
|
||||
}
|
||||
|
||||
Future<void> activateOffer(String id) async {
|
||||
await _client.post('/offers/$id/activate');
|
||||
}
|
||||
|
||||
Future<void> deactivateOffer(String id) async {
|
||||
await _client.post('/offers/$id/deactivate');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import 'api_client.dart';
|
||||
|
||||
class Payment {
|
||||
final String id;
|
||||
final int amount;
|
||||
final String currency;
|
||||
final String type;
|
||||
final String paymentMethod;
|
||||
final String status;
|
||||
final DateTime createdAt;
|
||||
|
||||
const Payment({
|
||||
required this.id,
|
||||
required this.amount,
|
||||
required this.currency,
|
||||
required this.type,
|
||||
required this.paymentMethod,
|
||||
required this.status,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory Payment.fromJson(Map<String, dynamic> json) => Payment(
|
||||
id: json['id'] as String,
|
||||
amount: json['amount'] as int? ?? 0,
|
||||
currency: json['currency'] as String? ?? 'USD',
|
||||
type: json['type'] as String? ?? 'CHARGE',
|
||||
paymentMethod: json['paymentMethod'] as String? ?? 'CASH',
|
||||
status: json['status'] as String? ?? 'COMPLETED',
|
||||
createdAt: DateTime.parse(
|
||||
json['createdAt'] as String? ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
|
||||
class PaymentService {
|
||||
final _client = ApiClient.instance;
|
||||
|
||||
Future<List<Payment>> getPayments(String reservationId) async {
|
||||
final res = await _client.get('/payments/reservations/$reservationId');
|
||||
final list = res.data as List<dynamic>;
|
||||
return list
|
||||
.map((e) => Payment.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<Payment> recordManualPayment(
|
||||
String reservationId, Map<String, dynamic> data) async {
|
||||
final res = await _client.post(
|
||||
'/payments/reservations/$reservationId/manual',
|
||||
data: data);
|
||||
return Payment.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,38 @@
|
||||
import '../models/reservation.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
class ReservationInspection {
|
||||
final String id;
|
||||
final String type; // CHECKIN | CHECKOUT
|
||||
final int? mileage;
|
||||
final String? fuelLevel;
|
||||
final String? generalCondition;
|
||||
final String? employeeNotes;
|
||||
final DateTime createdAt;
|
||||
|
||||
const ReservationInspection({
|
||||
required this.id,
|
||||
required this.type,
|
||||
this.mileage,
|
||||
this.fuelLevel,
|
||||
this.generalCondition,
|
||||
this.employeeNotes,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory ReservationInspection.fromJson(Map<String, dynamic> json) =>
|
||||
ReservationInspection(
|
||||
id: json['id'] as String,
|
||||
type: json['type'] as String,
|
||||
mileage: json['mileage'] as int?,
|
||||
fuelLevel: json['fuelLevel'] as String?,
|
||||
generalCondition: json['generalCondition'] as String?,
|
||||
employeeNotes: json['employeeNotes'] as String?,
|
||||
createdAt: DateTime.parse(
|
||||
json['createdAt'] as String? ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
|
||||
class ReservationService {
|
||||
final _client = ApiClient.instance;
|
||||
|
||||
@@ -8,13 +40,17 @@ class ReservationService {
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
String? status,
|
||||
String? source,
|
||||
String? search,
|
||||
String? customerId,
|
||||
}) async {
|
||||
final res = await _client.get('/reservations', params: {
|
||||
'page': page,
|
||||
'pageSize': pageSize,
|
||||
if (status != null) 'status': status,
|
||||
'status': ?status,
|
||||
'source': ?source,
|
||||
if (search != null && search.isNotEmpty) 'search': search,
|
||||
'customerId': ?customerId,
|
||||
});
|
||||
return ReservationListResponse.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
@@ -44,4 +80,26 @@ class ReservationService {
|
||||
Future<void> checkout(String id, int mileage) async {
|
||||
await _client.post('/reservations/$id/checkout', data: {'mileage': mileage});
|
||||
}
|
||||
|
||||
Future<Reservation> closeReservation(String id) async {
|
||||
final res = await _client.post('/reservations/$id/close');
|
||||
return Reservation.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<List<ReservationInspection>> getInspections(String id) async {
|
||||
final res = await _client.get('/reservations/$id/inspections');
|
||||
final list = res.data as List<dynamic>;
|
||||
return list
|
||||
.map((e) =>
|
||||
ReservationInspection.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<ReservationInspection> upsertInspection(
|
||||
String id, String type, Map<String, dynamic> data) async {
|
||||
final res = await _client.put(
|
||||
'/reservations/$id/inspections/$type',
|
||||
data: data);
|
||||
return ReservationInspection.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import '../models/settings.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
class SettingsService {
|
||||
final _client = ApiClient.instance;
|
||||
|
||||
Future<BrandSettings> getBrand() async {
|
||||
final res = await _client.get('/companies/me/brand');
|
||||
return BrandSettings.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<BrandSettings> updateBrand(Map<String, dynamic> data) async {
|
||||
final res = await _client.patch('/companies/me/brand', data: data);
|
||||
return BrandSettings.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import 'api_client.dart';
|
||||
|
||||
class TeamMember {
|
||||
final String id;
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final String email;
|
||||
final String role;
|
||||
final bool isActive;
|
||||
final DateTime createdAt;
|
||||
|
||||
const TeamMember({
|
||||
required this.id,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.email,
|
||||
required this.role,
|
||||
required this.isActive,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
String get fullName => '$firstName $lastName';
|
||||
|
||||
factory TeamMember.fromJson(Map<String, dynamic> json) => TeamMember(
|
||||
id: json['id'] as String,
|
||||
firstName: json['firstName'] as String,
|
||||
lastName: json['lastName'] as String,
|
||||
email: json['email'] as String,
|
||||
role: json['role'] as String? ?? 'AGENT',
|
||||
isActive: json['isActive'] as bool? ?? true,
|
||||
createdAt: DateTime.parse(
|
||||
json['createdAt'] as String? ?? DateTime.now().toIso8601String()),
|
||||
);
|
||||
}
|
||||
|
||||
class TeamService {
|
||||
final _client = ApiClient.instance;
|
||||
|
||||
Future<List<TeamMember>> getMembers() async {
|
||||
final res = await _client.get('/team');
|
||||
final list = res.data as List<dynamic>;
|
||||
return list
|
||||
.map((e) => TeamMember.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> inviteMember(Map<String, dynamic> data) async {
|
||||
await _client.post('/team/invite', data: data);
|
||||
}
|
||||
|
||||
Future<void> updateRole(String id, String role) async {
|
||||
await _client.patch('/team/$id/role', data: {'role': role});
|
||||
}
|
||||
|
||||
Future<void> deactivateMember(String id) async {
|
||||
await _client.post('/team/$id/deactivate');
|
||||
}
|
||||
|
||||
Future<void> reactivateMember(String id) async {
|
||||
await _client.post('/team/$id/reactivate');
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,69 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import '../models/vehicle.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
class MaintenanceLog {
|
||||
final String id;
|
||||
final String type;
|
||||
final String? description;
|
||||
final int? cost;
|
||||
final int? mileage;
|
||||
final DateTime performedAt;
|
||||
final DateTime? nextDueAt;
|
||||
final int? nextDueMileage;
|
||||
|
||||
const MaintenanceLog({
|
||||
required this.id,
|
||||
required this.type,
|
||||
this.description,
|
||||
this.cost,
|
||||
this.mileage,
|
||||
required this.performedAt,
|
||||
this.nextDueAt,
|
||||
this.nextDueMileage,
|
||||
});
|
||||
|
||||
factory MaintenanceLog.fromJson(Map<String, dynamic> json) => MaintenanceLog(
|
||||
id: json['id'] as String,
|
||||
type: json['type'] as String,
|
||||
description: json['description'] as String?,
|
||||
cost: json['cost'] as int?,
|
||||
mileage: json['mileage'] as int?,
|
||||
performedAt: DateTime.parse(json['performedAt'] as String),
|
||||
nextDueAt: json['nextDueAt'] != null
|
||||
? DateTime.parse(json['nextDueAt'] as String)
|
||||
: null,
|
||||
nextDueMileage: json['nextDueMileage'] as int?,
|
||||
);
|
||||
}
|
||||
|
||||
class CalendarEvent {
|
||||
final String id;
|
||||
final String type; // RESERVATION | MAINTENANCE | BLOCK
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
final String? status;
|
||||
final String label;
|
||||
|
||||
const CalendarEvent({
|
||||
required this.id,
|
||||
required this.type,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
this.status,
|
||||
required this.label,
|
||||
});
|
||||
|
||||
factory CalendarEvent.fromJson(Map<String, dynamic> json) => CalendarEvent(
|
||||
id: json['id'] as String,
|
||||
type: json['type'] as String,
|
||||
startDate: DateTime.parse(json['startDate'] as String),
|
||||
endDate: DateTime.parse(json['endDate'] as String),
|
||||
status: json['status'] as String?,
|
||||
label: json['label'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
class VehicleService {
|
||||
final _client = ApiClient.instance;
|
||||
|
||||
@@ -13,7 +76,7 @@ class VehicleService {
|
||||
final res = await _client.get('/vehicles', params: {
|
||||
'page': page,
|
||||
'pageSize': pageSize,
|
||||
if (status != null) 'status': status,
|
||||
'status': ?status,
|
||||
if (search != null && search.isNotEmpty) 'search': search,
|
||||
});
|
||||
return VehicleListResponse.fromJson(res.data as Map<String, dynamic>);
|
||||
@@ -34,7 +97,70 @@ class VehicleService {
|
||||
return Vehicle.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<void> deleteVehicle(String id) async {
|
||||
await _client.delete('/vehicles/$id');
|
||||
}
|
||||
|
||||
Future<void> togglePublish(String id, bool publish) async {
|
||||
await _client.patch('/vehicles/$id/publish', data: {'isPublished': publish});
|
||||
}
|
||||
|
||||
Future<Vehicle> uploadPhotos(String id, List<String> filePaths) async {
|
||||
final formData = FormData.fromMap({
|
||||
'photos': [
|
||||
for (final path in filePaths)
|
||||
await MultipartFile.fromFile(path),
|
||||
],
|
||||
});
|
||||
final res = await _client.dio.post(
|
||||
'/vehicles/$id/photos',
|
||||
data: formData,
|
||||
options: Options(headers: {'Content-Type': 'multipart/form-data'}),
|
||||
);
|
||||
final body = res.data;
|
||||
final payload = (body is Map<String, dynamic> && body.containsKey('data'))
|
||||
? body['data']
|
||||
: body;
|
||||
return Vehicle.fromJson(payload as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<Vehicle> deletePhoto(String id, int idx) async {
|
||||
final res = await _client.delete('/vehicles/$id/photos/$idx');
|
||||
return Vehicle.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<List<MaintenanceLog>> getMaintenance(String id) async {
|
||||
final res = await _client.get('/vehicles/$id/maintenance');
|
||||
final list = res.data as List<dynamic>;
|
||||
return list
|
||||
.map((e) => MaintenanceLog.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<MaintenanceLog> addMaintenance(
|
||||
String id, Map<String, dynamic> data) async {
|
||||
final res = await _client.post('/vehicles/$id/maintenance', data: data);
|
||||
return MaintenanceLog.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<List<CalendarEvent>> getCalendar(
|
||||
String id, int year, int month) async {
|
||||
final res = await _client.get('/vehicles/$id/calendar',
|
||||
params: {'year': year, 'month': month});
|
||||
final list = res.data as List<dynamic>;
|
||||
return list
|
||||
.map((e) => CalendarEvent.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<CalendarEvent> createCalendarBlock(
|
||||
String id, Map<String, dynamic> data) async {
|
||||
final res =
|
||||
await _client.post('/vehicles/$id/calendar/blocks', data: data);
|
||||
return CalendarEvent.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<void> deleteCalendarBlock(String id, String blockId) async {
|
||||
await _client.delete('/vehicles/$id/calendar/blocks/$blockId');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user