From 852c6ece5c56067a4f32dc29ad369683bdc3cca7 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sat, 25 Jul 2026 01:47:55 +0300 Subject: [PATCH] Fix admin status handling for the current ride pipeline; extend console MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ride table holds two generations of status values: the legacy CamelCase set ('Finished', 'CancelFromPassenger') and the lowercase set written by backend/ride/rides/* today ('completed', 'cancelled_by_passenger'). Admin queries only matched the legacy set, so on live data: - get_rides_by_status.php returned nothing meaningful for every filter, and the "in progress" default masked it. - dashbord.php reported total_driver_earnings as NULL, completed_rides as a fraction of the real count, and cancelled_rides as 0. - driver_avg_duration averaged in negative durations, yielding "-00h 22m". All three now match on LOWER(status) across both families. Staff/pending.php ran with no authentication at all, exposing pending admins' names and phone numbers to any caller; it now goes through connect.php with a role check. It also returned HTTP 400 for everything when the `users` table was absent — each source is queried independently and reports its own availability. Console: - Render rides from either schema generation (price/date/time and start_location coordinates, or the older address/created_at columns). - Null aggregates render as "—" rather than a measured 0.00. - Add tariff/promo, WhatsApp send and encryption modules, all super-admin gated; pricing remains read-only. Co-Authored-By: Claude Opus 5 --- backend/Admin/Staff/pending.php | 58 +++++--- backend/Admin/dashbord.php | 16 ++- backend/Admin/rides/get_rides_by_status.php | 18 ++- dashboard/siro-admin/css/main.css | 3 + dashboard/siro-admin/index.html | 20 +++ dashboard/siro-admin/js/app.js | 148 ++++++++++++++++---- 6 files changed, 204 insertions(+), 59 deletions(-) diff --git a/backend/Admin/Staff/pending.php b/backend/Admin/Staff/pending.php index 6a85407b..13b8d76e 100644 --- a/backend/Admin/Staff/pending.php +++ b/backend/Admin/Staff/pending.php @@ -3,40 +3,60 @@ * Admin/Staff/pending.php * جلب الحسابات المعلقة للإداريين والخدمة */ -require_once __DIR__ . '/../../core/bootstrap.php'; -require_once __DIR__ . '/../../functions.php'; +// connect.php يفرض JWT — بدونه كانت هذه النقطة تكشف أسماء وأرقام +// المشرفين المعلقين لأي زائر بلا أي مصادقة. +require_once __DIR__ . '/../../connect.php'; +if ($role !== 'admin' && $role !== 'super_admin') { + http_response_code(403); + echo json_encode(['error' => 'Unauthorized: Admin access required']); + exit; +} + +$allPending = []; +$sources = []; + +// كل مصدر يُجلب على حدة: غياب جدول users في بعض عمليات النشر كان يُفشل +// الطلب بالكامل ويخفي طلبات المشرفين المعلقة أيضاً. try { - $con = Database::get('main'); - - // جلب الإداريين المعلقين $stmt1 = $con->query("SELECT id, name, phone, role, created_at, 'admin' as type FROM adminUser WHERE status = 'pending'"); $admins = $stmt1->fetchAll(PDO::FETCH_ASSOC); - // فك التشفير للأسماء والأرقام للإداريين foreach ($admins as &$admin) { - $admin['name'] = $encryptionHelper->decryptData($admin['name']) ?: $admin['name']; + $admin['name'] = $encryptionHelper->decryptData($admin['name']) ?: $admin['name']; $admin['phone'] = $encryptionHelper->decryptData($admin['phone']) ?: $admin['phone']; } + unset($admin); - // جلب موظفي الخدمة المعلقين + $allPending = array_merge($allPending, $admins); + $sources['admins'] = 'ok'; +} catch (Throwable $e) { + error_log("[Staff Pending] adminUser query failed: " . $e->getMessage()); + $sources['admins'] = 'unavailable'; +} + +try { $stmt2 = $con->query("SELECT id, first_name, last_name, phone, user_type as role, created_at, 'service' as type FROM users WHERE status = 'pending' AND user_type = 'service'"); $services = $stmt2->fetchAll(PDO::FETCH_ASSOC); - // فك التشفير لموظفي الخدمة foreach ($services as &$service) { - $service['name'] = trim(($encryptionHelper->decryptData($service['first_name']) ?: $service['first_name']) . ' ' . ($encryptionHelper->decryptData($service['last_name']) ?: $service['last_name'])); + $service['name'] = trim( + ($encryptionHelper->decryptData($service['first_name']) ?: $service['first_name']) . ' ' . + ($encryptionHelper->decryptData($service['last_name']) ?: $service['last_name']) + ); $service['phone'] = $encryptionHelper->decryptData($service['phone']) ?: $service['phone']; } + unset($service); - $allPending = array_merge($admins, $services); - - printSuccess([ - "data" => $allPending - ]); - -} catch (Exception $e) { - error_log("[Staff Pending Error] " . $e->getMessage()); - jsonError("An internal error occurred. Please try again later."); + $allPending = array_merge($allPending, $services); + $sources['service_staff'] = 'ok'; +} catch (Throwable $e) { + error_log("[Staff Pending] users query failed: " . $e->getMessage()); + $sources['service_staff'] = 'unavailable'; } + +printSuccess([ + "data" => $allPending, + "sources" => $sources, +]); exit(); diff --git a/backend/Admin/dashbord.php b/backend/Admin/dashbord.php index af8764a5..92ec9ec2 100644 --- a/backend/Admin/dashbord.php +++ b/backend/Admin/dashbord.php @@ -29,14 +29,18 @@ SELECT -- المحافظ والتحويلات -- إحصائيات وقت ومسافة الرحلات - (SELECT TIME_FORMAT(SEC_TO_TIME(AVG(TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish))), '%Hh %im') FROM ride WHERE rideTimeStart IS NOT NULL AND rideTimeFinish IS NOT NULL) AS driver_avg_duration, + -- تُستثنى الفروق السالبة (رحلات سجّلت وقت نهاية أقدم من البداية) لأنها + -- كانت تُنتج متوسطاً سالباً مثل "-00h 22m". + (SELECT TIME_FORMAT(SEC_TO_TIME(AVG(TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish))), '%Hh %im') FROM ride WHERE rideTimeStart IS NOT NULL AND rideTimeFinish IS NOT NULL AND TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish) > 0) AS driver_avg_duration, (SELECT MAX(SEC_TO_TIME(TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish))) FROM ride WHERE rideTimeStart IS NOT NULL AND rideTimeFinish IS NOT NULL) AS longest_duration, (SELECT ROUND(SUM(distance),2) FROM ride) AS total_distance, (SELECT ROUND(AVG(distance),2) FROM ride) AS average_distance, (SELECT ROUND(MAX(distance),2) FROM ride) AS longest_distance, -- أرباح السائق والشركة - (SELECT SUM(price_for_driver) FROM ride WHERE status = 'Finished') AS total_driver_earnings, + -- ملاحظة: خط الرحلات الحالي يكتب 'completed' بينما القديم يكتب 'Finished'، + -- والاكتفاء بالقديم كان يُرجع NULL للأرباح وصفراً للرحلات المكتملة/الملغاة. + (SELECT SUM(price_for_driver) FROM ride WHERE LOWER(status) IN ('finished','completed')) AS total_driver_earnings, (SELECT ROUND(AVG(price_for_passenger),2) FROM ride) AS avg_passenger_price, -- توزيع الرحلات حسب الوقت @@ -49,10 +53,10 @@ SELECT (SELECT COUNT(*) FROM ride WHERE carType = 'Speed') AS speed, (SELECT COUNT(*) FROM ride WHERE carType = 'Lady') AS lady, - -- حالة الرحلات - (SELECT COUNT(*) FROM ride WHERE status = 'wait') AS ongoing_rides, - (SELECT COUNT(*) FROM ride WHERE status = 'Finished') AS completed_rides, - (SELECT COUNT(*) FROM ride WHERE status = 'cancel') AS cancelled_rides, + -- حالة الرحلات (تغطي عائلتي الحالات: القديمة CamelCase والجديدة lowercase) + (SELECT COUNT(*) FROM ride WHERE LOWER(status) IN ('wait','waiting','new','nothing','pending','searching')) AS ongoing_rides, + (SELECT COUNT(*) FROM ride WHERE LOWER(status) IN ('finished','completed')) AS completed_rides, + (SELECT COUNT(*) FROM ride WHERE LOWER(status) LIKE 'cancel%' OR LOWER(status) IN ('timeout','refused')) AS cancelled_rides, -- عدد السائقين الفريدين (SELECT COUNT(*) FROM (SELECT driver_id FROM ride GROUP BY driver_id) AS sub) AS num_Driver, diff --git a/backend/Admin/rides/get_rides_by_status.php b/backend/Admin/rides/get_rides_by_status.php index b7d8fe44..94700900 100644 --- a/backend/Admin/rides/get_rides_by_status.php +++ b/backend/Admin/rides/get_rides_by_status.php @@ -18,29 +18,37 @@ try { $whereClause = ""; // لا يوجد شرط، اجلب الكل break; + // ملاحظة: قاعدة البيانات تحتوي عائلتين من الحالات — القديمة بصيغة + // CamelCase ('Finished','Begin','CancelFromPassenger') والجديدة التي + // يكتبها خط الرحلات الحالي بأحرف صغيرة ('completed','accepted', + // 'cancelled_by_passenger'). المقارنة تتم بـ LOWER() لتغطية الاثنتين. case 'Pending': // الرحلات المعلقة/الجديدة: بانتظار سائق - $whereClause = "WHERE r.status IN ('New','nothing','waiting','wait')"; + $whereClause = "WHERE LOWER(r.status) IN ('new','nothing','waiting','wait','pending','searching')"; break; case 'Begin': // الرحلات الجارية: من قبول السائق إلى بدء التشغيل - $whereClause = "WHERE r.status IN ('Apply','Applied','Arrived','arrived','Begin')"; + $whereClause = "WHERE LOWER(r.status) IN ('apply','applied','arrived','begin','accepted','started','claimed')"; break; case 'Completed': // الرحلات المكتملة - $whereClause = "WHERE r.status = 'Finished'"; + $whereClause = "WHERE LOWER(r.status) IN ('finished','completed')"; break; case 'Canceled': // جميع أنواع الإلغاء - $whereClause = "WHERE r.status IN ('Cancel','CancelFromDriver','CancelFromDriverAfterApply','CancelFromPassenger','TimeOut')"; + $whereClause = "WHERE LOWER(r.status) IN ( + 'cancel','cancelfromdriver','cancelfromdriverafterapply','cancelfrompassenger', + 'timeout','refused','cancelled_by_passenger','cancelled_by_driver', + 'cancelled_no_driver_found' + )"; break; default: // في حال تم إرسال حالة محددة غير المذكورين - $whereClause = "WHERE r.status = ?"; + $whereClause = "WHERE LOWER(r.status) = LOWER(?)"; $params[] = $statusFilter; break; } diff --git a/dashboard/siro-admin/css/main.css b/dashboard/siro-admin/css/main.css index 47910b18..187ab239 100644 --- a/dashboard/siro-admin/css/main.css +++ b/dashboard/siro-admin/css/main.css @@ -1212,3 +1212,6 @@ h1, h2, h3, h4, h5, h6 { resize: vertical; min-height: 84px; } + +.coord-link { color: var(--text-muted); text-decoration: none; } +.coord-link:hover { color: var(--primary); } diff --git a/dashboard/siro-admin/index.html b/dashboard/siro-admin/index.html index e4d85d4d..90878fa8 100644 --- a/dashboard/siro-admin/index.html +++ b/dashboard/siro-admin/index.html @@ -499,6 +499,26 @@
+ +