diff --git a/backend/app/schemas/arrival.py b/backend/app/schemas/arrival.py
index 1fd42fc..fafbf8e 100644
--- a/backend/app/schemas/arrival.py
+++ b/backend/app/schemas/arrival.py
@@ -42,6 +42,7 @@ class ArrivalCreate(ArrivalBase):
class ArrivalUpdate(BaseModel):
+ registration: Optional[str] = None
type: Optional[str] = None
callsign: Optional[str] = None
pob: Optional[int] = None
diff --git a/backend/app/schemas/departure.py b/backend/app/schemas/departure.py
index dbbfb25..181f3ee 100644
--- a/backend/app/schemas/departure.py
+++ b/backend/app/schemas/departure.py
@@ -43,6 +43,7 @@ class DepartureCreate(DepartureBase):
class DepartureUpdate(BaseModel):
+ registration: Optional[str] = None
type: Optional[str] = None
callsign: Optional[str] = None
pob: Optional[int] = None
diff --git a/web/admin.html b/web/admin.html
index d9d7e97..834c4a7 100644
--- a/web/admin.html
+++ b/web/admin.html
@@ -353,6 +353,9 @@
+
@@ -431,8 +434,8 @@
-
@@ -509,8 +512,8 @@
-
- Cancel
+
+ Close
💾 Save Changes
@@ -576,8 +579,8 @@
-
- Cancel
+
+ Close
🛬 Book In
@@ -601,7 +604,7 @@
🛫 Mark Departed
- ❌ Cancel
+ ❌ Cancel Departure
@@ -611,37 +614,100 @@
-
+
Close
+
+ Save Changes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 🛬 Mark Landed
+
+
+ ❌ Cancel Arrival
+
+
+
+
@@ -659,7 +725,7 @@
@@ -738,8 +804,8 @@
-
- Cancel
+
+ Close
💾 Save User
@@ -772,8 +838,8 @@
-
- Cancel
+
+ Close
🔐 Change Password
@@ -801,8 +867,8 @@
-
- Cancel
+
+ Close
Confirm
@@ -827,8 +893,8 @@
-
- Cancel
+
+ Close
Record Circuit
@@ -1137,6 +1203,27 @@
return;
}
+ // Press 'Escape' to close local flight edit modal if it's open (allow even when typing in inputs)
+ if (e.key === 'Escape' && document.getElementById('localFlightEditModal').style.display === 'block') {
+ e.preventDefault();
+ closeLocalFlightEditModal();
+ return;
+ }
+
+ // Press 'Escape' to close departure edit modal if it's open (allow even when typing in inputs)
+ if (e.key === 'Escape' && document.getElementById('departureEditModal').style.display === 'block') {
+ e.preventDefault();
+ closeDepartureEditModal();
+ return;
+ }
+
+ // Press 'Escape' to close arrival edit modal if it's open (allow even when typing in inputs)
+ if (e.key === 'Escape' && document.getElementById('arrivalEditModal').style.display === 'block') {
+ e.preventDefault();
+ closeArrivalEditModal();
+ return;
+ }
+
// Only trigger other shortcuts when not typing in input fields
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') {
return;
@@ -1871,9 +1958,7 @@
if (isLocal) {
openLocalFlightEditModal(flight.id);
} else if (isBookedIn) {
- // For booked-in flights, we might add a view modal later
- // For now, just show a message
- showNotification(`Booked-in flight: ${flight.registration}`, false);
+ openArrivalEditModal(flight.id);
} else {
openPPRModal(flight.id);
}
@@ -3276,8 +3361,10 @@
document.getElementById('local_edit_notes').value = flight.notes || '';
// Parse and populate departure time if exists
- if (flight.departure_dt) {
- const dept = new Date(flight.departure_dt);
+ // Use departed_dt (actual departure) if available, otherwise etd (planned departure)
+ const departureTime = flight.departed_dt || flight.etd;
+ if (departureTime) {
+ const dept = new Date(departureTime);
document.getElementById('local_edit_departure_date').value = dept.toISOString().slice(0, 10);
document.getElementById('local_edit_departure_time').value = dept.toISOString().slice(11, 16);
}
@@ -3396,6 +3483,182 @@
currentDepartureId = null;
}
+ // Departure edit form submission
+ document.getElementById('departure-edit-form').addEventListener('submit', async function(e) {
+ e.preventDefault();
+
+ if (!currentDepartureId || !accessToken) return;
+
+ const formData = new FormData(this);
+ const updateData = {};
+
+ formData.forEach((value, key) => {
+ if (key === 'id') return;
+
+ // Handle date/time combination for ETD
+ if (key === 'etd_date' || key === 'etd_time') {
+ if (!updateData.etd && formData.get('etd_date') && formData.get('etd_time')) {
+ const dateStr = formData.get('etd_date');
+ const timeStr = formData.get('etd_time');
+ updateData.etd = new Date(`${dateStr}T${timeStr}`).toISOString();
+ }
+ return;
+ }
+
+ // Only include non-empty values
+ if (typeof value === 'number' || (typeof value === 'string' && value.trim() !== '')) {
+ if (value.trim) {
+ updateData[key] = value.trim();
+ } else {
+ updateData[key] = value;
+ }
+ }
+ });
+
+ try {
+ const response = await fetch(`/api/v1/departures/${currentDepartureId}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${accessToken}`
+ },
+ body: JSON.stringify(updateData)
+ });
+
+ if (!response.ok) throw new Error('Failed to update departure');
+
+ closeDepartureEditModal();
+ loadPPRs(); // Refresh departures display
+ showNotification('Departure updated successfully');
+ } catch (error) {
+ console.error('Error updating departure:', error);
+ showNotification('Error updating departure', true);
+ }
+ });
+
+ // Arrival Edit Modal Functions
+ let currentArrivalId = null;
+
+ async function openArrivalEditModal(arrivalId) {
+ if (!accessToken) return;
+
+ try {
+ const response = await fetch(`/api/v1/arrivals/${arrivalId}`, {
+ headers: { 'Authorization': `Bearer ${accessToken}` }
+ });
+
+ if (!response.ok) throw new Error('Failed to load arrival');
+
+ const arrival = await response.json();
+ currentArrivalId = arrival.id;
+
+ // Populate form
+ document.getElementById('arrival-edit-id').value = arrival.id;
+ document.getElementById('arrival_edit_registration').value = arrival.registration || '';
+ document.getElementById('arrival_edit_type').value = arrival.type || '';
+ document.getElementById('arrival_edit_callsign').value = arrival.callsign || '';
+ document.getElementById('arrival_edit_in_from').value = arrival.in_from || '';
+ document.getElementById('arrival_edit_pob').value = arrival.pob || '';
+ document.getElementById('arrival_edit_notes').value = arrival.notes || '';
+
+ // Update title
+ const regOrCallsign = arrival.callsign || arrival.registration;
+ document.getElementById('arrival-edit-title').textContent = `Arrival: ${regOrCallsign}`;
+
+ // Show/hide quick action buttons based on status
+ const landedBtn = document.getElementById('arrival-btn-landed');
+ const cancelBtn = document.getElementById('arrival-btn-cancel');
+ landedBtn.style.display = arrival.status === 'BOOKED_IN' ? 'block' : 'none';
+ cancelBtn.style.display = arrival.status === 'BOOKED_IN' ? 'block' : 'none';
+
+ // Show modal
+ document.getElementById('arrivalEditModal').style.display = 'block';
+ } catch (error) {
+ console.error('Error loading arrival:', error);
+ showNotification('Error loading arrival details', true);
+ }
+ }
+
+ function closeArrivalEditModal() {
+ document.getElementById('arrivalEditModal').style.display = 'none';
+ currentArrivalId = null;
+ }
+
+ // Arrival edit form submission
+ document.getElementById('arrival-edit-form').addEventListener('submit', async function(e) {
+ e.preventDefault();
+
+ if (!currentArrivalId || !accessToken) return;
+
+ const formData = new FormData(this);
+ const updateData = {};
+
+ formData.forEach((value, key) => {
+ if (key === 'id') return;
+
+ // Only include non-empty values
+ if (typeof value === 'number' || (typeof value === 'string' && value.trim() !== '')) {
+ if (key === 'pob') {
+ updateData[key] = parseInt(value);
+ } else if (value.trim) {
+ updateData[key] = value.trim();
+ } else {
+ updateData[key] = value;
+ }
+ }
+ });
+
+ try {
+ const response = await fetch(`/api/v1/arrivals/${currentArrivalId}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${accessToken}`
+ },
+ body: JSON.stringify(updateData)
+ });
+
+ if (!response.ok) throw new Error('Failed to update arrival');
+
+ closeArrivalEditModal();
+ loadPPRs(); // Refresh arrivals display
+ showNotification('Arrival updated successfully');
+ } catch (error) {
+ console.error('Error updating arrival:', error);
+ showNotification('Error updating arrival', true);
+ }
+ });
+
+ async function updateArrivalStatus(status) {
+ if (!currentArrivalId || !accessToken) return;
+
+ if (status === 'CANCELLED') {
+ if (!confirm('Are you sure you want to cancel this arrival?')) {
+ return;
+ }
+ }
+
+ try {
+ const response = await fetch(`/api/v1/arrivals/${currentArrivalId}/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 arrival status');
+
+ closeArrivalEditModal();
+ loadPPRs(); // Refresh arrivals display
+ showNotification(`Arrival marked as ${status.toLowerCase()}`);
+ } catch (error) {
+ console.error('Error updating arrival status:', error);
+ showNotification('Error updating arrival status', true);
+ }
+ }
+
// Update status from table buttons (with flight ID passed)
async function updateLocalFlightStatusFromTable(flightId, status) {
if (!accessToken) return;