Update: 2026-07-25 18:43:44
This commit is contained in:
@@ -43,5 +43,10 @@ foreach ($dailyRides as &$row) {
|
||||
$row['current_month_rides_count'] = $monthRides['current_month_rides_count'];
|
||||
}
|
||||
|
||||
jsonSuccess($dailyRides ?: []);
|
||||
// Return result
|
||||
if ($dailyRides) {
|
||||
jsonSuccess($dailyRides);
|
||||
} else {
|
||||
jsonError("No records found");
|
||||
}
|
||||
?>
|
||||
@@ -3,40 +3,46 @@
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$sql = "SELECT
|
||||
COUNT(r.id) AS driver_count,
|
||||
d.id,
|
||||
d.phone,
|
||||
d.first_name,
|
||||
d.last_name,
|
||||
d.name_arabic
|
||||
FROM driver d
|
||||
LEFT JOIN ride r ON d.id = r.driver_id AND LOWER(r.status) IN ('finished','completed')
|
||||
GROUP BY d.id, d.phone, d.first_name, d.last_name, d.name_arabic
|
||||
ORDER BY driver_count DESC
|
||||
LIMIT 20";
|
||||
COUNT(`car_locations`.driver_id) AS driver_count,
|
||||
driver.id,
|
||||
driver.phone,
|
||||
driver.name_arabic,
|
||||
MAX(dt.token) AS token
|
||||
FROM
|
||||
`car_locations`
|
||||
LEFT JOIN driver ON driver.id = car_locations.driver_id
|
||||
LEFT JOIN driverToken dt ON dt.captain_id = driver.id
|
||||
WHERE
|
||||
`car_locations`.created_at > TIMESTAMP(DATE_SUB(NOW(), INTERVAL 7 DAY))
|
||||
GROUP BY
|
||||
driver.id
|
||||
ORDER BY
|
||||
driver_count DESC
|
||||
LIMIT 19;
|
||||
";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// فك التشفير للحقول الحساسة
|
||||
foreach ($rows as &$row) {
|
||||
if (!empty($row['phone'])) {
|
||||
$row['phone'] = $encryptionHelper->decryptData($row['phone']) ?: $row['phone'];
|
||||
}
|
||||
if (!empty($row['first_name'])) {
|
||||
$row['first_name'] = $encryptionHelper->decryptData($row['first_name']) ?: $row['first_name'];
|
||||
}
|
||||
if (!empty($row['last_name'])) {
|
||||
$row['last_name'] = $encryptionHelper->decryptData($row['last_name']) ?: $row['last_name'];
|
||||
}
|
||||
if (!empty($row['name_arabic'])) {
|
||||
$row['name_arabic'] = $encryptionHelper->decryptData($row['name_arabic']) ?: $row['name_arabic'];
|
||||
// فك التشفير للحقول الحساسة
|
||||
foreach ($rows as &$row) {
|
||||
if (!empty($row['phone'])) {
|
||||
$row['phone'] = $encryptionHelper->decryptData($row['phone']);
|
||||
}
|
||||
if (!empty($row['name_arabic'])) {
|
||||
$row['name_arabic'] = $encryptionHelper->decryptData($row['name_arabic']);
|
||||
}
|
||||
if (!empty($row['token'])) {
|
||||
$row['token'] = $encryptionHelper->decryptData($row['token']);
|
||||
}
|
||||
}
|
||||
|
||||
jsonSuccess($rows);
|
||||
} else {
|
||||
jsonError($message = "No recent driver location activity found");
|
||||
}
|
||||
unset($row);
|
||||
|
||||
jsonSuccess($rows);
|
||||
|
||||
?>
|
||||
@@ -53,18 +53,14 @@ if ($stmt->rowCount() > 0) {
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Decrypt sensitive fields
|
||||
$fieldsToDecrypt = ['phone', 'email', 'first_name', 'last_name', 'name_arabic', 'national_number', 'address', 'gender', 'site', 'birthdate'];
|
||||
foreach ($rows as &$row) {
|
||||
foreach ($fieldsToDecrypt as $f) {
|
||||
if (!empty($row[$f])) {
|
||||
$dec = $encryptionHelper->decryptData($row[$f]);
|
||||
if ($dec !== false && $dec !== null) {
|
||||
$row[$f] = $dec;
|
||||
}
|
||||
}
|
||||
if (!empty($row['phone'])) {
|
||||
$row['phone'] = $encryptionHelper->decryptData($row['phone']);
|
||||
}
|
||||
if (!empty($row['name_arabic'])) {
|
||||
$row['name_arabic'] = $encryptionHelper->decryptData($row['name_arabic']);
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
jsonSuccess($rows);
|
||||
|
||||
|
||||
@@ -18,5 +18,12 @@ $stmt->execute();
|
||||
// Fetch all records as an associative array
|
||||
$employee_data = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
jsonSuccess($employee_data ?: []);
|
||||
// Check if any records were retrieved
|
||||
if ($employee_data) {
|
||||
// If records were found, print the data as JSON
|
||||
jsonSuccess($data = $employee_data);
|
||||
} else {
|
||||
// If no records were found, print a failure message
|
||||
jsonError($message = "No employee records found");
|
||||
}
|
||||
?>
|
||||
@@ -63,12 +63,14 @@ $sql = "SELECT
|
||||
FROM
|
||||
`passengers`
|
||||
WHERE
|
||||
passengers.id = :pid
|
||||
passengers.id = '$passengerID'
|
||||
GROUP BY
|
||||
`passengers`.`id`
|
||||
ORDER BY
|
||||
countPassengerRide DESC";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([':pid' => $passengerID]);
|
||||
$stmt->execute();
|
||||
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// ✅ فك تشفير الحقول الحساسة
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../connect.php';
|
||||
|
||||
$rawEmail = filterRequest("passengerEmail");
|
||||
$rawPhone = filterRequest("passengerphone");
|
||||
|
||||
$passengerEmail = !empty($rawEmail) ? $encryptionHelper->encryptData($rawEmail) : '___NON_EXISTENT___';
|
||||
$passengerEmail = $encryptionHelper->encryptData(filterRequest("passengerEmail"));
|
||||
$passengerId = filterRequest("passengerId");
|
||||
$passengerphone = !empty($rawPhone) ? $encryptionHelper->encryptData($rawPhone) : '___NON_EXISTENT___';
|
||||
$passengerphone = $encryptionHelper->encryptData(filterRequest("passengerphone"));
|
||||
|
||||
|
||||
/**
|
||||
* الفهرس الأعمى: يسمح بالبحث بعد نقل التخزين إلى AES-GCM العشوائي.
|
||||
* تُبقى المقارنة القديمة في نفس الاستعلام كاحتياط حتى تنتهي تعبئة الفهارس.
|
||||
*/
|
||||
global $blindIndex;
|
||||
$emailBidx = (!empty($rawEmail) && $blindIndex) ? $blindIndex->index('passengers.phone', $rawEmail) : null;
|
||||
$phoneBidx = (!empty($rawPhone) && $blindIndex) ? $blindIndex->index('passengers.phone', $rawPhone) : null;
|
||||
$emailBidx = $blindIndex ? $blindIndex->index('passengers.email', filterRequest("passengerEmail")) : null;
|
||||
$phoneBidx = $blindIndex ? $blindIndex->index('passengers.phone', filterRequest("passengerphone")) : null;
|
||||
|
||||
$sql = "SELECT
|
||||
`passengers`.`id`,
|
||||
|
||||
@@ -22,10 +22,6 @@ $raw = normalizePhone($phone);
|
||||
// شَفِّر قبل الاستعلام
|
||||
$enc_raw = $encryptionHelper->encryptData($raw);
|
||||
|
||||
global $blindIndex;
|
||||
$pBidx = $blindIndex ? $blindIndex->index('passengers.phone', $raw) : null;
|
||||
$dBidx = $blindIndex ? $blindIndex->index('driver.phone', $raw) : null;
|
||||
|
||||
try {
|
||||
error_log("[get_last_ride] Searching phone normalized=$raw");
|
||||
|
||||
|
||||
@@ -60,16 +60,16 @@ if ($driver) {
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// 2) جلب آخر رحلة حالتها نشطة
|
||||
// 2) جلب آخر رحلة حالتها نشطة (Apply, Applied, Arrived, Begin)
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
$activeStatuses = "'apply','applied','arrived','begin','accepted','started','claimed','new','nothing','waiting','wait','pending','searching'";
|
||||
$activeStatuses = "'Apply','Applied','Arrived','arrived','Begin'";
|
||||
|
||||
if ($userType == 'driver') {
|
||||
error_log("[MONITOR_RIDE] 4. Searching for active ride for Driver ID: " . $driverID);
|
||||
$rideQuery = $con->prepare("
|
||||
SELECT * FROM ride
|
||||
WHERE driver_id = :driverID AND LOWER(status) IN ($activeStatuses)
|
||||
WHERE driver_id = :driverID AND status IN ($activeStatuses)
|
||||
ORDER BY id DESC LIMIT 1
|
||||
");
|
||||
$rideQuery->execute([':driverID' => $driverID]);
|
||||
@@ -77,7 +77,7 @@ if ($userType == 'driver') {
|
||||
error_log("[MONITOR_RIDE] 4. Searching for active ride for Customer ID: " . $customerID);
|
||||
$rideQuery = $con->prepare("
|
||||
SELECT * FROM ride
|
||||
WHERE passenger_id = :customerID AND LOWER(status) IN ($activeStatuses)
|
||||
WHERE passenger_id = :customerID AND status IN ($activeStatuses)
|
||||
ORDER BY id DESC LIMIT 1
|
||||
");
|
||||
$rideQuery->execute([':customerID' => $customerID]);
|
||||
@@ -100,28 +100,25 @@ if (!$ride) {
|
||||
$rideDriverID = $ride['driverID'] ?? $ride['driver_id'];
|
||||
error_log("[MONITOR_RIDE] 5. Fetching info for Driver ID from Ride: " . $rideDriverID);
|
||||
|
||||
$driverInfo = null;
|
||||
if ($rideDriverID) {
|
||||
$driverInfoQuery = $con->prepare("
|
||||
SELECT id, first_name, last_name, phone
|
||||
FROM driver
|
||||
WHERE id = :driverID
|
||||
LIMIT 1
|
||||
");
|
||||
$driverInfoQuery = $con->prepare("
|
||||
SELECT id, first_name, last_name, phone
|
||||
FROM driver
|
||||
WHERE id = :driverID
|
||||
LIMIT 1
|
||||
");
|
||||
|
||||
$driverInfoQuery->execute([':driverID' => $rideDriverID]);
|
||||
$driverInfo = $driverInfoQuery->fetch(PDO::FETCH_ASSOC);
|
||||
$driverInfoQuery->execute([':driverID' => $rideDriverID]);
|
||||
$driverInfo = $driverInfoQuery->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($driverInfo) {
|
||||
$driverInfo['phone'] = $encryptionHelper->decryptData($driverInfo['phone']);
|
||||
$driverInfo['first_name'] = $encryptionHelper->decryptData($driverInfo['first_name']);
|
||||
$driverInfo['last_name'] = $encryptionHelper->decryptData($driverInfo['last_name']);
|
||||
$fullName = trim(($driverInfo['first_name'] ?? '') . " " . ($driverInfo['last_name'] ?? ''));
|
||||
$driverInfo['fullname'] = $fullName ?: "Unknown Driver";
|
||||
error_log("[MONITOR_RIDE] 5. Driver Info Found: " . $fullName);
|
||||
} else {
|
||||
error_log("[MONITOR_RIDE] 5. WARNING: Driver info not found for ID " . $rideDriverID);
|
||||
}
|
||||
if ($driverInfo) {
|
||||
$driverInfo['phone'] = $encryptionHelper->decryptData($driverInfo['phone']);
|
||||
$driverInfo['first_name'] = $encryptionHelper->decryptData($driverInfo['first_name']);
|
||||
$driverInfo['last_name'] = $encryptionHelper->decryptData($driverInfo['last_name']);
|
||||
$fullName = $driverInfo['first_name'] . " " . $driverInfo['last_name'];
|
||||
$driverInfo['fullname'] = $fullName;
|
||||
error_log("[MONITOR_RIDE] 5. Driver Info Found: " . $fullName);
|
||||
} else {
|
||||
error_log("[MONITOR_RIDE] 5. WARNING: Driver info not found for ID " . $rideDriverID);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
@@ -130,23 +127,14 @@ if ($rideDriverID) {
|
||||
|
||||
error_log("[MONITOR_RIDE] 6. Querying Tracking DB for Driver ID: " . $rideDriverID);
|
||||
|
||||
$location = null;
|
||||
if ($rideDriverID) {
|
||||
try {
|
||||
$trackingDb = null;
|
||||
try { $trackingDb = Database::get('tracking'); } catch (Throwable $e) { $trackingDb = $con; }
|
||||
$locationQuery = $trackingDb->prepare("
|
||||
SELECT latitude, longitude, speed, heading, updated_at
|
||||
FROM car_locations
|
||||
WHERE driver_id = :driverID AND status = 'ON'
|
||||
ORDER BY updated_at DESC LIMIT 1
|
||||
");
|
||||
$locationQuery->execute([':driverID' => $rideDriverID]);
|
||||
$location = $locationQuery->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
} catch (Throwable $e) {
|
||||
error_log("[MONITOR_RIDE] Tracking query exception: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
$locationQuery = $con_tracking->prepare("
|
||||
SELECT latitude, longitude, speed, heading, updated_at
|
||||
FROM car_locations
|
||||
WHERE driver_id = :driverID AND status = 'ON'
|
||||
ORDER BY updated_at DESC LIMIT 1
|
||||
");
|
||||
$locationQuery->execute([':driverID' => $rideDriverID]);
|
||||
$location = $locationQuery->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($location) {
|
||||
error_log("[MONITOR_RIDE] 6. Location Found: Lat=" . $location['latitude'] . " Lng=" . $location['longitude']);
|
||||
@@ -164,5 +152,7 @@ $response = [
|
||||
"driver_location" => $location ?: "No live location"
|
||||
];
|
||||
|
||||
error_log("[MONITOR_RIDE] 7. Sending Success Response.");
|
||||
jsonSuccess($response);
|
||||
error_log("[MONITOR_RIDE] 7. Sending Success Response.");
|
||||
jsonSuccess($response);
|
||||
@@ -18,7 +18,7 @@ try {
|
||||
COUNT(r.id) as total_rides
|
||||
FROM driver d
|
||||
LEFT JOIN ride r ON d.id = r.driver_id AND LOWER(r.status) IN ('finished','completed')
|
||||
GROUP BY d.id, d.first_name, d.last_name, d.phone
|
||||
GROUP BY d.id
|
||||
HAVING total_earned > 0
|
||||
ORDER BY total_earned DESC
|
||||
LIMIT 50
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Admin/v2/quality/blacklist_manager.php
|
||||
require_once __DIR__ . '/../../../connect.php';
|
||||
require_once __DIR__ . '/../../../encrypt_decrypt.php';
|
||||
require_once __DIR__ . '/../security/audit_logs_helper.php'; // إذا كان متاحاً، وإلا سننفذ الإدخال مباشرة
|
||||
|
||||
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||
jsonError("Unauthorized", 403);
|
||||
|
||||
@@ -10,22 +10,7 @@ if ($role !== 'admin' && $role !== 'super_admin') {
|
||||
|
||||
$driver_id = filterRequest('driver_id');
|
||||
if (!$driver_id) {
|
||||
try {
|
||||
$stmt = $con->prepare("SELECT id, first_name, last_name, phone, status FROM driver ORDER BY id DESC LIMIT 20");
|
||||
$stmt->execute();
|
||||
$drivers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($drivers as &$d) {
|
||||
if (!empty($d['first_name'])) $d['first_name'] = $encryptionHelper->decryptData($d['first_name']) ?: $d['first_name'];
|
||||
if (!empty($d['last_name'])) $d['last_name'] = $encryptionHelper->decryptData($d['last_name']) ?: $d['last_name'];
|
||||
if (!empty($d['phone'])) $d['phone'] = $encryptionHelper->decryptData($d['phone']) ?: $d['phone'];
|
||||
}
|
||||
unset($d);
|
||||
jsonSuccess($drivers);
|
||||
exit;
|
||||
} catch (Throwable $e) {
|
||||
jsonSuccess([]);
|
||||
exit;
|
||||
}
|
||||
jsonError("Missing driver_id", 400);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
require_once __DIR__ . '/../../../connect.php';
|
||||
|
||||
// 🔥 [Fix Broken Access Control] كان يتحقق من صلاحية التوكن فقط — أي مستخدم
|
||||
// مسجّل دخول (راكب/سائق آخر) كان يقدر يجلب بيانات أي سائق مفكوكة التشفير
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
require_once __DIR__ . '/../../../connect.php';
|
||||
|
||||
// 🔥 [Fix Broken Access Control] كان يتحقق من صلاحية التوكن فقط بدون التحقق
|
||||
// من الدور — أي توكن صالح (حتى راكب) كان يقدر يسحب قائمة السائقين المعلّقين
|
||||
|
||||
@@ -8,5 +8,11 @@ $stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
$passenger_data = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
jsonSuccess($passenger_data ?: []);
|
||||
if ($passenger_data) {
|
||||
// Print the passenger data as JSON
|
||||
jsonSuccess($data = $passenger_data);
|
||||
} else {
|
||||
// Print a failure message
|
||||
jsonError($message = "No passenger data found");
|
||||
}
|
||||
?>
|
||||
@@ -360,13 +360,13 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-i18n="ID">ID</th>
|
||||
<th data-i18n="Name">Name</th>
|
||||
<th data-i18n="Phone">Phone</th>
|
||||
<th data-i18n="Email">Email</th>
|
||||
<th data-i18n="Rating">Rating</th>
|
||||
<th data-i18n="Trips">Trips</th>
|
||||
<th data-i18n="Cancellations">Cancellations</th>
|
||||
<th data-i18n="Status">Status</th>
|
||||
<th>Name</th>
|
||||
<th>Phone</th>
|
||||
<th>Email</th>
|
||||
<th>Rating</th>
|
||||
<th>Trips</th>
|
||||
<th>Cancellations</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="driversTableBody"></tbody>
|
||||
@@ -398,13 +398,13 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-i18n="ID">ID</th>
|
||||
<th data-i18n="Name">Name</th>
|
||||
<th data-i18n="Contact">Contact</th>
|
||||
<th data-i18n="Trips">Trips</th>
|
||||
<th data-i18n="Rating">Rating</th>
|
||||
<th data-i18n="Cancellations">Cancellations</th>
|
||||
<th data-i18n="Joined">Joined</th>
|
||||
<th data-i18n="Status">Status</th>
|
||||
<th>Name</th>
|
||||
<th>Contact</th>
|
||||
<th>Trips</th>
|
||||
<th>Rating</th>
|
||||
<th>Cancellations</th>
|
||||
<th>Joined</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="passengersTableBody"></tbody>
|
||||
@@ -458,8 +458,8 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-i18n="ID">ID</th>
|
||||
<th data-i18n="Name">Name</th>
|
||||
<th data-i18n="Phone">Phone</th>
|
||||
<th>Name</th>
|
||||
<th>Phone</th>
|
||||
<th data-i18n="Type">Type</th>
|
||||
<th data-i18n="Requested role">Requested role</th>
|
||||
<th data-i18n="Requested at">Requested at</th>
|
||||
|
||||
@@ -458,59 +458,12 @@
|
||||
'This browser is registered as a trusted device:': 'هذا المتصفح مسجّل كجهاز موثوق:',
|
||||
'The endpoint responded successfully but returned no data yet.': 'استجاب الـ API بنجاح لكنه لم يُرجِع بياناتاً بعد.',
|
||||
'No records recorded for this yet — the table is empty in the database.': 'لا توجد سجلات بعد — الجدول فارغ في قاعدة البيانات.',
|
||||
// Database and API Field Translations
|
||||
'Total Revenue': 'إجمالي الإيرادات',
|
||||
'Company Profit': 'أرباح الشركة',
|
||||
'Total Driver Pay': 'إجمالي مستحقات السائقين',
|
||||
'Total Platform Commission': 'عمولة المنصة',
|
||||
'Cash Payments': 'الدفع النقدي',
|
||||
'Digital Payments': 'الدفع الرقمي',
|
||||
'Total Earned': 'إجمالي الأرباح',
|
||||
'First Name': 'الاسم الأول',
|
||||
'Last Name': 'اسم العائلة',
|
||||
'Full Name': 'الاسم الكامل',
|
||||
'Driver Id': 'معرّف السائق',
|
||||
'Passenger Id': 'معرّف الراكب',
|
||||
'Created At': 'تاريخ الإنشاء',
|
||||
'Updated At': 'تاريخ التحديث',
|
||||
'Birthdate': 'تاريخ الميلاد',
|
||||
'Gender': 'الجنس',
|
||||
'National Number': 'الرقم الوطني',
|
||||
'Address': 'العنوان',
|
||||
'Site': 'الموقع',
|
||||
'Active Routes': 'المسارات النشطة',
|
||||
'Active Enrollments': 'التسجيلات النشطة',
|
||||
'Average Pci': 'متوسط PCI',
|
||||
'Market Share Percent': 'نسبة الحصة السوقية',
|
||||
'Total Anomalies': 'حالات الشذوذ',
|
||||
'Total Surge Opportunities': 'فرص الفورة',
|
||||
'Report Date': 'تاريخ التقرير',
|
||||
'Date': 'التاريخ',
|
||||
'User Type': 'نوع المستخدم',
|
||||
'User': 'المستخدم',
|
||||
'Ride Details': 'تفاصيل الرحلة',
|
||||
'Driver Details': 'تفاصيل السائق',
|
||||
'Driver Location': 'موقع السائق',
|
||||
'Price': 'السعر',
|
||||
'Price For Driver': 'حصة السائق',
|
||||
'Price For Passenger': 'المبلغ من الراكب',
|
||||
'Distance': 'المسافة',
|
||||
'Payment Method': 'طريقة الدفع',
|
||||
'Car Type': 'نوع السيارة',
|
||||
'Start Location': 'موقع البداية',
|
||||
'End Location': 'موقع النهاية',
|
||||
'Cancel Reason': 'سبب الإلغاء',
|
||||
'Driver First Name': 'اسم السائق الأول',
|
||||
'Driver Last Name': 'اسم عائلة السائق',
|
||||
'Passenger First Name': 'اسم الراكب الأول',
|
||||
'Passenger Last Name': 'اسم عائلة الراكب',
|
||||
'Contact': 'معلومات الاتصال',
|
||||
'Click any row to open the full profile': 'اضغط على أي سطر لفتح الملف الشخصي الكامل',
|
||||
'Search by passenger ID, phone or email': 'البحث برقم الراكب، الهاتف، أو البريد الإلكتروني',
|
||||
'Search by captain ID, phone or email': 'البحث برقم الكابتن، الهاتف، أو البريد الإلكتروني',
|
||||
'Search rides by phone number': 'البحث في الرحلات برقم الهاتف',
|
||||
'Yes': 'نعم',
|
||||
'No': 'لا',
|
||||
'Click any row to open the full profile': 'اضغط على أي سطر لفتح الملف الشخصي الكامل',
|
||||
'Search rides by phone number': 'بحث في الرحلات برقم الهاتف',
|
||||
'Search by captain ID, phone or email': 'بحث بمعرّف الكابتن أو البريد الإلكتروني أو الهاتف',
|
||||
'Search by passenger ID, phone or email': 'بحث بمعرّف الراكب أو البريد الإلكتروني أو الهاتف',
|
||||
// API error messages
|
||||
' — the endpoint returned HTML, check the API base URL': ' — الـ API أرجع HTML، تحقق من عنوان الخادم',
|
||||
'Session rejected by the server': 'الخادم رفض الجلسة',
|
||||
@@ -1872,8 +1825,8 @@
|
||||
</div>
|
||||
<div class="chart-container"><canvas id="growthChart"></canvas></div>
|
||||
${activeDays === 0
|
||||
? `<div class="table-msg">${t('Nobody signed up in the last 30 days.')}</div>`
|
||||
: `<div class="stamp" style="display:block;text-align:center;margin-top:0.5rem;">
|
||||
? `<div class="table-msg">${t('Nobody signed up in the last 30 days.')}</div>`
|
||||
: `<div class="stamp" style="display:block;text-align:center;margin-top:0.5rem;">
|
||||
${activeDays} / 30 ${lang === 'ar' ? 'يوماً بها تسجيل واحد على الأقل' : 'days had at least one signup'}
|
||||
</div>`}
|
||||
</div>`;
|
||||
@@ -2045,7 +1998,7 @@
|
||||
|
||||
<div class="kpi-tiles">
|
||||
${Object.entries(counts).map(([k, v]) =>
|
||||
`<div class="kpi-tile"><div class="kpi-tile-value">${esc(formatValue(v, k))}</div><div class="kpi-tile-label">${esc(humanize(k))}</div></div>`).join('')}
|
||||
`<div class="kpi-tile"><div class="kpi-tile-value">${esc(formatValue(v, k))}</div><div class="kpi-tile-label">${esc(humanize(k))}</div></div>`).join('')}
|
||||
</div>
|
||||
|
||||
<div class="sub-panel">
|
||||
@@ -2227,8 +2180,8 @@
|
||||
trial_ends_at: $('orgTrialEnds').value,
|
||||
};
|
||||
if (params.contract_status !== org.contract_status &&
|
||||
!confirm(`Change the contract from "${org.contract_status}" to "${params.contract_status}"? ` +
|
||||
'Suspending stops the organisation using the service.')) return;
|
||||
!confirm(`Change the contract from "${org.contract_status}" to "${params.contract_status}"? ` +
|
||||
'Suspending stops the organisation using the service.')) return;
|
||||
|
||||
await api('/Admin/transit/org/update.php', { params });
|
||||
toast('Organisation updated.', 'success');
|
||||
@@ -2728,17 +2681,17 @@
|
||||
${documents.map((doc) => `
|
||||
<figure class="doc-card">
|
||||
${doc.link
|
||||
? `<a href="${esc(doc.link)}" target="_blank" rel="noopener">
|
||||
? `<a href="${esc(doc.link)}" target="_blank" rel="noopener">
|
||||
<img src="${esc(doc.link)}" alt="${esc(doc.doc_type || 'document')}" loading="lazy">
|
||||
</a>`
|
||||
: `<div class="doc-missing"><i class="ph ph-file-x"></i> ${lang === 'ar' ? 'لا ملف مرتبط' : 'no file linked'}</div>`}
|
||||
: `<div class="doc-missing"><i class="ph ph-file-x"></i> ${lang === 'ar' ? 'لا ملف مرتبط' : 'no file linked'}</div>`}
|
||||
<figcaption>
|
||||
<strong>${esc(humanize(doc.doc_type || 'document'))}</strong>
|
||||
<span class="stamp">${esc(doc.image_name || '—')}</span>
|
||||
</figcaption>
|
||||
</figure>`).join('')}
|
||||
</div>`
|
||||
: `<div class="table-msg">${lang === 'ar' ? 'هذا الكابتن لم يُرسل أي وثائق — الاعتماد الآن يعني تفعيل حساب غير موثّق.' : 'This captain has uploaded no documents — approving now would activate an unverified account.'}</div>`}
|
||||
: `<div class="table-msg">${lang === 'ar' ? 'هذا الكابتن لم يُرسل أي وثائق — الاعتماد الآن يعني تفعيل حساب غير موثّق.' : 'This captain has uploaded no documents — approving now would activate an unverified account.'}</div>`}
|
||||
</div>
|
||||
|
||||
<div class="sub-panel">
|
||||
@@ -3386,18 +3339,11 @@
|
||||
}
|
||||
|
||||
function humanize(key) {
|
||||
if (!key) return '';
|
||||
const rawKey = String(key).trim();
|
||||
if (lang === 'ar' && AR[rawKey]) return AR[rawKey];
|
||||
|
||||
const s = rawKey
|
||||
return String(key)
|
||||
.replace(/[_-]+/g, ' ')
|
||||
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
||||
.replace(/\b\w/g, (m) => m.toUpperCase())
|
||||
.trim();
|
||||
|
||||
if (lang === 'ar' && AR[s]) return AR[s];
|
||||
return s;
|
||||
}
|
||||
|
||||
function msgNode(text) {
|
||||
@@ -3873,13 +3819,13 @@
|
||||
|
||||
// Maps a sidebar entry to the loader that owns its data.
|
||||
const VIEW_LOADERS = {
|
||||
dashboardView: () => { loadStats().catch(() => {}); loadRidesTrend().catch(() => {}); },
|
||||
ridesView: () => loadRides().catch(() => {}),
|
||||
driversView: () => loadDrivers().catch(() => {}),
|
||||
passengersView: () => loadPassengers().catch(() => {}),
|
||||
financialsView: () => loadStats().catch(() => {}),
|
||||
complaintsView: () => loadStats().catch(() => {}),
|
||||
approvalsView: () => loadApprovals().catch(() => {}),
|
||||
dashboardView: () => { loadStats().catch(() => { }); loadRidesTrend().catch(() => { }); },
|
||||
ridesView: () => loadRides().catch(() => { }),
|
||||
driversView: () => loadDrivers().catch(() => { }),
|
||||
passengersView: () => loadPassengers().catch(() => { }),
|
||||
financialsView: () => loadStats().catch(() => { }),
|
||||
complaintsView: () => loadStats().catch(() => { }),
|
||||
approvalsView: () => loadApprovals().catch(() => { }),
|
||||
systemView: () => renderSessionInfo(),
|
||||
};
|
||||
|
||||
@@ -3905,13 +3851,13 @@
|
||||
else loadEverything();
|
||||
});
|
||||
$('langToggle')?.addEventListener('click', () => setLanguage(lang === 'ar' ? 'en' : 'ar'));
|
||||
el.rideStatusFilter?.addEventListener('change', () => loadRides().catch(() => {}));
|
||||
el.rideStatusFilter?.addEventListener('change', () => loadRides().catch(() => { }));
|
||||
|
||||
el.driversPrev?.addEventListener('click', () => {
|
||||
if (driversPage > 1) { driversPage--; loadDrivers().catch(() => {}); }
|
||||
if (driversPage > 1) { driversPage--; loadDrivers().catch(() => { }); }
|
||||
});
|
||||
el.driversNext?.addEventListener('click', () => {
|
||||
if (driversPage < driversPages) { driversPage++; loadDrivers().catch(() => {}); }
|
||||
if (driversPage < driversPages) { driversPage++; loadDrivers().catch(() => { }); }
|
||||
});
|
||||
|
||||
el.ridesMore?.addEventListener('click', () => {
|
||||
|
||||
Reference in New Issue
Block a user