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',
|
subtitle: 'Geofenced demand density',
|
||||||
panels: [{ title: 'Heatmap', path: '/Admin/geofence/get_heatmap.php' }],
|
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',
|
id: 'quality', group: 'Quality', icon: 'ph-prohibit', title: 'Blacklist',
|
||||||
subtitle: 'Blocked captains and passengers',
|
subtitle: 'Blocked captains and passengers',
|
||||||
@@ -872,6 +885,24 @@
|
|||||||
subtitle: 'Behaviour and reliability scoring per captain',
|
subtitle: 'Behaviour and reliability scoring per captain',
|
||||||
panels: [{ title: 'Scorecard', path: '/Admin/v2/quality/driver_scorecard.php' }],
|
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',
|
id: 'transit', group: 'Transit', icon: 'ph-bus', title: 'Mawasalati Organisations',
|
||||||
subtitle: 'Registered transit organisations and their pending routes',
|
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 ──────────────────────────────────────────────────
|
// ── Kazan tariff editor ──────────────────────────────────────────────────
|
||||||
// Only these columns are accepted by ride/kazan/update.php; anything else
|
// Only these columns are accepted by ride/kazan/update.php; anything else
|
||||||
// sent would be silently dropped, so the form mirrors that list exactly.
|
// sent would be silently dropped, so the form mirrors that list exactly.
|
||||||
@@ -1151,7 +1222,7 @@
|
|||||||
grid.className = 'kpi-tiles';
|
grid.className = 'kpi-tiles';
|
||||||
grid.innerHTML = scalars.map(([k, v]) => `
|
grid.innerHTML = scalars.map(([k, v]) => `
|
||||||
<div class="kpi-tile">
|
<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 class="kpi-tile-label">${esc(humanize(k))}</div>
|
||||||
</div>`).join('');
|
</div>`).join('');
|
||||||
frag.appendChild(grid);
|
frag.appendChild(grid);
|
||||||
|
|||||||
Reference in New Issue
Block a user