EALPR_FLASK / templates /admin /vehicles.html
Pant0x's picture
Final synced deploy v2 - Fix session and timeouts
cd01269
Raw
History Blame Contribute Delete
6.08 kB
{% extends "base.html" %}
{% block title %}Vehicles β€” Ain El Aql{% endblock %}
{% block body %}
<div class="app-layout">
{% with role='admin', active='vehicles' %}
{% include "partials/sidebar.html" %}
{% endwith %}
<main class="main-content">
<div class="page-header fade-in flex-between">
<div>
<h1 class="page-title">Vehicle Management</h1>
<p class="page-subtitle">All vehicles currently in the system</p>
</div>
<button class="btn btn-outline" onclick="loadVehicles()"><span>πŸ”„</span> Refresh</button>
</div>
<div class="stats-grid fade-in">
<div class="stat-card blue">
<div class="stat-label">Total Vehicles</div>
<div class="stat-value blue" id="statTotal">β€”</div>
</div>
<div class="stat-card green">
<div class="stat-label">Currently Inside</div>
<div class="stat-value green" id="statActive">β€”</div>
</div>
<div class="stat-card purple">
<div class="stat-label">Registered</div>
<div class="stat-value purple" id="statRegistered">β€”</div>
</div>
<div class="stat-card red">
<div class="stat-label">Unregistered</div>
<div class="stat-value red" id="statUnregistered">β€”</div>
</div>
</div>
<div class="card fade-in">
<div class="table-wrapper" style="max-height:500px;overflow-y:auto;">
<table>
<thead>
<tr>
<th>Plate (AR)</th>
<th>Owner / User</th>
<th>Status</th>
<th>Duration</th>
<th>Fee</th>
<th>Payment</th>
</tr>
</thead>
<tbody id="vehBody">
<tr><td colspan="6" class="text-center" style="padding:2rem;color:var(--text-muted);">Loading...</td></tr></tbody>
</table>
</div>
</div>
</main>
</div>
{% endblock %}
{% block scripts %}
<script>
async function loadVehicles() {
try {
// Use inside-cars feed + history to show all known vehicles
const [insideR, histR] = await Promise.all([
fetch('/admin/api/inside-cars'),
fetch('/admin/api/history')
]);
const insideData = await insideR.json();
const histData = await histR.json();
const insideItems = insideData.items || insideData.data || [];
const histItems = histData.history || histData.items || histData.data || [];
// Deduplicate by session id
const seen = new Set();
const allItems = [];
// Inside cars first (active)
for (const item of insideItems) {
const s = item.session || item;
const sid = s.id || '';
if (sid && !seen.has(sid)) { seen.add(sid); allItems.push({...item, _isInside: true}); }
}
// Then history (completed)
for (const item of histItems) {
const s = item.session || item;
const sid = s.id || '';
if (sid && !seen.has(sid)) { seen.add(sid); allItems.push({...item, _isInside: false}); }
}
// Update stats
// Update stats
const unregistered = allItems.filter(i => {
const v = i.vehicle || {};
return !v.id && !v.plate_letters_ar;
}).length;
const registered = allItems.length - unregistered;
const insideCount = allItems.filter(i => i._isInside).length;
document.getElementById('statTotal').textContent = allItems.length;
document.getElementById('statActive').textContent = insideCount;
document.getElementById('statRegistered').textContent = registered;
document.getElementById('statUnregistered').textContent = unregistered;
renderVehicles(allItems);
} catch(e) { console.error(e); }
}
function formatDuration(mins) {
if (mins == null) return 'β€”';
const h = Math.floor(mins / 60);
const m = Math.floor(mins % 60);
return h > 0 ? `${h}h ${m}m` : `${m}m`;
}
function renderVehicles(items) {
const body = document.getElementById('vehBody');
if (!items || !items.length) {
body.innerHTML = '<tr><td colspan="6" class="text-center" style="padding:2rem;color:var(--text-muted);">No vehicles found</td></tr>';
return;
}
body.innerHTML = items.map(item => {
const s = item.session || item;
const v = item.vehicle || {};
const p = item.pricing || {};
const plateAr = s.plate_arabic || ((v.plate_letters_ar||'') + ' ' + (v.plate_numbers_ar||'')).trim() || 'β€”';
// Attempt to extract username from backend's profile if available (depends on API return shape)
// Sometimes backend attaches `owner` to the vehicle object
let ownerName = 'β€”';
if (v.owner) {
ownerName = v.owner.username || v.owner.full_name || v.owner.first_name || 'β€”';
} else if (v.owner_id) {
ownerName = 'Registered';
}
const isGuest = !v.id && !v.plate_letters_ar;
if (isGuest) {
ownerName = '<span class="status status-unregistered" style="font-size:0.7rem;padding:2px 6px;">Unregistered</span>';
}
const isInside = item._isInside;
const status = isInside ? 'Inside' : 'Left';
const stClass = isInside ? 'status-active' : 'status-unregistered';
const duration = formatDuration(p.duration_minutes);
const fee = p.total_fee_egp != null ? p.total_fee_egp + ' EGP' : 'β€”';
// Payment badge
const notes = (s.notes || '').toLowerCase();
const hasCashIntent = notes.includes('intent_cash') || notes.includes('cash');
let isCash = isGuest || hasCashIntent;
let isPaidVisa = s.payment_confirmed_at != null && !isCash;
let payBadge = 'β€”';
if (isPaidVisa) payBadge = '<span style="color:var(--accent-blue);">πŸ’³ Visa</span>';
else if (isCash) payBadge = '<span style="color:var(--accent-green);">πŸ’΅ Cash</span>';
else if (isInside) payBadge = '⏳ Pending';
return `<tr>
<td class="mono">${plateAr}</td>
<td>${ownerName}</td>
<td><span class="status ${stClass}">${status}</span></td>
<td>${duration}</td><td>${fee}</td>
<td>${payBadge}</td>
</tr>`;
}).join('');
}
loadVehicles();
</script>
{% endblock %}