Files
Siro/siro_admin_ride_monitoring_analysis.md
2026-06-14 22:10:08 +03:00

26 KiB
Raw Permalink Blame History

تحليل نظام متابعة الرحلات في لوحة تحكم المشرف (Siro Admin)

دراسة شاملة للبحث، التتبع، التعديل، والتعامل مع الرحلات المعلقة


📋 فهرس المحتويات

  1. النظام الحالي لمتابعة الرحلات
  2. تحليل شاشات وأدوات متابعة الرحلات
  3. حالات الرحلة الفعلية من قاعدة البيانات
  4. مشكلة رقم الهاتف والدولة
  5. الـ Endpoints المستخدمة واستجاباتها
  6. ما هو موجود وما هو مفقود
  7. التوصيات والتحسينات المطلوبة مع الإصلاحات

النظام الحالي

الملفات المشاركة في متابعة الرحلات

الملف الدور
siro_admin/lib/views/admin/rides/ride_lookup_page.dart لوحة إدارة الرحلات (Dashboard) - الأحدث
siro_admin/lib/views/admin/rides/rides.dart إحصائيات الرحلات الشهرية (قديم)
siro_admin/lib/views/admin/drivers/monitor_ride.dart شاشة مراقبة رحلة محددة بالخريطة
siro_admin/lib/controller/rides/ride_lookup_controller.dart تحكم البحث عن رحلة وتحديث حالتها
siro_admin/lib/controller/admin/ride_admin_controller.dart تحكم إحصائيات الرحلات

ملفات الباك إند

الملف الوظيفة
backend/Admin/rides/get_rides_by_status.php جلب الرحلات حسب الحالة (Begin, New, Finished, Canceled)
backend/Admin/rides/admin_get_rides_by_phone.php البحث عن رحلة برقم هاتف الراكب (مشفر)
backend/Admin/rides/admin_update_ride_status.php تحديث حالة الرحلة (Pending→Completed...)
backend/Admin/rides/monitorRide.php مراقبة رحلة نشطة برقم الهاتف (يبحث في السائق والراكب)
backend/Admin/rides/get_driver_live_pos.php جلب الموقع اللحظي للسائق من car_locations

حالات الرحلة الفعلية في قاعدة البيانات

📌 هيكل الجدول

CREATE TABLE `ride` (
  `id` int NOT NULL AUTO_INCREMENT,
  `status` varchar(200) NOT NULL DEFAULT 'nothing',  -- ❌ NOT ENUM! نص عادي
  ...
) ENGINE=InnoDB;

CREATE TABLE `waitingRides` (
  `id` varchar(100) NOT NULL,
  `status` varchar(200) NOT NULL DEFAULT 'nothing',  -- ❌ NOT ENUM! نص عادي
  ...
) ENGINE=InnoDB;

🗺️ جميع حالات الرحلة المستخدمة فعلياً في الكود

الحالة في DB المعنى أين تستخدم
nothing القيمة الافتراضية عند إنشاء الرحلة في add.php و addWaitingRide.php
New رحلة جديدة (لم يتم تعيين سائق بعد) في Admin/rides/get_rides_by_status.php للـ Admin Dashboard
waiting الرحلة قيد البحث عن سائق في retry_search_drivers.php عند إعادة البحث
wait في انتظار (مستخدم في بعض السيرفرات) في getRideStatusFromStartApp.php و cancelRideByPassenger.php
Apply سائق قبل الرحلة (قيد التوجه) في acceptRide.php, overLay/get.php, driver_statistic.php
Applied تم قبول التطبيق في get_rides_by_status.php (مع Begin)
arrived السائق وصل إلى موقع الراكب (lowercase) في arrive_ride.php
Arrived السائق وصل (uppercase) في get_rides_by_status.php (مستخدم أيضاً)
Begin الرحلة قيد التشغيل 🚗 في start_ride.php, monitorRide.php, finish_ride_updates.php
started بدأت الرحلة (مرادف لـ Begin) في start_ride.php (رد API)
Finished الرحلة اكتملت في معظم التقارير والإحصائيات
Cancel ملغاة (عام) في cancelRideFromDriver.php, cancel_ride_by_passenger.php
CancelFromDriver ألغى السائق في get_rides_by_status.php
CancelFromDriverAfterApply ألغى السائق بعد القبول في get_rides_by_status.php
CancelFromPassenger ألغى الراكب في get_rides_by_status.php
TimeOut انتهت المهلة الزمنية في get_rides_by_status.php

