local-flights #5

Merged
jamesp merged 37 commits from local-flights into main 2025-12-20 12:29:32 -05:00
2 changed files with 218 additions and 21 deletions
Showing only changes of commit d2e7d3c3dd - Show all commits

View File

@@ -10,7 +10,7 @@ from app.schemas.ppr import PPRPublic
from app.models.local_flight import LocalFlightStatus from app.models.local_flight import LocalFlightStatus
from app.models.departure import DepartureStatus from app.models.departure import DepartureStatus
from app.models.arrival import ArrivalStatus from app.models.arrival import ArrivalStatus
from datetime import date from datetime import date, datetime, timedelta
router = APIRouter() router = APIRouter()
@@ -34,15 +34,23 @@ async def get_public_arrivals(db: Session = Depends(get_db)):
'isLocalFlight': False 'isLocalFlight': False
}) })
# Add local flights with DEPARTED status # Add local flights with DEPARTED status that were booked out today
local_flights = crud_local_flight.get_multi( local_flights = crud_local_flight.get_multi(
db, db,
status=LocalFlightStatus.DEPARTED, status=LocalFlightStatus.DEPARTED,
limit=1000 limit=1000
) )
# Get today's date boundaries
today = date.today()
today_start = datetime.combine(today, datetime.min.time())
today_end = datetime.combine(today + timedelta(days=1), datetime.min.time())
# Convert local flights to match the PPR format for display # Convert local flights to match the PPR format for display
for flight in local_flights: for flight in local_flights:
# Only include flights booked out today
if not (today_start <= flight.created_dt < today_end):
continue
arrivals_list.append({ arrivals_list.append({
'ac_call': flight.callsign or flight.registration, 'ac_call': flight.callsign or flight.registration,
'ac_reg': flight.registration, 'ac_reg': flight.registration,
@@ -78,15 +86,23 @@ async def get_public_departures(db: Session = Depends(get_db)):
'isDeparture': False 'isDeparture': False
}) })
# Add local flights with BOOKED_OUT status # Add local flights with BOOKED_OUT status that were booked out today
local_flights = crud_local_flight.get_multi( local_flights = crud_local_flight.get_multi(
db, db,
status=LocalFlightStatus.BOOKED_OUT, status=LocalFlightStatus.BOOKED_OUT,
limit=1000 limit=1000
) )
# Get today's date boundaries
today = date.today()
today_start = datetime.combine(today, datetime.min.time())
today_end = datetime.combine(today + timedelta(days=1), datetime.min.time())
# Convert local flights to match the PPR format for display # Convert local flights to match the PPR format for display
for flight in local_flights: for flight in local_flights:
# Only include flights booked out today
if not (today_start <= flight.created_dt < today_end):
continue
departures_list.append({ departures_list.append({
'ac_call': flight.callsign or flight.registration, 'ac_call': flight.callsign or flight.registration,
'ac_reg': flight.registration, 'ac_reg': flight.registration,

View File

@@ -493,6 +493,66 @@
</div> </div>
</div> </div>
<!-- Departure Edit Modal -->
<div id="departureEditModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2 id="departure-edit-title">Departure Details</h2>
<button class="close" onclick="closeDepartureEditModal()">&times;</button>
</div>
<div class="modal-body">
<div class="quick-actions">
<button id="departure-btn-departed" class="btn btn-primary btn-sm" onclick="updateDepartureStatus('DEPARTED')" style="display: none;">
🛫 Mark Departed
</button>
<button id="departure-btn-cancel" class="btn btn-danger btn-sm" onclick="updateDepartureStatus('CANCELLED')" style="display: none;">
❌ Cancel
</button>
</div>
<form id="departure-edit-form">
<input type="hidden" id="departure-edit-id" name="id">
<div class="form-grid">
<div class="form-group">
<label for="departure_edit_registration">Aircraft Registration</label>
<input type="text" id="departure_edit_registration" name="registration" readonly style="background-color: #f5f5f5; cursor: not-allowed;">
</div>
<div class="form-group">
<label for="departure_edit_type">Aircraft Type</label>
<input type="text" id="departure_edit_type" name="type" readonly style="background-color: #f5f5f5; cursor: not-allowed;">
</div>
<div class="form-group">
<label for="departure_edit_callsign">Callsign</label>
<input type="text" id="departure_edit_callsign" name="callsign" readonly style="background-color: #f5f5f5; cursor: not-allowed;">
</div>
<div class="form-group">
<label for="departure_edit_out_to">Destination</label>
<input type="text" id="departure_edit_out_to" name="out_to" readonly style="background-color: #f5f5f5; cursor: not-allowed;">
</div>
<div class="form-group">
<label for="departure_edit_etd">ETD (Estimated Time of Departure)</label>
<div style="display: flex; gap: 0.5rem;">
<input type="date" id="departure_edit_etd_date" name="etd_date" readonly style="flex: 1; background-color: #f5f5f5; cursor: not-allowed;">
<input type="time" id="departure_edit_etd_time" name="etd_time" readonly style="flex: 1; background-color: #f5f5f5; cursor: not-allowed;">
</div>
</div>
<div class="form-group full-width">
<label for="departure_edit_notes">Notes</label>
<textarea id="departure_edit_notes" name="notes" rows="3" readonly style="background-color: #f5f5f5; cursor: not-allowed;"></textarea>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-primary" onclick="closeDepartureEditModal()">
Close
</button>
</div>
</form>
</div>
</div>
</div>
<!-- User Management Modal --> <!-- User Management Modal -->
<div id="userManagementModal" class="modal"> <div id="userManagementModal" class="modal">
<div class="modal-content"> <div class="modal-content">
@@ -1112,13 +1172,21 @@
return etaDate === today; return etaDate === today;
}); });
// Add local flights in DEPARTED status (in the air, heading back) // Add local flights in DEPARTED status (in the air, heading back) - only those booked out today
if (localResponse.ok) { if (localResponse.ok) {
const localFlights = await localResponse.json(); const localFlights = await localResponse.json();
const localInAir = localFlights.map(flight => ({ const today = new Date().toISOString().split('T')[0];
...flight, const localInAir = localFlights
isLocalFlight: true // Flag to distinguish from PPR .filter(flight => {
})); // Only include flights booked out today (created_dt)
if (!flight.created_dt) return false;
const createdDate = flight.created_dt.split('T')[0];
return createdDate === today;
})
.map(flight => ({
...flight,
isLocalFlight: true // Flag to distinguish from PPR
}));
arrivals.push(...localInAir); arrivals.push(...localInAir);
} }
@@ -1164,13 +1232,21 @@
return etdDate === today; return etdDate === today;
}); });
// Add local flights (BOOKED_OUT status - ready to go) // Add local flights (BOOKED_OUT status - ready to go) - only those booked out today
if (localResponse.ok) { if (localResponse.ok) {
const localFlights = await localResponse.json(); const localFlights = await localResponse.json();
const localDepartures = localFlights.map(flight => ({ const today = new Date().toISOString().split('T')[0];
...flight, const localDepartures = localFlights
isLocalFlight: true // Flag to distinguish from PPR .filter(flight => {
})); // Only include flights booked out today (created_dt)
if (!flight.created_dt) return false;
const createdDate = flight.created_dt.split('T')[0];
return createdDate === today;
})
.map(flight => ({
...flight,
isLocalFlight: true // Flag to distinguish from PPR
}));
departures.push(...localDepartures); departures.push(...localDepartures);
} }
@@ -1202,9 +1278,10 @@
document.getElementById('departed-no-data').style.display = 'none'; document.getElementById('departed-no-data').style.display = 'none';
try { try {
const [pprResponse, localResponse] = await Promise.all([ const [pprResponse, localResponse, depResponse] = await Promise.all([
authenticatedFetch('/api/v1/pprs/?limit=1000'), authenticatedFetch('/api/v1/pprs/?limit=1000'),
authenticatedFetch('/api/v1/local-flights/?status=DEPARTED&limit=1000') authenticatedFetch('/api/v1/local-flights/?status=DEPARTED&limit=1000'),
authenticatedFetch('/api/v1/departures/?status=DEPARTED&limit=1000')
]); ]);
if (!pprResponse.ok) { if (!pprResponse.ok) {
@@ -1227,8 +1304,8 @@
if (localResponse.ok) { if (localResponse.ok) {
const localFlights = await localResponse.json(); const localFlights = await localResponse.json();
const localDeparted = localFlights.filter(flight => { const localDeparted = localFlights.filter(flight => {
if (!flight.departure_dt) return false; if (!flight.departed_dt) return false;
const departedDate = flight.departure_dt.split('T')[0]; const departedDate = flight.departed_dt.split('T')[0];
return departedDate === today; return departedDate === today;
}).map(flight => ({ }).map(flight => ({
...flight, ...flight,
@@ -1237,6 +1314,20 @@
departed.push(...localDeparted); departed.push(...localDeparted);
} }
// Add departures to other airports that departed today
if (depResponse.ok) {
const depFlights = await depResponse.json();
const depDeparted = depFlights.filter(flight => {
if (!flight.departed_dt) return false;
const departedDate = flight.departed_dt.split('T')[0];
return departedDate === today;
}).map(flight => ({
...flight,
isDeparture: true
}));
departed.push(...depDeparted);
}
displayDeparted(departed); displayDeparted(departed);
} catch (error) { } catch (error) {
console.error('Error loading departed aircraft:', error); console.error('Error loading departed aircraft:', error);
@@ -1259,8 +1350,8 @@
// Sort by departed time // Sort by departed time
departed.sort((a, b) => { departed.sort((a, b) => {
const aTime = a.departed_dt || a.departure_dt; const aTime = a.departed_dt;
const bTime = b.departed_dt || b.departure_dt; const bTime = b.departed_dt;
return new Date(aTime) - new Date(bTime); return new Date(aTime) - new Date(bTime);
}); });
@@ -1270,10 +1361,13 @@
for (const flight of departed) { for (const flight of departed) {
const row = document.createElement('tr'); const row = document.createElement('tr');
const isLocal = flight.isLocalFlight; const isLocal = flight.isLocalFlight;
const isDeparture = flight.isDeparture;
row.onclick = () => { row.onclick = () => {
if (isLocal) { if (isLocal) {
openLocalFlightEditModal(flight.id); openLocalFlightEditModal(flight.id);
} else if (isDeparture) {
openDepartureEditModal(flight.id);
} else { } else {
openPPRModal(flight.id); openPPRModal(flight.id);
} }
@@ -1285,7 +1379,14 @@
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${flight.registration || '-'}</td> <td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${flight.registration || '-'}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${flight.callsign || '-'}</td> <td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${flight.callsign || '-'}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">-</td> <td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">-</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${formatTimeOnly(flight.departure_dt)}</td> <td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${formatTimeOnly(flight.departed_dt)}</td>
`;
} else if (isDeparture) {
row.innerHTML = `
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${flight.registration || '-'}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${flight.callsign || '-'}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${flight.out_to || '-'}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${formatTimeOnly(flight.departed_dt)}</td>
`; `;
} else { } else {
row.innerHTML = ` row.innerHTML = `
@@ -1657,7 +1758,7 @@
if (isLocal) { if (isLocal) {
openLocalFlightEditModal(flight.id); openLocalFlightEditModal(flight.id);
} else if (isDeparture) { } else if (isDeparture) {
// TODO: Open departure edit modal openDepartureEditModal(flight.id);
} else { } else {
openPPRModal(flight.id); openPPRModal(flight.id);
} }
@@ -2789,6 +2890,55 @@
currentLocalFlightId = null; currentLocalFlightId = null;
} }
// Open departure edit modal
async function openDepartureEditModal(departureId) {
if (!accessToken) return;
try {
const response = await fetch(`/api/v1/departures/${departureId}`, {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
if (!response.ok) throw new Error('Failed to load departure');
const departure = await response.json();
currentDepartureId = departure.id;
// Populate form
document.getElementById('departure-edit-id').value = departure.id;
document.getElementById('departure_edit_registration').value = departure.registration;
document.getElementById('departure_edit_type').value = departure.type;
document.getElementById('departure_edit_callsign').value = departure.callsign || '';
document.getElementById('departure_edit_out_to').value = departure.out_to;
document.getElementById('departure_edit_notes').value = departure.notes || '';
// Parse and populate ETD if exists
if (departure.etd) {
const etd = new Date(departure.etd);
document.getElementById('departure_edit_etd_date').value = etd.toISOString().slice(0, 10);
document.getElementById('departure_edit_etd_time').value = etd.toISOString().slice(11, 16);
}
// Show/hide action buttons based on status
const deptBtn = document.getElementById('departure-btn-departed');
const cancelBtn = document.getElementById('departure-btn-cancel');
if (deptBtn) deptBtn.style.display = departure.status === 'BOOKED_OUT' ? 'inline-block' : 'none';
if (cancelBtn) cancelBtn.style.display = departure.status === 'BOOKED_OUT' ? 'inline-block' : 'none';
document.getElementById('departure-edit-title').textContent = `${departure.registration} to ${departure.out_to}`;
document.getElementById('departureEditModal').style.display = 'block';
} catch (error) {
console.error('Error loading departure:', error);
showNotification('Error loading departure details', true);
}
}
function closeDepartureEditModal() {
document.getElementById('departureEditModal').style.display = 'none';
currentDepartureId = null;
}
// Update status from table buttons (with flight ID passed) // Update status from table buttons (with flight ID passed)
async function updateLocalFlightStatusFromTable(flightId, status) { async function updateLocalFlightStatusFromTable(flightId, status) {
if (!accessToken) return; if (!accessToken) return;
@@ -2876,6 +3026,37 @@
} }
} }
async function updateDepartureStatus(status) {
if (!currentDepartureId || !accessToken) return;
// Show confirmation for cancel actions
if (status === 'CANCELLED') {
if (!confirm('Are you sure you want to cancel this departure? This action cannot be easily undone.')) {
return;
}
}
try {
const response = await fetch(`/api/v1/departures/${currentDepartureId}/status`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify({ status: status })
});
if (!response.ok) throw new Error('Failed to update status');
closeDepartureEditModal();
loadPPRs(); // Refresh display
showNotification(`Departure marked as ${status.toLowerCase()}`);
} catch (error) {
console.error('Error updating status:', error);
showNotification('Error updating departure status', true);
}
}
// Local flight edit form submission // Local flight edit form submission
document.getElementById('local-flight-edit-form').addEventListener('submit', async function(e) { document.getElementById('local-flight-edit-form').addEventListener('submit', async function(e) {
e.preventDefault(); e.preventDefault();