Flow improvements

This commit is contained in:
2026-06-29 07:15:01 -04:00
parent 8d8cb9ccad
commit 0a49dfe219
16 changed files with 281 additions and 102 deletions
+67 -41
View File
@@ -255,11 +255,11 @@
</div>
</div>
<!-- Row 1: Local Area -->
<!-- Row 1: Local Traffic -->
<div class="atc-section">
<h2>📍 Local Area <span class="count" id="local-count">0</span></h2>
<h2>📍 Local Traffic <span class="count" id="local-count">0</span></h2>
<div class="aircraft-list" id="local-list">
<div class="no-aircraft">No aircraft in local area</div>
<div class="no-aircraft">No local traffic</div>
</div>
</div>
@@ -445,15 +445,13 @@
document.getElementById('departures-no-data').style.display = 'none';
try {
// Load PPR departures, local flight departures, and airport departures simultaneously
const [pprResponse, localBookedOutResponse, localOutGroundResponse, localLocalResponse, depBookedOutResponse, depOutGroundResponse, depLocalResponse] = await Promise.all([
// Load PPR departures, local flight departures, and airport departures that are still pending departure
const [pprResponse, localBookedOutResponse, localOutGroundResponse, depBookedOutResponse, depOutGroundResponse] = 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/departures/?status=BOOKED_OUT&limit=1000'),
authenticatedFetch('/api/v1/departures/?status=GROUND&limit=1000'),
authenticatedFetch('/api/v1/departures/?status=LOCAL&limit=1000')
authenticatedFetch('/api/v1/departures/?status=GROUND&limit=1000')
]);
if (!pprResponse.ok) {
@@ -463,15 +461,13 @@
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 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];
const allLocalFlights = [...localBookedOut, ...localOutGround];
// Combine departures
const allDepartures = [...depBookedOut, ...depOutGround, ...depLocal];
const allDepartures = [...depBookedOut, ...depOutGround];
const today = new Date().toISOString().split('T')[0];
// Filter for PPR departures with ETD today and LANDED status only
@@ -484,7 +480,7 @@
return etdDate === today;
});
// Add local flights (GROUND and LOCAL status - ready to go) - only those booked out today
// Add local flights that are not yet airborne locally - only those booked out today
const localDepartures = allLocalFlights
.filter(flight => {
// Only include flights booked out today (created_dt)
@@ -498,7 +494,7 @@
}));
departures.push(...localDepartures);
// Add departures to other airports (BOOKED_OUT, GROUND, and LOCAL status)
// Add departures to other airports that are not yet airborne locally
const depDepartures = allDepartures.map(flight => ({
...flight,
isDeparture: true // Flag to distinguish from PPR
@@ -602,7 +598,7 @@
}
// Load departed aircraft (DEPARTED status with departed_dt today)
// Load departed aircraft (DEPARTED status with QSY/departed time today)
async function loadDeparted() {
document.getElementById('departed-loading').style.display = 'block';
document.getElementById('departed-table-content').style.display = 'none';
@@ -623,10 +619,10 @@
// Filter for PPRs departed today (only PPR'd departures, exclude local/circuits)
const departed = allPPRs.filter(ppr => {
if (!ppr.departed_dt || ppr.status !== 'DEPARTED') {
if (!ppr.qsy_dt || ppr.status !== 'DEPARTED') {
return false;
}
const departedDate = ppr.departed_dt.split('T')[0];
const departedDate = ppr.qsy_dt.split('T')[0];
return departedDate === today;
});
@@ -667,8 +663,8 @@
// Sort by departed time
departed.sort((a, b) => {
const aTime = a.departed_dt;
const bTime = b.departed_dt;
const aTime = a.isDeparture ? a.departed_dt : a.qsy_dt;
const bTime = b.isDeparture ? b.departed_dt : b.qsy_dt;
return parseUtcDate(aTime) - parseUtcDate(bTime);
});
@@ -713,7 +709,7 @@
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important; text-align: center; width: 30px;"><span style="color: #032cfc; font-weight: bold;" title="From PPR">P</span></td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${flight.ac_call || '-'}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${flight.out_to || '-'}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${formatTimeOnly(flight.departed_dt)}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${formatTimeOnly(flight.qsy_dt)}</td>
`;
}
tbody.appendChild(row);
@@ -1088,7 +1084,7 @@
T&G
</button>`;
actionButtons = `
<button class="btn btn-primary btn-icon" onclick="event.stopPropagation(); updateArrivalStatusFromTable(${flight.id}, 'LOCAL')" title="Move to Local Area">
<button class="btn btn-primary btn-icon" onclick="event.stopPropagation(); updateArrivalStatusFromTable(${flight.id}, 'LOCAL')" title="Move to Local Traffic">
LOCAL
</button>
${circuitButton}
@@ -1216,12 +1212,13 @@
</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('DEPARTED', ${flight.id}, true)" title="Mark as Departed">
<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 (flight.status === 'DEPARTED') {
} else if (['DEPARTED', 'LOCAL', 'CIRCUIT', 'CIRCUIT_DOWNWIND', 'CIRCUIT_BASE', 'CIRCUIT_FINAL'].includes(flight.status)) {
// Allow touch and go for all local flight types
let circuitButton = `<button class="btn btn-info btn-icon" onclick="event.stopPropagation(); currentLocalFlightId = ${flight.id}; showCircuitModal()" title="Record Touch & Go">
T&G
@@ -1293,11 +1290,21 @@
fuel = flight.fuel || '-';
landedDt = flight.landed_dt ? formatTimeOnly(flight.landed_dt) : '-';
actionButtons = `
<button class="btn btn-primary btn-icon" onclick="event.stopPropagation(); showTimestampModal('DEPARTED', ${flight.id})" title="Mark as Departed">
TAKE OFF
</button>
`;
if (flight.status === 'LANDED') {
actionButtons = `
<button class="btn btn-primary btn-icon" onclick="event.stopPropagation(); showTimestampModal('LOCAL', ${flight.id})" title="Mark as Local">
TAKE OFF
</button>
`;
} else if (flight.status === 'LOCAL') {
actionButtons = `
<button class="btn btn-success btn-icon" onclick="event.stopPropagation(); showTimestampModal('DEPARTED', ${flight.id})" title="Mark as Departed">
QSY
</button>
`;
} else {
actionButtons = '<span style="color: #999;">-</span>';
}
}
row.innerHTML = `
@@ -1412,19 +1419,26 @@
// Load departing aircraft (ready to take off)
async function loadDepartingAircraft() {
try {
const [groundDeparturesResponse, groundLocalResponse] = await Promise.all([
const [pprResponse, groundDeparturesResponse, groundLocalResponse] = await Promise.all([
authenticatedFetch('/api/v1/pprs/?limit=1000'),
authenticatedFetch('/api/v1/departures/?status=GROUND&limit=1000'),
authenticatedFetch('/api/v1/local-flights/?status=GROUND&limit=1000')
]);
let groundAircraft = [];
if (groundDeparturesResponse.ok) groundAircraft = await groundDeparturesResponse.json();
if (pprResponse.ok) {
const today = getLocalDateString();
groundAircraft = (await pprResponse.json())
.filter(ppr => ppr.status === 'LANDED' && ppr.etd && ppr.etd.split('T')[0] === today)
.map(ppr => ({ ...ppr, isPPR: true }));
}
if (groundDeparturesResponse.ok) groundAircraft = groundAircraft.concat(await groundDeparturesResponse.json());
if (groundLocalResponse.ok) groundAircraft = groundAircraft.concat((await groundLocalResponse.json()).map(l => ({ ...l, isLocalFlight: true })));
groundAircraft = groundAircraft.filter(ac => isTodayRecord(ac, ['created_dt', 'etd']));
displayDepartingAircraft(groundAircraft.map(ac => ({
...ac,
isDeparture: !ac.isLocalFlight
isDeparture: !ac.isLocalFlight && !ac.isPPR
})));
} catch (error) {
console.error('Error loading departing aircraft:', error);
@@ -1447,6 +1461,7 @@
const type = ac.ac_type || ac.type;
const dest = ac.out_to;
const isLocal = ac.isLocalFlight;
const isPPR = ac.isPPR;
// All aircraft in awaiting departure are in GROUND status
let takeoffOnclick, buttonText, buttonTitle, clickType;
@@ -1457,13 +1472,18 @@
buttonText = 'TAKE OFF';
buttonTitle = takeoffTitle;
clickType = 'local';
} else if (isPPR) {
takeoffOnclick = `event.stopPropagation(); showTimestampModal('LOCAL', ${ac.id})`;
buttonText = 'TAKE OFF';
buttonTitle = 'Mark as Local';
clickType = 'ppr';
} else {
takeoffOnclick = `event.stopPropagation(); currentDepartureId = '${ac.id}'; showTimestampModal('LOCAL', ${ac.id}, false, true)`;
buttonText = 'TAKE OFF';
buttonTitle = 'Mark as Local';
clickType = 'departure';
}
const itemClass = isLocal ? 'local-flight' : 'departure';
const itemClass = isLocal ? 'local-flight' : (isPPR ? 'departure' : 'departure');
return `
<div class="aircraft-item ${itemClass}" onclick="handleATCClick('${ac.id}', '${clickType}')">
@@ -1481,6 +1501,7 @@
async function loadLocalAircraft() {
try {
const response = await Promise.all([
authenticatedFetch('/api/v1/pprs/?status=LOCAL&limit=1000'),
authenticatedFetch('/api/v1/local-flights/?status=LOCAL&limit=1000'),
authenticatedFetch('/api/v1/departures/?status=LOCAL&limit=1000'),
authenticatedFetch('/api/v1/arrivals/?status=LOCAL&limit=1000'),
@@ -1488,12 +1509,14 @@
]);
let locals = [];
if (response[0].ok) locals = (await response[0].json()).map(l => ({ ...l, isLocalFlight: true }));
if (response[1].ok) locals = locals.concat((await response[1].json()).map(d => ({ ...d, isDeparture: true })));
if (response[2].ok) locals = locals.concat((await response[2].json()).map(a => ({ ...a, isArrival: true })));
if (response[3].ok) locals = locals.concat((await response[3].json()).map(o => ({ ...o, isOverflight: true })));
if (response[0].ok) locals = (await response[0].json()).map(p => ({ ...p, isPPR: true }));
if (response[1].ok) locals = locals.concat((await response[1].json()).map(l => ({ ...l, isLocalFlight: true })));
if (response[2].ok) locals = locals.concat((await response[2].json()).map(d => ({ ...d, isDeparture: true })));
if (response[3].ok) locals = locals.concat((await response[3].json()).map(a => ({ ...a, isArrival: true })));
if (response[4].ok) locals = locals.concat((await response[4].json()).map(o => ({ ...o, isOverflight: true })));
locals = locals.filter(ac => {
if (ac.isOverflight) return true;
if (ac.isPPR) return isTodayRecord(ac, ['etd', 'landed_dt', 'submitted_dt']);
if (ac.isLocalFlight) return isTodayRecord(ac, ['created_dt', 'etd', 'takeoff_dt', 'departed_dt']);
if (ac.isArrival) return isTodayRecord(ac, ['created_dt', 'eta']);
if (ac.isDeparture) return isTodayRecord(ac, ['created_dt', 'etd', 'takeoff_dt', 'departed_dt']);
@@ -1513,7 +1536,7 @@
countEl.textContent = aircraft.length;
if (aircraft.length === 0) {
container.innerHTML = '<div class="no-aircraft">No aircraft in local area</div>';
container.innerHTML = '<div class="no-aircraft">No local traffic</div>';
return;
}
@@ -1522,9 +1545,12 @@
const type = ac.type || ac.ac_type || ac.aircraft_type || '';
const dest = ac.out_to;
const isDeparture = ac.isDeparture;
const isPPR = ac.isPPR;
let buttons;
if (isDeparture) {
if (isPPR) {
buttons = `<button class="status-btn" onclick="event.stopPropagation(); showTimestampModal('DEPARTED', ${ac.id})">QSY</button>`;
} else if (isDeparture) {
// Departure in LOCAL status - show QSY and REJOIN buttons
buttons = `
<button class="status-btn" onclick="event.stopPropagation(); currentDepartureId = '${ac.id}'; showTimestampModal('DEPARTED', ${ac.id}, false, true)">QSY</button>
@@ -1537,9 +1563,9 @@
// Overflight in ACTIVE status - show QSY button
buttons = `<button class="status-btn" onclick="event.stopPropagation(); currentOverflightId = '${ac.id}'; showOverflightQSYModal()">QSY</button>`;
}
const itemClass = isDeparture ? 'departure' : (ac.isArrival ? 'inbound' : (ac.isOverflight ? 'overflight' : 'local-flight'));
const detailsText = isDeparture ? `${type}${dest ? `${dest}` : ` (Local)`}` : (ac.isOverflight ? `${ac.departure_airfield || '?'}${ac.destination_airfield || '?'}` : (ac.isArrival ? `${type} from ${ac.in_from || '?'}` : `${type}${dest ? `${dest}` : ` Local Flight`}`));
const entityType = isDeparture ? 'departure' : (ac.isArrival ? 'arrival' : (ac.isOverflight ? 'overflight' : 'local'));
const itemClass = isDeparture || isPPR ? 'departure' : (ac.isArrival ? 'inbound' : (ac.isOverflight ? 'overflight' : 'local-flight'));
const detailsText = isDeparture || isPPR ? `${type}${dest ? `${dest}` : ` (Local)`}` : (ac.isOverflight ? `${ac.departure_airfield || '?'}${ac.destination_airfield || '?'}` : (ac.isArrival ? `${type} from ${ac.in_from || '?'}` : `${type}${dest ? `${dest}` : ` Local Flight`}`));
const entityType = isPPR ? 'ppr' : (isDeparture ? 'departure' : (ac.isArrival ? 'arrival' : (ac.isOverflight ? 'overflight' : 'local')));
return `
<div class="aircraft-item ${itemClass}" onclick="handleATCClick('${ac.id}', '${entityType}')">