Public display improvements WIP

This commit is contained in:
James Pattinson
2025-12-10 11:25:33 +00:00
parent 169c3af29b
commit 86f1dc65f4
3 changed files with 137 additions and 33 deletions

View File

@@ -55,7 +55,8 @@ class CRUDPPR:
func.date(PPRRecord.eta) == today, func.date(PPRRecord.eta) == today,
or_( or_(
PPRRecord.status == PPRStatus.NEW, PPRRecord.status == PPRStatus.NEW,
PPRRecord.status == PPRStatus.CONFIRMED PPRRecord.status == PPRStatus.CONFIRMED,
PPRRecord.status == PPRStatus.LANDED
) )
) )
).order_by(PPRRecord.eta).all() ).order_by(PPRRecord.eta).all()
@@ -66,7 +67,10 @@ class CRUDPPR:
return db.query(PPRRecord).filter( return db.query(PPRRecord).filter(
and_( and_(
func.date(PPRRecord.etd) == today, func.date(PPRRecord.etd) == today,
PPRRecord.status == PPRStatus.LANDED or_(
PPRRecord.status == PPRStatus.LANDED,
PPRRecord.status == PPRStatus.DEPARTED
)
) )
).order_by(PPRRecord.etd).all() ).order_by(PPRRecord.etd).all()

View File

@@ -25,6 +25,8 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); box-shadow: 0 2px 10px rgba(0,0,0,0.1);
position: relative;
z-index: 100;
} }
.title h1 { .title h1 {
@@ -541,11 +543,13 @@
transform: translateY(-20px); transform: translateY(-20px);
transition: all 0.3s ease; transition: all 0.3s ease;
font-weight: 500; font-weight: 500;
pointer-events: none;
} }
.notification.show { .notification.show {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
pointer-events: auto;
} }
.notification.error { .notification.error {
@@ -932,6 +936,7 @@
let lastHeartbeatResponse = null; let lastHeartbeatResponse = null;
let sessionExpiryWarningShown = false; let sessionExpiryWarningShown = false;
let sessionExpiryCheckInterval = null; let sessionExpiryCheckInterval = null;
let etdManuallyEdited = false; // Track if user has manually edited ETD
// WebSocket connection for real-time updates // WebSocket connection for real-time updates
function connectWebSocket() { function connectWebSocket() {
@@ -1542,6 +1547,7 @@
function openNewPPRModal() { function openNewPPRModal() {
isNewPPR = true; isNewPPR = true;
currentPPRId = null; currentPPRId = null;
etdManuallyEdited = false; // Reset the manual edit flag for new PPR
document.getElementById('modal-title').textContent = 'New PPR Entry'; document.getElementById('modal-title').textContent = 'New PPR Entry';
document.getElementById('delete-btn').style.display = 'none'; document.getElementById('delete-btn').style.display = 'none';
document.getElementById('journal-section').style.display = 'none'; document.getElementById('journal-section').style.display = 'none';
@@ -1588,6 +1594,38 @@
}, 100); }, 100);
} }
// Function to update ETD based on ETA (2 hours later)
function updateETDFromETA() {
// Only auto-update if user hasn't manually edited ETD
if (etdManuallyEdited) {
return;
}
const etaDate = document.getElementById('eta-date').value;
const etaTime = document.getElementById('eta-time').value;
if (etaDate && etaTime) {
// Parse ETA
const eta = new Date(`${etaDate}T${etaTime}`);
// Calculate ETD (2 hours after ETA)
const etd = new Date(eta.getTime() + 2 * 60 * 60 * 1000);
// Format ETD
const etdDateStr = `${etd.getFullYear()}-${String(etd.getMonth() + 1).padStart(2, '0')}-${String(etd.getDate()).padStart(2, '0')}`;
const etdTimeStr = `${String(etd.getHours()).padStart(2, '0')}:${String(etd.getMinutes()).padStart(2, '0')}`;
// Update ETD fields
document.getElementById('etd-date').value = etdDateStr;
document.getElementById('etd-time').value = etdTimeStr;
}
}
// Function to mark ETD as manually edited
function markETDAsManuallyEdited() {
etdManuallyEdited = true;
}
async function openPPRModal(pprId) { async function openPPRModal(pprId) {
if (!accessToken) return; if (!accessToken) return;
@@ -2509,6 +2547,14 @@
setupKeyboardShortcuts(); setupKeyboardShortcuts();
initializeTimeDropdowns(); // Initialize time dropdowns initializeTimeDropdowns(); // Initialize time dropdowns
initializeAuth(); // Start authentication process initializeAuth(); // Start authentication process
// Add event listeners to ETA fields to auto-update ETD
document.getElementById('eta-date').addEventListener('change', updateETDFromETA);
document.getElementById('eta-time').addEventListener('change', updateETDFromETA);
// Add event listeners to ETD fields to mark as manually edited
document.getElementById('etd-date').addEventListener('change', markETDAsManuallyEdited);
document.getElementById('etd-time').addEventListener('change', markETDAsManuallyEdited);
}); });
</script> </script>
</body> </body>