🔄 تسلسل حالة الرحلة الطبيعي:

nothing / New
    │
    ├──→ waiting ──→ Apply ──→ Applied ──→ Arrived ──→ Begin ──→ Finished
    │       │          │           │           │           │
    │       │          │           │           │           └── ✅ مكتملة
    │       │          │           │           │
    │       │          │           │           └── CancelFromDriverAfterApply
    │       │          │           │
    │       │          │           └── CancelFromPassenger
    │       │          │
    │       │          └── TimeOut (انتهاء المهلة)
    │       │
    │       └── CancelFromDriver
    │
    └── Cancel (إلغاء مباشر)

المشكلة في Admin Dashboard الحالي:

في get_rides_by_status.php، التصنيف خاطئ:

case 'Begin':
    $whereClause = "WHERE r.status IN ('Begin','Apply','Applied')";  // ✅ Begin + Apply + Applied
    break;
case 'New':
    $whereClause = "WHERE r.status = 'New'";  // ✅ جديد
    break;
case 'Completed':
    $whereClause = "WHERE r.status = 'Finished'";  // ✅ مكتملة
    break;
case 'Canceled':
    $whereClause = "WHERE r.status IN ('Cancel', 'CancelFromDriverAfterApply', 'TimeOut')";  // ❌ ناقص!
    break;

🔴 ملاحظات على التصنيف:

  1. CancelFromDriver و CancelFromPassenger و Cancel و arrived (lowercase) غير مشمولة في أي تصنيف
  2. Apply و Applied مصنفين مع Begin (جارية) — هذا صحيح جزئياً لأن السائق في طريقه
  3. TimeOut مصنف مع Canceled — صحيح
  4. الرحلة التي حالتها wait أو waiting غير معروضة إطلاقاً في أي تبويب

تحليل الشاشات

1 شاشة إدارة الرحلات (RidesDashboardScreen)

الملف: ride_lookup_page.dart

الوظائف الحالية:

  • عرض الرحلات مصنفة حسب الحالة (جارية، جديدة، مكتملة، ملغاة)
  • إحصائيات فورية (عدد الرحلات لكل حالة + الإيرادات + المسافة)
  • شريط بحث ديناميكي يصفّي النتائج مباشرة
  • الضغط على رحلة → يفتح خريطة التتبع (RideMapMonitorScreen)
  • إظهار معلومات السائق والراكب مع أيقونة اتصال (للمشرف العام)

منطق البحث الحالي:

void filterRides(String query) {
  displayedRides.value = allRidesList.where((ride) {
    return ride.driverPhone.contains(query) ||
        ride.passengerPhone.contains(query) ||
        ride.driverName.toLowerCase().contains(query.toLowerCase()) ||
        ride.passengerName.toLowerCase().contains(query.toLowerCase()) ||
        ride.rideId.contains(query);
  }).toList();
}

🔴 مشكلة: البحث يتم محلياً (في الذاكرة) بعد جلب كل الرحلات من API. إذا كانت الرحلات كثيرة (100+ رحلة)، البحث لا يشمل الرحلات الغير محملة.

🔴 مشكلة: الـ API يجلب فقط آخر 100 رحلة (LIMIT 100) ولا يوجد Pagination.

الـ API المستخدم:

POST $server/Admin/rides/get_rides_by_status.php
payload: { "status": "Begin" }
← { "status": "success", "message": [ {...}, {...} ] }

خريطة التتبع (RideMapMonitorScreen):

  • يعرض مسار الرحلة (نقطة بداية → نقطة نهاية)
  • يتابع موقع السائق اللحظي (Polling كل 10 ثوانٍ)
  • زر اتصال بالسائق أو الراكب
  • 🔴 لا يوجد تحديث لحالة الرحلة من هذه الشاشة

2 شاشة مراقبة رحلة (RideMonitorScreen)

الملف: monitor_ride.dart

الوظائف الحالية:

  • إدخال رقم هاتف للبحث عن رحلة نشطة
  • عرض خريطة كاملة مع مسار الرحلة وموقع السائق
  • تحديث تلقائي (Polling كل 10 ثوانٍ)
  • إظهار اسم السائق، حالة الرحلة، السرعة، آخر تحديث

الـ API المستخدم:

