Filtering enhancements
This commit is contained in:
308
web/reports.html
308
web/reports.html
@@ -378,18 +378,26 @@
|
||||
<div class="container">
|
||||
<!-- Filters Section -->
|
||||
<div class="filters-section">
|
||||
<div class="filters-grid">
|
||||
<div class="filter-group">
|
||||
<label for="date-from">Date From:</label>
|
||||
<input type="date" id="date-from">
|
||||
<div style="display: flex; gap: 0.5rem; align-items: flex-end; flex-wrap: wrap;">
|
||||
<!-- Quick Filter Buttons -->
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<button class="btn btn-primary" id="filter-today" onclick="setDateRangeToday()" style="font-size: 0.9rem; padding: 0.5rem 1rem; white-space: nowrap;">📅 Today</button>
|
||||
<button class="btn btn-secondary" id="filter-week" onclick="setDateRangeThisWeek()" style="font-size: 0.9rem; padding: 0.5rem 1rem; white-space: nowrap;">📆 This Week</button>
|
||||
<button class="btn btn-secondary" id="filter-month" onclick="setDateRangeThisMonth()" style="font-size: 0.9rem; padding: 0.5rem 1rem; white-space: nowrap;">📊 This Month</button>
|
||||
<button class="btn btn-secondary" id="filter-custom" onclick="toggleCustomRange()" style="font-size: 0.9rem; padding: 0.5rem 1rem; white-space: nowrap;">📋 Custom Range</button>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label for="date-to">Date To:</label>
|
||||
<input type="date" id="date-to">
|
||||
|
||||
<!-- Custom Date Range (hidden by default) -->
|
||||
<div id="custom-range-container" style="display: none; display: flex; gap: 0.5rem; align-items: center;">
|
||||
<input type="date" id="date-from" style="padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
|
||||
<span style="font-weight: 600; color: #666;">to</span>
|
||||
<input type="date" id="date-to" style="padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label for="status-filter">Status:</label>
|
||||
<select id="status-filter">
|
||||
|
||||
<!-- Status Filter -->
|
||||
<div style="display: flex; flex-direction: column; gap: 0.3rem;">
|
||||
<label for="status-filter" style="font-weight: 600; font-size: 0.85rem; color: #555;">Status:</label>
|
||||
<select id="status-filter" style="padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; font-size: 0.9rem;">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="NEW">New</option>
|
||||
<option value="CONFIRMED">Confirmed</option>
|
||||
@@ -399,15 +407,19 @@
|
||||
<option value="DELETED">Deleted</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label for="search-input">Search:</label>
|
||||
<input type="text" id="search-input" placeholder="Aircraft reg, callsign, captain, or airport...">
|
||||
|
||||
<!-- Search Input -->
|
||||
<div style="flex: 1; min-width: 200px; display: flex; flex-direction: column; gap: 0.3rem;">
|
||||
<label for="search-input" style="font-weight: 600; font-size: 0.85rem; color: #555;">Search:</label>
|
||||
<input type="text" id="search-input" placeholder="Aircraft reg, callsign, captain, or airport..." style="padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; font-size: 0.9rem;">
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<button class="btn btn-primary" onclick="loadReports()">
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<button class="btn btn-primary" onclick="loadReports()" style="font-size: 0.9rem; padding: 0.5rem 1rem; white-space: nowrap;">
|
||||
🔍 Search
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="clearFilters()">
|
||||
<button class="btn btn-secondary" onclick="clearFilters()" style="font-size: 0.9rem; padding: 0.5rem 1rem; white-space: nowrap;">
|
||||
🗑️ Clear
|
||||
</button>
|
||||
</div>
|
||||
@@ -417,67 +429,69 @@
|
||||
<!-- Summary Box -->
|
||||
<div class="summary-box">
|
||||
<div class="summary-title">📊 Movements Summary</div>
|
||||
<div style="display: grid; grid-template-columns: 1fr auto; gap: 2rem; align-items: center;">
|
||||
<div class="summary-grid">
|
||||
<!-- 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;">PPR Movements</div>
|
||||
<div style="display: grid; grid-template-columns: repeat(3, 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;">Arrivals (Landings)</div>
|
||||
<div class="summary-item-value" style="font-size: 1.3rem;" id="ppr-arrivals">0</div>
|
||||
<div style="grid-column: 1/-1; padding-bottom: 0.8rem; border-bottom: 2px solid rgba(255,255,255,0.3);">
|
||||
<div style="font-size: 0.85rem; font-weight: 600; margin-bottom: 0.4rem;">PPR Movements</div>
|
||||
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.8rem;">
|
||||
<div class="summary-item" style="padding: 0.4rem;">
|
||||
<div class="summary-item-label" style="font-size: 0.7rem; margin-bottom: 0.1rem;">Arrivals</div>
|
||||
<div class="summary-item-value" style="font-size: 1.1rem;" id="ppr-arrivals">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;">Departures (Takeoffs)</div>
|
||||
<div class="summary-item-value" style="font-size: 1.3rem;" id="ppr-departures">0</div>
|
||||
<div class="summary-item" style="padding: 0.4rem;">
|
||||
<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="ppr-departures">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;">PPR Total</div>
|
||||
<div class="summary-item-value" style="font-size: 1.3rem;" id="ppr-total">0</div>
|
||||
<div class="summary-item" style="border-left-color: #ffd700; background: rgba(255,215,0,0.1); padding: 0.4rem;">
|
||||
<div class="summary-item-label" style="font-weight: 600; font-size: 0.7rem; margin-bottom: 0.1rem;">Total</div>
|
||||
<div class="summary-item-value" style="font-size: 1.1rem;" id="ppr-total">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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(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>
|
||||
<div style="grid-column: 1/-1; padding-top: 0.8rem;">
|
||||
<div style="font-size: 0.85rem; font-weight: 600; margin-bottom: 0.4rem;">Non-PPR Movements</div>
|
||||
<div style="display: grid; grid-template-columns: repeat(6, 1fr); gap: 0.8rem;">
|
||||
<div class="summary-item" style="padding: 0.4rem; cursor: pointer;" onclick="filterOtherFlights('LOCAL')">
|
||||
<div class="summary-item-label" style="font-size: 0.7rem; margin-bottom: 0.1rem;">Local</div>
|
||||
<div class="summary-item-value" style="font-size: 1.1rem;" id="local-flights-movements">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;">Circuits</div>
|
||||
<div class="summary-item-value" style="font-size: 1.3rem;" id="circuits-movements">0</div>
|
||||
<div class="summary-item" style="padding: 0.4rem; cursor: pointer;" onclick="filterOtherFlights('CIRCUIT')">
|
||||
<div class="summary-item-label" style="font-size: 0.7rem; margin-bottom: 0.1rem;">Circuits</div>
|
||||
<div class="summary-item-value" style="font-size: 1.1rem;" id="circuits-movements">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;">Arrivals</div>
|
||||
<div class="summary-item-value" style="font-size: 1.3rem;" id="non-ppr-arrivals">0</div>
|
||||
<div class="summary-item" style="padding: 0.4rem; cursor: pointer;" onclick="filterOtherFlights('ARRIVAL')">
|
||||
<div class="summary-item-label" style="font-size: 0.7rem; margin-bottom: 0.1rem;">Arrivals</div>
|
||||
<div class="summary-item-value" style="font-size: 1.1rem;" id="non-ppr-arrivals">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;">Departures</div>
|
||||
<div class="summary-item-value" style="font-size: 1.3rem;" id="non-ppr-departures">0</div>
|
||||
<div class="summary-item" style="padding: 0.4rem; cursor: pointer;" onclick="filterOtherFlights('DEPARTURE')">
|
||||
<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.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 class="summary-item" style="padding: 0.4rem; cursor: pointer;" onclick="filterOtherFlights('OVERFLIGHT')">
|
||||
<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>
|
||||
<div class="summary-item" style="border-left-color: #ffd700; background: rgba(255,215,0,0.1); padding: 0.4rem;">
|
||||
<div class="summary-item-label" style="font-weight: 600; font-size: 0.7rem; margin-bottom: 0.1rem;">Total</div>
|
||||
<div class="summary-item-value" style="font-size: 1.1rem;" id="non-ppr-total">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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Grand Total -->
|
||||
<div style="grid-column: 1/-1; text-align: center;">
|
||||
<div style="font-size: 0.85rem; opacity: 0.9; margin-bottom: 0.3rem;">Grand Total Movements</div>
|
||||
<div style="font-size: 2rem; font-weight: 700;" id="grand-total-movements">0</div>
|
||||
<!-- Grand Total - positioned on the right -->
|
||||
<div style="text-align: center; padding: 1rem; background: rgba(255,215,0,0.05); border-radius: 8px; border-left: 4px solid #ffd700; min-width: 120px;">
|
||||
<div style="font-size: 0.75rem; opacity: 0.85; margin-bottom: 0.2rem;">GRAND TOTAL</div>
|
||||
<div style="font-size: 2.2rem; font-weight: 700;" id="grand-total-movements">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reports Table -->
|
||||
<div class="reports-table">
|
||||
<div class="reports-table" id="ppr-reports-section">
|
||||
<div class="table-header">
|
||||
<div>
|
||||
<strong>PPR Records</strong>
|
||||
@@ -590,6 +604,7 @@
|
||||
let accessToken = null;
|
||||
let currentPPRs = []; // Store current results for export
|
||||
let currentOtherFlights = []; // Store other flights for export
|
||||
let otherFlightsFilterType = null; // Track which non-PPR flight type is selected for filtering
|
||||
|
||||
// Load UI configuration from API
|
||||
async function loadUIConfig() {
|
||||
@@ -639,14 +654,105 @@
|
||||
await loadReports();
|
||||
}
|
||||
|
||||
// Set default date range to current month
|
||||
// Set default date range to today
|
||||
function setupDefaultDateRange() {
|
||||
setDateRangeToday();
|
||||
}
|
||||
|
||||
// Toggle custom date range picker
|
||||
function toggleCustomRange() {
|
||||
const container = document.getElementById('custom-range-container');
|
||||
const customBtn = document.getElementById('filter-custom');
|
||||
|
||||
const isVisible = container.style.display !== 'none';
|
||||
container.style.display = isVisible ? 'none' : 'flex';
|
||||
|
||||
// Update button style
|
||||
if (isVisible) {
|
||||
customBtn.classList.remove('btn-primary');
|
||||
customBtn.classList.add('btn-secondary');
|
||||
} else {
|
||||
customBtn.classList.remove('btn-secondary');
|
||||
customBtn.classList.add('btn-primary');
|
||||
// Focus on the first date input when opening
|
||||
document.getElementById('date-from').focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Set date range to today
|
||||
function setDateRangeToday() {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('date-from').value = today;
|
||||
document.getElementById('date-to').value = today;
|
||||
|
||||
// Hide custom range picker if it's open
|
||||
document.getElementById('custom-range-container').style.display = 'none';
|
||||
|
||||
updateFilterButtonStyles('today');
|
||||
loadReports();
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
document.getElementById('date-from').value = monday.toISOString().split('T')[0];
|
||||
document.getElementById('date-to').value = sunday.toISOString().split('T')[0];
|
||||
|
||||
// Hide custom range picker if it's open
|
||||
document.getElementById('custom-range-container').style.display = 'none';
|
||||
|
||||
updateFilterButtonStyles('week');
|
||||
loadReports();
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
document.getElementById('date-from').value = firstDay.toISOString().split('T')[0];
|
||||
document.getElementById('date-to').value = lastDay.toISOString().split('T')[0];
|
||||
|
||||
// Hide custom range picker if it's open
|
||||
document.getElementById('custom-range-container').style.display = 'none';
|
||||
|
||||
updateFilterButtonStyles('month');
|
||||
loadReports();
|
||||
}
|
||||
|
||||
// Update button styles to show which filter is active
|
||||
function updateFilterButtonStyles(activeFilter) {
|
||||
const todayBtn = document.getElementById('filter-today');
|
||||
const weekBtn = document.getElementById('filter-week');
|
||||
const monthBtn = document.getElementById('filter-month');
|
||||
|
||||
// Reset all buttons
|
||||
[todayBtn, weekBtn, monthBtn].forEach(btn => {
|
||||
btn.classList.remove('btn-primary');
|
||||
btn.classList.add('btn-secondary');
|
||||
});
|
||||
|
||||
// Highlight active button
|
||||
switch(activeFilter) {
|
||||
case 'today':
|
||||
todayBtn.classList.remove('btn-secondary');
|
||||
todayBtn.classList.add('btn-primary');
|
||||
break;
|
||||
case 'week':
|
||||
weekBtn.classList.remove('btn-secondary');
|
||||
weekBtn.classList.add('btn-primary');
|
||||
break;
|
||||
case 'month':
|
||||
monthBtn.classList.remove('btn-secondary');
|
||||
monthBtn.classList.add('btn-primary');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Authentication management
|
||||
@@ -985,20 +1091,103 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Filter other flights by type
|
||||
function filterOtherFlights(flightType) {
|
||||
// Toggle filter if clicking the same type
|
||||
if (otherFlightsFilterType === flightType) {
|
||||
otherFlightsFilterType = null;
|
||||
} else {
|
||||
otherFlightsFilterType = flightType;
|
||||
}
|
||||
|
||||
// Show/hide PPR section based on filter
|
||||
const pprSection = document.getElementById('ppr-reports-section');
|
||||
if (pprSection) {
|
||||
pprSection.style.display = otherFlightsFilterType ? 'none' : 'block';
|
||||
}
|
||||
|
||||
// Update visual indication of active filter
|
||||
updateFilterIndicators();
|
||||
|
||||
// Re-display flights with new filter
|
||||
displayOtherFlights(currentOtherFlights);
|
||||
}
|
||||
|
||||
// Update visual indicators for active filter
|
||||
function updateFilterIndicators() {
|
||||
// Select all clickable non-PPR summary items (those with onclick attribute)
|
||||
const summaryItems = document.querySelectorAll('.summary-item[onclick*="filterOtherFlights"]');
|
||||
summaryItems.forEach(item => {
|
||||
item.style.opacity = '1';
|
||||
item.style.borderLeftColor = '';
|
||||
item.style.borderLeftWidth = '0';
|
||||
});
|
||||
|
||||
if (otherFlightsFilterType) {
|
||||
// Get the ID of the selected filter's summary item
|
||||
let selectedId = '';
|
||||
switch(otherFlightsFilterType) {
|
||||
case 'LOCAL':
|
||||
selectedId = 'local-flights-movements';
|
||||
break;
|
||||
case 'CIRCUIT':
|
||||
selectedId = 'circuits-movements';
|
||||
break;
|
||||
case 'ARRIVAL':
|
||||
selectedId = 'non-ppr-arrivals';
|
||||
break;
|
||||
case 'DEPARTURE':
|
||||
selectedId = 'non-ppr-departures';
|
||||
break;
|
||||
case 'OVERFLIGHT':
|
||||
selectedId = 'overflights-count';
|
||||
break;
|
||||
}
|
||||
|
||||
// Find and highlight the selected item
|
||||
if (selectedId) {
|
||||
const selectedElement = document.getElementById(selectedId);
|
||||
if (selectedElement) {
|
||||
const summaryItem = selectedElement.closest('.summary-item');
|
||||
if (summaryItem) {
|
||||
summaryItem.style.borderLeftColor = '#4CAF50';
|
||||
summaryItem.style.borderLeftWidth = '4px';
|
||||
summaryItem.style.opacity = '1';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dim other items that are clickable (non-PPR items)
|
||||
const allSummaryItems = document.querySelectorAll('.summary-item[onclick]');
|
||||
allSummaryItems.forEach(item => {
|
||||
if (item.querySelector('#' + selectedId) === null) {
|
||||
item.style.opacity = '0.5';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Display other flights in table
|
||||
function displayOtherFlights(flights) {
|
||||
const tbody = document.getElementById('other-flights-table-body');
|
||||
const tableInfo = document.getElementById('other-flights-info');
|
||||
|
||||
tableInfo.textContent = `${flights.length} flights found`;
|
||||
// Apply filter if one is selected
|
||||
let filteredFlights = flights;
|
||||
if (otherFlightsFilterType) {
|
||||
filteredFlights = flights.filter(flight => flight.flightType === otherFlightsFilterType);
|
||||
}
|
||||
|
||||
if (flights.length === 0) {
|
||||
tableInfo.textContent = `${filteredFlights.length} flights found` + (otherFlightsFilterType ? ` (filtered by ${otherFlightsFilterType})` : '');
|
||||
|
||||
if (filteredFlights.length === 0) {
|
||||
document.getElementById('other-flights-no-data').style.display = 'block';
|
||||
document.getElementById('other-flights-table-content').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by time field (ascending)
|
||||
flights.sort((a, b) => {
|
||||
filteredFlights.sort((a, b) => {
|
||||
const aTime = a.timeField;
|
||||
const bTime = b.timeField;
|
||||
if (!aTime) return 1;
|
||||
@@ -1008,8 +1197,9 @@
|
||||
|
||||
tbody.innerHTML = '';
|
||||
document.getElementById('other-flights-table-content').style.display = 'block';
|
||||
document.getElementById('other-flights-no-data').style.display = 'none';
|
||||
|
||||
for (const flight of flights) {
|
||||
for (const flight of filteredFlights) {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
const typeLabel = flight.flightType;
|
||||
|
||||
Reference in New Issue
Block a user