local-flights #5
@@ -10,7 +10,7 @@ from app.schemas.ppr import PPRPublic
|
||||
from app.models.local_flight import LocalFlightStatus
|
||||
from app.models.departure import DepartureStatus
|
||||
from app.models.arrival import ArrivalStatus
|
||||
from datetime import date
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -34,15 +34,23 @@ async def get_public_arrivals(db: Session = Depends(get_db)):
|
||||
'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(
|
||||
db,
|
||||
status=LocalFlightStatus.DEPARTED,
|
||||
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
|
||||
for flight in local_flights:
|
||||
# Only include flights booked out today
|
||||
if not (today_start <= flight.created_dt < today_end):
|
||||
continue
|
||||
arrivals_list.append({
|
||||
'ac_call': flight.callsign or flight.registration,
|
||||
'ac_reg': flight.registration,
|
||||
@@ -78,15 +86,23 @@ async def get_public_departures(db: Session = Depends(get_db)):
|
||||
'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(
|
||||
db,
|
||||
status=LocalFlightStatus.BOOKED_OUT,
|
||||
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
|
||||
for flight in local_flights:
|
||||
# Only include flights booked out today
|
||||
if not (today_start <= flight.created_dt < today_end):
|
||||
continue
|
||||
departures_list.append({
|
||||
'ac_call': flight.callsign or flight.registration,
|
||||
'ac_reg': flight.registration,
|
||||
|
||||
205
web/admin.html
205
web/admin.html
@@ -493,6 +493,66 @@
|
||||
</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()">×</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 -->
|
||||
<div id="userManagementModal" class="modal">
|
||||
<div class="modal-content">
|
||||
@@ -1112,10 +1172,18 @@
|
||||
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) {
|
||||
const localFlights = await localResponse.json();
|
||||
const localInAir = localFlights.map(flight => ({
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const localInAir = localFlights
|
||||
.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
|
||||
}));
|
||||
@@ -1164,10 +1232,18 @@
|
||||
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) {
|
||||
const localFlights = await localResponse.json();
|
||||
const localDepartures = localFlights.map(flight => ({
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const localDepartures = localFlights
|
||||
.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
|
||||
}));
|
||||
@@ -1202,9 +1278,10 @@
|
||||
document.getElementById('departed-no-data').style.display = 'none';
|
||||
|
||||
try {
|
||||
const [pprResponse, localResponse] = await Promise.all([
|
||||
const [pprResponse, localResponse, depResponse] = await Promise.all([
|
||||
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) {
|
||||
@@ -1227,8 +1304,8 @@
|
||||
if (localResponse.ok) {
|
||||
const localFlights = await localResponse.json();
|
||||
const localDeparted = localFlights.filter(flight => {
|
||||
if (!flight.departure_dt) return false;
|
||||
const departedDate = flight.departure_dt.split('T')[0];
|
||||
if (!flight.departed_dt) return false;
|
||||
const departedDate = flight.departed_dt.split('T')[0];
|
||||
return departedDate === today;
|
||||
}).map(flight => ({
|
||||
...flight,
|
||||
@@ -1237,6 +1314,20 @@
|
||||
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);
|
||||
} catch (error) {
|
||||
console.error('Error loading departed aircraft:', error);
|
||||
@@ -1259,8 +1350,8 @@
|
||||
|
||||
// Sort by departed time
|
||||
departed.sort((a, b) => {
|
||||
const aTime = a.departed_dt || a.departure_dt;
|
||||
const bTime = b.departed_dt || b.departure_dt;
|
||||
const aTime = a.departed_dt;
|
||||
const bTime = b.departed_dt;
|
||||
return new Date(aTime) - new Date(bTime);
|
||||
});
|
||||
|
||||
@@ -1270,10 +1361,13 @@
|
||||
for (const flight of departed) {
|
||||
const row = document.createElement('tr');
|
||||
const isLocal = flight.isLocalFlight;
|
||||
const isDeparture = flight.isDeparture;
|
||||
|
||||
row.onclick = () => {
|
||||
if (isLocal) {
|
||||
openLocalFlightEditModal(flight.id);
|
||||
} else if (isDeparture) {
|
||||
openDepartureEditModal(flight.id);
|
||||
} else {
|
||||
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.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;">${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 {
|
||||
row.innerHTML = `
|
||||
@@ -1657,7 +1758,7 @@
|
||||
if (isLocal) {
|
||||
openLocalFlightEditModal(flight.id);
|
||||
} else if (isDeparture) {
|
||||
// TODO: Open departure edit modal
|
||||
openDepartureEditModal(flight.id);
|
||||
} else {
|
||||
openPPRModal(flight.id);
|
||||
}
|
||||
@@ -2789,6 +2890,55 @@
|
||||
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)
|
||||
async function updateLocalFlightStatusFromTable(flightId, status) {
|
||||
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
|
||||
document.getElementById('local-flight-edit-form').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user