POST $server/Admin/rides/monitorRide.php
payload: { "phone": "963$phone" }
← { "status": "success", "message": {
    "ride_details": { ... },
    "driver_details": { ... },
    "driver_location": { "latitude": ..., "longitude": ..., "speed": ..., "heading": ... }
}}

منطق البحث الحالي:

final response = await CRUD().post(
  link: apiUrl,
  payload: {"phone": "963$phone"}, // ⚠️ Hardcoded Syria prefix!
);

🔴 مشكلة حرجة: 963$phone ثابت (سوريا فقط). الأردن يحتاج 962 ومصر تحتاج 20.

منطق الباك إند (monitorRide.php):

$encPhone = $encryptionHelper->encryptData($phone);
// يبحث أولاً في جدول driver
$driverQuery = $con->prepare("SELECT id FROM driver WHERE phone = :phone LIMIT 1");
// ثم في جدول passengers
$customerQuery = $con->prepare("SELECT id FROM passengers WHERE phone = :phone LIMIT 1");
// ثم يجلب آخر رحلة status='Begin'

🔴 ملاحظة: monitorRide.php يبحث فقط عن الرحلات التي حالتها Begin. إذا كانت الرحلة بحالة Apply أو Arrived لن يجدها.


3 شاشة البحث عن رحلة وتحديث حالتها (RideLookupController)

الملف: ride_lookup_controller.dart

الوظائف الحالية:

  • البحث برقم هاتف الراكب (مشفر) عبر admin_get_rides_by_phone.php
  • عرض بيانات الراكب + آخر رحلة له
  • تحديث حالة الرحلة عبر admin_update_ride_status.php
  • قائمة منسدلة للحالات المسموحة: Pending, Accepted, EnRoute, Arrived, Started, Completed, Canceled

🔴 هذه القائمة لا تطابق حالات الرحلة الفعلية في النظام!

القيمة في التطبيق هل تستخدم في DB؟ الحالة الصحيحة
Pending لا waiting أو New أو nothing
Accepted لا Apply أو Applied
EnRoute لا Apply أو Applied (السائق في الطريق)
Arrived نعم arrived أو Arrived
Started لا Begin
Completed نعم Finished
Canceled نعم Cancel أو CancelFrom*

الـ API المستخدم للبحث:

POST $server/Admin/rides/admin_get_rides_by_phone.php
payload: { "phone": "...", "status": "..." }
← { "status": "success", "message": {
    "passenger": { "id", "first_name", "last_name", "phone" },
    "ride": { "id", "status", "start_location", ... }
}}

🔴 مشكلة: هذا الـ API يبحث فقط في جدول passengers. إذا كان الرقم تابعاً لسائق، لن يعثر على شيء.


مشكلة رقم الهاتف والدولة

كيف يتم تخزين أرقام الهواتف؟

في قاعدة البيانات، جميع الأرقام مشفرة باستخدام encryptData():

$encryptedPhone = $encryptionHelper->encryptData($raw);

الأرقام تخزن مع كود الدولة:

  • الأردن: 9627XXXXXXXX
  • مصر: 2010XXXXXXXX
  • سوريا: 9639XXXXXXXX

كيف يتم البحث في تطبيق المشرف حالياً؟

1 في monitor_ride.dart:

payload: {"phone": "963$phone"}
// المستخدم يدخل: 0992952235
// التطبيق يرسل: 9630992952235 ← غلط! الصحيح: 963992952235

2 في admin_get_rides_by_phone.php:

$phone = filterRequest('phone');
$enc_raw = $encryptionHelper->encryptData($raw);
// يبحث فقط في passengers

جدول مقارنة تنسيق الأرقام:

الدولة كود الدولة تنسيق الإدخال التخزين في DB ما يرسله monitor_ride.dart هل يتطابق؟
🇯🇴 الأردن 962 079XXXXXXX 96279XXXXXXX 963079XXXXXXX لا
🇪🇬 مصر 20 010XXXXXXXX 2010XXXXXXXX 963010XXXXXXXX لا
🇸🇾 سوريا 963 0992952235 963992952235 9630992952235 لا (0 زائد)

تحليل المشكلة:

  1. المشرف يدخل: 0992952235 (سوريا) أو 079XXXXXXX (أردن)
  2. التطبيق يضيف: 963 ثابت (سوريا فقط)
  3. الباك إند يبحث: عن الرقم 9630992952235 أو 963079XXXXXXX
  4. الرقم المخزن: 963992952235 (بدون 0 بعد كود الدولة) أو 96279XXXXXXX
  5. النتيجة: فشل في البحث عن أي رقم غير سوري، وحتى السوري يفشل إذا كان المدخل بـ 0

