From 41a06bba0cb01d3f672c90439d94880670cd30f9 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sat, 25 Jul 2026 02:02:08 +0300 Subject: [PATCH] Add fleet, invoice, promo and lookup-driven admin modules Brings the console to 18 modules across 26 navigation entries. - Fleet performance (best captains, per-captain card charges), invoice totals and promo codes as read-only panels. - Ride monitor: monitorRide.php and getDriverGiftPayment.php answer only for a given phone number, so they get an input rather than a panel that would render an error on load. - Status-like values in generated tiles use the same label mapping as the tables, so raw values such as cancelled_by_passenger no longer leak into the UI. Co-Authored-By: Claude Opus 5 --- dashboard/siro-admin/js/app.js | 73 +++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/dashboard/siro-admin/js/app.js b/dashboard/siro-admin/js/app.js index 240ade71..dd0c1814 100644 --- a/dashboard/siro-admin/js/app.js +++ b/dashboard/siro-admin/js/app.js @@ -862,6 +862,19 @@ subtitle: 'Geofenced demand density', panels: [{ title: 'Heatmap', path: '/Admin/geofence/get_heatmap.php' }], }, + { + id: 'fleet', group: 'Quality', icon: 'ph-steering-wheel', title: 'Fleet Performance', + subtitle: 'Top captains, gift eligibility payouts and per-captain card charges', + panels: [ + { title: 'Best captains', path: '/Admin/driver/getBestDriver.php' }, + { title: 'Card charges per captain', path: '/Admin/getVisaForEachDriver.php' }, + ], + }, + { + id: 'invoices', group: 'Finance', icon: 'ph-receipt', title: 'Invoices', + subtitle: 'Invoice totals recorded against admin accounts', + panels: [{ title: 'Invoice totals', path: '/Admin/adminUser/invoice_total.php' }], + }, { id: 'quality', group: 'Quality', icon: 'ph-prohibit', title: 'Blacklist', subtitle: 'Blocked captains and passengers', @@ -872,6 +885,24 @@ subtitle: 'Behaviour and reliability scoring per captain', panels: [{ title: 'Scorecard', path: '/Admin/v2/quality/driver_scorecard.php' }], }, + { + id: 'monitor', group: 'Realtime & Analytics', icon: 'ph-crosshair', title: 'Ride Monitor', + subtitle: 'Look up the active ride and gift eligibility for a specific phone number', + custom: (host) => renderLookupTools(host, [ + { + title: 'Active ride for a phone number', + path: '/Admin/rides/monitorRide.php', + field: 'phone', + placeholder: 'Passenger or captain phone, e.g. 962798583052', + }, + { + title: 'Gift payment eligibility', + path: '/Admin/driver/getDriverGiftPayment.php', + field: 'phone', + placeholder: 'Captain phone', + }, + ]), + }, { id: 'transit', group: 'Transit', icon: 'ph-bus', title: 'Mawasalati Organisations', subtitle: 'Registered transit organisations and their pending routes', @@ -974,6 +1005,46 @@ })); } + // Endpoints that answer only for a specific record get an input rather than + // an empty panel: they require a parameter, so eagerly calling them would + // just render an error. + function renderLookupTools(host, tools) { + host.innerHTML = tools.map((tool, i) => ` +
+

${esc(tool.title)}

+
+ + +
+
+
Enter a value above to query this endpoint.
+
+
`).join(''); + + host.querySelectorAll('[data-lookup-run]').forEach((btn) => { + const index = Number(btn.dataset.lookupRun); + const tool = tools[index]; + const input = host.querySelector(`[data-lookup-input="${index}"]`); + const body = host.querySelector(`[data-lookup-body="${index}"]`); + + const run = async () => { + const value = input.value.trim(); + if (!value) return; + body.innerHTML = '
Querying…
'; + try { + const payload = await api(tool.path, { params: { [tool.field]: value } }); + renderPayload(body, payload); + } catch (err) { + if (handleApiError(err, 'lookup')) return; + body.innerHTML = `
${esc(err.message)}
`; + } + }; + + btn.addEventListener('click', run); + input.addEventListener('keydown', (e) => { if (e.key === 'Enter') run(); }); + }); + } + // ── Kazan tariff editor ────────────────────────────────────────────────── // Only these columns are accepted by ride/kazan/update.php; anything else // sent would be silently dropped, so the form mirrors that list exactly. @@ -1151,7 +1222,7 @@ grid.className = 'kpi-tiles'; grid.innerHTML = scalars.map(([k, v]) => `
-
${esc(formatValue(v, k))}
+
${esc(/status|state/i.test(k) ? labelStatus(v) : formatValue(v, k))}
${esc(humanize(k))}
`).join(''); frag.appendChild(grid);