ICAO code expansion

This commit is contained in:
James Pattinson
2025-10-23 20:31:23 +00:00
parent ef273c0c5d
commit 41c7bb352a
2 changed files with 87 additions and 35 deletions

View File

@@ -1256,40 +1256,57 @@
document.getElementById('departures-loading').style.display = 'none'; document.getElementById('departures-loading').style.display = 'none';
} }
function displayArrivals(arrivals) { // ICAO code to airport name cache
const airportNameCache = {};
async function getAirportDisplay(code) {
if (!code || code.length !== 4 || !/^[A-Z]{4}$/.test(code)) return code;
if (airportNameCache[code]) return `${code}<br><span style="font-size:0.8em;color:#666;font-style:italic;">${airportNameCache[code]}</span>`;
try {
const resp = await authenticatedFetch(`/api/v1/airport/lookup/${code}`);
if (resp.ok) {
const data = await resp.json();
if (data && data.length && data[0].name) {
airportNameCache[code] = data[0].name;
return `${code}<br><span style="font-size:0.8em;color:#666;font-style:italic;">${data[0].name}</span>`;
}
}
} catch {}
return code;
}
async function displayArrivals(arrivals) {
const tbody = document.getElementById('arrivals-table-body'); const tbody = document.getElementById('arrivals-table-body');
const recordCount = document.getElementById('arrivals-count'); const recordCount = document.getElementById('arrivals-count');
recordCount.textContent = arrivals.length; recordCount.textContent = arrivals.length;
if (arrivals.length === 0) { if (arrivals.length === 0) {
document.getElementById('arrivals-no-data').style.display = 'block'; document.getElementById('arrivals-no-data').style.display = 'block';
return; return;
} }
tbody.innerHTML = ''; tbody.innerHTML = '';
document.getElementById('arrivals-table-content').style.display = 'block'; document.getElementById('arrivals-table-content').style.display = 'block';
for (const ppr of arrivals) {
arrivals.forEach(ppr => {
const row = document.createElement('tr'); const row = document.createElement('tr');
row.onclick = () => openPPRModal(ppr.id); row.onclick = () => openPPRModal(ppr.id);
// Create notes indicator if notes exist // Create notes indicator if notes exist
const notesIndicator = ppr.notes && ppr.notes.trim() ? const notesIndicator = ppr.notes && ppr.notes.trim() ?
`<span class="notes-tooltip"> `<span class="notes-tooltip">
<span class="notes-indicator">📝</span> <span class="notes-indicator">📝</span>
<span class="tooltip-text">${ppr.notes}</span> <span class="tooltip-text">${ppr.notes}</span>
</span>` : ''; </span>` : '';
// Display callsign as main item if present, registration below; otherwise show registration // Display callsign as main item if present, registration below; otherwise show registration
const aircraftDisplay = ppr.ac_call && ppr.ac_call.trim() ? const aircraftDisplay = ppr.ac_call && ppr.ac_call.trim() ?
`<strong>${ppr.ac_call}</strong><br><span style="font-size: 0.8em; color: #666; font-style: italic;">${ppr.ac_reg}</span>` : `<strong>${ppr.ac_call}</strong><br><span style="font-size: 0.8em; color: #666; font-style: italic;">${ppr.ac_reg}</span>` :
`<strong>${ppr.ac_reg}</strong>`; `<strong>${ppr.ac_reg}</strong>`;
// Lookup airport name for in_from
let fromDisplay = ppr.in_from;
if (ppr.in_from && ppr.in_from.length === 4 && /^[A-Z]{4}$/.test(ppr.in_from)) {
fromDisplay = await getAirportDisplay(ppr.in_from);
}
row.innerHTML = ` row.innerHTML = `
<td>${aircraftDisplay}${notesIndicator}</td> <td>${aircraftDisplay}${notesIndicator}</td>
<td>${ppr.ac_type}</td> <td>${ppr.ac_type}</td>
<td>${ppr.in_from}</td> <td>${fromDisplay}</td>
<td>${formatTimeOnly(ppr.eta)}</td> <td>${formatTimeOnly(ppr.eta)}</td>
<td>${ppr.pob_in}</td> <td>${ppr.pob_in}</td>
<td>${ppr.fuel || '-'}</td> <td>${ppr.fuel || '-'}</td>
@@ -1302,45 +1319,42 @@
</button> </button>
</td> </td>
`; `;
tbody.appendChild(row); tbody.appendChild(row);
}); }
} }
function displayDepartures(departures) { async function displayDepartures(departures) {
const tbody = document.getElementById('departures-table-body'); const tbody = document.getElementById('departures-table-body');
const recordCount = document.getElementById('departures-count'); const recordCount = document.getElementById('departures-count');
recordCount.textContent = departures.length; recordCount.textContent = departures.length;
if (departures.length === 0) { if (departures.length === 0) {
document.getElementById('departures-no-data').style.display = 'block'; document.getElementById('departures-no-data').style.display = 'block';
return; return;
} }
tbody.innerHTML = ''; tbody.innerHTML = '';
document.getElementById('departures-table-content').style.display = 'block'; document.getElementById('departures-table-content').style.display = 'block';
for (const ppr of departures) {
departures.forEach(ppr => {
const row = document.createElement('tr'); const row = document.createElement('tr');
row.onclick = () => openPPRModal(ppr.id); row.onclick = () => openPPRModal(ppr.id);
// Create notes indicator if notes exist // Create notes indicator if notes exist
const notesIndicator = ppr.notes && ppr.notes.trim() ? const notesIndicator = ppr.notes && ppr.notes.trim() ?
`<span class="notes-tooltip"> `<span class="notes-tooltip">
<span class="notes-indicator">📝</span> <span class="notes-indicator">📝</span>
<span class="tooltip-text">${ppr.notes}</span> <span class="tooltip-text">${ppr.notes}</span>
</span>` : ''; </span>` : '';
// Display callsign as main item if present, registration below; otherwise show registration // Display callsign as main item if present, registration below; otherwise show registration
const aircraftDisplay = ppr.ac_call && ppr.ac_call.trim() ? const aircraftDisplay = ppr.ac_call && ppr.ac_call.trim() ?
`<strong>${ppr.ac_call}</strong><br><span style="font-size: 0.8em; color: #666; font-style: italic;">${ppr.ac_reg}</span>` : `<strong>${ppr.ac_call}</strong><br><span style="font-size: 0.8em; color: #666; font-style: italic;">${ppr.ac_reg}</span>` :
`<strong>${ppr.ac_reg}</strong>`; `<strong>${ppr.ac_reg}</strong>`;
// Lookup airport name for out_to
let toDisplay = ppr.out_to || '-';
if (ppr.out_to && ppr.out_to.length === 4 && /^[A-Z]{4}$/.test(ppr.out_to)) {
toDisplay = await getAirportDisplay(ppr.out_to);
}
row.innerHTML = ` row.innerHTML = `
<td>${aircraftDisplay}${notesIndicator}</td> <td>${aircraftDisplay}${notesIndicator}</td>
<td>${ppr.ac_type}</td> <td>${ppr.ac_type}</td>
<td>${ppr.out_to || '-'}</td> <td>${toDisplay}</td>
<td>${ppr.etd ? formatTimeOnly(ppr.etd) : '-'}</td> <td>${ppr.etd ? formatTimeOnly(ppr.etd) : '-'}</td>
<td>${ppr.pob_out || ppr.pob_in}</td> <td>${ppr.pob_out || ppr.pob_in}</td>
<td>${ppr.fuel || '-'}</td> <td>${ppr.fuel || '-'}</td>
@@ -1354,9 +1368,8 @@
</button> </button>
</td> </td>
`; `;
tbody.appendChild(row); tbody.appendChild(row);
}); }
} }
function formatTimeOnly(dateStr) { function formatTimeOnly(dateStr) {

View File

@@ -252,13 +252,40 @@
return `<span class="status ${status.toLowerCase()}">${status}</span>`; return `<span class="status ${status.toLowerCase()}">${status}</span>`;
} }
// Create PPR item HTML // ICAO code to airport name cache
function createPPRItem(ppr) { const airportNameCache = {};
async function getAirportDisplay(code) {
if (!code || code.length !== 4 || !/^[A-Z]{4}$/.test(code)) return code;
if (airportNameCache[code]) return `${code}<br><span style="font-size:0.8em;color:#888;font-style:italic;">${airportNameCache[code]}</span>`;
try {
const resp = await fetch(`/api/v1/airport/lookup/${code}`);
if (resp.ok) {
const data = await resp.json();
if (data && data.length && data[0].name) {
airportNameCache[code] = data[0].name;
return `${code}<br><span style="font-size:0.8em;color:#888;font-style:italic;">${data[0].name}</span>`;
}
}
} catch {}
return code;
}
// Create PPR item HTML (async)
async function createPPRItem(ppr) {
// Display callsign as main item if present, registration below; otherwise show registration // Display callsign as main item if present, registration below; otherwise show registration
const aircraftDisplay = ppr.ac_call && ppr.ac_call.trim() ? const aircraftDisplay = ppr.ac_call && ppr.ac_call.trim() ?
`${ppr.ac_call}<br><span style="font-size: 0.85em; color: #888; font-style: italic;">${ppr.ac_reg}</span>` : `${ppr.ac_call}<br><span style="font-size: 0.85em; color: #888; font-style: italic;">${ppr.ac_reg}</span>` :
ppr.ac_reg; ppr.ac_reg;
// Lookup airport name for in_from/out_to
let fromDisplay = ppr.in_from;
if (ppr.in_from && ppr.in_from.length === 4 && /^[A-Z]{4}$/.test(ppr.in_from)) {
fromDisplay = await getAirportDisplay(ppr.in_from);
}
let toDisplay = ppr.out_to;
if (ppr.out_to && ppr.out_to.length === 4 && /^[A-Z]{4}$/.test(ppr.out_to)) {
toDisplay = await getAirportDisplay(ppr.out_to);
}
return ` return `
<div class="ppr-item"> <div class="ppr-item">
<div class="ppr-field"> <div class="ppr-field">
@@ -269,6 +296,14 @@
<strong>Type</strong> <strong>Type</strong>
<span>${ppr.ac_type}</span> <span>${ppr.ac_type}</span>
</div> </div>
<div class="ppr-field">
<strong>From</strong>
<span>${fromDisplay || '-'}</span>
</div>
<div class="ppr-field">
<strong>To</strong>
<span>${toDisplay || '-'}</span>
</div>
<div class="ppr-field"> <div class="ppr-field">
<strong>Time</strong> <strong>Time</strong>
<span>${formatDateTime(ppr.eta || ppr.etd)}</span> <span>${formatDateTime(ppr.eta || ppr.etd)}</span>
@@ -298,19 +333,21 @@
try { try {
const response = await fetch('/api/v1/public/arrivals'); const response = await fetch('/api/v1/public/arrivals');
const arrivals = await response.json(); const arrivals = await response.json();
const loadingEl = document.getElementById('arrivals-loading'); const loadingEl = document.getElementById('arrivals-loading');
const listEl = document.getElementById('arrivals-list'); const listEl = document.getElementById('arrivals-list');
const countEl = document.getElementById('arrivals-count'); const countEl = document.getElementById('arrivals-count');
loadingEl.style.display = 'none'; loadingEl.style.display = 'none';
listEl.style.display = 'block'; listEl.style.display = 'block';
if (arrivals.length === 0) { if (arrivals.length === 0) {
listEl.innerHTML = createNoDataMessage('arrivals'); listEl.innerHTML = createNoDataMessage('arrivals');
countEl.textContent = '0 flights'; countEl.textContent = '0 flights';
} else { } else {
listEl.innerHTML = arrivals.map(createPPRItem).join(''); // Render each item async
const htmlArr = [];
for (const ppr of arrivals) {
htmlArr.push(await createPPRItem(ppr));
}
listEl.innerHTML = htmlArr.join('');
countEl.textContent = `${arrivals.length} flight${arrivals.length !== 1 ? 's' : ''}`; countEl.textContent = `${arrivals.length} flight${arrivals.length !== 1 ? 's' : ''}`;
} }
} catch (error) { } catch (error) {
@@ -330,19 +367,21 @@
try { try {
const response = await fetch('/api/v1/public/departures'); const response = await fetch('/api/v1/public/departures');
const departures = await response.json(); const departures = await response.json();
const loadingEl = document.getElementById('departures-loading'); const loadingEl = document.getElementById('departures-loading');
const listEl = document.getElementById('departures-list'); const listEl = document.getElementById('departures-list');
const countEl = document.getElementById('departures-count'); const countEl = document.getElementById('departures-count');
loadingEl.style.display = 'none'; loadingEl.style.display = 'none';
listEl.style.display = 'block'; listEl.style.display = 'block';
if (departures.length === 0) { if (departures.length === 0) {
listEl.innerHTML = createNoDataMessage('departures'); listEl.innerHTML = createNoDataMessage('departures');
countEl.textContent = '0 flights'; countEl.textContent = '0 flights';
} else { } else {
listEl.innerHTML = departures.map(createPPRItem).join(''); // Render each item async
const htmlArr = [];
for (const ppr of departures) {
htmlArr.push(await createPPRItem(ppr));
}
listEl.innerHTML = htmlArr.join('');
countEl.textContent = `${departures.length} flight${departures.length !== 1 ? 's' : ''}`; countEl.textContent = `${departures.length} flight${departures.length !== 1 ? 's' : ''}`;
} }
} catch (error) { } catch (error) {