الـ Endpoints

الحالية في backend/Admin/rides/:

الإند بوينت الطريقة المدخلات الاستجابة المشاكل
get_rides_by_status.php POST status (Begin/New/Completed/Canceled) قائمة رحلات (100) مع تفاصيل السائق والراكب لا يوجد Pagination + بعض الحالات غير مشمولة (wait, CancelFromDriver, arrived)
admin_get_rides_by_phone.php POST phone بيانات الراكب + آخر رحلة يبحث فقط في passengers
admin_update_ride_status.php POST id, status, reason (optional) الرحلة المحدّثة Whitelist لا يطابق حالات DB الفعلية
monitorRide.php POST phone تفاصيل الرحلة + السائق + الموقع يبحث فقط عن Begin + كود الدولة ثابت 963
get_driver_live_pos.php POST driver_id آخر موقع للسائق يعمل بشكل صحيح

التوصيات والإصلاحات

🔴 الإصلاح 1: تصحيح حالات الرحلة في Admin Dashboard

المشكلة: get_rides_by_status.php يستخدم حالات غير دقيقة.

الحل: تعديل mapping الحالات ليطابق الواقع:

// backend/Admin/rides/get_rides_by_status.php - تصحيح
switch ($statusFilter) {
    case 'Begin':
        // الرحلات الجارية: من Apply إلى Begin
        $whereClause = "WHERE r.status IN ('Apply','Applied','Arrived','arrived','Begin')";
        break;

    case 'New':
        // الرحلات الجديدة: بانتظار سائق
        $whereClause = "WHERE r.status IN ('New','nothing','waiting','wait')";
        break;

    case 'Completed':
        $whereClause = "WHERE r.status = 'Finished'";
        break;

    case 'Canceled':
        // جميع أنواع الإلغاء
        $whereClause = "WHERE r.status IN ('Cancel','CancelFromDriver','CancelFromDriverAfterApply','CancelFromPassenger','TimeOut')";
        break;

    default:
        $whereClause = "WHERE r.status = ?";
        $params[] = $statusFilter;
        break;
}

🔴 الإصلاح 2: تصحيح قائمة الحالات المسموحة في RideLookupController

المشكلة: statusOptions في ride_lookup_controller.dart لا تطابق حالات DB.

الحل: تعديل القائمة:

// في ride_lookup_controller.dart
final List<String> statusOptions = const [
  'New',           // جديد (بدلاً من Pending)
  'waiting',       // في انتظار سائق
  'Apply',         // سائق قبل (بدلاً من Accepted)
  'Arrived',       // وصل السائق
  'Begin',         // الرحلة بدأت (بدلاً من Started)
  'Finished',      // مكتملة (بدلاً من Completed)
  'Cancel',        // إلغاء (بدلاً من Canceled)
];

وتحديث admin_update_ride_status.php:

// backend/Admin/rides/admin_update_ride_status.php
$allowed = [
    'New', 'waiting', 'wait', 'Apply', 'Applied',
    'Arrived', 'arrived', 'Begin', 'Finished',
    'Cancel', 'CancelFromDriver', 'CancelFromPassenger', 'TimeOut'
];

🔴 الإصلاح 3: معالجة أرقام الهواتف حسب الدولة

المشكلة: monitor_ride.dart يستخدم 963 ثابت.

الحل: إضافة دالة لتوحيد تنسيق الرقم:

// في monitor_ride.dart
String normalizePhone(String input) {
  final clean = input.replaceAll(RegExp(r'\D+'), '');
  
  // Syria: 099XXXXXXX or 9639XXXXXXX
  if (clean.length == 10 && clean.startsWith('09'))
    return '963${clean.substring(1)}';
  if (clean.length == 12 && clean.startsWith('963'))
    return clean;
  if (clean.length == 9 && clean.startsWith('9'))
    return '963$clean';
  
  // Jordan: 079XXXXXXX or 9627XXXXXXX
  if (clean.length == 10 && clean.startsWith('07'))
    return '962${clean.substring(1)}';
  if (clean.length == 12 && clean.startsWith('962'))
    return clean;
  if (clean.length == 9 && clean.startsWith('7'))
    return '962$clean';
  
  // Egypt: 010XXXXXXXX or 2010XXXXXXXX
  if (clean.length == 11 && clean.startsWith('01'))
    return '20${clean.substring(1)}';
  if (clean.length == 13 && clean.startsWith('20'))
    return clean;
  
  return clean;
}

