add 8-tab review page, review controller, updateDriverToActive handles new fields, drivers_cant_register navigates to review

This commit is contained in:
Hamza-Ayed
2026-06-25 17:05:11 +03:00
parent d4db89f04e
commit 28b8558b6d
5 changed files with 1100 additions and 15 deletions

View File

@@ -5,30 +5,35 @@ require_once __DIR__ . '/../connect.php'; // يفترض أن يحتوي على
// --- استقبال البيانات من التطبيق ---
$driverId = filterRequest("driverId");
$phone = filterRequest("phone"); // استقبال رقم الهاتف مباشرة
$phone = filterRequest("phone");
$email = filterRequest("email");
$status = filterRequest("status") ?: 'actives';
// --- بيانات جدول السائق (driver) ---
$firstName = filterRequest("first_name");
$lastName = filterRequest("last_name");
$site = filterRequest("site"); // مكان القيد/الولادة
$site = filterRequest("site");
$address = filterRequest("address") ?: $site;
$nationalNumber = filterRequest("national_number");
$licenseCategories = filterRequest("license_categories"); // فئة الرخصة
$expiryDate = filterRequest("expiry_date"); // تاريخ انتهاء رخصة السائق
$licenseCategories = filterRequest("license_categories");
$licenseType = filterRequest("license_type");
$expiryDate = filterRequest("expiry_date");
$licenseIssueDate = filterRequest("license_issue_date");
$gender = filterRequest("gender"); // الحقل الجديد
$birthdate = filterRequest("birthdate"); // الحقل الجديد
$maritalStatus=filterRequest("maritalStatus");
$gender = filterRequest("gender");
$birthdate = filterRequest("birthdate");
$maritalStatus = filterRequest("maritalStatus");
// --- بيانات جدول السيارة (CarRegistration) ---
$owner = filterRequest("owner");
$color = filterRequest("color");
$colorHex = filterRequest("color_hex");
$model = filterRequest("model"); // الموديل
$model = filterRequest("model");
$carPlate = filterRequest("car_plate");
$make = filterRequest("make"); // الصانع
$make = filterRequest("make");
$fuel = filterRequest("fuel");
$year = filterRequest("year");
$carExpirationDate = filterRequest("expiration_date"); // تاريخ انتهاء رخصة السيارة
$carExpirationDate = filterRequest("expiration_date");
$vin = filterRequest("vin");
// --- بدء المعاملة لضمان سلامة البيانات ---
$con->beginTransaction();
@@ -36,7 +41,6 @@ $con->beginTransaction();
try {
// --- 1. معالجة وتشفير البيانات ---
$nameArabic = $firstName . ' ' . $lastName;
$address = $site;
// تشفير الحقول الحساسة
$encryptedFirstName = $encryptionHelper->encryptData($firstName);
@@ -50,6 +54,9 @@ try {
$encryptedBirthdate = $encryptionHelper->encryptData($birthdate);
$encryptedGender = $encryptionHelper->encryptData($gender);
$encryptedPhone = !empty($phone) ? $encryptionHelper->encryptData($phone) : null;
$encryptedEmail = !empty($email) ? $encryptionHelper->encryptData($email) : null;
// --- 2. تحديث جدول السائق ---
$sqlDriver = "UPDATE `driver` SET
`first_name` = :first_name,
@@ -58,13 +65,14 @@ try {
`address` = :address,
`national_number` = :national_number,
`license_categories` = :license_categories,
`license_type` = :license_type,
`expiry_date` = :expiry_date,
`issue_date` = :issue_date,
`gender` = :gender,
`birthdate` = :birthdate,
`name_arabic` = :name_arabic,
`maritalStatus` = :maritalStatus,
`status` = 'actives'
`status` = :status
WHERE `id` = :driverId";
$stmtDriver = $con->prepare($sqlDriver);
@@ -75,15 +83,27 @@ try {
':address' => $encryptedAddress,
':national_number' => $encryptedNationalNumber,
':license_categories' => $licenseCategories,
':license_type' => $licenseType,
':expiry_date' => $expiryDate,
':issue_date' => $licenseIssueDate,
':gender' => $encryptedGender,
':birthdate' => $encryptedBirthdate,
':name_arabic' => $encryptedNameArabic,
':maritalStatus' => $maritalStatus,
':status' => $status,
':driverId' => $driverId,
':maritalStatus' =>$maritalStatus
]);
// --- تحديث الهاتف والايميل إذا وجدا ---
if ($encryptedPhone) {
$stmtPhone = $con->prepare("UPDATE `driver` SET `phone` = :phone WHERE `id` = :id");
$stmtPhone->execute([':phone' => $encryptedPhone, ':id' => $driverId]);
}
if ($encryptedEmail) {
$stmtEmail = $con->prepare("UPDATE `driver` SET `email` = :email WHERE `id` = :id");
$stmtEmail->execute([':email' => $encryptedEmail, ':id' => $driverId]);
}
// --- 3. تحديث جدول السيارة ---
$sqlCar = "UPDATE `CarRegistration` SET
`owner` = :owner,
@@ -94,6 +114,7 @@ try {
`make` = :make,
`fuel` = :fuel,
`year` = :year,
`vin` = :vin,
`expiration_date` = :expiration_date
WHERE `driverID` = :driverId";
@@ -107,6 +128,7 @@ try {
':make' => $make,
':fuel' => $fuel,
':year' => $year,
':vin' => $vin ?: '',
':expiration_date' => $carExpirationDate,
':driverId' => $driverId
]);

View File

@@ -181,6 +181,7 @@ class AppLink {
static String getComplaintAllData = "$serviceApp/getComplaintAllData.php";
static String getComplaintAllDataForDriver =
"$serviceApp/getComplaintAllDataForDriver.php";
static String rejectDriver = "$serviceApp/rejectDriver.php";
static String addCriminalDocuments = "$authCaptin/addCriminalDocuments.php";
static String ride = '$server/ride';
static String addRegisrationCar = "$ride/RegisrationCar/add.php";

View File

@@ -7,7 +7,7 @@ import 'package:siro_service/views/widgets/my_scafold.dart';
import 'package:siro_service/main.dart';
import 'package:siro_service/constant/box_name.dart';
import 'registration_captain_page.dart';
import 'review_driver_page.dart';
class DriversCantRegister extends StatelessWidget {
DriversCantRegister({super.key});
@@ -151,7 +151,7 @@ class DriversCantRegister extends StatelessWidget {
color: AppColor.gold,
onPressed: () {
Get.to(
() => RegisterCaptain(),
() => const ReviewDriverPage(),
arguments: {
"phone": driver['phone_number'],
"driverId": driver['driverId'],

View File

@@ -0,0 +1,643 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/constant/colors.dart';
import 'package:siro_service/controller/mainController/review_driver_controller.dart';
import 'package:siro_service/views/widgets/elevated_btn.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
class ReviewDriverPage extends StatelessWidget {
const ReviewDriverPage({super.key});
@override
Widget build(BuildContext context) {
final controller = Get.put(ReviewDriverController());
return MyScaffold(
title: 'Review Driver'.tr,
isleading: true,
body: [
Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
return Column(
children: [
_buildTabBar(controller),
Expanded(child: _buildTabBarView(controller)),
_buildBottomActions(controller, context),
],
);
}),
],
);
}
Widget _buildTabBar(ReviewDriverController c) {
final keys = c.docUrls.keys.toList();
return SizedBox(
height: 64,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
itemCount: keys.length,
itemBuilder: (context, index) {
final key = keys[index];
final isSelected = c.currentTabIndex.value == index;
return GestureDetector(
onTap: () => c.currentTabIndex.value = index,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 3),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: isSelected
? AppColor.primaryColor
: AppColor.primaryLight,
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
c.tabIcons[key] ?? Icons.image,
size: 16,
color: isSelected ? Colors.white : AppColor.primaryColor,
),
const SizedBox(width: 4),
Text(
c.tabLabels[key] ?? key,
style: TextStyle(
fontSize: 11,
fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal,
color:
isSelected ? Colors.white : AppColor.primaryColor,
),
),
],
),
),
);
},
),
);
}
Widget _buildTabBarView(ReviewDriverController c) {
return Obx(() {
final index = c.currentTabIndex.value;
final keys = c.docUrls.keys.toList();
final key = keys[index];
return SingleChildScrollView(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildDocumentImage(c.docUrls[key]!.value),
const SizedBox(height: 16),
_buildFormFieldsForTab(c, key),
],
),
);
});
}
Widget _buildDocumentImage(String url) {
if (url.isEmpty) {
return Container(
height: 180,
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.image_not_supported, size: 48, color: Colors.grey[400]),
const SizedBox(height: 8),
Text('No image available', style: TextStyle(color: Colors.grey[500])),
],
),
),
);
}
return GestureDetector(
onTap: () => _showImageFullscreen(url),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
url,
height: 200,
fit: BoxFit.contain,
width: double.infinity,
errorBuilder: (_, __, ___) => Container(
height: 180,
color: Colors.grey[200],
child: Center(
child: Text('Failed to load image',
style: TextStyle(color: Colors.grey[500])),
),
),
),
),
);
}
void _showImageFullscreen(String url) {
Get.dialog(
Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.black,
iconTheme: const IconThemeData(color: Colors.white),
),
body: Center(
child: InteractiveViewer(
child: Image.network(url, fit: BoxFit.contain),
),
),
),
);
}
Widget _buildFormFieldsForTab(ReviewDriverController c, String tabKey) {
switch (tabKey) {
case 'id_front':
return _buildIdFrontFields(c);
case 'id_back':
return _buildIdBackFields(c);
case 'driver_license':
return _buildDriverLicenseFields(c);
case 'driver_license_back':
return _buildLicenseBackFields(c);
case 'car_license_front':
return _buildCarFrontFields(c);
case 'car_license_back':
return _buildCarBackFields(c);
case 'criminal_record':
return _buildCriminalRecordFields();
case 'profile_picture':
return _buildProfilePhotoFields(c);
default:
return const SizedBox();
}
}
Widget _buildTextField({
required String label,
required TextEditingController controller,
IconData? icon,
TextInputType keyboardType = TextInputType.text,
VoidCallback? onTap,
String? hint,
int maxLines = 1,
}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: TextFormField(
controller: controller,
keyboardType: keyboardType,
maxLines: maxLines,
readOnly: onTap != null,
onTap: onTap,
decoration: InputDecoration(
labelText: label.tr,
hintText: hint,
prefixIcon: icon != null
? Icon(icon, color: AppColor.secondaryColor)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
),
);
}
Widget _buildGenderDropdown(ReviewDriverController c) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Obx(
() => DropdownButtonFormField<String>(
value: c.selectedGender.value.isEmpty
? null
: c.selectedGender.value,
isExpanded: true,
decoration: InputDecoration(
labelText: 'Gender'.tr,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
items: ['Male', 'Female'].map((v) {
return DropdownMenuItem(value: v, child: Text(v.tr));
}).toList(),
onChanged: (v) {
if (v != null) c.selectedGender.value = v;
},
),
),
);
}
Widget _buildColorDropdown(ReviewDriverController c) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Obx(
() => DropdownButtonFormField<String>(
value: c.colorHex.value.isEmpty ? null : c.colorHex.value,
isExpanded: true,
decoration: InputDecoration(
labelText: 'Car Color'.tr,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
items: ReviewDriverController.kCarColorOptions.map((opt) {
return DropdownMenuItem<String>(
value: opt['hex'],
child: Row(
children: [
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: c.hexToColor(opt['hex']!),
shape: BoxShape.circle,
border: Border.all(color: Colors.black12),
),
),
const SizedBox(width: 10),
Text(opt['key']!.tr),
],
),
);
}).toList(),
onChanged: (hex) {
if (hex != null) c.updateColorSelection(hex);
},
),
),
);
}
Widget _buildFuelDropdown(ReviewDriverController c) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Obx(
() => DropdownButtonFormField<String>(
value: ReviewDriverController.kFuelOptions
.contains(c.selectedFuel.value)
? c.selectedFuel.value
: null,
isExpanded: true,
decoration: InputDecoration(
labelText: 'Fuel Type'.tr,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
items: ReviewDriverController.kFuelOptions.map((v) {
return DropdownMenuItem(value: v, child: Text(v));
}).toList(),
onChanged: (v) {
if (v != null) c.selectedFuel.value = v;
},
),
),
);
}
Widget _buildIdFrontFields(ReviewDriverController c) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('ID Card Information (Front)',
style: Get.textTheme.titleMedium),
const Divider(),
Row(
children: [
Expanded(
child: _buildTextField(
label: 'First Name',
controller: c.firstNameController,
icon: Icons.person,
),
),
const SizedBox(width: 8),
Expanded(
child: _buildTextField(
label: 'Last Name',
controller: c.lastNameController,
icon: Icons.person,
),
),
],
),
Row(
children: [
Expanded(
child: _buildTextField(
label: 'National Number',
controller: c.nationalNumberController,
icon: Icons.fingerprint,
),
),
const SizedBox(width: 8),
Expanded(child: _buildGenderDropdown(c)),
],
),
_buildTextField(
label: 'Birthdate',
controller: c.birthdateController,
icon: Icons.cake,
onTap: () => c.selectDate(Get.context!, c.birthdateController),
),
],
);
}
Widget _buildIdBackFields(ReviewDriverController c) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('ID Card Information (Back)',
style: Get.textTheme.titleMedium),
const Divider(),
_buildTextField(
label: 'Address',
controller: c.addressController,
icon: Icons.location_on,
maxLines: 2,
),
_buildTextField(
label: 'Place of Registration',
controller: c.siteController,
icon: Icons.location_city,
),
],
);
}
Widget _buildDriverLicenseFields(ReviewDriverController c) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Driver License Information',
style: Get.textTheme.titleMedium),
const Divider(),
_buildTextField(
label: 'License Type',
controller: c.licenseTypeController,
icon: Icons.card_membership,
),
_buildTextField(
label: 'License Categories',
controller: c.licenseCategoriesController,
icon: Icons.category,
),
_buildTextField(
label: 'License Issue Date',
controller: c.licenseIssueDateController,
icon: Icons.event,
onTap: () =>
c.selectDate(Get.context!, c.licenseIssueDateController),
),
],
);
}
Widget _buildLicenseBackFields(ReviewDriverController c) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Driver License (Back)', style: Get.textTheme.titleMedium),
const Divider(),
_buildTextField(
label: 'Expiry Date',
controller: c.expiryDateController,
icon: Icons.event_busy,
onTap: () => c.selectDate(Get.context!, c.expiryDateController),
),
],
);
}
Widget _buildCarFrontFields(ReviewDriverController c) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Car Registration (Front)',
style: Get.textTheme.titleMedium),
const Divider(),
_buildTextField(
label: 'Owner Name',
controller: c.ownerController,
icon: Icons.person_search,
),
Row(
children: [
Expanded(child: _buildColorDropdown(c)),
const SizedBox(width: 8),
Expanded(
child: _buildTextField(
label: 'Car Plate',
controller: c.carPlateController,
icon: Icons.confirmation_number,
),
),
],
),
_buildTextField(
label: 'License Issue Date',
controller: c.licenseIssueDateController,
icon: Icons.event,
onTap: () =>
c.selectDate(Get.context!, c.licenseIssueDateController),
),
],
);
}
Widget _buildCarBackFields(ReviewDriverController c) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Car Registration (Back)',
style: Get.textTheme.titleMedium),
const Divider(),
Row(
children: [
Expanded(
child: _buildTextField(
label: 'Make',
controller: c.makeController,
icon: Icons.directions_car,
),
),
const SizedBox(width: 8),
Expanded(
child: _buildTextField(
label: 'Model',
controller: c.modelController,
icon: Icons.model_training,
),
),
],
),
Row(
children: [
Expanded(
child: _buildTextField(
label: 'Year',
controller: c.yearController,
keyboardType: TextInputType.number,
),
),
const SizedBox(width: 8),
Expanded(child: _buildFuelDropdown(c)),
],
),
_buildTextField(
label: 'VIN (Chassis Number)',
controller: c.vinController,
icon: Icons.confirmation_number,
),
_buildTextField(
label: 'Expiration Date',
controller: c.carLicenseExpiryDateController,
icon: Icons.event_busy,
onTap: () => c.selectDate(
Get.context!, c.carLicenseExpiryDateController),
),
],
);
}
Widget _buildCriminalRecordFields() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Criminal Record', style: Get.textTheme.titleMedium),
const Divider(),
Card(
color: Colors.green[50],
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(Icons.check_circle, color: Colors.green[700], size: 32),
const SizedBox(width: 12),
Expanded(
child: Text(
'Review the criminal record document above. '
'Verify the name matches the driver and the record is valid.',
style: TextStyle(color: Colors.green[800]),
),
),
],
),
),
),
],
);
}
Widget _buildProfilePhotoFields(ReviewDriverController c) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Profile Photo', style: Get.textTheme.titleMedium),
const Divider(),
Card(
color: Colors.blue[50],
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(Icons.face, color: Colors.blue[700], size: 32),
const SizedBox(width: 12),
Expanded(
child: Text(
'Verify the profile photo matches the person in the ID '
'and Driver License photos above.',
style: TextStyle(color: Colors.blue[800]),
),
),
],
),
),
),
const SizedBox(height: 12),
_buildTextField(
label: 'Phone Number',
controller: c.phoneController,
icon: Icons.phone,
keyboardType: TextInputType.phone,
),
_buildTextField(
label: 'Email',
controller: c.emailController,
icon: Icons.email,
keyboardType: TextInputType.emailAddress,
),
],
);
}
Widget _buildBottomActions(ReviewDriverController c, BuildContext context) {
return Obx(() {
final saving = c.isSaving.value;
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, -4),
),
],
),
child: Row(
children: [
Expanded(
child: MyElevatedButton(
title: 'Save'.tr,
onPressed: () { c.saveChanges(); },
kolor: AppColor.blueColor,
loading: saving,
),
),
const SizedBox(width: 8),
Expanded(
child: MyElevatedButton(
title: 'Activate'.tr,
onPressed: () { c.activateDriver(); },
kolor: AppColor.greenColor,
loading: saving,
),
),
const SizedBox(width: 8),
Expanded(
child: MyElevatedButton(
title: 'Reject'.tr,
onPressed: () { c.showRejectDialog(); },
kolor: AppColor.redColor,
loading: saving,
),
),
],
),
);
});
}
}

