Admin cleanup
This commit is contained in:
+190
-95
@@ -48,8 +48,47 @@
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- Arrivals Table -->
|
||||
<!-- Local Flights Table -->
|
||||
<div class="ppr-table">
|
||||
<div class="table-header">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>🛩️ Today's Local Flights - <span id="local-flights-count">0</span></span>
|
||||
<span class="info-icon" onclick="showTableHelp('local-flights')" title="What is this?">ℹ️</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="local-flights-loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
Loading local flights...
|
||||
</div>
|
||||
|
||||
<div id="local-flights-table-content" style="display: none;">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Registration</th>
|
||||
<th style="width: 30px; text-align: center;"></th>
|
||||
<th>Type</th>
|
||||
<th>Flight</th>
|
||||
<th>ETD</th>
|
||||
<th>POB</th>
|
||||
<th>Status</th>
|
||||
<th>Circuits</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="local-flights-table-body">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="local-flights-no-data" class="no-data" style="display: none;">
|
||||
<h3>No Local Flights</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Arrivals Table -->
|
||||
<div class="ppr-table" style="margin-top: 2rem;">
|
||||
<div class="table-header">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>🛬 Today's Pending Arrivals - <span id="arrivals-count">0</span></span>
|
||||
@@ -292,22 +331,21 @@
|
||||
|
||||
loadPPRsTimeout = setTimeout(async () => {
|
||||
// Load all tables simultaneously
|
||||
await Promise.all([loadArrivals(), loadDepartures(), loadOverflights(), loadDeparted(), loadParked(), loadUpcoming()]);
|
||||
await Promise.all([loadArrivals(), loadDepartures(), loadLocalFlights(), loadOverflights(), loadDeparted(), loadParked(), loadUpcoming()]);
|
||||
loadPPRsTimeout = null;
|
||||
}, 100); // Wait 100ms before executing to batch multiple calls
|
||||
}
|
||||
|
||||
// Load arrivals (NEW and CONFIRMED status for PPR, DEPARTED for local flights)
|
||||
// Load arrivals (NEW and CONFIRMED status for PPR only)
|
||||
async function loadArrivals() {
|
||||
document.getElementById('arrivals-loading').style.display = 'block';
|
||||
document.getElementById('arrivals-table-content').style.display = 'none';
|
||||
document.getElementById('arrivals-no-data').style.display = 'none';
|
||||
|
||||
try {
|
||||
// Load PPRs, local flights, and booked-in arrivals
|
||||
const [pprResponse, localResponse, bookInResponse] = await Promise.all([
|
||||
// Load PPRs and booked-in arrivals
|
||||
const [pprResponse, bookInResponse] = await Promise.all([
|
||||
authenticatedFetch('/api/v1/pprs/?limit=1000'),
|
||||
authenticatedFetch('/api/v1/local-flights/?status=DEPARTED&limit=1000'),
|
||||
authenticatedFetch('/api/v1/arrivals/?limit=1000')
|
||||
]);
|
||||
|
||||
@@ -328,24 +366,6 @@
|
||||
return etaDate === today;
|
||||
});
|
||||
|
||||
// Add local flights in DEPARTED status (in the air, heading back) - only those booked out today
|
||||
if (localResponse.ok) {
|
||||
const localFlights = await localResponse.json();
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const localInAir = localFlights
|
||||
.filter(flight => {
|
||||
// Only include flights booked out today (created_dt)
|
||||
if (!flight.created_dt) return false;
|
||||
const createdDate = flight.created_dt.split('T')[0];
|
||||
return createdDate === today;
|
||||
})
|
||||
.map(flight => ({
|
||||
...flight,
|
||||
isLocalFlight: true // Flag to distinguish from PPR
|
||||
}));
|
||||
arrivals.push(...localInAir);
|
||||
}
|
||||
|
||||
// Add booked-in arrivals from the arrivals table
|
||||
if (bookInResponse.ok) {
|
||||
const bookedInArrivals = await bookInResponse.json();
|
||||
@@ -375,20 +395,16 @@
|
||||
document.getElementById('arrivals-loading').style.display = 'none';
|
||||
}
|
||||
|
||||
// Load departures (LANDED status for PPR, GROUND/LOCAL for local flights)
|
||||
// Load departures (LANDED status for PPR plus non-local airport departures)
|
||||
async function loadDepartures() {
|
||||
document.getElementById('departures-loading').style.display = 'block';
|
||||
document.getElementById('departures-table-content').style.display = 'none';
|
||||
document.getElementById('departures-no-data').style.display = 'none';
|
||||
|
||||
try {
|
||||
// Load PPR departures, local flight departures, and airport departures simultaneously
|
||||
const [pprResponse, localBookedOutResponse, localOutGroundResponse, localLocalResponse, localCircuitResponse, depBookedOutResponse, depOutGroundResponse, depLocalResponse] = await Promise.all([
|
||||
// Load PPR departures and airport departures simultaneously
|
||||
const [pprResponse, depBookedOutResponse, depOutGroundResponse, depLocalResponse] = await Promise.all([
|
||||
authenticatedFetch('/api/v1/pprs/?limit=1000'),
|
||||
authenticatedFetch('/api/v1/local-flights/?status=BOOKED_OUT&limit=1000'),
|
||||
authenticatedFetch('/api/v1/local-flights/?status=GROUND&limit=1000'),
|
||||
authenticatedFetch('/api/v1/local-flights/?status=LOCAL&limit=1000'),
|
||||
authenticatedFetch('/api/v1/local-flights/?status=CIRCUIT&limit=1000'),
|
||||
authenticatedFetch('/api/v1/departures/?status=BOOKED_OUT&limit=1000'),
|
||||
authenticatedFetch('/api/v1/departures/?status=GROUND&limit=1000'),
|
||||
authenticatedFetch('/api/v1/departures/?status=LOCAL&limit=1000')
|
||||
@@ -399,16 +415,10 @@
|
||||
}
|
||||
|
||||
const allPPRs = await pprResponse.json();
|
||||
const localBookedOut = localBookedOutResponse.ok ? await localBookedOutResponse.json() : [];
|
||||
const localOutGround = localOutGroundResponse.ok ? await localOutGroundResponse.json() : [];
|
||||
const localLocal = localLocalResponse.ok ? await localLocalResponse.json() : [];
|
||||
const localCircuit = localCircuitResponse.ok ? await localCircuitResponse.json() : [];
|
||||
const depBookedOut = depBookedOutResponse.ok ? await depBookedOutResponse.json() : [];
|
||||
const depOutGround = depOutGroundResponse.ok ? await depOutGroundResponse.json() : [];
|
||||
const depLocal = depLocalResponse.ok ? await depLocalResponse.json() : [];
|
||||
|
||||
// Combine local flights
|
||||
const allLocalFlights = [...localBookedOut, ...localOutGround, ...localLocal, ...localCircuit];
|
||||
// Combine departures
|
||||
const allDepartures = [...depBookedOut, ...depOutGround, ...depLocal];
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
@@ -423,20 +433,6 @@
|
||||
return etdDate === today;
|
||||
});
|
||||
|
||||
// Add local flights (GROUND and LOCAL status - ready to go) - only those booked out today
|
||||
const localDepartures = allLocalFlights
|
||||
.filter(flight => {
|
||||
// Only include flights booked out today (created_dt)
|
||||
if (!flight.created_dt) return false;
|
||||
const createdDate = flight.created_dt.split('T')[0];
|
||||
return createdDate === today;
|
||||
})
|
||||
.map(flight => ({
|
||||
...flight,
|
||||
isLocalFlight: true // Flag to distinguish from PPR
|
||||
}));
|
||||
departures.push(...localDepartures);
|
||||
|
||||
// Add departures to other airports (BOOKED_OUT, GROUND, and LOCAL status)
|
||||
const depDepartures = allDepartures.map(flight => ({
|
||||
...flight,
|
||||
@@ -455,6 +451,35 @@
|
||||
document.getElementById('departures-loading').style.display = 'none';
|
||||
}
|
||||
|
||||
async function loadLocalFlights() {
|
||||
document.getElementById('local-flights-loading').style.display = 'block';
|
||||
document.getElementById('local-flights-table-content').style.display = 'none';
|
||||
document.getElementById('local-flights-no-data').style.display = 'none';
|
||||
|
||||
try {
|
||||
const response = await authenticatedFetch('/api/v1/local-flights/?limit=1000');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch local flights');
|
||||
}
|
||||
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const localFlights = (await response.json()).filter(flight => {
|
||||
if (!flight.created_dt || ['CANCELLED', 'LANDED'].includes(flight.status)) return false;
|
||||
return flight.created_dt.split('T')[0] === today;
|
||||
});
|
||||
|
||||
displayLocalFlights(localFlights);
|
||||
} catch (error) {
|
||||
console.error('Error loading local flights:', error);
|
||||
if (error.message !== 'Session expired. Please log in again.') {
|
||||
showNotification('Error loading local flights', true);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('local-flights-loading').style.display = 'none';
|
||||
}
|
||||
|
||||
// Load overflights (ACTIVE status only)
|
||||
async function loadOverflights() {
|
||||
document.getElementById('overflights-loading').style.display = 'block';
|
||||
@@ -1011,9 +1036,6 @@
|
||||
// Different action buttons based on status
|
||||
if (flight.status === 'INBOUND') {
|
||||
actionButtons = `
|
||||
<button class="btn btn-primary btn-icon" onclick="event.stopPropagation(); updateArrivalStatusFromTable(${flight.id}, 'LOCAL')" title="Mark as Local">
|
||||
LOCAL
|
||||
</button>
|
||||
<button class="btn btn-success btn-icon" onclick="event.stopPropagation(); currentBookedInArrivalId = ${flight.id}; showTimestampModal('LANDED', ${flight.id}, false, false, true)" title="Mark as Landed">
|
||||
LAND
|
||||
</button>
|
||||
@@ -1027,9 +1049,6 @@
|
||||
T&G
|
||||
</button>`;
|
||||
actionButtons = `
|
||||
<button class="btn btn-warning btn-icon" onclick="event.stopPropagation(); updateArrivalStatusFromTable(${flight.id}, 'CIRCUIT')" title="Join Circuit">
|
||||
CIRCUIT
|
||||
</button>
|
||||
${circuitButton}
|
||||
<button class="btn btn-success btn-icon" onclick="event.stopPropagation(); currentArrivalId = ${flight.id}; showTimestampModal('LANDED', ${flight.id}, false, false, true)" title="Mark as Landed">
|
||||
LAND
|
||||
@@ -1041,9 +1060,6 @@
|
||||
T&G
|
||||
</button>`;
|
||||
actionButtons = `
|
||||
<button class="btn btn-primary btn-icon" onclick="event.stopPropagation(); updateArrivalStatusFromTable(${flight.id}, 'LOCAL')" title="Move to Local Area">
|
||||
LOCAL
|
||||
</button>
|
||||
${circuitButton}
|
||||
<button class="btn btn-success btn-icon" onclick="event.stopPropagation(); currentArrivalId = ${flight.id}; showTimestampModal('LANDED', ${flight.id}, false, false, true)" title="Mark as Landed">
|
||||
LAND
|
||||
@@ -1086,9 +1102,6 @@
|
||||
: '';
|
||||
actionButtons = `
|
||||
${ackButton}
|
||||
<button class="btn btn-primary btn-icon" onclick="event.stopPropagation(); activatePPR(${flight.id}, '${flight.ac_reg}', ${flight.out_to ? 'true' : 'false'})" title="Activate PPR - create arrival/departure records">
|
||||
ACTIVATE
|
||||
</button>
|
||||
<button class="btn btn-warning btn-icon" onclick="event.stopPropagation(); showTimestampModal('LANDED', ${flight.id})" title="Mark as Landed">
|
||||
LAND
|
||||
</button>
|
||||
@@ -1111,6 +1124,115 @@
|
||||
setupTooltips();
|
||||
}
|
||||
|
||||
function localFlightStatusBadge(status) {
|
||||
const labels = {
|
||||
BOOKED_OUT: 'BOOKED OUT',
|
||||
GROUND: 'GROUND',
|
||||
DEPARTED: 'AIRBORNE',
|
||||
LOCAL: 'LOCAL',
|
||||
CIRCUIT: 'CIRCUIT',
|
||||
CIRCUIT_DOWNWIND: 'DOWNWIND',
|
||||
CIRCUIT_BASE: 'BASE',
|
||||
CIRCUIT_FINAL: 'FINAL',
|
||||
LANDED: 'LANDED'
|
||||
};
|
||||
return `<span style="background-color: #6c757d; color: white; padding: 2px 6px; border-radius: 3px; font-size: 0.8rem;">${labels[status] || status || '-'}</span>`;
|
||||
}
|
||||
|
||||
async function displayLocalFlights(localFlights) {
|
||||
const tbody = document.getElementById('local-flights-table-body');
|
||||
const recordCount = document.getElementById('local-flights-count');
|
||||
|
||||
recordCount.textContent = localFlights.length;
|
||||
if (localFlights.length === 0) {
|
||||
document.getElementById('local-flights-no-data').style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
localFlights.sort((a, b) => {
|
||||
const aTime = a.etd || a.created_dt;
|
||||
const bTime = b.etd || b.created_dt;
|
||||
if (!aTime) return 1;
|
||||
if (!bTime) return -1;
|
||||
return new Date(aTime) - new Date(bTime);
|
||||
});
|
||||
|
||||
tbody.innerHTML = '';
|
||||
document.getElementById('local-flights-table-content').style.display = 'block';
|
||||
|
||||
const circuitCounts = await loadLocalFlightCircuitCounts(localFlights);
|
||||
|
||||
for (const flight of localFlights) {
|
||||
const row = document.createElement('tr');
|
||||
row.onclick = () => openLocalFlightEditModal(flight.id);
|
||||
|
||||
const aircraftDisplay = flight.callsign && flight.callsign.trim()
|
||||
? `<strong>${flight.callsign}</strong><br><span style="font-size: 0.8em; color: #666; font-style: italic;">${flight.registration}</span>`
|
||||
: `<strong>${flight.registration}</strong>`;
|
||||
const typeIcon = flight.submitted_via === 'PUBLIC'
|
||||
? '<span style="color: #b8860b; font-weight: bold; font-size: 0.9em;" title="Submitted by Pilot Online">O</span>'
|
||||
: '<span style="color: #228b22; font-weight: bold; font-size: 0.9em;" title="Local flight">L</span>';
|
||||
const flightType = flight.flight_type === 'CIRCUITS' ? 'Circuits' : flight.flight_type === 'LOCAL' ? 'Local' : 'Departure';
|
||||
const etd = flight.etd ? formatTimeOnly(flight.etd) : (flight.created_dt ? formatTimeOnly(flight.created_dt) : '-');
|
||||
const circuits = circuitCounts[flight.id] ?? flight.circuits ?? 0;
|
||||
|
||||
let actionButtons = '';
|
||||
if (flight.status === 'BOOKED_OUT') {
|
||||
actionButtons = `
|
||||
<button class="btn btn-warning btn-icon" onclick="event.stopPropagation(); currentLocalFlightId = ${flight.id}; showTimestampModal('GROUND', ${flight.id}, true)" title="Contact Pilot">
|
||||
CONTACT
|
||||
</button>
|
||||
`;
|
||||
} else if (flight.status === 'GROUND') {
|
||||
const takeoffStatus = flight.flight_type === 'CIRCUITS' ? 'CIRCUIT' : 'LOCAL';
|
||||
actionButtons = `
|
||||
<button class="btn btn-primary btn-icon" onclick="event.stopPropagation(); currentLocalFlightId = ${flight.id}; showTimestampModal('${takeoffStatus}', ${flight.id}, true)" title="Mark as airborne">
|
||||
TAKE OFF
|
||||
</button>
|
||||
`;
|
||||
} else if (['DEPARTED', 'LOCAL', 'CIRCUIT', 'CIRCUIT_DOWNWIND', 'CIRCUIT_BASE', 'CIRCUIT_FINAL'].includes(flight.status)) {
|
||||
actionButtons = `
|
||||
<button class="btn btn-info btn-icon" onclick="event.stopPropagation(); currentLocalFlightId = ${flight.id}; showCircuitModal()" title="Record Touch & Go">
|
||||
T&G
|
||||
</button>
|
||||
<button class="btn btn-success btn-icon" onclick="event.stopPropagation(); currentLocalFlightId = ${flight.id}; showTimestampModal('LANDED', ${flight.id}, true)" title="Mark as Landed">
|
||||
LAND
|
||||
</button>
|
||||
`;
|
||||
} else {
|
||||
actionButtons = '<span style="color: #999;">-</span>';
|
||||
}
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${aircraftDisplay}</td>
|
||||
<td style="text-align: center; width: 30px;">${typeIcon}</td>
|
||||
<td>${flight.type || '-'}</td>
|
||||
<td>${flightType}</td>
|
||||
<td>${etd}</td>
|
||||
<td>${flight.pob || '-'}</td>
|
||||
<td>${localFlightStatusBadge(flight.status)}</td>
|
||||
<td>${circuits}</td>
|
||||
<td style="white-space: nowrap;">${actionButtons}</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadLocalFlightCircuitCounts(localFlights) {
|
||||
const counts = {};
|
||||
await Promise.all(localFlights.map(async (flight) => {
|
||||
try {
|
||||
const response = await authenticatedFetch(`/api/v1/circuits/flight/${flight.id}`);
|
||||
if (!response.ok) return;
|
||||
const circuits = await response.json();
|
||||
counts[flight.id] = circuits.length;
|
||||
} catch (error) {
|
||||
console.warn(`Unable to load circuits for local flight ${flight.id}:`, error);
|
||||
}
|
||||
}));
|
||||
return counts;
|
||||
}
|
||||
|
||||
function pprNeedsStripAck(ppr) {
|
||||
return (ppr.status === 'NEW' || ppr.status === 'CONFIRMED') && !ppr.acknowledged_dt;
|
||||
}
|
||||
@@ -1134,27 +1256,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function activatePPR(pprId, acReg, hasDeparture) {
|
||||
const msg = `Activate PPR for ${acReg}?\nThis will create an INBOUND arrival.`
|
||||
+ (hasDeparture ? '\nThe outbound departure will appear automatically when the aircraft lands.' : '');
|
||||
if (!confirm(msg)) return;
|
||||
|
||||
try {
|
||||
const response = await authenticatedFetch(`/api/v1/pprs/${pprId}/activate`, { method: 'POST' });
|
||||
if (!response.ok) {
|
||||
const err = await response.json().catch(() => ({}));
|
||||
showNotification(err.detail || 'Failed to activate PPR', true);
|
||||
return;
|
||||
}
|
||||
const result = await response.json();
|
||||
showNotification(result.message || 'PPR activated');
|
||||
await loadPPRs();
|
||||
} catch (error) {
|
||||
console.error('Error activating PPR:', error);
|
||||
showNotification('Error activating PPR', true);
|
||||
}
|
||||
}
|
||||
|
||||
async function displayDepartures(departures) {
|
||||
const tbody = document.getElementById('departures-table-body');
|
||||
const recordCount = document.getElementById('departures-count');
|
||||
@@ -1245,8 +1346,8 @@
|
||||
`;
|
||||
} else if (flight.status === 'LOCAL') {
|
||||
actionButtons = `
|
||||
<button class="btn btn-info btn-icon" onclick="event.stopPropagation(); currentLocalFlightId = ${flight.id}; updateLocalFlightStatusFromTable(${flight.id}, 'CIRCUIT')" title="Rejoin Circuit">
|
||||
REJOIN
|
||||
<button class="btn btn-success btn-icon" onclick="event.stopPropagation(); currentLocalFlightId = ${flight.id}; showTimestampModal('LANDED', ${flight.id}, true)" title="Mark as Landed">
|
||||
LAND
|
||||
</button>
|
||||
`;
|
||||
} else if (flight.status === 'CIRCUIT') {
|
||||
@@ -1255,9 +1356,6 @@
|
||||
T&G
|
||||
</button>`;
|
||||
actionButtons = `
|
||||
<button class="btn btn-warning btn-icon" onclick="event.stopPropagation(); currentLocalFlightId = ${flight.id}; updateLocalFlightStatusFromTable(${flight.id}, 'LOCAL')" title="Move to Local Area">
|
||||
LOCAL
|
||||
</button>
|
||||
${circuitButton}
|
||||
<button class="btn btn-success btn-icon" onclick="event.stopPropagation(); currentLocalFlightId = ${flight.id}; showTimestampModal('LANDED', ${flight.id}, true)" title="Mark as Landed">
|
||||
LAND
|
||||
@@ -1324,15 +1422,12 @@
|
||||
// Action buttons for arrival
|
||||
if (flight.status === 'LOCAL') {
|
||||
actionButtons = `
|
||||
<button class="btn btn-info btn-icon" onclick="event.stopPropagation(); currentArrivalId = ${flight.id}; updateArrivalStatusFromTable(${flight.id}, 'CIRCUIT')" title="Rejoin Circuit">
|
||||
REJOIN
|
||||
<button class="btn btn-success btn-icon" onclick="event.stopPropagation(); currentArrivalId = ${flight.id}; updateArrivalStatusFromTable(${flight.id}, 'LANDED')" title="Mark as Landed">
|
||||
LAND
|
||||
</button>
|
||||
`;
|
||||
} else if (flight.status === 'CIRCUIT') {
|
||||
actionButtons = `
|
||||
<button class="btn btn-warning btn-icon" onclick="event.stopPropagation(); currentArrivalId = ${flight.id}; updateArrivalStatusFromTable(${flight.id}, 'LOCAL')" title="Move to Local Area">
|
||||
LOCAL
|
||||
</button>
|
||||
<button class="btn btn-info btn-icon" onclick="event.stopPropagation(); currentArrivalId = ${flight.id}; showCircuitModal(null, ${flight.id})" title="Record Touch & Go">
|
||||
T&G
|
||||
</button>
|
||||
@@ -1472,7 +1567,7 @@
|
||||
// Override: admin uses loadDepartures after circuit save
|
||||
async function afterCircuitSaved() {
|
||||
closeCircuitModal();
|
||||
loadDepartures();
|
||||
loadLocalFlights();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user