// عند البحث:
final normalizedPhone = normalizePhone(phoneInputController.text.trim());
final response = await CRUD().post(
  link: apiUrl,
  payload: {"phone": normalizedPhone},
);

وفي الباك إند (monitorRide.php و admin_get_rides_by_phone.php):

function normalizePhone($phone) {
    $clean = preg_replace('/\D+/', '', $phone);
    // Syria: remove leading 0 after country code
    if (strlen($clean) === 10 && strpos($clean, '09') === 0) return '963' . substr($clean, 1);
    if (strlen($clean) === 12 && strpos($clean, '963') === 0) return $clean;
    if (strlen($clean) === 9 && strpos($clean, '9') === 0) return '963' . $clean;
    // Jordan
    if (strlen($clean) === 10 && strpos($clean, '07') === 0) return '962' . substr($clean, 1);
    if (strlen($clean) === 12 && strpos($clean, '962') === 0) return $clean;
    if (strlen($clean) === 9 && strpos($clean, '7') === 0) return '962' . $clean;
    // Egypt
    if (strlen($clean) === 11 && strpos($clean, '01') === 0) return '20' . substr($clean, 1);
    if (strlen($clean) === 13 && strpos($clean, '20') === 0) return $clean;
    return $clean;
}

🔴 الإصلاح 4: البحث في السائق والراكب معاً (مع جميع حالات الرحلة)

المشكلة: admin_get_rides_by_phone.php يبحث فقط في passengers وحالة Begin فقط.

الحل: تعديل admin_get_rides_by_phone.php و monitorRide.php ليشمل driver + passenger + جميع الحالات النشطة:

// backend/Admin/rides/admin_get_rides_by_phone.php - معدل
$phone = filterRequest('phone');
if (!$phone) { jsonError("Phone is required"); exit; }

$normalizedPhone = normalizePhone($phone);
$encPhone = $encryptionHelper->encryptData($normalizedPhone);

// 1) ابحث في driver
$driverStmt = $con->prepare("SELECT id FROM driver WHERE phone = :phone LIMIT 1");
$driverStmt->execute([':phone' => $encPhone]);
$driver = $driverStmt->fetch();

// 2) ابحث في passengers
$passengerStmt = $con->prepare("SELECT id FROM passengers WHERE phone = :phone LIMIT 1");
$passengerStmt->execute([':phone' => $encPhone]);
$passenger = $passengerStmt->fetch();

if (!$driver && !$passenger) {
    jsonError('Phone number not found in system');
    exit;
}

// 3) اجلب الرحلة حسب النوع
$userId = $driver ? $driver['id'] : $passenger['id'];
$userField = $driver ? 'r.driver_id' : 'r.passenger_id';
$filterStatus = filterRequest('status') ?: 'all';

$whereExtra = '';
if ($filterStatus !== 'all') {
    $whereExtra = "AND r.status = :filter_status";
}

