Compare commits
2 Commits
86f1dc65f4
...
bd1200f377
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd1200f377 | ||
|
|
f4b69aace0 |
@@ -33,7 +33,11 @@ async def login_for_access_token(
|
|||||||
subject=user.username, expires_delta=access_token_expires
|
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)
|
@router.post("/test-token", response_model=User)
|
||||||
|
|||||||
@@ -3,19 +3,19 @@ from fastapi import APIRouter, Depends
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from app.api.deps import get_db
|
from app.api.deps import get_db
|
||||||
from app.crud.crud_ppr import ppr as crud_ppr
|
from app.crud.crud_ppr import ppr as crud_ppr
|
||||||
from app.schemas.ppr import PPR
|
from app.schemas.ppr import PPRPublic
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/arrivals", response_model=List[PPR])
|
@router.get("/arrivals", response_model=List[PPRPublic])
|
||||||
async def get_public_arrivals(db: Session = Depends(get_db)):
|
async def get_public_arrivals(db: Session = Depends(get_db)):
|
||||||
"""Get today's arrivals for public display"""
|
"""Get today's arrivals for public display"""
|
||||||
arrivals = crud_ppr.get_arrivals_today(db)
|
arrivals = crud_ppr.get_arrivals_today(db)
|
||||||
return arrivals
|
return arrivals
|
||||||
|
|
||||||
|
|
||||||
@router.get("/departures", response_model=List[PPR])
|
@router.get("/departures", response_model=List[PPRPublic])
|
||||||
async def get_public_departures(db: Session = Depends(get_db)):
|
async def get_public_departures(db: Session = Depends(get_db)):
|
||||||
"""Get today's departures for public display"""
|
"""Get today's departures for public display"""
|
||||||
departures = crud_ppr.get_departures_today(db)
|
departures = crud_ppr.get_departures_today(db)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class CRUDPPR:
|
|||||||
return query.order_by(desc(PPRRecord.submitted_dt)).offset(skip).limit(limit).all()
|
return query.order_by(desc(PPRRecord.submitted_dt)).offset(skip).limit(limit).all()
|
||||||
|
|
||||||
def get_arrivals_today(self, db: Session) -> List[PPRRecord]:
|
def get_arrivals_today(self, db: Session) -> List[PPRRecord]:
|
||||||
"""Get today's arrivals"""
|
"""Get today's arrivals - includes aircraft that have arrived and may have departed"""
|
||||||
today = date.today()
|
today = date.today()
|
||||||
return db.query(PPRRecord).filter(
|
return db.query(PPRRecord).filter(
|
||||||
and_(
|
and_(
|
||||||
@@ -56,7 +56,8 @@ class CRUDPPR:
|
|||||||
or_(
|
or_(
|
||||||
PPRRecord.status == PPRStatus.NEW,
|
PPRRecord.status == PPRStatus.NEW,
|
||||||
PPRRecord.status == PPRStatus.CONFIRMED,
|
PPRRecord.status == PPRStatus.CONFIRMED,
|
||||||
PPRRecord.status == PPRStatus.LANDED
|
PPRRecord.status == PPRStatus.LANDED,
|
||||||
|
PPRRecord.status == PPRStatus.DEPARTED
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).order_by(PPRRecord.eta).all()
|
).order_by(PPRRecord.eta).all()
|
||||||
|
|||||||
@@ -96,6 +96,25 @@ class PPR(PPRInDBBase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PPRPublic(BaseModel):
|
||||||
|
"""Public schema for arrivals/departures board - excludes sensitive data"""
|
||||||
|
id: int
|
||||||
|
status: PPRStatus
|
||||||
|
ac_reg: str
|
||||||
|
ac_type: str
|
||||||
|
ac_call: Optional[str] = None
|
||||||
|
in_from: str
|
||||||
|
eta: datetime
|
||||||
|
out_to: Optional[str] = None
|
||||||
|
etd: Optional[datetime] = None
|
||||||
|
landed_dt: Optional[datetime] = None
|
||||||
|
departed_dt: Optional[datetime] = None
|
||||||
|
submitted_dt: datetime
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
class PPRInDB(PPRInDBBase):
|
class PPRInDB(PPRInDBBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -135,6 +154,7 @@ class UserInDB(UserInDBBase):
|
|||||||
class Token(BaseModel):
|
class Token(BaseModel):
|
||||||
access_token: str
|
access_token: str
|
||||||
token_type: str
|
token_type: str
|
||||||
|
expires_in: int # Token expiry in seconds
|
||||||
|
|
||||||
|
|
||||||
class TokenData(BaseModel):
|
class TokenData(BaseModel):
|
||||||
|
|||||||
297
web/admin.html
297
web/admin.html
@@ -564,7 +564,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="menu-buttons">
|
<div class="menu-buttons">
|
||||||
<button class="btn btn-success" onclick="openNewPPRModal()">
|
<button class="btn btn-success" onclick="openNewPPRModal()">
|
||||||
➕ New PPR Entry
|
➕ New PPR
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-primary" onclick="window.open('reports.html', '_blank')">
|
<button class="btn btn-primary" onclick="window.open('reports.html', '_blank')">
|
||||||
📊 Reports
|
📊 Reports
|
||||||
@@ -587,7 +587,7 @@
|
|||||||
<!-- Arrivals Table -->
|
<!-- Arrivals Table -->
|
||||||
<div class="ppr-table">
|
<div class="ppr-table">
|
||||||
<div class="table-header">
|
<div class="table-header">
|
||||||
🛬 Today's Arrivals - <span id="arrivals-count">0</span> entries
|
🛬 Pending Arrivals - <span id="arrivals-count">0</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="arrivals-loading" class="loading">
|
<div id="arrivals-loading" class="loading">
|
||||||
@@ -614,15 +614,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="arrivals-no-data" class="no-data" style="display: none;">
|
<div id="arrivals-no-data" class="no-data" style="display: none;">
|
||||||
<h3>No arrivals for today</h3>
|
<h3>No Pending Arrivals</h3>
|
||||||
<p>No NEW or CONFIRMED arrivals scheduled for today.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Departures Table -->
|
<!-- Departures Table -->
|
||||||
<div class="ppr-table" style="margin-top: 2rem;">
|
<div class="ppr-table" style="margin-top: 2rem;">
|
||||||
<div class="table-header">
|
<div class="table-header">
|
||||||
🛫 Today's Departures - <span id="departures-count">0</span> entries
|
🛫 Pending Departures - <span id="departures-count">0</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="departures-loading" class="loading">
|
<div id="departures-loading" class="loading">
|
||||||
@@ -650,10 +649,76 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="departures-no-data" class="no-data" style="display: none;">
|
<div id="departures-no-data" class="no-data" style="display: none;">
|
||||||
<h3>No departures for today</h3>
|
<h3>No Pending Departures</h3>
|
||||||
<p>No aircraft currently landed and ready to depart.</p>
|
<p>No aircraft currently landed and ready to depart.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br>
|
||||||
|
<!-- Departed and Parked Tables -->
|
||||||
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: 1rem;">
|
||||||
|
<!-- Departed Today -->
|
||||||
|
<div class="ppr-table">
|
||||||
|
<div class="table-header" style="padding: 0.3rem 0.5rem; font-size: 0.85rem;">
|
||||||
|
✈️ Departed Today - <span id="departed-count">0</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="departed-loading" class="loading" style="display: none;">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
Loading departed aircraft...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="departed-table-content" style="display: none;">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr style="font-size: 0.85rem !important;">
|
||||||
|
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">Registration</th>
|
||||||
|
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">Callsign</th>
|
||||||
|
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">Destination</th>
|
||||||
|
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">Departed</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="departed-table-body">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="departed-no-data" class="no-data" style="display: none; padding: 1rem; font-size: 0.9rem;">
|
||||||
|
<p>No departures today.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Parked Visitors -->
|
||||||
|
<div class="ppr-table">
|
||||||
|
<div class="table-header" style="padding: 0.3rem 0.5rem; font-size: 0.85rem;">
|
||||||
|
🅿️ Parked Visitors - <span id="parked-count">0</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="parked-loading" class="loading" style="display: none;">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
Loading parked visitors...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="parked-table-content" style="display: none;">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr style="font-size: 0.85rem !important;">
|
||||||
|
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">Registration</th>
|
||||||
|
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">Type</th>
|
||||||
|
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">From</th>
|
||||||
|
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">Arrived</th>
|
||||||
|
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">ETD</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="parked-table-body">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="parked-no-data" class="no-data" style="display: none; padding: 1rem; font-size: 0.9rem;">
|
||||||
|
<p>No parked visitors.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Login Modal -->
|
<!-- Login Modal -->
|
||||||
@@ -1227,8 +1292,9 @@
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (response.ok && data.access_token) {
|
if (response.ok && data.access_token) {
|
||||||
// Store token and user info with expiry (30 minutes from now)
|
// Store token and user info with expiry from server response
|
||||||
const expiryTime = new Date().getTime() + (30 * 60 * 1000); // 30 minutes
|
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_access_token', data.access_token);
|
||||||
localStorage.setItem('ppr_username', username);
|
localStorage.setItem('ppr_username', username);
|
||||||
@@ -1309,12 +1375,12 @@
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load PPR records - now loads both arrivals and departures
|
// Load PPR records - now loads all tables
|
||||||
async function loadPPRs() {
|
async function loadPPRs() {
|
||||||
if (!accessToken) return;
|
if (!accessToken) return;
|
||||||
|
|
||||||
// Load both arrivals and departures simultaneously
|
// Load all tables simultaneously
|
||||||
await Promise.all([loadArrivals(), loadDepartures()]);
|
await Promise.all([loadArrivals(), loadDepartures(), loadDeparted(), loadParked()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load arrivals (NEW and CONFIRMED status)
|
// Load arrivals (NEW and CONFIRMED status)
|
||||||
@@ -1324,22 +1390,26 @@
|
|||||||
document.getElementById('arrivals-no-data').style.display = 'none';
|
document.getElementById('arrivals-no-data').style.display = 'none';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Always load today's date
|
// Load all PPRs and filter client-side for today's arrivals
|
||||||
const today = new Date().toISOString().split('T')[0];
|
// We filter by ETA date (not ETD) and NEW/CONFIRMED status
|
||||||
let url = `/api/v1/pprs/?limit=1000&date_from=${today}&date_to=${today}`;
|
const response = await authenticatedFetch('/api/v1/pprs/?limit=1000');
|
||||||
|
|
||||||
const response = await authenticatedFetch(url);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to fetch arrivals');
|
throw new Error('Failed to fetch arrivals');
|
||||||
}
|
}
|
||||||
|
|
||||||
const allPPRs = await response.json();
|
const allPPRs = await response.json();
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
// Filter for arrivals (NEW and CONFIRMED with ETA only)
|
// Filter for arrivals with ETA today and NEW or CONFIRMED status
|
||||||
const arrivals = allPPRs.filter(ppr =>
|
const arrivals = allPPRs.filter(ppr => {
|
||||||
(ppr.status === 'NEW' || ppr.status === 'CONFIRMED') && ppr.eta
|
if (!ppr.eta || (ppr.status !== 'NEW' && ppr.status !== 'CONFIRMED')) {
|
||||||
);
|
return false;
|
||||||
|
}
|
||||||
|
// Extract date from ETA (UTC)
|
||||||
|
const etaDate = ppr.eta.split('T')[0];
|
||||||
|
return etaDate === today;
|
||||||
|
});
|
||||||
|
|
||||||
displayArrivals(arrivals);
|
displayArrivals(arrivals);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1359,20 +1429,26 @@
|
|||||||
document.getElementById('departures-no-data').style.display = 'none';
|
document.getElementById('departures-no-data').style.display = 'none';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Always load today's date
|
// Load all PPRs and filter client-side for today's departures
|
||||||
const today = new Date().toISOString().split('T')[0];
|
// We filter by ETD date and LANDED status only (exclude DEPARTED)
|
||||||
let url = `/api/v1/pprs/?limit=1000&date_from=${today}&date_to=${today}`;
|
const response = await authenticatedFetch('/api/v1/pprs/?limit=1000');
|
||||||
|
|
||||||
const response = await authenticatedFetch(url);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to fetch departures');
|
throw new Error('Failed to fetch departures');
|
||||||
}
|
}
|
||||||
|
|
||||||
const allPPRs = await response.json();
|
const allPPRs = await response.json();
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
// Filter for departures (LANDED status only)
|
// Filter for departures with ETD today and LANDED status only
|
||||||
const departures = allPPRs.filter(ppr => ppr.status === 'LANDED');
|
const departures = allPPRs.filter(ppr => {
|
||||||
|
if (!ppr.etd || ppr.status !== 'LANDED') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Extract date from ETD (UTC)
|
||||||
|
const etdDate = ppr.etd.split('T')[0];
|
||||||
|
return etdDate === today;
|
||||||
|
});
|
||||||
|
|
||||||
displayDepartures(departures);
|
displayDepartures(departures);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1385,6 +1461,171 @@
|
|||||||
document.getElementById('departures-loading').style.display = 'none';
|
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 = `
|
||||||
|
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${ppr.ac_reg || '-'}</td>
|
||||||
|
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${ppr.ac_call || '-'}</td>
|
||||||
|
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${ppr.out_to || '-'}</td>
|
||||||
|
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${formatTimeOnly(ppr.departed_dt)}</td>
|
||||||
|
`;
|
||||||
|
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 = `
|
||||||
|
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${ppr.ac_reg || '-'}</td>
|
||||||
|
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${ppr.ac_type || '-'}</td>
|
||||||
|
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${ppr.in_from || '-'}</td>
|
||||||
|
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${arrivedDisplay}</td>
|
||||||
|
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${etdDisplay}</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ICAO code to airport name cache
|
// ICAO code to airport name cache
|
||||||
const airportNameCache = {};
|
const airportNameCache = {};
|
||||||
|
|
||||||
@@ -1548,7 +1789,7 @@
|
|||||||
isNewPPR = true;
|
isNewPPR = true;
|
||||||
currentPPRId = null;
|
currentPPRId = null;
|
||||||
etdManuallyEdited = false; // Reset the manual edit flag for new PPR
|
etdManuallyEdited = false; // Reset the manual edit flag for new PPR
|
||||||
document.getElementById('modal-title').textContent = 'New PPR Entry';
|
document.getElementById('modal-title').textContent = 'New PPR';
|
||||||
document.getElementById('delete-btn').style.display = 'none';
|
document.getElementById('delete-btn').style.display = 'none';
|
||||||
document.getElementById('journal-section').style.display = 'none';
|
document.getElementById('journal-section').style.display = 'none';
|
||||||
document.querySelector('.quick-actions').style.display = 'none';
|
document.querySelector('.quick-actions').style.display = 'none';
|
||||||
|
|||||||
@@ -309,7 +309,7 @@
|
|||||||
|
|
||||||
// Show landed time if available, otherwise ETA
|
// Show landed time if available, otherwise ETA
|
||||||
let timeDisplay;
|
let timeDisplay;
|
||||||
if (arrival.status === 'LANDED' && arrival.landed_dt) {
|
if ((arrival.status === 'LANDED' || arrival.status === 'DEPARTED') && arrival.landed_dt) {
|
||||||
const time = convertToLocalTime(arrival.landed_dt);
|
const time = convertToLocalTime(arrival.landed_dt);
|
||||||
timeDisplay = `<div style="display: flex; align-items: center; gap: 8px;"><span style="color: #27ae60; font-weight: bold;">${time}</span><span style="font-size: 0.7em; background: #27ae60; color: white; padding: 2px 4px; border-radius: 3px; white-space: nowrap;">LANDED</span></div>`;
|
timeDisplay = `<div style="display: flex; align-items: center; gap: 8px;"><span style="color: #27ae60; font-weight: bold;">${time}</span><span style="font-size: 0.7em; background: #27ae60; color: white; padding: 2px 4px; border-radius: 3px; white-space: nowrap;">LANDED</span></div>`;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
47
web/ppr.html
47
web/ppr.html
@@ -454,6 +454,41 @@
|
|||||||
console.log('Source:', window.PPR_CONFIG?.apiBase ? 'config.js' : 'fallback');
|
console.log('Source:', window.PPR_CONFIG?.apiBase ? 'config.js' : 'fallback');
|
||||||
console.log('=======================');
|
console.log('=======================');
|
||||||
|
|
||||||
|
// Track if user has manually edited ETD
|
||||||
|
let etdManuallyEdited = false;
|
||||||
|
|
||||||
|
// Function to update ETD based on ETA (2 hours later)
|
||||||
|
function updateETDFromETA() {
|
||||||
|
// Only auto-update if user hasn't manually edited ETD
|
||||||
|
if (etdManuallyEdited) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const etaDate = document.getElementById('eta-date').value;
|
||||||
|
const etaTime = document.getElementById('eta-time').value;
|
||||||
|
|
||||||
|
if (etaDate && etaTime) {
|
||||||
|
// Parse ETA
|
||||||
|
const eta = new Date(`${etaDate}T${etaTime}`);
|
||||||
|
|
||||||
|
// Calculate ETD (2 hours after ETA)
|
||||||
|
const etd = new Date(eta.getTime() + 2 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
// Format ETD
|
||||||
|
const etdDateStr = `${etd.getFullYear()}-${String(etd.getMonth() + 1).padStart(2, '0')}-${String(etd.getDate()).padStart(2, '0')}`;
|
||||||
|
const etdTimeStr = `${String(etd.getHours()).padStart(2, '0')}:${String(etd.getMinutes()).padStart(2, '0')}`;
|
||||||
|
|
||||||
|
// Update ETD fields
|
||||||
|
document.getElementById('etd-date').value = etdDateStr;
|
||||||
|
document.getElementById('etd-time').value = etdTimeStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to mark ETD as manually edited
|
||||||
|
function markETDAsManuallyEdited() {
|
||||||
|
etdManuallyEdited = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Iframe resizing functionality
|
// Iframe resizing functionality
|
||||||
function sendHeightToParent() {
|
function sendHeightToParent() {
|
||||||
const height = document.body.scrollHeight || document.documentElement.scrollHeight;
|
const height = document.body.scrollHeight || document.documentElement.scrollHeight;
|
||||||
@@ -814,9 +849,9 @@
|
|||||||
const nextHour = new Date(now);
|
const nextHour = new Date(now);
|
||||||
nextHour.setHours(now.getHours() + 1, 0, 0, 0);
|
nextHour.setHours(now.getHours() + 1, 0, 0, 0);
|
||||||
|
|
||||||
// ETD is 1 hour after ETA
|
// ETD is 2 hours after ETA
|
||||||
const etd = new Date(nextHour);
|
const etd = new Date(nextHour);
|
||||||
etd.setHours(nextHour.getHours() + 1);
|
etd.setHours(nextHour.getHours() + 2);
|
||||||
|
|
||||||
// Format date and time for separate inputs
|
// Format date and time for separate inputs
|
||||||
function formatDate(date) {
|
function formatDate(date) {
|
||||||
@@ -845,6 +880,14 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
initializeTimeDropdowns();
|
initializeTimeDropdowns();
|
||||||
setDefaultDateTime();
|
setDefaultDateTime();
|
||||||
|
|
||||||
|
// Add event listeners to ETA fields to auto-update ETD
|
||||||
|
document.getElementById('eta-date').addEventListener('change', updateETDFromETA);
|
||||||
|
document.getElementById('eta-time').addEventListener('change', updateETDFromETA);
|
||||||
|
|
||||||
|
// Add event listeners to ETD fields to mark as manually edited
|
||||||
|
document.getElementById('etd-date').addEventListener('change', markETDAsManuallyEdited);
|
||||||
|
document.getElementById('etd-time').addEventListener('change', markETDAsManuallyEdited);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user