add 8-tab review page, review controller, updateDriverToActive handles new fields, drivers_cant_register navigates to review
This commit is contained in:
@@ -5,30 +5,35 @@ require_once __DIR__ . '/../connect.php'; // يفترض أن يحتوي على
|
|||||||
|
|
||||||
// --- استقبال البيانات من التطبيق ---
|
// --- استقبال البيانات من التطبيق ---
|
||||||
$driverId = filterRequest("driverId");
|
$driverId = filterRequest("driverId");
|
||||||
$phone = filterRequest("phone"); // استقبال رقم الهاتف مباشرة
|
$phone = filterRequest("phone");
|
||||||
|
$email = filterRequest("email");
|
||||||
|
$status = filterRequest("status") ?: 'actives';
|
||||||
|
|
||||||
// --- بيانات جدول السائق (driver) ---
|
// --- بيانات جدول السائق (driver) ---
|
||||||
$firstName = filterRequest("first_name");
|
$firstName = filterRequest("first_name");
|
||||||
$lastName = filterRequest("last_name");
|
$lastName = filterRequest("last_name");
|
||||||
$site = filterRequest("site"); // مكان القيد/الولادة
|
$site = filterRequest("site");
|
||||||
|
$address = filterRequest("address") ?: $site;
|
||||||
$nationalNumber = filterRequest("national_number");
|
$nationalNumber = filterRequest("national_number");
|
||||||
$licenseCategories = filterRequest("license_categories"); // فئة الرخصة
|
$licenseCategories = filterRequest("license_categories");
|
||||||
$expiryDate = filterRequest("expiry_date"); // تاريخ انتهاء رخصة السائق
|
$licenseType = filterRequest("license_type");
|
||||||
|
$expiryDate = filterRequest("expiry_date");
|
||||||
$licenseIssueDate = filterRequest("license_issue_date");
|
$licenseIssueDate = filterRequest("license_issue_date");
|
||||||
$gender = filterRequest("gender"); // الحقل الجديد
|
$gender = filterRequest("gender");
|
||||||
$birthdate = filterRequest("birthdate"); // الحقل الجديد
|
$birthdate = filterRequest("birthdate");
|
||||||
$maritalStatus=filterRequest("maritalStatus");
|
$maritalStatus = filterRequest("maritalStatus");
|
||||||
|
|
||||||
// --- بيانات جدول السيارة (CarRegistration) ---
|
// --- بيانات جدول السيارة (CarRegistration) ---
|
||||||
$owner = filterRequest("owner");
|
$owner = filterRequest("owner");
|
||||||
$color = filterRequest("color");
|
$color = filterRequest("color");
|
||||||
$colorHex = filterRequest("color_hex");
|
$colorHex = filterRequest("color_hex");
|
||||||
$model = filterRequest("model"); // الموديل
|
$model = filterRequest("model");
|
||||||
$carPlate = filterRequest("car_plate");
|
$carPlate = filterRequest("car_plate");
|
||||||
$make = filterRequest("make"); // الصانع
|
$make = filterRequest("make");
|
||||||
$fuel = filterRequest("fuel");
|
$fuel = filterRequest("fuel");
|
||||||
$year = filterRequest("year");
|
$year = filterRequest("year");
|
||||||
$carExpirationDate = filterRequest("expiration_date"); // تاريخ انتهاء رخصة السيارة
|
$carExpirationDate = filterRequest("expiration_date");
|
||||||
|
$vin = filterRequest("vin");
|
||||||
|
|
||||||
// --- بدء المعاملة لضمان سلامة البيانات ---
|
// --- بدء المعاملة لضمان سلامة البيانات ---
|
||||||
$con->beginTransaction();
|
$con->beginTransaction();
|
||||||
@@ -36,7 +41,6 @@ $con->beginTransaction();
|
|||||||
try {
|
try {
|
||||||
// --- 1. معالجة وتشفير البيانات ---
|
// --- 1. معالجة وتشفير البيانات ---
|
||||||
$nameArabic = $firstName . ' ' . $lastName;
|
$nameArabic = $firstName . ' ' . $lastName;
|
||||||
$address = $site;
|
|
||||||
|
|
||||||
// تشفير الحقول الحساسة
|
// تشفير الحقول الحساسة
|
||||||
$encryptedFirstName = $encryptionHelper->encryptData($firstName);
|
$encryptedFirstName = $encryptionHelper->encryptData($firstName);
|
||||||
@@ -50,6 +54,9 @@ try {
|
|||||||
$encryptedBirthdate = $encryptionHelper->encryptData($birthdate);
|
$encryptedBirthdate = $encryptionHelper->encryptData($birthdate);
|
||||||
$encryptedGender = $encryptionHelper->encryptData($gender);
|
$encryptedGender = $encryptionHelper->encryptData($gender);
|
||||||
|
|
||||||
|
$encryptedPhone = !empty($phone) ? $encryptionHelper->encryptData($phone) : null;
|
||||||
|
$encryptedEmail = !empty($email) ? $encryptionHelper->encryptData($email) : null;
|
||||||
|
|
||||||
// --- 2. تحديث جدول السائق ---
|
// --- 2. تحديث جدول السائق ---
|
||||||
$sqlDriver = "UPDATE `driver` SET
|
$sqlDriver = "UPDATE `driver` SET
|
||||||
`first_name` = :first_name,
|
`first_name` = :first_name,
|
||||||
@@ -58,13 +65,14 @@ try {
|
|||||||
`address` = :address,
|
`address` = :address,
|
||||||
`national_number` = :national_number,
|
`national_number` = :national_number,
|
||||||
`license_categories` = :license_categories,
|
`license_categories` = :license_categories,
|
||||||
|
`license_type` = :license_type,
|
||||||
`expiry_date` = :expiry_date,
|
`expiry_date` = :expiry_date,
|
||||||
`issue_date` = :issue_date,
|
`issue_date` = :issue_date,
|
||||||
`gender` = :gender,
|
`gender` = :gender,
|
||||||
`birthdate` = :birthdate,
|
`birthdate` = :birthdate,
|
||||||
`name_arabic` = :name_arabic,
|
`name_arabic` = :name_arabic,
|
||||||
`maritalStatus` = :maritalStatus,
|
`maritalStatus` = :maritalStatus,
|
||||||
`status` = 'actives'
|
`status` = :status
|
||||||
WHERE `id` = :driverId";
|
WHERE `id` = :driverId";
|
||||||
|
|
||||||
$stmtDriver = $con->prepare($sqlDriver);
|
$stmtDriver = $con->prepare($sqlDriver);
|
||||||
@@ -75,15 +83,27 @@ try {
|
|||||||
':address' => $encryptedAddress,
|
':address' => $encryptedAddress,
|
||||||
':national_number' => $encryptedNationalNumber,
|
':national_number' => $encryptedNationalNumber,
|
||||||
':license_categories' => $licenseCategories,
|
':license_categories' => $licenseCategories,
|
||||||
|
':license_type' => $licenseType,
|
||||||
':expiry_date' => $expiryDate,
|
':expiry_date' => $expiryDate,
|
||||||
':issue_date' => $licenseIssueDate,
|
':issue_date' => $licenseIssueDate,
|
||||||
':gender' => $encryptedGender,
|
':gender' => $encryptedGender,
|
||||||
':birthdate' => $encryptedBirthdate,
|
':birthdate' => $encryptedBirthdate,
|
||||||
':name_arabic' => $encryptedNameArabic,
|
':name_arabic' => $encryptedNameArabic,
|
||||||
|
':maritalStatus' => $maritalStatus,
|
||||||
|
':status' => $status,
|
||||||
':driverId' => $driverId,
|
':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. تحديث جدول السيارة ---
|
// --- 3. تحديث جدول السيارة ---
|
||||||
$sqlCar = "UPDATE `CarRegistration` SET
|
$sqlCar = "UPDATE `CarRegistration` SET
|
||||||
`owner` = :owner,
|
`owner` = :owner,
|
||||||
@@ -94,6 +114,7 @@ try {
|
|||||||
`make` = :make,
|
`make` = :make,
|
||||||
`fuel` = :fuel,
|
`fuel` = :fuel,
|
||||||
`year` = :year,
|
`year` = :year,
|
||||||
|
`vin` = :vin,
|
||||||
`expiration_date` = :expiration_date
|
`expiration_date` = :expiration_date
|
||||||
WHERE `driverID` = :driverId";
|
WHERE `driverID` = :driverId";
|
||||||
|
|
||||||
@@ -107,6 +128,7 @@ try {
|
|||||||
':make' => $make,
|
':make' => $make,
|
||||||
':fuel' => $fuel,
|
':fuel' => $fuel,
|
||||||
':year' => $year,
|
':year' => $year,
|
||||||
|
':vin' => $vin ?: '',
|
||||||
':expiration_date' => $carExpirationDate,
|
':expiration_date' => $carExpirationDate,
|
||||||
':driverId' => $driverId
|
':driverId' => $driverId
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ class AppLink {
|
|||||||
static String getComplaintAllData = "$serviceApp/getComplaintAllData.php";
|
static String getComplaintAllData = "$serviceApp/getComplaintAllData.php";
|
||||||
static String getComplaintAllDataForDriver =
|
static String getComplaintAllDataForDriver =
|
||||||
"$serviceApp/getComplaintAllDataForDriver.php";
|
"$serviceApp/getComplaintAllDataForDriver.php";
|
||||||
|
static String rejectDriver = "$serviceApp/rejectDriver.php";
|
||||||
static String addCriminalDocuments = "$authCaptin/addCriminalDocuments.php";
|
static String addCriminalDocuments = "$authCaptin/addCriminalDocuments.php";
|
||||||
static String ride = '$server/ride';
|
static String ride = '$server/ride';
|
||||||
static String addRegisrationCar = "$ride/RegisrationCar/add.php";
|
static String addRegisrationCar = "$ride/RegisrationCar/add.php";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import 'package:siro_service/views/widgets/my_scafold.dart';
|
|||||||
import 'package:siro_service/main.dart';
|
import 'package:siro_service/main.dart';
|
||||||
import 'package:siro_service/constant/box_name.dart';
|
import 'package:siro_service/constant/box_name.dart';
|
||||||
|
|
||||||
import 'registration_captain_page.dart';
|
import 'review_driver_page.dart';
|
||||||
|
|
||||||
class DriversCantRegister extends StatelessWidget {
|
class DriversCantRegister extends StatelessWidget {
|
||||||
DriversCantRegister({super.key});
|
DriversCantRegister({super.key});
|
||||||
@@ -151,7 +151,7 @@ class DriversCantRegister extends StatelessWidget {
|
|||||||
color: AppColor.gold,
|
color: AppColor.gold,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Get.to(
|
Get.to(
|
||||||
() => RegisterCaptain(),
|
() => const ReviewDriverPage(),
|
||||||
arguments: {
|
arguments: {
|
||||||
"phone": driver['phone_number'],
|
"phone": driver['phone_number'],
|
||||||
"driverId": driver['driverId'],
|
"driverId": driver['driverId'],
|
||||||
|
|||||||
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user