Public display improvements WIP
This commit is contained in:
@@ -55,7 +55,8 @@ class CRUDPPR:
|
||||
func.date(PPRRecord.eta) == today,
|
||||
or_(
|
||||
PPRRecord.status == PPRStatus.NEW,
|
||||
PPRRecord.status == PPRStatus.CONFIRMED
|
||||
PPRRecord.status == PPRStatus.CONFIRMED,
|
||||
PPRRecord.status == PPRStatus.LANDED
|
||||
)
|
||||
)
|
||||
).order_by(PPRRecord.eta).all()
|
||||
@@ -66,7 +67,10 @@ class CRUDPPR:
|
||||
return db.query(PPRRecord).filter(
|
||||
and_(
|
||||
func.date(PPRRecord.etd) == today,
|
||||
PPRRecord.status == PPRStatus.LANDED
|
||||
or_(
|
||||
PPRRecord.status == PPRStatus.LANDED,
|
||||
PPRRecord.status == PPRStatus.DEPARTED
|
||||
)
|
||||
)
|
||||
).order_by(PPRRecord.etd).all()
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.title h1 {
|
||||
@@ -541,11 +543,13 @@
|
||||
transform: translateY(-20px);
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 500;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.notification.show {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.notification.error {
|
||||
@@ -932,6 +936,7 @@
|
||||
let lastHeartbeatResponse = null;
|
||||
let sessionExpiryWarningShown = false;
|
||||
let sessionExpiryCheckInterval = null;
|
||||
let etdManuallyEdited = false; // Track if user has manually edited ETD
|
||||
|
||||
// WebSocket connection for real-time updates
|
||||
function connectWebSocket() {
|
||||
@@ -1542,6 +1547,7 @@
|
||||
function openNewPPRModal() {
|
||||
isNewPPR = true;
|
||||
currentPPRId = null;
|
||||
etdManuallyEdited = false; // Reset the manual edit flag for new PPR
|
||||
document.getElementById('modal-title').textContent = 'New PPR Entry';
|
||||
document.getElementById('delete-btn').style.display = 'none';
|
||||
document.getElementById('journal-section').style.display = 'none';
|
||||
@@ -1588,6 +1594,38 @@
|
||||
}, 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) {
|
||||
if (!accessToken) return;
|
||||
|
||||
@@ -2509,6 +2547,14 @@
|
||||
setupKeyboardShortcuts();
|
||||
initializeTimeDropdowns(); // Initialize time dropdowns
|
||||
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>
|
||||
</body>
|
||||
|
||||
116
web/index.html
116
web/index.html
@@ -62,6 +62,9 @@
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border: 1px solid #ccc;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
th {
|
||||
@@ -134,7 +137,7 @@
|
||||
<body>
|
||||
<header>
|
||||
<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">
|
||||
</header>
|
||||
|
||||
@@ -145,15 +148,14 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Registration</th>
|
||||
<th>Aircraft Type</th>
|
||||
<th>From</th>
|
||||
<th>Due</th>
|
||||
<th style="width: 25%;">Aircraft</th>
|
||||
<th style="width: 60%;">From</th>
|
||||
<th style="width: 15%;">Due</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="arrivals-tbody">
|
||||
<tr>
|
||||
<td colspan="4" class="loading">Loading arrivals...</td>
|
||||
<td colspan="3" class="loading">Loading arrivals...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -165,15 +167,14 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Registration</th>
|
||||
<th>Aircraft Type</th>
|
||||
<th>To</th>
|
||||
<th>Due</th>
|
||||
<th style="width: 25%;">Aircraft</th>
|
||||
<th style="width: 60%;">To</th>
|
||||
<th style="width: 15%;">Due</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="departures-tbody">
|
||||
<tr>
|
||||
<td colspan="4" class="loading">Loading departures...</td>
|
||||
<td colspan="3" class="loading">Loading departures...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -191,6 +192,27 @@
|
||||
|
||||
<script>
|
||||
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
|
||||
function connectWebSocket() {
|
||||
@@ -276,22 +298,38 @@
|
||||
const arrivals = await response.json();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
tbody.innerHTML = arrivals.map(arrival => `
|
||||
<tr>
|
||||
<td>${escapeHtml(arrival.ac_reg || '')}</td>
|
||||
<td>${escapeHtml(arrival.ac_type || '')}</td>
|
||||
<td>${escapeHtml(arrival.in_from || '')}</td>
|
||||
<td>${convertToLocalTime(arrival.eta)}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
// 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>
|
||||
<td>${aircraftDisplay}</td>
|
||||
<td>${escapeHtml(fromDisplay)}</td>
|
||||
<td>${timeDisplay}</td>
|
||||
</tr>
|
||||
`;
|
||||
}));
|
||||
|
||||
tbody.innerHTML = rows.join('');
|
||||
|
||||
} catch (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();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
tbody.innerHTML = departures.map(departure => `
|
||||
<tr>
|
||||
<td>${escapeHtml(departure.ac_reg || '')}</td>
|
||||
<td>${escapeHtml(departure.ac_type || '')}</td>
|
||||
<td>${escapeHtml(departure.out_to || '')}</td>
|
||||
<td>${convertToLocalTime(departure.etd)}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
// 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>
|
||||
<td>${aircraftDisplay}</td>
|
||||
<td>${escapeHtml(toDisplay)}</td>
|
||||
<td>${timeDisplay}</td>
|
||||
</tr>
|
||||
`;
|
||||
}));
|
||||
|
||||
tbody.innerHTML = rows.join('');
|
||||
|
||||
} catch (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>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user