View File

@@ -0,0 +1,419 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:siro_service/constant/box_name.dart';
import 'package:siro_service/constant/links.dart';
import 'package:siro_service/controller/functions/crud.dart';
import 'package:siro_service/main.dart';
class ReviewDriverController extends GetxController {
var isLoading = true.obs;
var isSaving = false.obs;
var driverId = ''.obs;
var phone = ''.obs;
var serverData = <String, dynamic>{}.obs;
var currentTabIndex = 0.obs;
final Map<String, RxString> docUrls = {
'id_front': ''.obs,
'id_back': ''.obs,
'driver_license': ''.obs,
'driver_license_back': ''.obs,
'car_license_front': ''.obs,
'car_license_back': ''.obs,
'criminal_record': ''.obs,
'profile_picture': ''.obs,
};
final Map<String, String> tabLabels = {
'id_front': 'ID Front',
'id_back': 'ID Back',
'driver_license': 'Driver License',
'driver_license_back': 'License Back',
'car_license_front': 'Car Reg Front',
'car_license_back': 'Car Reg Back',
'criminal_record': 'Criminal Record',
'profile_picture': 'Profile Photo',
};
final Map<String, IconData> tabIcons = {
'id_front': Icons.badge,
'id_back': Icons.badge_outlined,
'driver_license': Icons.drive_eta,
'driver_license_back': Icons.drive_eta_outlined,
'car_license_front': Icons.description,
'car_license_back': Icons.description_outlined,
'criminal_record': Icons.gavel,
'profile_picture': Icons.person,
};
late TextEditingController firstNameController;
late TextEditingController lastNameController;
late TextEditingController phoneController;
late TextEditingController emailController;
late TextEditingController siteController;
late TextEditingController nationalNumberController;
late TextEditingController birthdateController;
late TextEditingController addressController;
late TextEditingController licenseCategoriesController;
late TextEditingController licenseTypeController;
late TextEditingController expiryDateController;
late TextEditingController licenseIssueDateController;
late TextEditingController ownerController;
late TextEditingController carPlateController;
late TextEditingController vinController;
late TextEditingController carLicenseExpiryDateController;
late TextEditingController makeController;
late TextEditingController modelController;
late TextEditingController yearController;
var selectedGender = ''.obs;
var colorHex = ''.obs;
var colorName = ''.obs;
var selectedFuel = ''.obs;
@override
void onInit() {
super.onInit();
_initializeTextControllers();
final args = Get.arguments;
if (args != null) {
driverId.value = args['driverId'] ?? '';
phone.value = args['phone'] ?? '';
}
fetchData();
}
void _initializeTextControllers() {
firstNameController = TextEditingController();
lastNameController = TextEditingController();
phoneController = TextEditingController();
emailController = TextEditingController();
siteController = TextEditingController();
nationalNumberController = TextEditingController();
birthdateController = TextEditingController();
addressController = TextEditingController();
licenseCategoriesController = TextEditingController();
licenseTypeController = TextEditingController();
expiryDateController = TextEditingController();
licenseIssueDateController = TextEditingController();
ownerController = TextEditingController();
carPlateController = TextEditingController();
vinController = TextEditingController();
carLicenseExpiryDateController = TextEditingController();
makeController = TextEditingController();
modelController = TextEditingController();
yearController = TextEditingController();
}
Future<void> fetchData() async {
isLoading.value = true;
try {
var response = await CRUD().post(
link: AppLink.getDriverDetailsForActivate,
payload: {'driverId': driverId.value},
);
if (response != 'failure' &&
response['status'] == 'success' &&
(response['message'] as List).isNotEmpty) {
var raw = response['message'][0] as Map<String, dynamic>;
serverData.value =
raw.map((key, value) => MapEntry(key, value?.toString() ?? ''));
_populateControllers();
_populateDocUrls(raw['documents']);
}
} catch (e) {
Get.snackbar('Error', 'Failed to load data: $e');
} finally {
isLoading.value = false;
}
}
void _populateDocUrls(dynamic docs) {
if (docs is List) {
for (var doc in docs) {
if (doc is Map && doc['doc_type'] != null && doc['link'] != null) {
String type = doc['doc_type'].toString();
if (docUrls.containsKey(type)) {
docUrls[type]!.value = doc['link'].toString();
}
}
}
}
}
void _populateControllers() {
firstNameController.text = serverData['first_name'] ?? '';
lastNameController.text = serverData['last_name'] ?? '';
phoneController.text = serverData['phone'] ?? '';
emailController.text = serverData['email'] ?? '';
siteController.text = serverData['site'] ?? '';
nationalNumberController.text = serverData['national_number'] ?? '';
birthdateController.text = serverData['birthdate'] ?? '';
addressController.text = serverData['address'] ?? '';
licenseCategoriesController.text = serverData['license_categories'] ?? '';
licenseTypeController.text = serverData['license_type'] ?? '';
expiryDateController.text = serverData['expiry_date'] ?? '';
licenseIssueDateController.text =
serverData['licenseIssueDate'] ?? serverData['issue_date'] ?? '';
selectedGender.value = serverData['gender'] ?? '';
ownerController.text = serverData['owner'] ?? '';
carPlateController.text = serverData['car_plate'] ?? '';
vinController.text = serverData['vin'] ?? '';
carLicenseExpiryDateController.text =
serverData['expiration_date'] ?? '';
makeController.text = serverData['make'] ?? '';
modelController.text = serverData['model'] ?? '';
yearController.text = serverData['year'] ?? '';
selectedFuel.value = serverData['fuel'] ?? '';
final serverColorName = serverData['color'] ?? '';
final colorOption = kCarColorOptions.firstWhere(
(opt) => opt['key'] == serverColorName,
orElse: () => {'key': serverColorName, 'hex': '#000000'},
);
colorName.value = colorOption['key']!;
colorHex.value = colorOption['hex']!;
phone.value = serverData['phone'] ?? '';
}
Future<void> selectDate(
BuildContext context, TextEditingController controller) async {
DateTime initialDate = DateTime.now();
try {
if (controller.text.isNotEmpty) {
initialDate = DateFormat('yyyy-MM-dd').parse(controller.text);
}
} catch (_) {}
DateTime? pickedDate = initialDate;
await showCupertinoModalPopup<void>(
context: context,
builder: (context) => Container(
height: 280,
color: CupertinoColors.systemBackground.resolveFrom(context),
child: Column(
children: [
SizedBox(
height: 200,
child: CupertinoDatePicker(
initialDateTime: initialDate,
minimumDate: DateTime(1950),
maximumDate: DateTime(2050),
mode: CupertinoDatePickerMode.date,
onDateTimeChanged: (DateTime newDate) {
pickedDate = newDate;
},
),
),
CupertinoButton(
child: const Text('Done'),
onPressed: () {
if (pickedDate != null) {
controller.text =
DateFormat('yyyy-MM-dd').format(pickedDate!);
}
Navigator.of(context).pop();
},
),
],
),
),
);
}
Map<String, dynamic> _buildPayload() {
return {
'driverId': driverId.value,
'phone': phoneController.text,
'first_name': firstNameController.text,
'last_name': lastNameController.text,
'site': siteController.text,
'national_number': nationalNumberController.text,
'gender': selectedGender.value,
'birthdate': birthdateController.text,
'address': addressController.text,
'email': emailController.text,
'license_type': licenseTypeController.text,
'license_categories': licenseCategoriesController.text,
'expiry_date': expiryDateController.text,
'license_issue_date': licenseIssueDateController.text,
'maritalStatus': box.read(BoxName.employeename) ?? 'unknown',
'owner': ownerController.text,
'color': colorName.value,
'color_hex': colorHex.value,
'car_plate': carPlateController.text,
'expiration_date': carLicenseExpiryDateController.text,
'make': makeController.text,
'model': modelController.text,
'fuel': selectedFuel.value,
'year': yearController.text,
'vin': vinController.text,
};
}
Future<void> saveChanges() async {
isSaving.value = true;
try {
var payload = _buildPayload();
payload['status'] = 'pending_review';
var response = await CRUD().post(
link: AppLink.updateDriverToActive,
payload: payload,
);
if (response != 'failure' && response['status'] == 'success') {
Get.snackbar('Success', 'Data saved successfully');
} else {
Get.snackbar('Error', 'Failed to save changes');
}
} catch (e) {
Get.snackbar('Error', 'Error: $e');
} finally {
isSaving.value = false;
}
}
Future<void> activateDriver() async {
isSaving.value = true;
try {
var payload = _buildPayload();
var response = await CRUD().post(
link: AppLink.updateDriverToActive,
payload: payload,
);
if (response != 'failure' && response['status'] == 'success') {
Get.snackbar('Success', 'Driver activated successfully!');
await Future.delayed(const Duration(milliseconds: 500));
Get.back();
} else {
Get.snackbar('Error', 'Failed to activate driver');
}
} catch (e) {
Get.snackbar('Error', 'Error: $e');
} finally {
isSaving.value = false;
}
}
Future<void> rejectDriver(String reason) async {
if (reason.trim().isEmpty) {
Get.snackbar('Error', 'Please enter a rejection reason');
return;
}
isSaving.value = true;
try {
var response = await CRUD().post(
link: AppLink.rejectDriver,
payload: {
'driverId': driverId.value,
'reason': reason.trim(),
},
);
if (response != 'failure' && response['status'] == 'success') {
Get.snackbar('Success', 'Driver rejected');
await Future.delayed(const Duration(milliseconds: 500));
Get.back();
} else {
Get.snackbar('Error', 'Failed to reject driver');
}
} catch (e) {
Get.snackbar('Error', 'Error: $e');
} finally {
isSaving.value = false;
}
}
void showRejectDialog() {
final reasonController = TextEditingController();
Get.defaultDialog(
title: 'Reject Driver'.tr,
content: Padding(
padding: const EdgeInsets.all(8),
child: TextField(
controller: reasonController,
maxLines: 3,
decoration: InputDecoration(
hintText: 'Enter rejection reason...'.tr,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
),
confirm: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () => rejectDriver(reasonController.text),
child: Text('Reject'.tr, style: const TextStyle(color: Colors.white)),
),
cancel: ElevatedButton(
onPressed: () => Get.back(),
child: Text('Cancel'.tr),
),
);
}
static const List<Map<String, String>> kCarColorOptions = [
{'key': 'white', 'hex': '#FFFFFF'},
{'key': 'black', 'hex': '#000000'},
{'key': 'silver', 'hex': '#C0C0C0'},
{'key': 'gray', 'hex': '#808080'},
{'key': 'gunmetal', 'hex': '#2A3439'},
{'key': 'red', 'hex': '#C62828'},
{'key': 'blue', 'hex': '#1565C0'},
{'key': 'navy', 'hex': '#0D47A1'},
{'key': 'green', 'hex': '#2E7D32'},
{'key': 'darkGreen', 'hex': '#1B5E20'},
{'key': 'beige', 'hex': '#D7CCC8'},
{'key': 'brown', 'hex': '#5D4037'},
{'key': 'maroon', 'hex': '#800000'},
{'key': 'burgundy', 'hex': '#800020'},
{'key': 'yellow', 'hex': '#F9A825'},
{'key': 'orange', 'hex': '#EF6C00'},
{'key': 'gold', 'hex': '#D4AF37'},
{'key': 'bronze', 'hex': '#CD7F32'},
{'key': 'champagne', 'hex': '#EFE1C6'},
{'key': 'purple', 'hex': '#6A1B9A'},
];
static const List<String> kFuelOptions = [
'بنزين',
'ديزل',
'هايبرد',
'كهربائي',
];
Color hexToColor(String code) =>
Color(int.parse(code.substring(1, 7), radix: 16) + 0xFF000000);
void updateColorSelection(String hex) {
colorHex.value = hex;
colorName.value =
kCarColorOptions.firstWhere((o) => o['hex'] == hex)['key']!;
}
@override
void onClose() {
firstNameController.dispose();
lastNameController.dispose();
phoneController.dispose();
emailController.dispose();
siteController.dispose();
nationalNumberController.dispose();
birthdateController.dispose();
addressController.dispose();
licenseCategoriesController.dispose();
licenseTypeController.dispose();
expiryDateController.dispose();
licenseIssueDateController.dispose();
ownerController.dispose();
carPlateController.dispose();
vinController.dispose();
carLicenseExpiryDateController.dispose();
makeController.dispose();
modelController.dispose();
yearController.dispose();
super.onClose();
}
}