diff --git a/backend/app/api/endpoints/auth.py b/backend/app/api/endpoints/auth.py index f736778..12ab494 100644 --- a/backend/app/api/endpoints/auth.py +++ b/backend/app/api/endpoints/auth.py @@ -33,7 +33,11 @@ async def login_for_access_token( subject=user.username, expires_delta=access_token_expires ) - return {"access_token": access_token, "token_type": "bearer"} + return { + "access_token": access_token, + "token_type": "bearer", + "expires_in": settings.access_token_expire_minutes * 60 # seconds + } @router.post("/test-token", response_model=User) diff --git a/backend/app/schemas/ppr.py b/backend/app/schemas/ppr.py index f04dbc6..b699778 100644 --- a/backend/app/schemas/ppr.py +++ b/backend/app/schemas/ppr.py @@ -154,6 +154,7 @@ class UserInDB(UserInDBBase): class Token(BaseModel): access_token: str token_type: str + expires_in: int # Token expiry in seconds class TokenData(BaseModel): diff --git a/web/admin.html b/web/admin.html index 4d68f2e..e4d5d13 100644 --- a/web/admin.html +++ b/web/admin.html @@ -653,6 +653,72 @@

No aircraft currently landed and ready to depart.

+
+ +
+ +
+
+ ✈️ Departed Today - 0 +
+ + + + + + +
+ + +
+
+ 🅿️ Parked Visitors - 0 +
+ + + + + + +
+
@@ -1226,8 +1292,9 @@ const data = await response.json(); if (response.ok && data.access_token) { - // Store token and user info with expiry (30 minutes from now) - const expiryTime = new Date().getTime() + (30 * 60 * 1000); // 30 minutes + // Store token and user info with expiry from server response + const expiresInMs = (data.expires_in || 1800) * 1000; // Use server value or default to 30 min + const expiryTime = new Date().getTime() + expiresInMs; localStorage.setItem('ppr_access_token', data.access_token); localStorage.setItem('ppr_username', username); @@ -1308,12 +1375,12 @@ return response; } - // Load PPR records - now loads both arrivals and departures + // Load PPR records - now loads all tables async function loadPPRs() { if (!accessToken) return; - // Load both arrivals and departures simultaneously - await Promise.all([loadArrivals(), loadDepartures()]); + // Load all tables simultaneously + await Promise.all([loadArrivals(), loadDepartures(), loadDeparted(), loadParked()]); } // Load arrivals (NEW and CONFIRMED status) @@ -1394,6 +1461,171 @@ document.getElementById('departures-loading').style.display = 'none'; } + // Load departed aircraft (DEPARTED status with departed_dt today) + async function loadDeparted() { + document.getElementById('departed-loading').style.display = 'block'; + document.getElementById('departed-table-content').style.display = 'none'; + document.getElementById('departed-no-data').style.display = 'none'; + + try { + const response = await authenticatedFetch('/api/v1/pprs/?limit=1000'); + + if (!response.ok) { + throw new Error('Failed to fetch departed aircraft'); + } + + const allPPRs = await response.json(); + const today = new Date().toISOString().split('T')[0]; + + // Filter for aircraft departed today + const departed = allPPRs.filter(ppr => { + if (!ppr.departed_dt || ppr.status !== 'DEPARTED') { + return false; + } + const departedDate = ppr.departed_dt.split('T')[0]; + return departedDate === today; + }); + + displayDeparted(departed); + } catch (error) { + console.error('Error loading departed aircraft:', error); + if (error.message !== 'Session expired. Please log in again.') { + showNotification('Error loading departed aircraft', true); + } + } + + document.getElementById('departed-loading').style.display = 'none'; + } + + function displayDeparted(departed) { + const tbody = document.getElementById('departed-table-body'); + document.getElementById('departed-count').textContent = departed.length; + + if (departed.length === 0) { + document.getElementById('departed-no-data').style.display = 'block'; + return; + } + + // Sort by departed time + departed.sort((a, b) => new Date(a.departed_dt) - new Date(b.departed_dt)); + + tbody.innerHTML = ''; + document.getElementById('departed-table-content').style.display = 'block'; + + for (const ppr of departed) { + const row = document.createElement('tr'); + row.onclick = () => openPPRModal(ppr.id); + row.style.cssText = 'font-size: 0.85rem !important; font-style: italic;'; + + row.innerHTML = ` + ${ppr.ac_reg || '-'} + ${ppr.ac_call || '-'} + ${ppr.out_to || '-'} + ${formatTimeOnly(ppr.departed_dt)} + `; + tbody.appendChild(row); + } + } + + // Load parked visitors (LANDED status with no ETD today or ETD not today) + async function loadParked() { + document.getElementById('parked-loading').style.display = 'block'; + document.getElementById('parked-table-content').style.display = 'none'; + document.getElementById('parked-no-data').style.display = 'none'; + + try { + const response = await authenticatedFetch('/api/v1/pprs/?limit=1000'); + + if (!response.ok) { + throw new Error('Failed to fetch parked visitors'); + } + + const allPPRs = await response.json(); + const today = new Date().toISOString().split('T')[0]; + + // Filter for parked visitors: LANDED status and (no ETD or ETD not today) + // Show all parked aircraft regardless of when they arrived + const parked = allPPRs.filter(ppr => { + if (ppr.status !== 'LANDED') { + return false; + } + // No ETD means parked + if (!ppr.etd) { + return true; + } + // ETD exists but is not today + const etdDate = ppr.etd.split('T')[0]; + return etdDate !== today; + }); + + displayParked(parked); + } catch (error) { + console.error('Error loading parked visitors:', error); + if (error.message !== 'Session expired. Please log in again.') { + showNotification('Error loading parked visitors', true); + } + } + + document.getElementById('parked-loading').style.display = 'none'; + } + + function displayParked(parked) { + const tbody = document.getElementById('parked-table-body'); + document.getElementById('parked-count').textContent = parked.length; + + if (parked.length === 0) { + document.getElementById('parked-no-data').style.display = 'block'; + return; + } + + // Sort by landed time + parked.sort((a, b) => { + if (!a.landed_dt) return 1; + if (!b.landed_dt) return -1; + return new Date(a.landed_dt) - new Date(b.landed_dt); + }); + + tbody.innerHTML = ''; + document.getElementById('parked-table-content').style.display = 'block'; + + for (const ppr of parked) { + const row = document.createElement('tr'); + row.onclick = () => openPPRModal(ppr.id); + row.style.cssText = 'font-size: 0.85rem !important; font-style: italic;'; + + // Format arrival: time if today, date if not + const today = new Date().toISOString().split('T')[0]; + let arrivedDisplay = '-'; + if (ppr.landed_dt) { + const landedDate = ppr.landed_dt.split('T')[0]; + if (landedDate === today) { + // Today - show time only + arrivedDisplay = formatTimeOnly(ppr.landed_dt); + } else { + // Not today - show date (DD/MM) + const date = new Date(ppr.landed_dt); + arrivedDisplay = date.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit' }); + } + } + + // Format ETD as just the date (DD/MM) + let etdDisplay = '-'; + if (ppr.etd) { + const etdDate = new Date(ppr.etd); + etdDisplay = etdDate.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit' }); + } + + row.innerHTML = ` + ${ppr.ac_reg || '-'} + ${ppr.ac_type || '-'} + ${ppr.in_from || '-'} + ${arrivedDisplay} + ${etdDisplay} + `; + tbody.appendChild(row); + } + } + // ICAO code to airport name cache const airportNameCache = {};