Fix admin status handling for the current ride pipeline; extend console
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
915a148ebf
commit
852c6ece5c
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -499,6 +499,26 @@
|
||||
<div class="mini-list" id="sessionInfo"></div>
|
||||
</div>
|
||||
|
||||
<div class="card" data-requires-super hidden>
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Send WhatsApp message <span class="card-sub">super admin</span></h3>
|
||||
</div>
|
||||
<p class="card-note">
|
||||
Sends a single message through the platform's WhatsApp provider. This leaves the system
|
||||
and reaches a real person — you will be asked to confirm before it is sent.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-input" id="waReceiver" placeholder="Recipient phone, e.g. 962798583052" style="padding-left:1rem;">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<textarea class="form-input decrypt-area" id="waMessage" rows="3" placeholder="Message text"></textarea>
|
||||
</div>
|
||||
<div class="api-base-row">
|
||||
<button class="btn btn-secondary btn-sm" id="waSendBtn"><i class="ph ph-paper-plane-tilt"></i> <span>Send message</span></button>
|
||||
<span class="stamp" id="waStatus"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" data-requires-super hidden>
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Encryption tool <span class="card-sub">super admin</span></h3>
|
||||
|
||||
+119
-29
@@ -438,8 +438,10 @@
|
||||
setKpi('passengers', fmtInt(stats.countPassengers));
|
||||
setKpi('passengersMonth', fmtInt(stats.countPassengersThisMonth));
|
||||
|
||||
setKpi('driverEarnings', fmtMoney(stats.total_driver_earnings));
|
||||
setKpi('avgFare', fmtMoney(stats.avg_passenger_price));
|
||||
// A null aggregate means "nothing recorded yet" — showing 0.00 would read
|
||||
// as a measured zero.
|
||||
setKpi('driverEarnings', stats.total_driver_earnings == null ? '—' : fmtMoney(stats.total_driver_earnings));
|
||||
setKpi('avgFare', stats.avg_passenger_price == null ? '—' : fmtMoney(stats.avg_passenger_price));
|
||||
setKpi('totalDistance', `${fmtInt(stats.total_distance)} km`);
|
||||
|
||||
setKpi('complaintsToday', fmtInt(stats.countComplaintToday));
|
||||
@@ -505,20 +507,17 @@
|
||||
tableMessage(el.ridesTableBody, 8, 'No rides match this filter.');
|
||||
return;
|
||||
}
|
||||
el.ridesTableBody.innerHTML = rides.map((r) => {
|
||||
const fare = r.price_for_passenger ?? r.price ?? 0;
|
||||
return `
|
||||
el.ridesTableBody.innerHTML = rides.map((r) => `
|
||||
<tr>
|
||||
<td><strong>#${esc(r.id)}</strong></td>
|
||||
<td>${esc(r.passenger_full_name || 'Unknown')}</td>
|
||||
<td>${esc(r.driver_full_name || 'Unassigned')}</td>
|
||||
<td class="route-cell"><i class="ph ph-map-pin"></i> ${esc(shorten(r.address_start))} <i class="ph ph-arrow-right"></i> ${esc(shorten(r.address_end))}</td>
|
||||
<td><strong>${fmtMoney(fare)}</strong></td>
|
||||
<td class="route-cell">${routeCell(r)}</td>
|
||||
<td><strong>${fmtMoney(rideFare(r))}</strong></td>
|
||||
<td><span class="badge ${badgeClass(r.status)}">${esc(labelStatus(r.status))}</span></td>
|
||||
<td>${esc(fmtDate(r.created_at || r.date))}</td>
|
||||
<td>${esc(rideTimestamp(r))}</td>
|
||||
<td><button class="btn btn-secondary btn-sm" data-ride="${esc(r.id)}"><i class="ph ph-eye"></i></button></td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
</tr>`).join('');
|
||||
|
||||
el.ridesTableBody.querySelectorAll('[data-ride]').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
@@ -528,6 +527,42 @@
|
||||
});
|
||||
}
|
||||
|
||||
// The ride table carries two generations of columns. Older rows expose
|
||||
// address_start/address_end, price_for_passenger and created_at; rows written
|
||||
// by the current ride pipeline expose start_location/end_location as
|
||||
// "lat,lng" pairs, a plain `price`, and separate date + time columns.
|
||||
function rideFare(r) {
|
||||
return r.price_for_passenger ?? r.price ?? 0;
|
||||
}
|
||||
|
||||
function rideTimestamp(r) {
|
||||
if (r.created_at) return fmtDate(r.created_at);
|
||||
if (r.date) return fmtDate(`${r.date} ${r.time && r.time !== '00:00:00' ? r.time : ''}`.trim());
|
||||
return '—';
|
||||
}
|
||||
|
||||
function routeCell(r) {
|
||||
if (r.address_start || r.address_end) {
|
||||
return `<i class="ph ph-map-pin"></i> ${esc(shorten(r.address_start))} <i class="ph ph-arrow-right"></i> ${esc(shorten(r.address_end))}`;
|
||||
}
|
||||
if (r.start_location) {
|
||||
const link = mapLink(r.start_location);
|
||||
return `<a class="coord-link" href="${esc(link)}" target="_blank" rel="noopener">
|
||||
<i class="ph ph-map-pin"></i> ${esc(shortCoord(r.start_location))} <i class="ph ph-arrow-right"></i> ${esc(shortCoord(r.end_location))}
|
||||
</a>`;
|
||||
}
|
||||
return '<span class="stamp">—</span>';
|
||||
}
|
||||
|
||||
function shortCoord(value) {
|
||||
if (!value) return '—';
|
||||
return String(value).split(',').map((n) => Number(n).toFixed(4)).join(', ');
|
||||
}
|
||||
|
||||
function mapLink(coords) {
|
||||
return `https://www.openstreetmap.org/?mlat=${encodeURIComponent(String(coords).split(',')[0])}&mlon=${encodeURIComponent(String(coords).split(',')[1] || '')}#map=15/`;
|
||||
}
|
||||
|
||||
// ── Role model ───────────────────────────────────────────────────────────
|
||||
// Mirrors the Flutter admin app: a plain `admin` observes, a `super_admin`
|
||||
// edits, approves and sees unmasked contact details.
|
||||
@@ -812,6 +847,14 @@
|
||||
{ title: 'Telemetry', path: '/Admin/marketing/get_telemetry.php' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'tariff', group: 'Growth & Pricing', icon: 'ph-currency-circle-dollar', title: 'Tariff & Promos',
|
||||
subtitle: 'The live Kazan tariff table and active promo codes (read-only)',
|
||||
panels: [
|
||||
{ title: 'Kazan tariff', path: '/ride/kazan/get.php' },
|
||||
{ title: 'Promo codes', path: '/ride/promo/get.php' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'geofence', group: 'Growth & Pricing', icon: 'ph-map-trifold', title: 'Demand Heatmap',
|
||||
subtitle: 'Geofenced demand density',
|
||||
@@ -1121,6 +1164,35 @@
|
||||
busy(el.runDiagnosticsBtn, false, 'Run diagnostics');
|
||||
}
|
||||
|
||||
// Outbound message — confirmed explicitly because it reaches a real person
|
||||
// and cannot be recalled.
|
||||
async function sendWhatsApp() {
|
||||
if (!isSuperAdmin()) {
|
||||
toast('Sending messages is restricted to super admins.', 'warning');
|
||||
return;
|
||||
}
|
||||
const receiver = $('waReceiver').value.trim();
|
||||
const message = $('waMessage').value.trim();
|
||||
const status = $('waStatus');
|
||||
|
||||
if (!receiver || !message) {
|
||||
toast('Enter both a recipient and a message.', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!confirm(`Send this WhatsApp message to ${receiver}?\n\n${message}`)) return;
|
||||
|
||||
status.textContent = 'Sending…';
|
||||
try {
|
||||
await api('/Admin/send_whatsapp_message.php', { params: { receiver, message } });
|
||||
status.textContent = `Sent to ${receiver}`;
|
||||
$('waMessage').value = '';
|
||||
toast('Message sent.', 'success');
|
||||
} catch (err) {
|
||||
status.textContent = '';
|
||||
toast(`Send failed: ${err.message}`, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// Super-admin only: mirrors the Flutter EncryptToolPage (Admin/ggg.php).
|
||||
async function runCryptoTool(action) {
|
||||
if (!isSuperAdmin()) {
|
||||
@@ -1180,6 +1252,8 @@
|
||||
|
||||
document.querySelectorAll('[data-action="decrypt"], [data-action="encrypt"]').forEach((btn) =>
|
||||
btn.addEventListener('click', () => runCryptoTool(btn.dataset.action)));
|
||||
|
||||
$('waSendBtn')?.addEventListener('click', sendWhatsApp);
|
||||
el.copyDiagnosticsBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(el.diagnosticsOutput.textContent);
|
||||
@@ -1200,14 +1274,16 @@
|
||||
['Captain', r.driver_full_name],
|
||||
['Captain phone', maskPhone(r.d_phone)],
|
||||
['Captain completed / cancelled', `${fmtInt(r.d_completed)} / ${fmtInt(r.d_canceled)}`],
|
||||
['Pickup', r.address_start],
|
||||
['Drop-off', r.address_end],
|
||||
['Pickup', r.address_start || r.start_location],
|
||||
['Drop-off', r.address_end || r.end_location],
|
||||
['Distance', r.distance ? `${fmtNum(r.distance)} km` : '—'],
|
||||
['Passenger fare', fmtMoney(r.price_for_passenger)],
|
||||
['Captain earning', fmtMoney(r.price_for_driver)],
|
||||
['Passenger fare', fmtMoney(rideFare(r))],
|
||||
['Captain earning', r.price_for_driver ? fmtMoney(r.price_for_driver) : '—'],
|
||||
['Payment method', r.paymentMethod],
|
||||
['Service', r.carType],
|
||||
['Started', fmtDate(r.rideTimeStart)],
|
||||
['Finished', fmtDate(r.rideTimeFinish)],
|
||||
['Requested', rideTimestamp(r)],
|
||||
['Started', r.rideTimeStart ? fmtDate(r.rideTimeStart) : '—'],
|
||||
['Finished', r.rideTimeFinish ? fmtDate(r.rideTimeFinish) : '—'],
|
||||
['Cancellation note', r.cancel_reason],
|
||||
];
|
||||
body.innerHTML = `
|
||||
@@ -1514,24 +1590,38 @@
|
||||
return s.length > max ? s.slice(0, max - 1) + '…' : s;
|
||||
}
|
||||
|
||||
// Two status generations coexist in the ride table: the legacy CamelCase set
|
||||
// and the lowercase set written by the current ride pipeline.
|
||||
const STATUS_LABELS = {
|
||||
finished: 'Completed', completed: 'Completed',
|
||||
begin: 'In progress', started: 'In progress',
|
||||
apply: 'Captain assigned', applied: 'Captain assigned',
|
||||
accepted: 'Captain assigned', claimed: 'Captain assigned',
|
||||
arrived: 'Captain arrived',
|
||||
new: 'Waiting', nothing: 'Waiting', waiting: 'Waiting',
|
||||
wait: 'Waiting', pending: 'Waiting', searching: 'Searching for a captain',
|
||||
cancel: 'Cancelled',
|
||||
cancelfromdriver: 'Cancelled by captain',
|
||||
cancelfromdriverafterapply: 'Cancelled by captain',
|
||||
cancelfrompassenger: 'Cancelled by passenger',
|
||||
cancelled_by_driver: 'Cancelled by captain',
|
||||
cancelled_by_passenger: 'Cancelled by passenger',
|
||||
cancelled_no_driver_found: 'No captain found',
|
||||
timeout: 'Timed out', refused: 'Refused',
|
||||
pending_review: 'Pending review',
|
||||
};
|
||||
|
||||
function labelStatus(status) {
|
||||
const map = {
|
||||
Finished: 'Completed', Begin: 'In progress', Apply: 'Captain assigned',
|
||||
Applied: 'Captain assigned', Arrived: 'Captain arrived', arrived: 'Captain arrived',
|
||||
New: 'Waiting', nothing: 'Waiting', waiting: 'Waiting', wait: 'Waiting',
|
||||
Cancel: 'Cancelled', CancelFromDriver: 'Cancelled by captain',
|
||||
CancelFromDriverAfterApply: 'Cancelled by captain', CancelFromPassenger: 'Cancelled by passenger',
|
||||
TimeOut: 'Timed out',
|
||||
};
|
||||
return map[status] || status || 'Unknown';
|
||||
if (!status) return 'Unknown';
|
||||
return STATUS_LABELS[String(status).toLowerCase()] || humanize(status);
|
||||
}
|
||||
|
||||
function badgeClass(status) {
|
||||
const s = String(status || '').toLowerCase();
|
||||
if (['finished', 'active', 'approved', 'online'].includes(s)) return 'badge-success';
|
||||
if (s.startsWith('cancel') || ['timeout', 'suspended', 'rejected', 'blocked'].includes(s)) return 'badge-danger';
|
||||
if (['begin', 'apply', 'applied', 'arrived'].includes(s)) return 'badge-primary';
|
||||
if (['pending', 'new', 'wait', 'waiting', 'nothing'].includes(s)) return 'badge-warning';
|
||||
if (['finished', 'completed', 'active', 'approved', 'online', 'success'].includes(s)) return 'badge-success';
|
||||
if (s.startsWith('cancel') || ['timeout', 'refused', 'suspended', 'rejected', 'blocked', 'failure', 'error'].includes(s)) return 'badge-danger';
|
||||
if (['begin', 'apply', 'applied', 'accepted', 'claimed', 'started', 'arrived'].includes(s)) return 'badge-primary';
|
||||
if (['pending', 'pending_review', 'new', 'wait', 'waiting', 'nothing', 'searching'].includes(s)) return 'badge-warning';
|
||||
return 'badge-info';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user