Booking out improvements
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
217
web/admin.html
217
web/admin.html
@@ -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()">×</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();
|
||||||
|
|||||||
Reference in New Issue
Block a user