Reporting and TZ updates

This commit is contained in:
2026-06-28 07:37:41 -04:00
parent 5e12561fb2
commit c2e4d2adeb
18 changed files with 719 additions and 268 deletions
+391 -89
View File
@@ -155,6 +155,14 @@
overflow: hidden;
}
.clickable-row {
cursor: pointer;
}
.clickable-row:hover {
background-color: #eef6ff;
}
.table-header {
background: #34495e;
color: white;
@@ -311,6 +319,98 @@
background-color: #e74c3c;
}
.modal {
display: none;
position: fixed;
z-index: 9999;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.45);
}
.modal-content {
background: white;
margin: 4% auto;
width: min(920px, calc(100% - 2rem));
border-radius: 8px;
box-shadow: 0 10px 30px rgba(0,0,0,0.25);
overflow: hidden;
}
.modal-header {
background: #34495e;
color: white;
padding: 1rem 1.25rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h2 {
margin: 0;
font-size: 1.2rem;
}
.close {
background: none;
border: none;
color: white;
font-size: 1.8rem;
cursor: pointer;
line-height: 1;
}
.modal-body {
padding: 1.25rem;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 0.75rem;
}
.detail-field {
border: 1px solid #e5e7eb;
border-radius: 6px;
padding: 0.65rem;
background: #fafafa;
}
.detail-label {
font-size: 0.72rem;
text-transform: uppercase;
color: #667085;
font-weight: 700;
margin-bottom: 0.25rem;
}
.detail-value {
overflow-wrap: anywhere;
}
.journal-section {
margin-top: 1rem;
border-top: 1px solid #e5e7eb;
padding-top: 1rem;
}
.journal-entry {
border-left: 3px solid #3498db;
padding: 0.5rem 0.75rem;
background: #f8fafc;
margin-top: 0.5rem;
}
.journal-meta {
font-size: 0.78rem;
color: #667085;
margin-bottom: 0.25rem;
}
/* Responsive design */
@media (max-width: 768px) {
.container {
@@ -474,7 +574,7 @@
<div class="summary-item-label" style="font-size: 0.7rem; margin-bottom: 0.1rem;">Departures</div>
<div class="summary-item-value" style="font-size: 1.1rem;" id="non-ppr-departures">0</div>
</div>
<div class="summary-item" style="padding: 0.4rem; cursor: pointer;" onclick="filterOtherFlights('OVERFLIGHT')">
<div class="summary-item" style="padding: 0.4rem;">
<div class="summary-item-label" style="font-size: 0.7rem; margin-bottom: 0.1rem;">Overflights</div>
<div class="summary-item-value" style="font-size: 1.1rem;" id="overflights-count">0</div>
</div>
@@ -524,14 +624,12 @@
<th>Callsign</th>
<th>Captain</th>
<th>From</th>
<th>ETA</th>
<th>POB In</th>
<th>To</th>
<th>ETD</th>
<th>Takeoff</th>
<th>Landing</th>
<th>POB In</th>
<th>POB Out</th>
<th>Fuel</th>
<th>Landed</th>
<th>Departed</th>
<th>Email</th>
<th>Phone</th>
<th>Notes</th>
@@ -582,8 +680,8 @@
<th>Callsign</th>
<th>From</th>
<th>To</th>
<th>ETA / ETD / Called</th>
<th>Landed / Departed / QSY</th>
<th>Takeoff</th>
<th>Landing</th>
<th>Circuits</th>
</tr>
</thead>
@@ -600,6 +698,22 @@
</div>
</div>
<div id="reportDetailModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2 id="report-detail-title">Details</h2>
<button class="close" onclick="closeReportDetailModal()">&times;</button>
</div>
<div class="modal-body">
<div id="report-detail-body" class="detail-grid"></div>
<div class="journal-section">
<h3 style="margin: 0 0 0.5rem 0;">Journal</h3>
<div id="report-detail-journal">Loading...</div>
</div>
</div>
</div>
</div>
<!-- Success Notification -->
<div id="notification" class="notification"></div>
@@ -699,10 +813,10 @@
// Set date range to this week (Monday to Sunday)
function setDateRangeThisWeek() {
const now = new Date();
const dayOfWeek = now.getDay();
const diff = now.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1); // Adjust when day is Sunday
const monday = new Date(now.setDate(diff));
const sunday = new Date(now.setDate(diff + 6));
const dayOfWeek = now.getUTCDay();
const diff = now.getUTCDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1); // Adjust when day is Sunday
const monday = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), diff));
const sunday = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), diff + 6));
document.getElementById('date-from').value = monday.toISOString().split('T')[0];
document.getElementById('date-to').value = sunday.toISOString().split('T')[0];
@@ -717,8 +831,8 @@
// Set date range to this month
function setDateRangeThisMonth() {
const now = new Date();
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
const firstDay = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
const lastDay = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 0));
document.getElementById('date-from').value = firstDay.toISOString().split('T')[0];
document.getElementById('date-to').value = lastDay.toISOString().split('T')[0];
@@ -871,9 +985,12 @@
const arrivals = await arrivalsResponse.json();
otherFlights.push(...arrivals.map(f => ({
...f,
entityType: 'ARRIVAL',
flightType: 'ARRIVAL',
aircraft_type: f.type,
timeField: f.eta || f.landed_dt,
sortTime: f.landed_dt || f.eta || f.created_dt,
takeoffTime: null,
landingTime: f.landed_dt,
fromField: f.in_from,
toField: 'EGFH'
})));
@@ -883,9 +1000,12 @@
const departures = await departuresResponse.json();
otherFlights.push(...departures.map(f => ({
...f,
entityType: 'DEPARTURE',
flightType: 'DEPARTURE',
aircraft_type: f.type,
timeField: f.etd || f.departed_dt,
sortTime: f.takeoff_dt || f.departed_dt || f.etd || f.created_dt,
takeoffTime: f.takeoff_dt || f.departed_dt,
landingTime: null,
fromField: 'EGFH',
toField: f.out_to
})));
@@ -895,10 +1015,13 @@
const localFlights = await localFlightsResponse.json();
otherFlights.push(...localFlights.map(f => ({
...f,
entityType: 'LOCAL_FLIGHT',
flightType: f.flight_type === 'CIRCUITS' ? 'CIRCUIT' : f.flight_type,
aircraft_type: f.type,
circuits: f.circuits,
timeField: f.departed_dt,
sortTime: f.takeoff_dt || f.departed_dt || f.landed_dt || f.etd || f.created_dt,
takeoffTime: f.takeoff_dt || f.departed_dt,
landingTime: f.landed_dt,
fromField: 'EGFH',
toField: 'EGFH'
})));
@@ -908,10 +1031,13 @@
const overflights = await overflightsResponse.json();
otherFlights.push(...overflights.map(f => ({
...f,
entityType: 'OVERFLIGHT',
flightType: 'OVERFLIGHT',
aircraft_type: f.type,
circuits: null,
timeField: f.call_dt,
sortTime: f.call_dt,
takeoffTime: null,
landingTime: null,
fromField: f.departure_airfield,
toField: f.destination_airfield,
callsign: f.registration
@@ -959,21 +1085,14 @@
let dateRangeText = '';
if (dateFrom && dateTo && dateFrom === dateTo) {
// Single day
const date = new Date(dateFrom + 'T00:00:00Z');
dateRangeText = `for ${date.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })}`;
dateRangeText = `for ${formatDateOnly(dateFrom)}`;
} else if (dateFrom && dateTo) {
// Date range
const fromDate = new Date(dateFrom + 'T00:00:00Z');
const toDate = new Date(dateTo + 'T00:00:00Z');
const fromText = fromDate.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' });
const toText = toDate.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' });
dateRangeText = `for ${fromText} to ${toText}`;
dateRangeText = `for ${formatDateOnly(dateFrom)} to ${formatDateOnly(dateTo)}`;
} else if (dateFrom) {
const date = new Date(dateFrom + 'T00:00:00Z');
dateRangeText = `from ${date.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })}`;
dateRangeText = `from ${formatDateOnly(dateFrom)}`;
} else if (dateTo) {
const date = new Date(dateTo + 'T00:00:00Z');
dateRangeText = `until ${date.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })}`;
dateRangeText = `until ${formatDateOnly(dateTo)}`;
}
// Update summary title with date range
@@ -1045,11 +1164,13 @@
return;
}
// Sort by ETA (ascending)
// Sort by first actual movement time, then planned times as a fallback.
pprs.sort((a, b) => {
if (!a.eta) return 1;
if (!b.eta) return -1;
return new Date(a.eta) - new Date(b.eta);
const aTime = getPPRSortTime(a);
const bTime = getPPRSortTime(b);
if (!aTime) return 1;
if (!bTime) return -1;
return parseUtcDate(aTime) - parseUtcDate(bTime);
});
tbody.innerHTML = '';
@@ -1057,12 +1178,11 @@
for (const ppr of pprs) {
const row = document.createElement('tr');
row.className = 'clickable-row';
row.onclick = () => openReportDetail('PPR', ppr.id);
// Format dates
const eta = ppr.eta ? formatDateTime(ppr.eta) : '-';
const etd = ppr.etd ? formatDateTime(ppr.etd) : '-';
const landed = ppr.landed_dt ? formatDateTime(ppr.landed_dt) : '-';
const departed = ppr.departed_dt ? formatDateTime(ppr.departed_dt) : '-';
const takeoff = ppr.departed_dt ? formatDateTime(ppr.departed_dt) : '-';
const landing = ppr.landed_dt ? formatDateTime(ppr.landed_dt) : '-';
const submitted = ppr.submitted_dt ? formatDateTime(ppr.submitted_dt) : '-';
// Status styling
@@ -1076,14 +1196,12 @@
<td>${ppr.ac_call || '-'}</td>
<td>${ppr.captain}</td>
<td>${ppr.in_from}</td>
<td>${eta}</td>
<td>${ppr.pob_in}</td>
<td>${ppr.out_to || '-'}</td>
<td>${etd}</td>
<td>${takeoff}</td>
<td>${landing}</td>
<td>${ppr.pob_in}</td>
<td>${ppr.pob_out || '-'}</td>
<td>${ppr.fuel || '-'}</td>
<td>${landed}</td>
<td>${departed}</td>
<td>${ppr.email || '-'}</td>
<td>${ppr.phone || '-'}</td>
<td>${ppr.notes || '-'}</td>
@@ -1176,10 +1294,10 @@
const tbody = document.getElementById('other-flights-table-body');
const tableInfo = document.getElementById('other-flights-info');
// Apply filter if one is selected
let filteredFlights = flights;
// Overflights are counted in the summary but omitted from the detail table for now.
let filteredFlights = flights.filter(flight => flight.flightType !== 'OVERFLIGHT');
if (otherFlightsFilterType) {
filteredFlights = flights.filter(flight => flight.flightType === otherFlightsFilterType);
filteredFlights = filteredFlights.filter(flight => flight.flightType === otherFlightsFilterType);
}
tableInfo.textContent = `${filteredFlights.length} flights found` + (otherFlightsFilterType ? ` (filtered by ${otherFlightsFilterType})` : '');
@@ -1190,13 +1308,13 @@
return;
}
// Sort by time field (ascending)
// Sort by the first pertinent movement time.
filteredFlights.sort((a, b) => {
const aTime = a.timeField;
const bTime = b.timeField;
const aTime = a.sortTime;
const bTime = b.sortTime;
if (!aTime) return 1;
if (!bTime) return -1;
return new Date(aTime) - new Date(bTime);
return parseUtcDate(aTime) - parseUtcDate(bTime);
});
tbody.innerHTML = '';
@@ -1205,6 +1323,8 @@
for (const flight of filteredFlights) {
const row = document.createElement('tr');
row.className = 'clickable-row';
row.onclick = () => openReportDetail(flight.entityType, flight.id);
const typeLabel = flight.flightType;
const registration = flight.registration || '-';
@@ -1212,18 +1332,8 @@
const callsign = flight.callsign || '-';
const from = flight.fromField || '-';
const to = flight.toField || '-';
const timeDisplay = flight.timeField ? formatDateTime(flight.timeField) : '-';
// Different display for different flight types
let actualDisplay = '-';
if (flight.flightType === 'ARRIVAL') {
actualDisplay = flight.landed_dt ? formatDateTime(flight.landed_dt) : '-';
} else if (flight.flightType === 'OVERFLIGHT') {
// For overflights, show qsy_dt (frequency change time)
actualDisplay = flight.qsy_dt ? formatDateTime(flight.qsy_dt) : '-';
} else {
actualDisplay = flight.departed_dt ? formatDateTime(flight.departed_dt) : '-';
}
const takeoff = flight.takeoffTime ? formatDateTime(flight.takeoffTime) : '-';
const landing = flight.landingTime ? formatDateTime(flight.landingTime) : '-';
const status = flight.status || (flight.flightType === 'CIRCUIT' ? 'COMPLETED' : 'PENDING');
const circuits = (flight.flightType === 'CIRCUIT' || flight.flightType === 'LOCAL') ? (flight.circuits > 0 ? flight.circuits : '-') : '-';
@@ -1236,8 +1346,8 @@
<td>${callsign}</td>
<td>${from}</td>
<td>${to}</td>
<td>${timeDisplay}</td>
<td>${actualDisplay}</td>
<td>${takeoff}</td>
<td>${landing}</td>
<td>${circuits}</td>
`;
@@ -1247,23 +1357,201 @@
function formatDateTime(dateStr) {
if (!dateStr) return '-';
let utcDateStr = dateStr;
const date = parseUtcDate(dateStr);
// Format as dd/mm/yy hh:mm
const day = String(date.getUTCDate()).padStart(2, '0');
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
const year = String(date.getUTCFullYear()).slice(-2);
const hours = String(date.getUTCHours()).padStart(2, '0');
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
return `${day}/${month}/${year} ${hours}:${minutes}`;
}
function parseUtcDate(dateStr) {
let utcDateStr = String(dateStr).trim();
if (!utcDateStr.includes('T')) {
utcDateStr = utcDateStr.replace(' ', 'T');
}
if (!utcDateStr.includes('Z')) {
if (!/[zZ]|[+-]\d{2}:?\d{2}$/.test(utcDateStr)) {
utcDateStr += 'Z';
}
const date = new Date(utcDateStr);
// Format as dd/mm/yy hh:mm
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = String(date.getFullYear()).slice(-2);
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${day}/${month}/${year} ${hours}:${minutes}`;
return new Date(utcDateStr);
}
function getPPRSortTime(ppr) {
return ppr.landed_dt || ppr.departed_dt || ppr.eta || ppr.etd || ppr.submitted_dt;
}
const detailConfig = {
PPR: {
endpoint: id => `/api/v1/pprs/${id}`,
journalType: 'PPR',
title: record => `PPR: ${record.ac_reg || '-'}`,
fields: [
['Status', r => r.status],
['Aircraft', r => r.ac_reg],
['Type', r => r.ac_type],
['Callsign', r => r.ac_call],
['Captain', r => r.captain],
['From', r => r.in_from],
['To', r => r.out_to],
['Takeoff', r => formatOptionalDateTime(r.departed_dt)],
['Landing', r => formatOptionalDateTime(r.landed_dt)],
['ETA', r => formatOptionalDateTime(r.eta)],
['ETD', r => formatOptionalDateTime(r.etd)],
['POB In', r => r.pob_in],
['POB Out', r => r.pob_out],
['Fuel', r => r.fuel],
['Email', r => r.email],
['Phone', r => r.phone],
['Submitted', r => formatOptionalDateTime(r.submitted_dt)],
['Created By', r => r.created_by],
['Notes', r => r.notes]
]
},
LOCAL_FLIGHT: {
endpoint: id => `/api/v1/local-flights/${id}`,
journalType: 'LOCAL_FLIGHT',
title: record => `${record.flight_type || 'LOCAL'}: ${record.registration || '-'}`,
fields: [
['Status', r => r.status],
['Aircraft', r => r.registration],
['Type', r => r.type],
['Callsign', r => r.callsign],
['Flight Type', r => r.flight_type],
['From', () => 'EGFH'],
['To', () => 'EGFH'],
['Takeoff', r => formatOptionalDateTime(r.takeoff_dt || r.departed_dt)],
['Landing', r => formatOptionalDateTime(r.landed_dt)],
['ETD', r => formatOptionalDateTime(r.etd)],
['POB', r => r.pob],
['Duration', r => r.duration ? `${r.duration} min` : null],
['Circuits', r => r.circuits],
['Created', r => formatOptionalDateTime(r.created_dt)],
['Created By', r => r.created_by],
['Notes', r => r.notes]
]
},
ARRIVAL: {
endpoint: id => `/api/v1/arrivals/${id}`,
journalType: 'ARRIVAL',
title: record => `Arrival: ${record.registration || record.callsign || '-'}`,
fields: [
['Status', r => r.status],
['Aircraft', r => r.registration],
['Type', r => r.type],
['Callsign', r => r.callsign],
['From', r => r.in_from],
['To', () => 'EGFH'],
['Landing', r => formatOptionalDateTime(r.landed_dt)],
['ETA', r => formatOptionalDateTime(r.eta)],
['POB', r => r.pob],
['Created', r => formatOptionalDateTime(r.created_dt)],
['Created By', r => r.created_by],
['Notes', r => r.notes]
]
},
DEPARTURE: {
endpoint: id => `/api/v1/departures/${id}`,
journalType: 'DEPARTURE',
title: record => `Departure: ${record.registration || '-'}`,
fields: [
['Status', r => r.status],
['Aircraft', r => r.registration],
['Type', r => r.type],
['Callsign', r => r.callsign],
['From', () => 'EGFH'],
['To', r => r.out_to],
['Takeoff', r => formatOptionalDateTime(r.takeoff_dt || r.departed_dt)],
['ETD', r => formatOptionalDateTime(r.etd)],
['POB', r => r.pob],
['Created', r => formatOptionalDateTime(r.created_dt)],
['Created By', r => r.created_by],
['Notes', r => r.notes]
]
}
};
async function openReportDetail(type, id) {
const config = detailConfig[type];
if (!config) return;
document.getElementById('report-detail-title').textContent = 'Loading...';
document.getElementById('report-detail-body').innerHTML = '';
document.getElementById('report-detail-journal').textContent = 'Loading...';
document.getElementById('reportDetailModal').style.display = 'block';
try {
const response = await authenticatedFetch(config.endpoint(id));
if (!response.ok) throw new Error('Unable to load details');
const record = await response.json();
document.getElementById('report-detail-title').textContent = config.title(record);
document.getElementById('report-detail-body').innerHTML = config.fields
.map(([label, getter]) => detailField(label, getter(record)))
.join('');
await loadReportJournal(config.journalType, id);
} catch (error) {
console.error('Error loading report detail:', error);
document.getElementById('report-detail-title').textContent = 'Unable to load details';
document.getElementById('report-detail-body').innerHTML = `<div class="detail-field"><div class="detail-value">${escapeHtml(error.message)}</div></div>`;
document.getElementById('report-detail-journal').textContent = '-';
}
}
async function loadReportJournal(entityType, entityId) {
try {
const response = await authenticatedFetch(`/api/v1/journal/${entityType}/${entityId}`);
if (!response.ok) throw new Error('Unable to load journal');
const data = await response.json();
const entries = data.entries || [];
document.getElementById('report-detail-journal').innerHTML = entries.length
? entries.map(entry => `
<div class="journal-entry">
<div class="journal-meta">${formatOptionalDateTime(entry.entry_dt)} by ${escapeHtml(entry.user || '-')}</div>
<div>${escapeHtml(entry.entry || '-')}</div>
</div>
`).join('')
: '<p>No journal entries yet.</p>';
} catch (error) {
document.getElementById('report-detail-journal').textContent = error.message;
}
}
function closeReportDetailModal() {
document.getElementById('reportDetailModal').style.display = 'none';
}
function detailField(label, value) {
const displayValue = value === null || value === undefined || value === '' ? '-' : value;
return `
<div class="detail-field">
<div class="detail-label">${escapeHtml(label)}</div>
<div class="detail-value">${escapeHtml(displayValue)}</div>
</div>
`;
}
function formatOptionalDateTime(value) {
return value ? formatDateTime(value) : '-';
}
function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, char => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
}[char]));
}
function formatDateOnly(dateStr) {
const [year, month, day] = dateStr.split('-');
return `${day}/${month}/${year}`;
}
// Clear filters
@@ -1283,8 +1571,8 @@
const headers = [
'ID', 'Status', 'Aircraft Reg', 'Aircraft Type', 'Callsign', 'Captain',
'From', 'ETA', 'POB In', 'To', 'ETD', 'POB Out', 'Fuel',
'Landed', 'Departed', 'Email', 'Phone', 'Notes', 'Submitted', 'Created By'
'From', 'To', 'Takeoff', 'Landing', 'POB In', 'POB Out', 'Fuel',
'Email', 'Phone', 'Notes', 'Submitted', 'Created By'
];
const csvData = currentPPRs.map(ppr => [
@@ -1295,14 +1583,12 @@
ppr.ac_call || '',
ppr.captain,
ppr.in_from,
ppr.eta ? formatDateTime(ppr.eta) : '',
ppr.pob_in,
ppr.out_to || '',
ppr.etd ? formatDateTime(ppr.etd) : '',
ppr.departed_dt ? formatDateTime(ppr.departed_dt) : '',
ppr.landed_dt ? formatDateTime(ppr.landed_dt) : '',
ppr.pob_in,
ppr.pob_out || '',
ppr.fuel || '',
ppr.landed_dt ? formatDateTime(ppr.landed_dt) : '',
ppr.departed_dt ? formatDateTime(ppr.departed_dt) : '',
ppr.email || '',
ppr.phone || '',
ppr.notes || '',
@@ -1319,22 +1605,26 @@
return;
}
const exportFlights = currentOtherFlights.filter(flight => flight.flightType !== 'OVERFLIGHT');
if (exportFlights.length === 0) {
showNotification('No table data to export', true);
return;
}
const headers = [
'Flight Type', 'Aircraft Registration', 'Aircraft Type', 'Callsign', 'From', 'To',
'ETA/ETD', 'Landed/Departed', 'Status', 'Circuits'
'Takeoff', 'Landing', 'Status', 'Circuits'
];
const csvData = currentOtherFlights.map(flight => [
const csvData = exportFlights.map(flight => [
flight.flightType,
flight.registration || '',
flight.aircraft_type || '',
flight.callsign || '',
flight.fromField || '',
flight.toField || '',
flight.timeField ? formatDateTime(flight.timeField) : '',
flight.flightType === 'ARRIVAL'
? (flight.landed_dt ? formatDateTime(flight.landed_dt) : '')
: (flight.departed_dt ? formatDateTime(flight.departed_dt) : ''),
flight.takeoffTime ? formatDateTime(flight.takeoffTime) : '',
flight.landingTime ? formatDateTime(flight.landingTime) : '',
flight.status || (flight.flightType === 'CIRCUIT' ? 'COMPLETED' : 'PENDING'),
(flight.flightType === 'CIRCUIT' || flight.flightType === 'LOCAL') ? (flight.circuits || '') : ''
]);
@@ -1383,6 +1673,18 @@
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && document.getElementById('reportDetailModal').style.display === 'block') {
closeReportDetailModal();
}
});
window.addEventListener('click', function(e) {
if (e.target === document.getElementById('reportDetailModal')) {
closeReportDetailModal();
}
});
// Initialize when page loads
document.addEventListener('DOMContentLoaded', initializePage);
</script>