Update: 2026-06-14 22:10:07

This commit is contained in:
Hamza-Ayed
2026-06-14 22:10:08 +03:00
parent 8e3b9eca4d
commit f021ba5a35
21 changed files with 3669 additions and 636 deletions

View File

@@ -0,0 +1,610 @@
<div dir="rtl" lang="ar">
# تحليل نظام متابعة الرحلات في لوحة تحكم المشرف (Siro Admin)
## دراسة شاملة للبحث، التتبع، التعديل، والتعامل مع الرحلات المعلقة
---
## 📋 فهرس المحتويات
1. [النظام الحالي لمتابعة الرحلات](#النظام-الحالي)
2. [تحليل شاشات وأدوات متابعة الرحلات](#تحليل-الشاشات)
3. [حالات الرحلة الفعلية من قاعدة البيانات](#حالات-الرحلة)
4. [مشكلة رقم الهاتف والدولة](#مشكلة-رقم-الهاتف-والدولة)
5. [الـ Endpoints المستخدمة واستجاباتها](#الـ-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` |
---
## حالات الرحلة الفعلية في قاعدة البيانات
### 📌 هيكل الجدول
```sql
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`، التصنيف خاطئ:
```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)
- ✅ إظهار معلومات السائق والراكب مع أيقونة اتصال (للمشرف العام)
#### منطق البحث الحالي:
```dart
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": ... }
}}
```
#### منطق البحث الحالي:
```dart
final response = await CRUD().post(
link: apiUrl,
payload: {"phone": "963$phone"}, // ⚠️ Hardcoded Syria prefix!
);
```
🔴 **مشكلة حرجة**: `963$phone` ثابت (سوريا فقط). الأردن يحتاج `962` ومصر تحتاج `20`.
#### منطق الباك إند (`monitorRide.php`):
```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()`:
```php
$encryptedPhone = $encryptionHelper->encryptData($raw);
```
**الأرقام تخزن مع كود الدولة:**
- الأردن: `9627XXXXXXXX`
- مصر: `2010XXXXXXXX`
- سوريا: `9639XXXXXXXX`
### كيف يتم البحث في تطبيق المشرف حالياً؟
#### 1⃣ في `monitor_ride.dart`:
```dart
payload: {"phone": "963$phone"}
// المستخدم يدخل: 0992952235
// التطبيق يرسل: 9630992952235 ← غلط! الصحيح: 963992952235
```
#### 2⃣ في `admin_get_rides_by_phone.php`:
```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 الحالات ليطابق الواقع:
```php
// 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.
**الحل**: تعديل القائمة:
```dart
// في 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`**:
```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` ثابت.
**الحل**: إضافة دالة لتوحيد تنسيق الرقم:
```dart
// في 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`)**:
```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 + جميع الحالات النشطة:
```php
// 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` ليدعم الإلغاء مع إشعار
```dart
// إضافة في 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%
**مثال الإند بوينت الجديد**:
```php
// 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`**:
```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. **بحث بنطاق تاريخي** — لاستعراض الرحلات حسب الفترة الزمنية
</div>