Better movement reporting
This commit is contained in:
@@ -79,6 +79,7 @@ def upgrade() -> None:
|
|||||||
sa.Column('flight_type', sa.Enum('LOCAL', 'CIRCUITS', 'DEPARTURE', name='localflighttype'), nullable=False),
|
sa.Column('flight_type', sa.Enum('LOCAL', 'CIRCUITS', 'DEPARTURE', name='localflighttype'), nullable=False),
|
||||||
sa.Column('status', sa.Enum('BOOKED_OUT', 'DEPARTED', 'LANDED', 'CANCELLED', name='localflightstatus'), nullable=False, server_default='BOOKED_OUT'),
|
sa.Column('status', sa.Enum('BOOKED_OUT', 'DEPARTED', 'LANDED', 'CANCELLED', name='localflightstatus'), nullable=False, server_default='BOOKED_OUT'),
|
||||||
sa.Column('duration', sa.Integer(), nullable=True, comment='Duration in minutes'),
|
sa.Column('duration', sa.Integer(), nullable=True, comment='Duration in minutes'),
|
||||||
|
sa.Column('circuits', sa.Integer(), nullable=True, default=0, comment='Actual number of circuits completed'),
|
||||||
sa.Column('notes', sa.Text(), nullable=True),
|
sa.Column('notes', sa.Text(), nullable=True),
|
||||||
sa.Column('created_dt', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
|
sa.Column('created_dt', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
|
||||||
sa.Column('etd', sa.DateTime(), nullable=True),
|
sa.Column('etd', sa.DateTime(), nullable=True),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from datetime import date, datetime
|
|||||||
from app.models.local_flight import LocalFlight, LocalFlightStatus, LocalFlightType
|
from app.models.local_flight import LocalFlight, LocalFlightStatus, LocalFlightType
|
||||||
from app.schemas.local_flight import LocalFlightCreate, LocalFlightUpdate, LocalFlightStatusUpdate
|
from app.schemas.local_flight import LocalFlightCreate, LocalFlightUpdate, LocalFlightStatusUpdate
|
||||||
from app.models.journal import EntityType
|
from app.models.journal import EntityType
|
||||||
|
from app.models.circuit import Circuit
|
||||||
from app.crud.crud_journal import journal
|
from app.crud.crud_journal import journal
|
||||||
|
|
||||||
|
|
||||||
@@ -149,6 +150,11 @@ class CRUDLocalFlight:
|
|||||||
db_obj.departed_dt = current_time
|
db_obj.departed_dt = current_time
|
||||||
elif status == LocalFlightStatus.LANDED:
|
elif status == LocalFlightStatus.LANDED:
|
||||||
db_obj.landed_dt = current_time
|
db_obj.landed_dt = current_time
|
||||||
|
# Count circuits from the circuits table and populate the circuits column
|
||||||
|
circuit_count = db.query(func.count(Circuit.id)).filter(
|
||||||
|
Circuit.local_flight_id == flight_id
|
||||||
|
).scalar()
|
||||||
|
db_obj.circuits = circuit_count
|
||||||
|
|
||||||
db.add(db_obj)
|
db.add(db_obj)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class LocalFlight(Base):
|
|||||||
flight_type = Column(SQLEnum(LocalFlightType), nullable=False, index=True)
|
flight_type = Column(SQLEnum(LocalFlightType), nullable=False, index=True)
|
||||||
status = Column(SQLEnum(LocalFlightStatus), nullable=False, default=LocalFlightStatus.BOOKED_OUT, index=True)
|
status = Column(SQLEnum(LocalFlightStatus), nullable=False, default=LocalFlightStatus.BOOKED_OUT, index=True)
|
||||||
duration = Column(Integer, nullable=True) # Duration in minutes
|
duration = Column(Integer, nullable=True) # Duration in minutes
|
||||||
|
circuits = Column(Integer, nullable=True, default=0) # Actual number of circuits completed
|
||||||
notes = Column(Text, nullable=True)
|
notes = Column(Text, nullable=True)
|
||||||
created_dt = Column(DateTime, nullable=False, server_default=func.current_timestamp(), index=True)
|
created_dt = Column(DateTime, nullable=False, server_default=func.current_timestamp(), index=True)
|
||||||
etd = Column(DateTime, nullable=True, index=True) # Estimated Time of Departure
|
etd = Column(DateTime, nullable=True, index=True) # Estimated Time of Departure
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ class LocalFlightUpdate(BaseModel):
|
|||||||
status: Optional[LocalFlightStatus] = None
|
status: Optional[LocalFlightStatus] = None
|
||||||
etd: Optional[datetime] = None
|
etd: Optional[datetime] = None
|
||||||
departed_dt: Optional[datetime] = None
|
departed_dt: Optional[datetime] = None
|
||||||
|
circuits: Optional[int] = None
|
||||||
notes: Optional[str] = None
|
notes: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
@@ -77,6 +78,7 @@ class LocalFlightInDBBase(LocalFlightBase):
|
|||||||
etd: Optional[datetime] = None
|
etd: Optional[datetime] = None
|
||||||
departed_dt: Optional[datetime] = None
|
departed_dt: Optional[datetime] = None
|
||||||
landed_dt: Optional[datetime] = None
|
landed_dt: Optional[datetime] = None
|
||||||
|
circuits: Optional[int] = None
|
||||||
created_by: Optional[str] = None
|
created_by: Optional[str] = None
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
|
|
||||||
|
|||||||
@@ -2626,7 +2626,7 @@
|
|||||||
showNotification(`✈️ Circuit recorded at ${formatTimeOnly(circuit.circuit_timestamp)}`);
|
showNotification(`✈️ Circuit recorded at ${formatTimeOnly(circuit.circuit_timestamp)}`);
|
||||||
closeCircuitModal();
|
closeCircuitModal();
|
||||||
|
|
||||||
// Refresh departures to show updated circuit count
|
// Refresh departures to show updated flight info
|
||||||
loadDepartures();
|
loadDepartures();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error recording circuit:', error);
|
console.error('Error recording circuit:', error);
|
||||||
|
|||||||
362
web/reports.html
362
web/reports.html
@@ -227,6 +227,45 @@
|
|||||||
.status.canceled { background: #ffebee; color: #d32f2f; }
|
.status.canceled { background: #ffebee; color: #d32f2f; }
|
||||||
.status.deleted { background: #f3e5f5; color: #7b1fa2; }
|
.status.deleted { background: #f3e5f5; color: #7b1fa2; }
|
||||||
|
|
||||||
|
.summary-box {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-item {
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 1rem;
|
||||||
|
border-left: 4px solid rgba(255,255,255,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-item-label {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-item-value {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.no-data {
|
.no-data {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 3rem;
|
padding: 3rem;
|
||||||
@@ -366,6 +405,64 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Summary Box -->
|
||||||
|
<div class="summary-box">
|
||||||
|
<div class="summary-title">📊 Movements Summary</div>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</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(5, 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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Reports Table -->
|
<!-- Reports Table -->
|
||||||
<div class="reports-table">
|
<div class="reports-table">
|
||||||
<div class="table-header">
|
<div class="table-header">
|
||||||
@@ -422,6 +519,54 @@
|
|||||||
<p>No records match your current filters.</p>
|
<p>No records match your current filters.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Other Flights Table -->
|
||||||
|
<div class="reports-table" style="margin-top: 2rem;">
|
||||||
|
<div class="table-header">
|
||||||
|
<div>
|
||||||
|
<strong>Other Flights</strong>
|
||||||
|
<div class="table-info" id="other-flights-info">Loading...</div>
|
||||||
|
</div>
|
||||||
|
<div class="export-buttons">
|
||||||
|
<button class="btn btn-success" onclick="exportOtherFlightsToCSV()">
|
||||||
|
📊 Export CSV
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="other-flights-loading" class="loading">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
Loading flights...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="other-flights-table-content" style="display: none;">
|
||||||
|
<div class="table-container">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Aircraft</th>
|
||||||
|
<th>Aircraft Type</th>
|
||||||
|
<th>Callsign</th>
|
||||||
|
<th>From</th>
|
||||||
|
<th>To</th>
|
||||||
|
<th>ETA / ETD</th>
|
||||||
|
<th>Landed / Departed</th>
|
||||||
|
<th>Circuits</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="other-flights-table-body">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="other-flights-no-data" class="no-data" style="display: none;">
|
||||||
|
<h3>No other flights found</h3>
|
||||||
|
<p>No flights match your current filters.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Success Notification -->
|
<!-- Success Notification -->
|
||||||
@@ -431,6 +576,7 @@
|
|||||||
let currentUser = null;
|
let currentUser = null;
|
||||||
let accessToken = null;
|
let accessToken = null;
|
||||||
let currentPPRs = []; // Store current results for export
|
let currentPPRs = []; // Store current results for export
|
||||||
|
let currentOtherFlights = []; // Store other flights for export
|
||||||
|
|
||||||
// Initialize the page
|
// Initialize the page
|
||||||
async function initializePage() {
|
async function initializePage() {
|
||||||
@@ -508,6 +654,9 @@
|
|||||||
document.getElementById('reports-loading').style.display = 'block';
|
document.getElementById('reports-loading').style.display = 'block';
|
||||||
document.getElementById('reports-table-content').style.display = 'none';
|
document.getElementById('reports-table-content').style.display = 'none';
|
||||||
document.getElementById('reports-no-data').style.display = 'none';
|
document.getElementById('reports-no-data').style.display = 'none';
|
||||||
|
document.getElementById('other-flights-loading').style.display = 'block';
|
||||||
|
document.getElementById('other-flights-table-content').style.display = 'none';
|
||||||
|
document.getElementById('other-flights-no-data').style.display = 'none';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dateFrom = document.getElementById('date-from').value;
|
const dateFrom = document.getElementById('date-from').value;
|
||||||
@@ -521,13 +670,19 @@
|
|||||||
if (dateTo) url += `&date_to=${dateTo}`;
|
if (dateTo) url += `&date_to=${dateTo}`;
|
||||||
if (status) url += `&status=${status}`;
|
if (status) url += `&status=${status}`;
|
||||||
|
|
||||||
const response = await authenticatedFetch(url);
|
// Fetch all data in parallel
|
||||||
|
const [pprResponse, arrivalsResponse, departuresResponse, localFlightsResponse] = 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}` : ''}`)
|
||||||
|
]);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!pprResponse.ok) {
|
||||||
throw new Error('Failed to fetch PPR records');
|
throw new Error('Failed to fetch PPR records');
|
||||||
}
|
}
|
||||||
|
|
||||||
let pprs = await response.json();
|
let pprs = await pprResponse.json();
|
||||||
|
|
||||||
// Apply client-side search filtering
|
// Apply client-side search filtering
|
||||||
if (search) {
|
if (search) {
|
||||||
@@ -543,6 +698,64 @@
|
|||||||
|
|
||||||
currentPPRs = pprs; // Store for export
|
currentPPRs = pprs; // Store for export
|
||||||
displayReports(pprs);
|
displayReports(pprs);
|
||||||
|
|
||||||
|
// Process other flights
|
||||||
|
let otherFlights = [];
|
||||||
|
|
||||||
|
if (arrivalsResponse.ok) {
|
||||||
|
const arrivals = await arrivalsResponse.json();
|
||||||
|
otherFlights.push(...arrivals.map(f => ({
|
||||||
|
...f,
|
||||||
|
flightType: 'ARRIVAL',
|
||||||
|
aircraft_type: f.type,
|
||||||
|
timeField: f.eta || f.landed_dt,
|
||||||
|
fromField: f.in_from,
|
||||||
|
toField: 'EGFH'
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (departuresResponse.ok) {
|
||||||
|
const departures = await departuresResponse.json();
|
||||||
|
otherFlights.push(...departures.map(f => ({
|
||||||
|
...f,
|
||||||
|
flightType: 'DEPARTURE',
|
||||||
|
aircraft_type: f.type,
|
||||||
|
timeField: f.etd || f.departed_dt,
|
||||||
|
fromField: 'EGFH',
|
||||||
|
toField: f.out_to
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localFlightsResponse.ok) {
|
||||||
|
const localFlights = await localFlightsResponse.json();
|
||||||
|
otherFlights.push(...localFlights.map(f => ({
|
||||||
|
...f,
|
||||||
|
flightType: f.flight_type === 'CIRCUITS' ? 'CIRCUIT' : f.flight_type,
|
||||||
|
aircraft_type: f.type,
|
||||||
|
circuits: f.circuits,
|
||||||
|
timeField: f.departed_dt,
|
||||||
|
fromField: 'EGFH',
|
||||||
|
toField: 'EGFH'
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply search filtering to other flights
|
||||||
|
if (search) {
|
||||||
|
const searchLower = search.toLowerCase();
|
||||||
|
otherFlights = otherFlights.filter(f =>
|
||||||
|
(f.registration && f.registration.toLowerCase().includes(searchLower)) ||
|
||||||
|
(f.callsign && f.callsign.toLowerCase().includes(searchLower)) ||
|
||||||
|
(f.fromField && f.fromField.toLowerCase().includes(searchLower)) ||
|
||||||
|
(f.toField && f.toField.toLowerCase().includes(searchLower))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentOtherFlights = otherFlights;
|
||||||
|
displayOtherFlights(otherFlights);
|
||||||
|
|
||||||
|
// Calculate and display movements summary
|
||||||
|
calculateMovementsSummary(pprs, otherFlights);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading reports:', error);
|
console.error('Error loading reports:', error);
|
||||||
if (error.message !== 'Session expired. Please log in again.') {
|
if (error.message !== 'Session expired. Please log in again.') {
|
||||||
@@ -551,6 +764,63 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('reports-loading').style.display = 'none';
|
document.getElementById('reports-loading').style.display = 'none';
|
||||||
|
document.getElementById('other-flights-loading').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate and display movements summary
|
||||||
|
function calculateMovementsSummary(pprs, otherFlights) {
|
||||||
|
let pprArrivals = 0; // PPR landings
|
||||||
|
let pprDepartures = 0; // PPR takeoffs
|
||||||
|
let localFlightsMovements = 0;
|
||||||
|
let circuitsMovements = 0;
|
||||||
|
let nonPprArrivals = 0;
|
||||||
|
let nonPprDepartures = 0;
|
||||||
|
|
||||||
|
// PPR movements:
|
||||||
|
// - LANDED = 1 arrival (landing)
|
||||||
|
// - DEPARTED = 1 departure + 1 arrival (because departure implies a prior landing)
|
||||||
|
pprs.forEach(ppr => {
|
||||||
|
if (ppr.status === 'LANDED') {
|
||||||
|
pprArrivals += 1;
|
||||||
|
} else if (ppr.status === 'DEPARTED') {
|
||||||
|
pprDepartures += 1;
|
||||||
|
pprArrivals += 1; // Each departure implies a landing happened
|
||||||
|
}
|
||||||
|
// CANCELED = 0 movements, not counted
|
||||||
|
});
|
||||||
|
|
||||||
|
// Other flights movements
|
||||||
|
otherFlights.forEach(flight => {
|
||||||
|
if (flight.flightType === 'ARRIVAL') {
|
||||||
|
nonPprArrivals += 1;
|
||||||
|
} else if (flight.flightType === 'DEPARTURE') {
|
||||||
|
nonPprDepartures += 1;
|
||||||
|
} else if (flight.flightType === 'LOCAL') {
|
||||||
|
// 2 movements (takeoff + landing) for the flight itself
|
||||||
|
localFlightsMovements += 2;
|
||||||
|
} else if (flight.flightType === 'CIRCUIT') {
|
||||||
|
// 2 movements (takeoff + landing) plus the circuit count
|
||||||
|
const circuits = flight.circuits || 0;
|
||||||
|
circuitsMovements += 2 + circuits;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const pprTotal = pprArrivals + pprDepartures;
|
||||||
|
const nonPprTotal = localFlightsMovements + circuitsMovements + nonPprArrivals + nonPprDepartures;
|
||||||
|
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('local-flights-movements').textContent = localFlightsMovements;
|
||||||
|
document.getElementById('circuits-movements').textContent = circuitsMovements;
|
||||||
|
document.getElementById('non-ppr-arrivals').textContent = nonPprArrivals;
|
||||||
|
document.getElementById('non-ppr-departures').textContent = nonPprDepartures;
|
||||||
|
document.getElementById('non-ppr-total').textContent = nonPprTotal;
|
||||||
|
|
||||||
|
document.getElementById('grand-total-movements').textContent = grandTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display reports in table
|
// Display reports in table
|
||||||
@@ -615,6 +885,63 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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`;
|
||||||
|
|
||||||
|
if (flights.length === 0) {
|
||||||
|
document.getElementById('other-flights-no-data').style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by time field (ascending)
|
||||||
|
flights.sort((a, b) => {
|
||||||
|
const aTime = a.timeField;
|
||||||
|
const bTime = b.timeField;
|
||||||
|
if (!aTime) return 1;
|
||||||
|
if (!bTime) return -1;
|
||||||
|
return new Date(aTime) - new Date(bTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
document.getElementById('other-flights-table-content').style.display = 'block';
|
||||||
|
|
||||||
|
for (const flight of flights) {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
|
||||||
|
const typeLabel = flight.flightType;
|
||||||
|
const registration = flight.registration || '-';
|
||||||
|
const aircraftType = flight.aircraft_type || '-';
|
||||||
|
const callsign = flight.callsign || '-';
|
||||||
|
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) : '-');
|
||||||
|
const status = flight.status || (flight.flightType === 'CIRCUIT' ? 'COMPLETED' : 'PENDING');
|
||||||
|
const circuits = (flight.flightType === 'CIRCUIT' || flight.flightType === 'LOCAL') ? (flight.circuits > 0 ? flight.circuits : '-') : '-';
|
||||||
|
|
||||||
|
row.innerHTML = `
|
||||||
|
<td><span class="status ${status.toLowerCase()}">${status}</span></td>
|
||||||
|
<td><strong>${typeLabel}</strong></td>
|
||||||
|
<td>${registration}</td>
|
||||||
|
<td>${aircraftType}</td>
|
||||||
|
<td>${callsign}</td>
|
||||||
|
<td>${from}</td>
|
||||||
|
<td>${to}</td>
|
||||||
|
<td>${timeDisplay}</td>
|
||||||
|
<td>${actualDisplay}</td>
|
||||||
|
<td>${circuits}</td>
|
||||||
|
`;
|
||||||
|
|
||||||
|
tbody.appendChild(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function formatDateTime(dateStr) {
|
function formatDateTime(dateStr) {
|
||||||
if (!dateStr) return '-';
|
if (!dateStr) return '-';
|
||||||
let utcDateStr = dateStr;
|
let utcDateStr = dateStr;
|
||||||
@@ -683,6 +1010,35 @@
|
|||||||
downloadCSV(headers, csvData, 'ppr_reports.csv');
|
downloadCSV(headers, csvData, 'ppr_reports.csv');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exportOtherFlightsToCSV() {
|
||||||
|
if (currentOtherFlights.length === 0) {
|
||||||
|
showNotification('No data to export', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = [
|
||||||
|
'Flight Type', 'Aircraft Registration', 'Aircraft Type', 'Callsign', 'From', 'To',
|
||||||
|
'ETA/ETD', 'Landed/Departed', 'Status', 'Circuits'
|
||||||
|
];
|
||||||
|
|
||||||
|
const csvData = currentOtherFlights.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.status || (flight.flightType === 'CIRCUIT' ? 'COMPLETED' : 'PENDING'),
|
||||||
|
(flight.flightType === 'CIRCUIT' || flight.flightType === 'LOCAL') ? (flight.circuits || '') : ''
|
||||||
|
]);
|
||||||
|
|
||||||
|
downloadCSV(headers, csvData, 'other_flights_reports.csv');
|
||||||
|
}
|
||||||
|
|
||||||
function downloadCSV(headers, data, filename) {
|
function downloadCSV(headers, data, filename) {
|
||||||
const csvContent = [
|
const csvContent = [
|
||||||
headers.join(','),
|
headers.join(','),
|
||||||
|
|||||||
Reference in New Issue
Block a user