Drone req handling update
This commit is contained in:
+89
-20
@@ -123,6 +123,17 @@
|
||||
background: #eef6fb;
|
||||
}
|
||||
|
||||
.request-group {
|
||||
background: #f4f7f9;
|
||||
border-bottom: 1px solid #dfe6ed;
|
||||
color: #34495e;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.02em;
|
||||
padding: 0.55rem 1rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.request-ref {
|
||||
font-weight: 700;
|
||||
color: #263645;
|
||||
@@ -150,7 +161,6 @@
|
||||
.status-NEW { background: #3498db; }
|
||||
.status-APPROVED { background: #27ae60; }
|
||||
.status-DENIED { background: #c0392b; }
|
||||
.status-PENDING { background: #f39c12; }
|
||||
.status-CANCELED { background: #7f8c8d; }
|
||||
.status-INFLIGHT { background: #8e44ad; }
|
||||
.status-COMPLETED { background: #2c3e50; }
|
||||
@@ -373,15 +383,10 @@
|
||||
<div class="detail-meta">Requests from the public drone flight form</div>
|
||||
</div>
|
||||
<div class="filter-row">
|
||||
<select id="status-filter" aria-label="Status filter">
|
||||
<option value="">All statuses</option>
|
||||
<option value="NEW">New</option>
|
||||
<option value="PENDING">Pending</option>
|
||||
<option value="APPROVED">Approved</option>
|
||||
<option value="DENIED">Denied</option>
|
||||
<option value="CANCELED">Canceled</option>
|
||||
<option value="INFLIGHT">Inflight</option>
|
||||
<option value="COMPLETED">Completed</option>
|
||||
<select id="request-view-filter" aria-label="Request view">
|
||||
<option value="active">Active queue</option>
|
||||
<option value="older">Earlier dates</option>
|
||||
<option value="closed">Denied / canceled</option>
|
||||
</select>
|
||||
<button class="btn btn-primary" onclick="loadRequests()">Refresh</button>
|
||||
</div>
|
||||
@@ -479,6 +484,7 @@
|
||||
let currentUser = null;
|
||||
let selectedRequest = null;
|
||||
let requests = [];
|
||||
let requestGroups = [];
|
||||
let map = null;
|
||||
let mapLayers = [];
|
||||
let frzGeometry = null;
|
||||
@@ -486,7 +492,10 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.getElementById('login-form').addEventListener('submit', handleLogin);
|
||||
document.getElementById('message-form').addEventListener('submit', sendMessageFromModal);
|
||||
document.getElementById('status-filter').addEventListener('change', loadRequests);
|
||||
document.getElementById('request-view-filter').addEventListener('change', () => {
|
||||
clearSelectedRequest();
|
||||
loadRequests();
|
||||
});
|
||||
initializeAuth();
|
||||
initializeMap();
|
||||
connectWebSocket();
|
||||
@@ -607,15 +616,13 @@
|
||||
|
||||
async function loadRequests() {
|
||||
if (!accessToken) return;
|
||||
const status = document.getElementById('status-filter').value;
|
||||
const url = status ? `/api/v1/drone-requests/?status=${encodeURIComponent(status)}` : '/api/v1/drone-requests/';
|
||||
const view = document.getElementById('request-view-filter').value;
|
||||
const body = document.getElementById('request-list-body');
|
||||
body.innerHTML = '<div class="empty-state">Loading requests...</div>';
|
||||
|
||||
try {
|
||||
const response = await authenticatedFetch(url);
|
||||
if (!response.ok) throw new Error('Failed to load drone requests');
|
||||
requests = await response.json();
|
||||
requestGroups = await loadRequestGroups(view);
|
||||
requests = requestGroups.flatMap(group => group.items);
|
||||
renderRequestList();
|
||||
if (selectedRequest) {
|
||||
const fresh = requests.find(r => r.id === selectedRequest.id);
|
||||
@@ -634,16 +641,79 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRequestGroups(view) {
|
||||
const today = getLocalDateString(new Date());
|
||||
const yesterday = getLocalDateString(addDays(new Date(), -1));
|
||||
|
||||
if (view === 'older') {
|
||||
const older = await fetchDroneRequests({ date_to: yesterday });
|
||||
return [{ label: 'Earlier dated requests', items: older }];
|
||||
}
|
||||
|
||||
if (view === 'closed') {
|
||||
const [denied, canceled] = await Promise.all([
|
||||
fetchDroneRequests({ status: 'DENIED' }),
|
||||
fetchDroneRequests({ status: 'CANCELED' }),
|
||||
]);
|
||||
return [
|
||||
{ label: 'Denied requests', items: denied },
|
||||
{ label: 'Canceled requests', items: canceled },
|
||||
];
|
||||
}
|
||||
|
||||
const [newRequests, approvedToday, approvedUpcoming] = await Promise.all([
|
||||
fetchDroneRequests({ status: 'NEW' }),
|
||||
fetchDroneRequests({ status: 'APPROVED', date_from: today, date_to: today }),
|
||||
fetchDroneRequests({ status: 'APPROVED', date_from: getLocalDateString(addDays(new Date(), 1)) }),
|
||||
]);
|
||||
return [
|
||||
{ label: 'New requests', items: newRequests },
|
||||
{ label: "Today's approved flights", items: approvedToday },
|
||||
{ label: 'Upcoming approved flights', items: approvedUpcoming },
|
||||
];
|
||||
}
|
||||
|
||||
async function fetchDroneRequests(params) {
|
||||
const search = new URLSearchParams();
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value) search.set(key, value);
|
||||
});
|
||||
const url = `/api/v1/drone-requests/${search.toString() ? `?${search.toString()}` : ''}`;
|
||||
const response = await authenticatedFetch(url);
|
||||
if (!response.ok) throw new Error('Failed to load drone requests');
|
||||
return response.json();
|
||||
}
|
||||
|
||||
function addDays(date, days) {
|
||||
const next = new Date(date);
|
||||
next.setDate(next.getDate() + days);
|
||||
return next;
|
||||
}
|
||||
|
||||
function getLocalDateString(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
function renderRequestList() {
|
||||
document.getElementById('request-count').textContent = requests.length;
|
||||
setQueueOpen(!selectedRequest || document.getElementById('workspace').classList.contains('queue-open'));
|
||||
const body = document.getElementById('request-list-body');
|
||||
if (!requests.length) {
|
||||
if (!requestGroups.length) {
|
||||
body.innerHTML = '<div class="empty-state">No requests match the current filter.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
body.innerHTML = requests.map(req => `
|
||||
body.innerHTML = requestGroups.map(group => `
|
||||
<div class="request-group">${escapeHtml(group.label)} - ${group.items.length}</div>
|
||||
${group.items.length ? group.items.map(renderRequestRow).join('') : '<div class="empty-state">None</div>'}
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function renderRequestRow(req) {
|
||||
return `
|
||||
<button class="request-row ${selectedRequest && selectedRequest.id === req.id ? 'active' : ''}" onclick="selectRequest(${req.id})">
|
||||
<div>
|
||||
<div class="request-ref">${escapeHtml(req.reference_number)}</div>
|
||||
@@ -656,7 +726,7 @@
|
||||
</div>
|
||||
<span class="status-pill status-${req.status}">${req.status}</span>
|
||||
</button>
|
||||
`).join('');
|
||||
`;
|
||||
}
|
||||
|
||||
async function selectRequest(id, collapseQueue = true) {
|
||||
@@ -753,7 +823,6 @@
|
||||
actions.innerHTML = `
|
||||
<select id="status-select" class="lifecycle-control" aria-label="New status">
|
||||
<option value="NEW">NEW</option>
|
||||
<option value="PENDING">PENDING</option>
|
||||
<option value="APPROVED">APPROVED</option>
|
||||
<option value="DENIED">DENIED</option>
|
||||
<option value="CANCELED">CANCELED</option>
|
||||
|
||||
Reference in New Issue
Block a user