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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
db4ca7dd7a
commit
41a06bba0c
@@ -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) => `
|
||||
<div class="card">
|
||||
<div class="card-header"><h3 class="card-title">${esc(tool.title)}</h3></div>
|
||||
<div class="api-base-row">
|
||||
<input type="text" class="form-input" data-lookup-input="${i}" placeholder="${esc(tool.placeholder)}">
|
||||
<button class="btn btn-secondary btn-sm" data-lookup-run="${i}"><i class="ph ph-magnifying-glass"></i> <span>Look up</span></button>
|
||||
</div>
|
||||
<div class="panel-body" data-lookup-body="${i}">
|
||||
<div class="table-msg">Enter a value above to query this endpoint.</div>
|
||||
</div>
|
||||
</div>`).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 = '<div class="table-msg">Querying…</div>';
|
||||
try {
|
||||
const payload = await api(tool.path, { params: { [tool.field]: value } });
|
||||
renderPayload(body, payload);
|
||||
} catch (err) {
|
||||
if (handleApiError(err, 'lookup')) return;
|
||||
body.innerHTML = `<div class="table-msg is-error">${esc(err.message)}</div>`;
|
||||
}
|
||||
};
|
||||
|
||||
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]) => `
|
||||
<div class="kpi-tile">
|
||||
<div class="kpi-tile-value">${esc(formatValue(v, k))}</div>
|
||||
<div class="kpi-tile-value">${esc(/status|state/i.test(k) ? labelStatus(v) : formatValue(v, k))}</div>
|
||||
<div class="kpi-tile-label">${esc(humanize(k))}</div>
|
||||
</div>`).join('');
|
||||
frag.appendChild(grid);
|
||||
|
||||
Reference in New Issue
Block a user