From 28b8558b6d0cdf610395f451448c13a77a9ce060 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Thu, 25 Jun 2026 17:05:11 +0300 Subject: [PATCH] add 8-tab review page, review controller, updateDriverToActive handles new fields, drivers_cant_register navigates to review --- backend/serviceapp/updateDriverToActive.php | 48 +- siro_service/lib/constant/links.dart | 1 + .../pages/drivers_cant_register.dart | 4 +- .../pages/review_driver_page.dart | 643 ++++++++++++++++++ .../review_driver_controller.dart | 419 ++++++++++++ 5 files changed, 1100 insertions(+), 15 deletions(-) create mode 100644 siro_service/lib/controller/mainController/pages/review_driver_page.dart create mode 100644 siro_service/lib/controller/mainController/review_driver_controller.dart diff --git a/backend/serviceapp/updateDriverToActive.php b/backend/serviceapp/updateDriverToActive.php index e2b33c1..4d12233 100644 --- a/backend/serviceapp/updateDriverToActive.php +++ b/backend/serviceapp/updateDriverToActive.php @@ -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 ]); diff --git a/siro_service/lib/constant/links.dart b/siro_service/lib/constant/links.dart index cb0f70f..57d5b59 100644 --- a/siro_service/lib/constant/links.dart +++ b/siro_service/lib/constant/links.dart @@ -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"; diff --git a/siro_service/lib/controller/mainController/pages/drivers_cant_register.dart b/siro_service/lib/controller/mainController/pages/drivers_cant_register.dart index ee35e66..65cb87b 100644 --- a/siro_service/lib/controller/mainController/pages/drivers_cant_register.dart +++ b/siro_service/lib/controller/mainController/pages/drivers_cant_register.dart @@ -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'], diff --git a/siro_service/lib/controller/mainController/pages/review_driver_page.dart b/siro_service/lib/controller/mainController/pages/review_driver_page.dart new file mode 100644 index 0000000..d5a3b72 --- /dev/null +++ b/siro_service/lib/controller/mainController/pages/review_driver_page.dart @@ -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( + 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( + 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( + 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( + 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, + ), + ), + ], + ), + ); + }); + } +} diff --git a/siro_service/lib/controller/mainController/review_driver_controller.dart b/siro_service/lib/controller/mainController/review_driver_controller.dart new file mode 100644 index 0000000..cd8d36b --- /dev/null +++ b/siro_service/lib/controller/mainController/review_driver_controller.dart @@ -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 = {}.obs; + var currentTabIndex = 0.obs; + + final Map 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 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 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 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; + 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 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( + 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 _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 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 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 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> 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 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(); + } +}