Overflights reporting and menu layout changes
This commit is contained in:
@@ -16,22 +16,95 @@ body {
|
||||
color: white;
|
||||
padding: 0.5rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.title {
|
||||
order: 2;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title h1 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.menu-buttons {
|
||||
order: 1;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.top-bar .user-info {
|
||||
order: 3;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown-toggle {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
min-width: 200px;
|
||||
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
|
||||
border-radius: 5px;
|
||||
z-index: 1000;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
margin-top: 0.5rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dropdown-menu a {
|
||||
color: #333;
|
||||
padding: 0.75rem 1.5rem;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: background-color 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.shortcut {
|
||||
font-size: 0.8rem;
|
||||
color: #999;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.dropdown-menu a:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.dropdown-menu a:first-child {
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
.dropdown-menu a:last-child {
|
||||
border-radius: 0 0 5px 5px;
|
||||
}
|
||||
|
||||
.dropdown-menu.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.top-bar .user-info {
|
||||
|
||||
@@ -13,27 +13,26 @@
|
||||
<h1>✈️ Swansea Tower</h1>
|
||||
</div>
|
||||
<div class="menu-buttons">
|
||||
<button class="btn btn-success" onclick="openNewPPRModal()">
|
||||
➕ New PPR
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-success dropdown-toggle" id="actionsDropdownBtn">
|
||||
📋 Actions
|
||||
</button>
|
||||
<button class="btn btn-info" onclick="openLocalFlightModal()">
|
||||
🛫 Book Out
|
||||
</button>
|
||||
<button class="btn btn-info" onclick="openBookInModal()">
|
||||
🛬 Book In
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="openOverflightModal()">
|
||||
🔄 Overflight
|
||||
</button>
|
||||
<button class="btn btn-primary" onclick="window.location.href = '/reports'">
|
||||
📊 Reports
|
||||
</button>
|
||||
<button class="btn btn-warning" onclick="openUserManagementModal()" id="user-management-btn" style="display: none;">
|
||||
👥 User Management
|
||||
</button>
|
||||
<button class="btn btn-primary" onclick="loadPPRs()">
|
||||
🔄 Refresh
|
||||
<div class="dropdown-menu" id="actionsDropdownMenu">
|
||||
<a href="#" onclick="openNewPPRModal(); closeActionsDropdown()">➕ New PPR <span class="shortcut">(N)</span></a>
|
||||
<a href="#" onclick="openLocalFlightModal('LOCAL'); closeActionsDropdown()">🛫 Book Out <span class="shortcut">(L)</span></a>
|
||||
<a href="#" onclick="openBookInModal(); closeActionsDropdown()">🛬 Book In <span class="shortcut">(I)</span></a>
|
||||
<a href="#" onclick="openOverflightModal(); closeActionsDropdown()">🔄 Overflight <span class="shortcut">(O)</span></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-warning dropdown-toggle" id="adminDropdownBtn">
|
||||
⚙️ Admin
|
||||
</button>
|
||||
<div class="dropdown-menu" id="adminDropdownMenu">
|
||||
<a href="#" onclick="window.location.href = '/reports'">📊 Reports</a>
|
||||
<a href="#" onclick="openUserManagementModal(); closeAdminDropdown()" id="user-management-dropdown" style="display: none;">👥 User Management</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
Logged in as: <span id="current-user">Loading...</span> |
|
||||
@@ -1399,6 +1398,40 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Dropdown menu handlers
|
||||
document.addEventListener('click', function(e) {
|
||||
const actionsBtn = document.getElementById('actionsDropdownBtn');
|
||||
const actionsMenu = document.getElementById('actionsDropdownMenu');
|
||||
const adminBtn = document.getElementById('adminDropdownBtn');
|
||||
const adminMenu = document.getElementById('adminDropdownMenu');
|
||||
|
||||
// Handle Actions dropdown
|
||||
if (e.target === actionsBtn) {
|
||||
e.preventDefault();
|
||||
actionsMenu.classList.toggle('active');
|
||||
adminMenu.classList.remove('active');
|
||||
} else if (!actionsMenu.contains(e.target)) {
|
||||
actionsMenu.classList.remove('active');
|
||||
}
|
||||
|
||||
// Handle Admin dropdown
|
||||
if (e.target === adminBtn) {
|
||||
e.preventDefault();
|
||||
adminMenu.classList.toggle('active');
|
||||
actionsMenu.classList.remove('active');
|
||||
} else if (!adminMenu.contains(e.target)) {
|
||||
adminMenu.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
function closeActionsDropdown() {
|
||||
document.getElementById('actionsDropdownMenu').classList.remove('active');
|
||||
}
|
||||
|
||||
function closeAdminDropdown() {
|
||||
document.getElementById('adminDropdownMenu').classList.remove('active');
|
||||
}
|
||||
|
||||
function showLogin() {
|
||||
document.getElementById('loginModal').style.display = 'block';
|
||||
document.getElementById('login-username').focus();
|
||||
@@ -3335,20 +3368,20 @@
|
||||
currentUserRole = userData.role;
|
||||
console.log('User role from API:', currentUserRole); // Debug log
|
||||
|
||||
// Show user management button only for administrators
|
||||
const userManagementBtn = document.getElementById('user-management-btn');
|
||||
// Show user management in dropdown only for administrators
|
||||
const userManagementDropdown = document.getElementById('user-management-dropdown');
|
||||
if (currentUserRole && currentUserRole.toUpperCase() === 'ADMINISTRATOR') {
|
||||
userManagementBtn.style.display = 'inline-block';
|
||||
console.log('Showing user management button'); // Debug log
|
||||
userManagementDropdown.style.display = 'block';
|
||||
console.log('Showing user management in dropdown'); // Debug log
|
||||
} else {
|
||||
userManagementBtn.style.display = 'none';
|
||||
console.log('Hiding user management button, current role:', currentUserRole); // Debug log
|
||||
userManagementDropdown.style.display = 'none';
|
||||
console.log('Hiding user management, current role:', currentUserRole); // Debug log
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating user role:', error);
|
||||
// Hide user management by default on error
|
||||
document.getElementById('user-management-btn').style.display = 'none';
|
||||
document.getElementById('user-management-dropdown').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,23 +22,32 @@
|
||||
color: white;
|
||||
padding: 0.5rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.title {
|
||||
order: 2;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title h1 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.menu-buttons {
|
||||
order: 1;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-bar .user-info {
|
||||
order: 3;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
display: flex;
|
||||
@@ -352,14 +361,14 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<div class="title">
|
||||
<h1>📊 PPR Reports</h1>
|
||||
</div>
|
||||
<div class="menu-buttons">
|
||||
<button class="btn btn-secondary" onclick="window.location.href='admin'">
|
||||
← Back to Admin
|
||||
</button>
|
||||
</div>
|
||||
<div class="title">
|
||||
<h1>📊 PPR Reports</h1>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
Logged in as: <span id="current-user">Loading...</span> |
|
||||
<a href="#" onclick="logout()" style="color: white;">Logout</a>
|
||||
@@ -431,7 +440,7 @@
|
||||
<!-- Non-PPR Section -->
|
||||
<div style="grid-column: 1/-1; padding-bottom: 1rem; border-bottom: 2px solid rgba(255,255,255,0.3);">
|
||||
<div style="font-size: 0.95rem; font-weight: 600; margin-bottom: 0.6rem;">Non-PPR Movements</div>
|
||||
<div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 1rem;">
|
||||
<div style="display: grid; grid-template-columns: repeat(6, 1fr); gap: 1rem;">
|
||||
<div class="summary-item" style="padding: 0.6rem;">
|
||||
<div class="summary-item-label" style="font-size: 0.75rem; margin-bottom: 0.2rem;">Local Flights</div>
|
||||
<div class="summary-item-value" style="font-size: 1.3rem;" id="local-flights-movements">0</div>
|
||||
@@ -448,6 +457,10 @@
|
||||
<div class="summary-item-label" style="font-size: 0.75rem; margin-bottom: 0.2rem;">Departures</div>
|
||||
<div class="summary-item-value" style="font-size: 1.3rem;" id="non-ppr-departures">0</div>
|
||||
</div>
|
||||
<div class="summary-item" style="padding: 0.6rem;">
|
||||
<div class="summary-item-label" style="font-size: 0.75rem; margin-bottom: 0.2rem;">Overflights</div>
|
||||
<div class="summary-item-value" style="font-size: 1.3rem;" id="overflights-count">0</div>
|
||||
</div>
|
||||
<div class="summary-item" style="border-left-color: #ffd700; background: rgba(255,215,0,0.1); padding: 0.6rem;">
|
||||
<div class="summary-item-label" style="font-weight: 600; font-size: 0.75rem; margin-bottom: 0.2rem;">Non-PPR Total</div>
|
||||
<div class="summary-item-value" style="font-size: 1.3rem;" id="non-ppr-total">0</div>
|
||||
@@ -551,8 +564,8 @@
|
||||
<th>Callsign</th>
|
||||
<th>From</th>
|
||||
<th>To</th>
|
||||
<th>ETA / ETD</th>
|
||||
<th>Landed / Departed</th>
|
||||
<th>ETA / ETD / Called</th>
|
||||
<th>Landed / Departed / QSY</th>
|
||||
<th>Circuits</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -671,11 +684,12 @@
|
||||
if (status) url += `&status=${status}`;
|
||||
|
||||
// Fetch all data in parallel
|
||||
const [pprResponse, arrivalsResponse, departuresResponse, localFlightsResponse] = await Promise.all([
|
||||
const [pprResponse, arrivalsResponse, departuresResponse, localFlightsResponse, overflightsResponse] = await Promise.all([
|
||||
authenticatedFetch(url),
|
||||
authenticatedFetch(`/api/v1/arrivals/?limit=10000${dateFrom ? `&date_from=${dateFrom}` : ''}${dateTo ? `&date_to=${dateTo}` : ''}`),
|
||||
authenticatedFetch(`/api/v1/departures/?limit=10000${dateFrom ? `&date_from=${dateFrom}` : ''}${dateTo ? `&date_to=${dateTo}` : ''}`),
|
||||
authenticatedFetch(`/api/v1/local-flights/?limit=10000${dateFrom ? `&date_from=${dateFrom}` : ''}${dateTo ? `&date_to=${dateTo}` : ''}`)
|
||||
authenticatedFetch(`/api/v1/local-flights/?limit=10000${dateFrom ? `&date_from=${dateFrom}` : ''}${dateTo ? `&date_to=${dateTo}` : ''}`),
|
||||
authenticatedFetch(`/api/v1/overflights/?limit=10000${dateFrom ? `&date_from=${dateFrom}` : ''}${dateTo ? `&date_to=${dateTo}` : ''}`)
|
||||
]);
|
||||
|
||||
if (!pprResponse.ok) {
|
||||
@@ -739,6 +753,20 @@
|
||||
})));
|
||||
}
|
||||
|
||||
if (overflightsResponse.ok) {
|
||||
const overflights = await overflightsResponse.json();
|
||||
otherFlights.push(...overflights.map(f => ({
|
||||
...f,
|
||||
flightType: 'OVERFLIGHT',
|
||||
aircraft_type: f.type,
|
||||
circuits: null,
|
||||
timeField: f.call_dt,
|
||||
fromField: f.departure_airfield,
|
||||
toField: f.destination_airfield,
|
||||
callsign: f.registration
|
||||
})));
|
||||
}
|
||||
|
||||
// Apply search filtering to other flights
|
||||
if (search) {
|
||||
const searchLower = search.toLowerCase();
|
||||
@@ -816,6 +844,7 @@
|
||||
});
|
||||
|
||||
// Other flights movements (excluding CANCELLED)
|
||||
let overflightCount = 0;
|
||||
otherFlights.filter(flight => flight.status !== 'CANCELLED').forEach(flight => {
|
||||
if (flight.flightType === 'ARRIVAL') {
|
||||
nonPprArrivals += 1;
|
||||
@@ -828,17 +857,21 @@
|
||||
// 2 movements (takeoff + landing) plus the circuit count
|
||||
const circuits = flight.circuits || 0;
|
||||
circuitsMovements += 2 + circuits;
|
||||
} else if (flight.flightType === 'OVERFLIGHT') {
|
||||
// 1 movement for each overflight (they're just talking to tower)
|
||||
overflightCount += 1;
|
||||
}
|
||||
});
|
||||
|
||||
const pprTotal = pprArrivals + pprDepartures;
|
||||
const nonPprTotal = localFlightsMovements + circuitsMovements + nonPprArrivals + nonPprDepartures;
|
||||
const nonPprTotal = localFlightsMovements + circuitsMovements + nonPprArrivals + nonPprDepartures + overflightCount;
|
||||
const grandTotal = pprTotal + nonPprTotal;
|
||||
|
||||
// Update the summary display
|
||||
document.getElementById('ppr-arrivals').textContent = pprArrivals;
|
||||
document.getElementById('ppr-departures').textContent = pprDepartures;
|
||||
document.getElementById('ppr-total').textContent = pprTotal;
|
||||
document.getElementById('overflights-count').textContent = overflightCount;
|
||||
|
||||
document.getElementById('local-flights-movements').textContent = localFlightsMovements;
|
||||
document.getElementById('circuits-movements').textContent = circuitsMovements;
|
||||
@@ -945,9 +978,18 @@
|
||||
const from = flight.fromField || '-';
|
||||
const to = flight.toField || '-';
|
||||
const timeDisplay = flight.timeField ? formatDateTime(flight.timeField) : '-';
|
||||
const actualDisplay = flight.flightType === 'ARRIVAL'
|
||||
? (flight.landed_dt ? formatDateTime(flight.landed_dt) : '-')
|
||||
: (flight.departed_dt ? formatDateTime(flight.departed_dt) : '-');
|
||||
|
||||
// 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 status = flight.status || (flight.flightType === 'CIRCUIT' ? 'COMPLETED' : 'PENDING');
|
||||
const circuits = (flight.flightType === 'CIRCUIT' || flight.flightType === 'LOCAL') ? (flight.circuits > 0 ? flight.circuits : '-') : '-';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user