Before refactor
This commit is contained in:
@@ -25,7 +25,7 @@ def upgrade() -> None:
|
|||||||
op.add_column('local_flights', sa.Column('takeoff_dt', sa.DateTime(), nullable=True))
|
op.add_column('local_flights', sa.Column('takeoff_dt', sa.DateTime(), nullable=True))
|
||||||
|
|
||||||
# Add GROUND and ARRIVED to arrivals status enum
|
# Add GROUND and ARRIVED to arrivals status enum
|
||||||
op.execute("ALTER TABLE arrivals MODIFY COLUMN status ENUM('BOOKED_IN','LANDED','GROUND','ARRIVED','CANCELLED')")
|
op.execute("ALTER TABLE arrivals MODIFY COLUMN status ENUM('BOOKED_IN','LANDED','GROUND','LOCAL','CIRCUIT','ARRIVED','CANCELLED')")
|
||||||
|
|
||||||
# Add timestamp column to arrivals
|
# Add timestamp column to arrivals
|
||||||
op.add_column('arrivals', sa.Column('arrived_dt', sa.DateTime(), nullable=True))
|
op.add_column('arrivals', sa.Column('arrived_dt', sa.DateTime(), nullable=True))
|
||||||
@@ -36,9 +36,22 @@ def upgrade() -> None:
|
|||||||
# Add timestamp columns to departures
|
# Add timestamp columns to departures
|
||||||
op.add_column('departures', sa.Column('contact_dt', sa.DateTime(), nullable=True))
|
op.add_column('departures', sa.Column('contact_dt', sa.DateTime(), nullable=True))
|
||||||
op.add_column('departures', sa.Column('takeoff_dt', sa.DateTime(), nullable=True))
|
op.add_column('departures', sa.Column('takeoff_dt', sa.DateTime(), nullable=True))
|
||||||
|
|
||||||
|
# Add arrival_id column to circuits table to support circuit logging for arrivals
|
||||||
|
op.add_column('circuits', sa.Column('arrival_id', sa.BigInteger(), nullable=True))
|
||||||
|
op.create_foreign_key('fk_circuits_arrival_id', 'circuits', 'arrivals', ['arrival_id'], ['id'], ondelete='CASCADE')
|
||||||
|
op.create_index('idx_circuit_arrival_id', 'circuits', ['arrival_id'])
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
# Remove arrival_id column from circuits table
|
||||||
|
op.drop_constraint('fk_circuits_arrival_id', 'circuits', type_='foreignkey')
|
||||||
|
op.drop_index('idx_circuit_arrival_id', table_name='circuits')
|
||||||
|
op.drop_column('circuits', 'arrival_id')
|
||||||
|
|
||||||
|
# Update departures with new status values to valid old values before modifying enum
|
||||||
|
op.execute("UPDATE departures SET status = 'DEPARTED' WHERE status IN ('GROUND', 'LOCAL')")
|
||||||
|
|
||||||
# Remove timestamp columns from departures
|
# Remove timestamp columns from departures
|
||||||
op.drop_column('departures', 'takeoff_dt')
|
op.drop_column('departures', 'takeoff_dt')
|
||||||
op.drop_column('departures', 'contact_dt')
|
op.drop_column('departures', 'contact_dt')
|
||||||
@@ -46,12 +59,18 @@ def downgrade() -> None:
|
|||||||
# Remove GROUND and LOCAL from departures status enum
|
# Remove GROUND and LOCAL from departures status enum
|
||||||
op.execute("ALTER TABLE departures MODIFY COLUMN status ENUM('BOOKED_OUT','DEPARTED','CANCELLED')")
|
op.execute("ALTER TABLE departures MODIFY COLUMN status ENUM('BOOKED_OUT','DEPARTED','CANCELLED')")
|
||||||
|
|
||||||
|
# Update arrivals with new status values to valid old values before modifying enum
|
||||||
|
op.execute("UPDATE arrivals SET status = 'LANDED' WHERE status IN ('GROUND', 'LOCAL', 'CIRCUIT', 'ARRIVED')")
|
||||||
|
|
||||||
# Remove timestamp column from arrivals
|
# Remove timestamp column from arrivals
|
||||||
op.drop_column('arrivals', 'arrived_dt')
|
op.drop_column('arrivals', 'arrived_dt')
|
||||||
|
|
||||||
# Remove GROUND and ARRIVED from arrivals status enum
|
# Remove GROUND and ARRIVED from arrivals status enum
|
||||||
op.execute("ALTER TABLE arrivals MODIFY COLUMN status ENUM('BOOKED_IN','LANDED','CANCELLED')")
|
op.execute("ALTER TABLE arrivals MODIFY COLUMN status ENUM('BOOKED_IN','LANDED','CANCELLED')")
|
||||||
|
|
||||||
|
# Update local_flights with new status values to valid old values before modifying enum
|
||||||
|
op.execute("UPDATE local_flights SET status = 'DEPARTED' WHERE status IN ('GROUND', 'LOCAL', 'CIRCUIT')")
|
||||||
|
|
||||||
# Remove timestamp columns from local_flights
|
# Remove timestamp columns from local_flights
|
||||||
op.drop_column('local_flights', 'takeoff_dt')
|
op.drop_column('local_flights', 'takeoff_dt')
|
||||||
op.drop_column('local_flights', 'contact_dt')
|
op.drop_column('local_flights', 'contact_dt')
|
||||||
|
|||||||
@@ -33,6 +33,17 @@ async def get_circuits_by_flight(
|
|||||||
return circuits
|
return circuits
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/arrival/{arrival_id}", response_model=List[Circuit])
|
||||||
|
async def get_circuits_by_arrival(
|
||||||
|
arrival_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_read_user)
|
||||||
|
):
|
||||||
|
"""Get all circuits for a specific arrival"""
|
||||||
|
circuits = crud_circuit.get_by_arrival(db, arrival_id=arrival_id)
|
||||||
|
return circuits
|
||||||
|
|
||||||
|
|
||||||
@router.post("/", response_model=Circuit)
|
@router.post("/", response_model=Circuit)
|
||||||
async def create_circuit(
|
async def create_circuit(
|
||||||
request: Request,
|
request: Request,
|
||||||
@@ -40,7 +51,19 @@ async def create_circuit(
|
|||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user: User = Depends(get_current_operator_user)
|
current_user: User = Depends(get_current_operator_user)
|
||||||
):
|
):
|
||||||
"""Record a new circuit (touch and go) for a local flight"""
|
"""Record a new circuit (touch and go) for a local flight or arrival"""
|
||||||
|
# Validate that exactly one of local_flight_id or arrival_id is provided
|
||||||
|
if not circuit_in.local_flight_id and not circuit_in.arrival_id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Either local_flight_id or arrival_id must be provided"
|
||||||
|
)
|
||||||
|
if circuit_in.local_flight_id and circuit_in.arrival_id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Cannot provide both local_flight_id and arrival_id"
|
||||||
|
)
|
||||||
|
|
||||||
circuit = crud_circuit.create(db, obj_in=circuit_in)
|
circuit = crud_circuit.create(db, obj_in=circuit_in)
|
||||||
|
|
||||||
# Send real-time update via WebSocket
|
# Send real-time update via WebSocket
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ class CRUDCircuit:
|
|||||||
Circuit.local_flight_id == local_flight_id
|
Circuit.local_flight_id == local_flight_id
|
||||||
).order_by(Circuit.circuit_timestamp).all()
|
).order_by(Circuit.circuit_timestamp).all()
|
||||||
|
|
||||||
|
def get_by_arrival(self, db: Session, arrival_id: int) -> List[Circuit]:
|
||||||
|
"""Get all circuits for a specific arrival"""
|
||||||
|
return db.query(Circuit).filter(
|
||||||
|
Circuit.arrival_id == arrival_id
|
||||||
|
).order_by(Circuit.circuit_timestamp).all()
|
||||||
|
|
||||||
def get_multi(
|
def get_multi(
|
||||||
self,
|
self,
|
||||||
db: Session,
|
db: Session,
|
||||||
@@ -27,6 +33,7 @@ class CRUDCircuit:
|
|||||||
def create(self, db: Session, obj_in: CircuitCreate) -> Circuit:
|
def create(self, db: Session, obj_in: CircuitCreate) -> Circuit:
|
||||||
db_obj = Circuit(
|
db_obj = Circuit(
|
||||||
local_flight_id=obj_in.local_flight_id,
|
local_flight_id=obj_in.local_flight_id,
|
||||||
|
arrival_id=obj_in.arrival_id,
|
||||||
circuit_timestamp=obj_in.circuit_timestamp
|
circuit_timestamp=obj_in.circuit_timestamp
|
||||||
)
|
)
|
||||||
db.add(db_obj)
|
db.add(db_obj)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from app.models.journal import JournalEntry
|
|||||||
from app.models.local_flight import LocalFlight
|
from app.models.local_flight import LocalFlight
|
||||||
from app.models.departure import Departure
|
from app.models.departure import Departure
|
||||||
from app.models.arrival import Arrival
|
from app.models.arrival import Arrival
|
||||||
|
from app.models.circuit import Circuit
|
||||||
|
|
||||||
# Set up logging
|
# Set up logging
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
from sqlalchemy import Column, BigInteger, String, Integer, Text, DateTime, Enum as SQLEnum, func
|
from sqlalchemy import Column, BigInteger, String, Integer, Text, DateTime, Enum as SQLEnum, func
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from app.db.session import Base
|
||||||
Base = declarative_base()
|
|
||||||
|
|
||||||
|
|
||||||
class SubmissionSource(str, Enum):
|
class SubmissionSource(str, Enum):
|
||||||
@@ -15,6 +13,8 @@ class ArrivalStatus(str, Enum):
|
|||||||
BOOKED_IN = "BOOKED_IN"
|
BOOKED_IN = "BOOKED_IN"
|
||||||
LANDED = "LANDED"
|
LANDED = "LANDED"
|
||||||
GROUND = "GROUND"
|
GROUND = "GROUND"
|
||||||
|
LOCAL = "LOCAL"
|
||||||
|
CIRCUIT = "CIRCUIT"
|
||||||
ARRIVED = "ARRIVED"
|
ARRIVED = "ARRIVED"
|
||||||
CANCELLED = "CANCELLED"
|
CANCELLED = "CANCELLED"
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class Circuit(Base):
|
|||||||
__tablename__ = "circuits"
|
__tablename__ = "circuits"
|
||||||
|
|
||||||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||||
local_flight_id = Column(BigInteger, ForeignKey("local_flights.id", ondelete="CASCADE"), nullable=False, index=True)
|
local_flight_id = Column(BigInteger, ForeignKey("local_flights.id", ondelete="CASCADE"), nullable=True, index=True)
|
||||||
|
arrival_id = Column(BigInteger, ForeignKey("arrivals.id", ondelete="CASCADE"), nullable=True, index=True)
|
||||||
circuit_timestamp = Column(DateTime, nullable=False, index=True)
|
circuit_timestamp = Column(DateTime, nullable=False, index=True)
|
||||||
created_at = Column(DateTime, nullable=False, server_default=func.current_timestamp())
|
created_at = Column(DateTime, nullable=False, server_default=func.current_timestamp())
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ class ArrivalStatus(str, Enum):
|
|||||||
BOOKED_IN = "BOOKED_IN"
|
BOOKED_IN = "BOOKED_IN"
|
||||||
LANDED = "LANDED"
|
LANDED = "LANDED"
|
||||||
GROUND = "GROUND"
|
GROUND = "GROUND"
|
||||||
|
LOCAL = "LOCAL"
|
||||||
|
CIRCUIT = "CIRCUIT"
|
||||||
ARRIVED = "ARRIVED"
|
ARRIVED = "ARRIVED"
|
||||||
CANCELLED = "CANCELLED"
|
CANCELLED = "CANCELLED"
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ from typing import Optional
|
|||||||
|
|
||||||
|
|
||||||
class CircuitBase(BaseModel):
|
class CircuitBase(BaseModel):
|
||||||
local_flight_id: int
|
local_flight_id: Optional[int] = None
|
||||||
|
arrival_id: Optional[int] = None
|
||||||
circuit_timestamp: datetime
|
circuit_timestamp: datetime
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+80
-9
@@ -1868,7 +1868,7 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Load PPR departures, local flight departures, and airport departures simultaneously
|
// Load PPR departures, local flight departures, and airport departures simultaneously
|
||||||
const [pprResponse, localBookedOutResponse, localOutGroundResponse, localLocalResponse, localCircuitResponse, depBookedOutResponse, depOutGroundResponse, depLocalResponse] = await Promise.all([
|
const [pprResponse, localBookedOutResponse, localOutGroundResponse, localLocalResponse, localCircuitResponse, depBookedOutResponse, depOutGroundResponse, depLocalResponse, arrLocalResponse, arrCircuitResponse] = await Promise.all([
|
||||||
authenticatedFetch('/api/v1/pprs/?limit=1000'),
|
authenticatedFetch('/api/v1/pprs/?limit=1000'),
|
||||||
authenticatedFetch('/api/v1/local-flights/?status=BOOKED_OUT&limit=1000'),
|
authenticatedFetch('/api/v1/local-flights/?status=BOOKED_OUT&limit=1000'),
|
||||||
authenticatedFetch('/api/v1/local-flights/?status=GROUND&limit=1000'),
|
authenticatedFetch('/api/v1/local-flights/?status=GROUND&limit=1000'),
|
||||||
@@ -1876,7 +1876,9 @@
|
|||||||
authenticatedFetch('/api/v1/local-flights/?status=CIRCUIT&limit=1000'),
|
authenticatedFetch('/api/v1/local-flights/?status=CIRCUIT&limit=1000'),
|
||||||
authenticatedFetch('/api/v1/departures/?status=BOOKED_OUT&limit=1000'),
|
authenticatedFetch('/api/v1/departures/?status=BOOKED_OUT&limit=1000'),
|
||||||
authenticatedFetch('/api/v1/departures/?status=GROUND&limit=1000'),
|
authenticatedFetch('/api/v1/departures/?status=GROUND&limit=1000'),
|
||||||
authenticatedFetch('/api/v1/departures/?status=LOCAL&limit=1000')
|
authenticatedFetch('/api/v1/departures/?status=LOCAL&limit=1000'),
|
||||||
|
authenticatedFetch('/api/v1/arrivals/?status=LOCAL&limit=1000'),
|
||||||
|
authenticatedFetch('/api/v1/arrivals/?status=CIRCUIT&limit=1000')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!pprResponse.ok) {
|
if (!pprResponse.ok) {
|
||||||
@@ -1891,6 +1893,8 @@
|
|||||||
const depBookedOut = depBookedOutResponse.ok ? await depBookedOutResponse.json() : [];
|
const depBookedOut = depBookedOutResponse.ok ? await depBookedOutResponse.json() : [];
|
||||||
const depOutGround = depOutGroundResponse.ok ? await depOutGroundResponse.json() : [];
|
const depOutGround = depOutGroundResponse.ok ? await depOutGroundResponse.json() : [];
|
||||||
const depLocal = depLocalResponse.ok ? await depLocalResponse.json() : [];
|
const depLocal = depLocalResponse.ok ? await depLocalResponse.json() : [];
|
||||||
|
const arrLocal = arrLocalResponse.ok ? await arrLocalResponse.json() : [];
|
||||||
|
const arrCircuit = arrCircuitResponse.ok ? await arrCircuitResponse.json() : [];
|
||||||
|
|
||||||
// Combine local flights
|
// Combine local flights
|
||||||
const allLocalFlights = [...localBookedOut, ...localOutGround, ...localLocal, ...localCircuit];
|
const allLocalFlights = [...localBookedOut, ...localOutGround, ...localLocal, ...localCircuit];
|
||||||
@@ -1929,6 +1933,20 @@
|
|||||||
}));
|
}));
|
||||||
departures.push(...depDepartures);
|
departures.push(...depDepartures);
|
||||||
|
|
||||||
|
// Add arrivals in LOCAL status
|
||||||
|
const arrDepartures = arrLocal.map(flight => ({
|
||||||
|
...flight,
|
||||||
|
isArrival: true // Flag to distinguish from PPR
|
||||||
|
}));
|
||||||
|
departures.push(...arrDepartures);
|
||||||
|
|
||||||
|
// Add arrivals in CIRCUIT status
|
||||||
|
const arrCircuitDepartures = arrCircuit.map(flight => ({
|
||||||
|
...flight,
|
||||||
|
isArrival: true // Flag to distinguish from PPR
|
||||||
|
}));
|
||||||
|
departures.push(...arrCircuitDepartures);
|
||||||
|
|
||||||
displayDepartures(departures);
|
displayDepartures(departures);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading departures:', error);
|
console.error('Error loading departures:', error);
|
||||||
@@ -2557,6 +2575,7 @@
|
|||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
const isLocal = flight.isLocalFlight;
|
const isLocal = flight.isLocalFlight;
|
||||||
const isDeparture = flight.isDeparture;
|
const isDeparture = flight.isDeparture;
|
||||||
|
const isArrival = flight.isArrival;
|
||||||
|
|
||||||
// Click handler that routes to correct modal
|
// Click handler that routes to correct modal
|
||||||
row.onclick = () => {
|
row.onclick = () => {
|
||||||
@@ -2564,6 +2583,8 @@
|
|||||||
openLocalFlightEditModal(flight.id);
|
openLocalFlightEditModal(flight.id);
|
||||||
} else if (isDeparture) {
|
} else if (isDeparture) {
|
||||||
openDepartureEditModal(flight.id);
|
openDepartureEditModal(flight.id);
|
||||||
|
} else if (isArrival) {
|
||||||
|
openArrivalEditModal(flight.id);
|
||||||
} else {
|
} else {
|
||||||
openPPRModal(flight.id);
|
openPPRModal(flight.id);
|
||||||
}
|
}
|
||||||
@@ -2682,6 +2703,42 @@
|
|||||||
} else {
|
} else {
|
||||||
actionButtons = '<span style="color: #999;">-</span>';
|
actionButtons = '<span style="color: #999;">-</span>';
|
||||||
}
|
}
|
||||||
|
} else if (isArrival) {
|
||||||
|
// Arrival display
|
||||||
|
if (flight.callsign && flight.callsign.trim()) {
|
||||||
|
aircraftDisplay = `<strong>${flight.callsign}</strong><br><span style="font-size: 0.8em; color: #666; font-style: italic;">${flight.registration}</span>`;
|
||||||
|
} else {
|
||||||
|
aircraftDisplay = `<strong>${flight.registration}</strong>`;
|
||||||
|
}
|
||||||
|
typeIcon = '<span style="color: #228b22; font-weight: bold; font-size: 0.9em;" title="Arrival">A</span>';
|
||||||
|
toDisplay = `<i>Arrival from ${flight.in_from || '?'}</i>`;
|
||||||
|
etd = flight.eta ? formatTimeOnly(flight.eta) : (flight.created_dt ? formatTimeOnly(flight.created_dt) : '-');
|
||||||
|
pob = flight.pob || '-';
|
||||||
|
fuel = '-';
|
||||||
|
landedDt = flight.arrived_dt ? formatTimeOnly(flight.arrived_dt) : '-';
|
||||||
|
|
||||||
|
// Action buttons for arrival
|
||||||
|
if (flight.status === 'LOCAL') {
|
||||||
|
actionButtons = `
|
||||||
|
<button class="btn btn-info btn-icon" onclick="event.stopPropagation(); currentArrivalId = ${flight.id}; updateArrivalStatusFromTable(${flight.id}, 'CIRCUIT')" title="Rejoin Circuit">
|
||||||
|
REJOIN
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
} else if (flight.status === 'CIRCUIT') {
|
||||||
|
actionButtons = `
|
||||||
|
<button class="btn btn-warning btn-icon" onclick="event.stopPropagation(); currentArrivalId = ${flight.id}; updateArrivalStatusFromTable(${flight.id}, 'LOCAL')" title="Move to Local Area">
|
||||||
|
LOCAL
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-info btn-icon" onclick="event.stopPropagation(); currentArrivalId = ${flight.id}; showCircuitModal(null, ${flight.id})" title="Record Touch & Go">
|
||||||
|
T&G
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-success btn-icon" onclick="event.stopPropagation(); currentArrivalId = ${flight.id}; updateArrivalStatusFromTable(${flight.id}, 'LANDED')" title="Mark as Landed">
|
||||||
|
LAND
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
actionButtons = '<span style="color: #999;">-</span>';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// PPR display
|
// PPR display
|
||||||
if (flight.ac_call && flight.ac_call.trim()) {
|
if (flight.ac_call && flight.ac_call.trim()) {
|
||||||
@@ -3043,8 +3100,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Circuit modal functions
|
// Circuit modal functions
|
||||||
function showCircuitModal() {
|
function showCircuitModal(localFlightId = null, arrivalId = null) {
|
||||||
if (!currentLocalFlightId) return;
|
if (!localFlightId && !arrivalId) return;
|
||||||
|
|
||||||
|
// Set the current IDs
|
||||||
|
currentLocalFlightId = localFlightId;
|
||||||
|
currentArrivalId = arrivalId;
|
||||||
|
|
||||||
// Set default timestamp to current time
|
// Set default timestamp to current time
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -3061,13 +3122,15 @@
|
|||||||
function closeCircuitModal() {
|
function closeCircuitModal() {
|
||||||
document.getElementById('circuitModal').style.display = 'none';
|
document.getElementById('circuitModal').style.display = 'none';
|
||||||
document.getElementById('circuit-form').reset();
|
document.getElementById('circuit-form').reset();
|
||||||
|
currentLocalFlightId = null;
|
||||||
|
currentArrivalId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Circuit form submission
|
// Circuit form submission
|
||||||
document.getElementById('circuit-form').addEventListener('submit', async function(e) {
|
document.getElementById('circuit-form').addEventListener('submit', async function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!currentLocalFlightId || !accessToken) return;
|
if ((!currentLocalFlightId && !currentArrivalId) || !accessToken) return;
|
||||||
|
|
||||||
const circuitTimestampInput = document.getElementById('circuit-timestamp').value;
|
const circuitTimestampInput = document.getElementById('circuit-timestamp').value;
|
||||||
if (!circuitTimestampInput) {
|
if (!circuitTimestampInput) {
|
||||||
@@ -3080,15 +3143,23 @@
|
|||||||
const localDate = new Date(circuitTimestampInput);
|
const localDate = new Date(circuitTimestampInput);
|
||||||
const circuitTimestamp = localDate.toISOString();
|
const circuitTimestamp = localDate.toISOString();
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
circuit_timestamp: circuitTimestamp
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the appropriate ID based on what we're tracking
|
||||||
|
if (currentLocalFlightId) {
|
||||||
|
requestBody.local_flight_id = currentLocalFlightId;
|
||||||
|
} else if (currentArrivalId) {
|
||||||
|
requestBody.arrival_id = currentArrivalId;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await authenticatedFetch('/api/v1/circuits/', {
|
const response = await authenticatedFetch('/api/v1/circuits/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(requestBody)
|
||||||
local_flight_id: currentLocalFlightId,
|
|
||||||
circuit_timestamp: circuitTimestamp
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
+67
-59
@@ -3009,8 +3009,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Circuit modal functions
|
// Circuit modal functions
|
||||||
function showCircuitModal() {
|
function showCircuitModal(localFlightId = null, arrivalId = null) {
|
||||||
if (!currentLocalFlightId) return;
|
if (!localFlightId && !arrivalId) return;
|
||||||
|
|
||||||
|
// Set the current IDs
|
||||||
|
currentLocalFlightId = localFlightId;
|
||||||
|
currentArrivalId = arrivalId;
|
||||||
|
|
||||||
// Set default timestamp to current time
|
// Set default timestamp to current time
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -3027,13 +3031,15 @@
|
|||||||
function closeCircuitModal() {
|
function closeCircuitModal() {
|
||||||
document.getElementById('circuitModal').style.display = 'none';
|
document.getElementById('circuitModal').style.display = 'none';
|
||||||
document.getElementById('circuit-form').reset();
|
document.getElementById('circuit-form').reset();
|
||||||
|
currentLocalFlightId = null;
|
||||||
|
currentArrivalId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Circuit form submission
|
// Circuit form submission
|
||||||
document.getElementById('circuit-form').addEventListener('submit', async function(e) {
|
document.getElementById('circuit-form').addEventListener('submit', async function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!currentLocalFlightId || !accessToken) return;
|
if ((!currentLocalFlightId && !currentArrivalId) || !accessToken) return;
|
||||||
|
|
||||||
const circuitTimestampInput = document.getElementById('circuit-timestamp').value;
|
const circuitTimestampInput = document.getElementById('circuit-timestamp').value;
|
||||||
if (!circuitTimestampInput) {
|
if (!circuitTimestampInput) {
|
||||||
@@ -3046,15 +3052,21 @@
|
|||||||
const localDate = new Date(circuitTimestampInput);
|
const localDate = new Date(circuitTimestampInput);
|
||||||
const circuitTimestamp = localDate.toISOString();
|
const circuitTimestamp = localDate.toISOString();
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
circuit_timestamp: circuitTimestamp
|
||||||
|
};
|
||||||
|
if (currentLocalFlightId) {
|
||||||
|
requestBody.local_flight_id = currentLocalFlightId;
|
||||||
|
} else if (currentArrivalId) {
|
||||||
|
requestBody.arrival_id = currentArrivalId;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await authenticatedFetch('/api/v1/circuits/', {
|
const response = await authenticatedFetch('/api/v1/circuits/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(requestBody)
|
||||||
local_flight_id: currentLocalFlightId,
|
|
||||||
circuit_timestamp: circuitTimestamp
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -4602,41 +4614,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update status from table for departures
|
// Update status from table for arrivals
|
||||||
async function updateDepartureStatusFromTable(departureId, status) {
|
|
||||||
if (!accessToken) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/v1/departures/${departureId}/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');
|
|
||||||
|
|
||||||
loadPPRs(); // Refresh display
|
|
||||||
showNotification(`Departure marked as ${status.toLowerCase()}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error updating status:', error);
|
|
||||||
showNotification('Error updating departure status', true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update status from table for booked-in arrivals
|
|
||||||
async function updateArrivalStatusFromTable(arrivalId, status) {
|
async function updateArrivalStatusFromTable(arrivalId, status) {
|
||||||
if (!accessToken) return;
|
if (!accessToken) return;
|
||||||
|
|
||||||
// Show confirmation for cancel actions
|
|
||||||
if (status === 'CANCELLED') {
|
|
||||||
if (!confirm('Are you sure you want to cancel this arrival? This action cannot be easily undone.')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/v1/arrivals/${arrivalId}/status`, {
|
const response = await fetch(`/api/v1/arrivals/${arrivalId}/status`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
@@ -4649,7 +4630,7 @@
|
|||||||
|
|
||||||
if (!response.ok) throw new Error('Failed to update status');
|
if (!response.ok) throw new Error('Failed to update status');
|
||||||
|
|
||||||
loadArrivals(); // Refresh arrivals table
|
loadATCAircraft(); // Refresh display
|
||||||
showNotification(`Arrival marked as ${status.toLowerCase()}`);
|
showNotification(`Arrival marked as ${status.toLowerCase()}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating status:', error);
|
console.error('Error updating status:', error);
|
||||||
@@ -5230,12 +5211,14 @@
|
|||||||
try {
|
try {
|
||||||
const response = await Promise.all([
|
const response = await Promise.all([
|
||||||
authenticatedFetch('/api/v1/local-flights/?status=LOCAL&limit=1000'),
|
authenticatedFetch('/api/v1/local-flights/?status=LOCAL&limit=1000'),
|
||||||
authenticatedFetch('/api/v1/departures/?status=LOCAL&limit=1000')
|
authenticatedFetch('/api/v1/departures/?status=LOCAL&limit=1000'),
|
||||||
|
authenticatedFetch('/api/v1/arrivals/?status=LOCAL&limit=1000')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let locals = [];
|
let locals = [];
|
||||||
if (response[0].ok) locals = (await response[0].json()).map(l => ({ ...l, isLocalFlight: true }));
|
if (response[0].ok) locals = (await response[0].json()).map(l => ({ ...l, isLocalFlight: true }));
|
||||||
if (response[1].ok) locals = locals.concat((await response[1].json()).map(d => ({ ...d, isDeparture: true })));
|
if (response[1].ok) locals = locals.concat((await response[1].json()).map(d => ({ ...d, isDeparture: true })));
|
||||||
|
if (response[2].ok) locals = locals.concat((await response[2].json()).map(a => ({ ...a, isArrival: true })));
|
||||||
|
|
||||||
displayLocalAircraft(locals);
|
displayLocalAircraft(locals);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -5264,9 +5247,9 @@
|
|||||||
if (isDeparture) {
|
if (isDeparture) {
|
||||||
// Departure in LOCAL status - show QSY button
|
// Departure in LOCAL status - show QSY button
|
||||||
buttons = `<button class="status-btn" onclick="event.stopPropagation(); currentDepartureId = '${ac.id}'; showTimestampModal('DEPARTED', ${ac.id}, false, true)">QSY</button>`;
|
buttons = `<button class="status-btn" onclick="event.stopPropagation(); currentDepartureId = '${ac.id}'; showTimestampModal('DEPARTED', ${ac.id}, false, true)">QSY</button>`;
|
||||||
} else if (ac.isLocalFlight) {
|
} else if (ac.isLocalFlight || ac.isArrival) {
|
||||||
// Local flight in LOCAL status - show REJOIN button
|
// Local flight or arrival in LOCAL status - show REJOIN button
|
||||||
buttons = `<button class="status-btn" onclick="event.stopPropagation(); updateLocalFlightStatusFromTable('${ac.id}', 'CIRCUIT')">REJOIN</button>`;
|
buttons = `<button class="status-btn" onclick="event.stopPropagation(); ${ac.isArrival ? `updateArrivalStatusFromTable('${ac.id}', 'CIRCUIT')` : `updateLocalFlightStatusFromTable('${ac.id}', 'CIRCUIT')`}">REJOIN</button>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
@@ -5322,6 +5305,12 @@
|
|||||||
const from = ac.in_from;
|
const from = ac.in_from;
|
||||||
const eta = ac.eta;
|
const eta = ac.eta;
|
||||||
|
|
||||||
|
let buttons = '';
|
||||||
|
if (ac.isArrival) {
|
||||||
|
// Arrival in BOOKED_IN status - show CONTACT button
|
||||||
|
buttons = `<button class="status-btn" onclick="event.stopPropagation(); updateArrivalStatusFromTable('${ac.id}', 'LOCAL')">CONTACT</button>`;
|
||||||
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="aircraft-item" onclick="handleATCClick('${ac.id}', '${ac.isArrival ? 'arrival' : 'ppr'}')">
|
<div class="aircraft-item" onclick="handleATCClick('${ac.id}', '${ac.isArrival ? 'arrival' : 'ppr'}')">
|
||||||
<div class="aircraft-info">
|
<div class="aircraft-info">
|
||||||
@@ -5329,7 +5318,7 @@
|
|||||||
<div class="aircraft-details">${type} from ${from || '?'}</div>
|
<div class="aircraft-details">${type} from ${from || '?'}</div>
|
||||||
<div class="aircraft-time">${eta ? formatTimeOnly(eta) : ''}</div>
|
<div class="aircraft-time">${eta ? formatTimeOnly(eta) : ''}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="aircraft-status status-inbound">IB</div>
|
${buttons ? `<div style="display: flex; gap: 0.5rem;">${buttons}</div>` : '<div class="aircraft-status status-inbound">IB</div>'}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
@@ -5340,12 +5329,14 @@
|
|||||||
try {
|
try {
|
||||||
const response = await Promise.all([
|
const response = await Promise.all([
|
||||||
authenticatedFetch('/api/v1/local-flights/?status=DEPARTED&limit=1000&flight_type=CIRCUITS'),
|
authenticatedFetch('/api/v1/local-flights/?status=DEPARTED&limit=1000&flight_type=CIRCUITS'),
|
||||||
authenticatedFetch('/api/v1/local-flights/?status=CIRCUIT&limit=1000')
|
authenticatedFetch('/api/v1/local-flights/?status=CIRCUIT&limit=1000'),
|
||||||
|
authenticatedFetch('/api/v1/arrivals/?status=CIRCUIT&limit=1000')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let circuits = [];
|
let circuits = [];
|
||||||
if (response[0].ok) circuits = await response[0].json();
|
if (response[0].ok) circuits = await response[0].json();
|
||||||
if (response[1].ok) circuits = circuits.concat(await response[1].json());
|
if (response[1].ok) circuits = circuits.concat(await response[1].json());
|
||||||
|
if (response[2].ok) circuits = circuits.concat((await response[2].json()).map(a => ({ ...a, isArrival: true })));
|
||||||
|
|
||||||
displayCircuitAircraft(circuits);
|
displayCircuitAircraft(circuits);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -5364,20 +5355,37 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
container.innerHTML = aircraft.map(ac => `
|
container.innerHTML = aircraft.map(ac => {
|
||||||
<div class="aircraft-item" onclick="handleATCClick('${ac.id}', 'local')">
|
const isArrival = ac.isArrival;
|
||||||
<div class="aircraft-info">
|
const entityType = isArrival ? 'arrival' : 'local';
|
||||||
<div class="aircraft-reg">${ac.registration}</div>
|
const updateFunction = isArrival ? 'updateArrivalStatusFromTable' : 'updateLocalFlightStatusFromTable';
|
||||||
<div class="aircraft-details">${ac.type}</div>
|
const landFunction = isArrival ? `${updateFunction}('${ac.id}', 'LANDED')` : `showTimestampModal('LANDED', ${ac.id}, true)`;
|
||||||
<div class="aircraft-time">${formatTimeOnly(ac.created_dt)}</div>
|
|
||||||
|
let buttons = `
|
||||||
|
<button class="status-btn" onclick="event.stopPropagation(); ${updateFunction}('${ac.id}', 'LOCAL')">LOCAL</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Show T&G for both local flights and arrivals
|
||||||
|
const tgFunction = isArrival
|
||||||
|
? `currentArrivalId = '${ac.id}'; showCircuitModal(null, '${ac.id}')`
|
||||||
|
: `currentLocalFlightId = '${ac.id}'; showCircuitModal('${ac.id}')`;
|
||||||
|
buttons += `<button class="status-btn" onclick="event.stopPropagation(); ${tgFunction}">T&G</button>`;
|
||||||
|
|
||||||
|
buttons += `<button class="status-btn" onclick="event.stopPropagation(); ${landFunction}">LAND</button>`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="aircraft-item" onclick="handleATCClick('${ac.id}', '${entityType}')">
|
||||||
|
<div class="aircraft-info">
|
||||||
|
<div class="aircraft-reg">${ac.registration}</div>
|
||||||
|
<div class="aircraft-details">${ac.type}</div>
|
||||||
|
<div class="aircraft-time">${formatTimeOnly(ac.created_dt)}</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 0.5rem;">
|
||||||
|
${buttons}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; gap: 0.5rem;">
|
`;
|
||||||
<button class="status-btn" onclick="event.stopPropagation(); updateLocalFlightStatusFromTable('${ac.id}', 'LOCAL')">LOCAL</button>
|
}).join('');
|
||||||
<button class="status-btn" onclick="event.stopPropagation(); currentLocalFlightId = '${ac.id}'; showCircuitModal()">T&G</button>
|
|
||||||
<button class="status-btn" onclick="event.stopPropagation(); currentLocalFlightId = '${ac.id}'; showTimestampModal('LANDED', ${ac.id}, true)">LAND</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load pending PPRs
|
// Load pending PPRs
|
||||||
|
|||||||
Reference in New Issue
Block a user