$rideStmt = $con->prepare("
    SELECT r.*, d.first_name as d_fname, d.last_name as d_lname, d.phone as d_phone,
           p.first_name as p_fname, p.last_name as p_lname, p.phone as p_phone
    FROM ride r
    LEFT JOIN driver d ON r.driver_id = d.id
    LEFT JOIN passengers p ON r.passenger_id = p.id
    WHERE $userField = :uid $whereExtra
    ORDER BY r.id DESC
    LIMIT 20
");

$params = [':uid' => $userId];
if ($filterStatus !== 'all') $params[':filter_status'] = $filterStatus;
$rideStmt->execute($params);
$rides = $rideStmt->fetchAll(PDO::FETCH_ASSOC);

🟡 الإصلاح 5: إضافة شاشة للرحلات المعلقة (Pending/Waiting)

المشكلة: لا توجد واجهة لعرض الرحلات العالقة (waiting, New, nothing).

الحل المقترح:

  • إضافة تبويب خامس باسم "معلقة" في RidesDashboardScreen
  • يعرض الرحلات ذات الحالة: New, waiting, wait, nothing
  • إضافة زر "إلغاء الرحلة" مع إدخال سبب
  • تحديث admin_update_ride_status.php ليدعم الإلغاء مع إشعار
// إضافة في ride_lookup_page.dart (RidesListController)
void changeTab(String status) {
  currentStatus = status;
  searchController.clear();
  fetchRides();
}

// إضافة في RidesDashboardScreen
Tab(text: 'معلقة', icon: Icon(Icons.hourglass_empty_rounded)),
// عند اختيار هذا التبويب، يرسل status='Pending'
// الـ API يتعامل معها كـ: IN ('New','waiting','wait','nothing')

🟢 الإصلاح 6: إضافة البحث برقم الرحلة (Ride ID) + Pagination

لماذا Ride ID هو الأفضل؟

  • المعرف الوحيد الفريد (Primary Key)
  • لا توجد مشاكل تشفير (الأرقام المعرفية غير مشفرة)
  • لا توجد مشاكل تنسيق دولة
  • دقيق 100%

مثال الإند بوينت الجديد:

// backend/Admin/rides/admin_get_ride_by_id.php
$rideId = filterRequest('id');
$stmt = $con->prepare("
    SELECT r.*, 
           d.first_name as d_fname, d.last_name as d_lname, d.phone as d_phone,
           p.first_name as p_fname, p.last_name as p_lname, p.phone as p_phone
    FROM ride r
    LEFT JOIN driver d ON r.driver_id = d.id
    LEFT JOIN passengers p ON r.passenger_id = p.id
    WHERE r.id = :id
    LIMIT 1
");
$stmt->execute([':id' => $rideId]);
jsonSuccess($stmt->fetch());

Pagination في get_rides_by_status.php:

$page = (int)(filterRequest('page') ?? 1);
$limit = 50;
$offset = ($page - 1) * $limit;
$sql .= " ORDER BY r.id DESC LIMIT $limit OFFSET $offset";

الملخص النهائي

جدول حالات الرحلة - ما هو صحيح وما هو خاطئ:

ما في النظام (DB) ما في التطبيق (Admin) التصحيح
nothing / New New صحيح (جديد)
waiting / wait غير موجود 🟡 يجب إضافته (معلق)
Apply / Applied Begin (مصنف مع الجارية) صحيح (جاري التوصيل)
arrived / Arrived غير موجود 🟡 يجب إضافته مع الجارية
Begin Begin صحيح (قيد التشغيل)
Finished Completed صحيح (مكتملة)
Cancel / CancelFrom* / TimeOut Canceled (ناقص) 🔴 يجب إضافة CancelFromDriver و CancelFromPassenger

قائمة الإصلاحات:

# الإصلاح الملف الأولوية
1 تصحيح حالات الرحلة في get_rides_by_status.php backend/Admin/rides/get_rides_by_status.php 🔴 عاجل
2 تصحيح statusOptions في ride_lookup_controller.dart siro_admin/lib/controller/rides/ride_lookup_controller.dart 🔴 عاجل
3 تصحيح whitelist في admin_update_ride_status.php backend/Admin/rides/admin_update_ride_status.php 🔴 عاجل
4 إضافة normalizePhone() في monitor_ride.dart siro_admin/lib/views/admin/drivers/monitor_ride.dart 🔴 عاجل
5 إضافة normalizePhone() في admin_get_rides_by_phone.php backend/Admin/rides/admin_get_rides_by_phone.php 🔴 عاجل
6 إضافة normalizePhone() في monitorRide.php backend/Admin/rides/monitorRide.php 🔴 عاجل
7 إضافة البحث في driver + passenger في admin_get_rides_by_phone.php backend/Admin/rides/admin_get_rides_by_phone.php 🟡 ضروري
8 إضافة شاشة للرحلات المعلقة (Pending/Waiting) siro_admin/lib/views/admin/rides/ride_lookup_page.dart 🟡 ضروري
9 إضافة البحث برقم الرحلة (Ride ID) Endpoint + Flutter جديد 🟡 ضروري
10 إضافة Pagination backend/Admin/rides/get_rides_by_status.php 🟢 مفيد

أفضل طرق البحث (مرتبة حسب الأولوية):

  1. رقم الرحلة (Ride ID) — الأدق، بدون مشاكل تشفير أو دولة
  2. رقم هاتف الراكب/السائق — مع معالجة الدولة (normalizePhone)
  3. بحث بنطاق تاريخي — لاستعراض الرحلات حسب الفترة الزمنية