View File

@@ -62,6 +62,9 @@
padding: 12px; padding: 12px;
text-align: left; text-align: left;
border: 1px solid #ccc; border: 1px solid #ccc;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
th { th {
@@ -134,7 +137,7 @@
<body> <body>
<header> <header>
<img src="assets/logo.png" alt="EGFH Logo" class="left-image"> <img src="assets/logo.png" alt="EGFH Logo" class="left-image">
<h1>Arrivals/Departures Information</h1> <h1>Flight Information</h1>
<img src="assets/flightImg.png" alt="EGFH Logo" class="right-image"> <img src="assets/flightImg.png" alt="EGFH Logo" class="right-image">
</header> </header>
@@ -145,15 +148,14 @@
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Registration</th> <th style="width: 25%;">Aircraft</th>
<th>Aircraft Type</th> <th style="width: 60%;">From</th>
<th>From</th> <th style="width: 15%;">Due</th>
<th>Due</th>
</tr> </tr>
</thead> </thead>
<tbody id="arrivals-tbody"> <tbody id="arrivals-tbody">
<tr> <tr>
<td colspan="4" class="loading">Loading arrivals...</td> <td colspan="3" class="loading">Loading arrivals...</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -165,15 +167,14 @@
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Registration</th> <th style="width: 25%;">Aircraft</th>
<th>Aircraft Type</th> <th style="width: 60%;">To</th>
<th>To</th> <th style="width: 15%;">Due</th>
<th>Due</th>
</tr> </tr>
</thead> </thead>
<tbody id="departures-tbody"> <tbody id="departures-tbody">
<tr> <tr>
<td colspan="4" class="loading">Loading departures...</td> <td colspan="3" class="loading">Loading departures...</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -192,6 +193,27 @@
<script> <script>
let wsConnection = null; let wsConnection = null;
// ICAO code to airport name cache
const airportNameCache = {};
async function getAirportName(code) {
if (!code || code.length !== 4 || !/^[A-Z]{4}$/.test(code)) return code;
if (airportNameCache[code]) return airportNameCache[code];
try {
const resp = await fetch(`/api/v1/airport/public/lookup/${code}`);
if (resp.ok) {
const data = await resp.json();
if (data && data.length && data[0].name) {
airportNameCache[code] = data[0].name;
return data[0].name;
}
}
} catch (error) {
console.error('Error looking up airport:', error);
}
return code;
}
// WebSocket connection for real-time updates // WebSocket connection for real-time updates
function connectWebSocket() { function connectWebSocket() {
if (wsConnection && wsConnection.readyState === WebSocket.OPEN) { if (wsConnection && wsConnection.readyState === WebSocket.OPEN) {
@@ -276,22 +298,38 @@
const arrivals = await response.json(); const arrivals = await response.json();
if (arrivals.length === 0) { if (arrivals.length === 0) {
tbody.innerHTML = '<tr><td colspan="4">No arrivals found.</td></tr>'; tbody.innerHTML = '<tr><td colspan="3">No arrivals found.</td></tr>';
return; return;
} }
tbody.innerHTML = arrivals.map(arrival => ` // Build rows asynchronously to lookup airport names
const rows = await Promise.all(arrivals.map(async (arrival) => {
const aircraftDisplay = `${escapeHtml(arrival.ac_reg || '')} <span style="font-size: 0.8em; color: #666;">(${escapeHtml(arrival.ac_type || '')})</span>`;
const fromDisplay = await getAirportName(arrival.in_from || '');
// Show landed time if available, otherwise ETA
let timeDisplay;
if (arrival.status === 'LANDED' && arrival.landed_dt) {
const time = convertToLocalTime(arrival.landed_dt);
timeDisplay = `<div style="display: flex; align-items: center; gap: 8px;"><span style="color: #27ae60; font-weight: bold;">${time}</span><span style="font-size: 0.7em; background: #27ae60; color: white; padding: 2px 4px; border-radius: 3px; white-space: nowrap;">LANDED</span></div>`;
} else {
timeDisplay = convertToLocalTime(arrival.eta);
}
return `
<tr> <tr>
<td>${escapeHtml(arrival.ac_reg || '')}</td> <td>${aircraftDisplay}</td>
<td>${escapeHtml(arrival.ac_type || '')}</td> <td>${escapeHtml(fromDisplay)}</td>
<td>${escapeHtml(arrival.in_from || '')}</td> <td>${timeDisplay}</td>
<td>${convertToLocalTime(arrival.eta)}</td>
</tr> </tr>
`).join(''); `;
}));
tbody.innerHTML = rows.join('');
} catch (error) { } catch (error) {
console.error('Error loading arrivals:', error); console.error('Error loading arrivals:', error);
tbody.innerHTML = '<tr><td colspan="4" class="error">Error loading arrivals</td></tr>'; tbody.innerHTML = '<tr><td colspan="3" class="error">Error loading arrivals</td></tr>';
} }
} }
@@ -309,22 +347,38 @@
const departures = await response.json(); const departures = await response.json();
if (departures.length === 0) { if (departures.length === 0) {
tbody.innerHTML = '<tr><td colspan="4">No departures found.</td></tr>'; tbody.innerHTML = '<tr><td colspan="3">No departures found.</td></tr>';
return; return;
} }
tbody.innerHTML = departures.map(departure => ` // Build rows asynchronously to lookup airport names
const rows = await Promise.all(departures.map(async (departure) => {
const aircraftDisplay = `${escapeHtml(departure.ac_reg || '')} <span style="font-size: 0.8em; color: #666;">(${escapeHtml(departure.ac_type || '')})</span>`;
const toDisplay = await getAirportName(departure.out_to || '');
// Show departed time if available, otherwise ETD
let timeDisplay;
if (departure.status === 'DEPARTED' && departure.departed_dt) {
const time = convertToLocalTime(departure.departed_dt);
timeDisplay = `<div style="display: flex; align-items: center; gap: 8px;"><span style="color: #3498db; font-weight: bold;">${time}</span><span style="font-size: 0.7em; background: #3498db; color: white; padding: 2px 4px; border-radius: 3px; white-space: nowrap;">DEPARTED</span></div>`;
} else {
timeDisplay = convertToLocalTime(departure.etd);
}
return `
<tr> <tr>
<td>${escapeHtml(departure.ac_reg || '')}</td> <td>${aircraftDisplay}</td>
<td>${escapeHtml(departure.ac_type || '')}</td> <td>${escapeHtml(toDisplay)}</td>
<td>${escapeHtml(departure.out_to || '')}</td> <td>${timeDisplay}</td>
<td>${convertToLocalTime(departure.etd)}</td>
</tr> </tr>
`).join(''); `;
}));
tbody.innerHTML = rows.join('');
} catch (error) { } catch (error) {
console.error('Error loading departures:', error); console.error('Error loading departures:', error);
tbody.innerHTML = '<tr><td colspan="4" class="error">Error loading departures</td></tr>'; tbody.innerHTML = '<tr><td colspan="3" class="error">Error loading departures</td></tr>';
} }
} }