Admin cleanup

This commit is contained in:
2026-06-20 07:38:09 -04:00
parent 733e9b426f
commit a9b5ec67ba
4 changed files with 296 additions and 118 deletions
+190 -95
View File
@@ -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>