Files
ppr-ng/web/book.html
T
2026-06-28 07:37:41 -04:00

1287 lines
48 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flight Booking - Pilot Portal</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: #f5f5f5;
padding: 10px;
color: #333;
}
.container {
max-width: 600px;
margin: 0 auto;
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header {
background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
color: white;
padding: 20px;
margin: -20px -20px 20px -20px;
border-radius: 8px 8px 0 0;
text-align: center;
}
.header h1 {
font-size: 24px;
margin-bottom: 5px;
}
.header p {
font-size: 14px;
opacity: 0.9;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.tab-btn {
flex: 1;
padding: 12px;
border: 2px solid #ddd;
background: white;
color: #333;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
transition: all 0.3s ease;
min-width: 120px;
}
.tab-btn.active {
background: #3498db;
color: white;
border-color: #3498db;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 600;
font-size: 14px;
color: #2c3e50;
}
input, select, textarea {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 16px;
font-family: inherit;
}
input[type="number"] {
text-align: center;
}
input:focus, select:focus, textarea:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
}
textarea {
resize: vertical;
min-height: 80px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.form-row-3col {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
}
.btn-submit {
width: 100%;
padding: 14px;
background: #27ae60;
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-submit:hover {
background: #229954;
}
.btn-submit:active {
transform: scale(0.98);
}
.btn-submit:disabled {
background: #bdc3c7;
cursor: not-allowed;
}
.success-message {
display: none;
background: #d4edda;
color: #155724;
padding: 15px;
border-radius: 6px;
margin-bottom: 15px;
border-left: 4px solid #28a745;
}
.error-message {
display: none;
background: #f8d7da;
color: #721c24;
padding: 15px;
border-radius: 6px;
margin-bottom: 15px;
border-left: 4px solid #f5c6cb;
}
.info-box {
background: #e3f2fd;
border-left: 4px solid #2196f3;
padding: 12px;
border-radius: 4px;
font-size: 13px;
margin-bottom: 15px;
color: #1565c0;
}
.required {
color: #e74c3c;
}
.loading {
display: none;
text-align: center;
padding: 20px;
}
.modal-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-overlay.active {
display: flex;
}
.modal {
background: white;
border-radius: 8px;
padding: 30px;
max-width: 400px;
text-align: center;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.modal-icon {
font-size: 48px;
margin-bottom: 20px;
}
.modal h2 {
color: #27ae60;
margin-bottom: 15px;
font-size: 20px;
}
.modal p {
color: #333;
margin-bottom: 20px;
line-height: 1.5;
}
.modal-button {
background: #27ae60;
color: white;
border: none;
padding: 12px 30px;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background 0.3s ease;
}
.modal-button:hover {
background: #229954;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.footer {
text-align: center;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #eee;
font-size: 13px;
color: #666;
}
.aircraft-lookup-results {
margin-top: 5px;
padding: 5px;
background-color: #f8f9fa;
border-radius: 4px;
font-size: 13px;
min-height: 20px;
border: 1px solid #e9ecef;
}
.airport-lookup-results {
margin-top: 5px;
padding: 5px;
background-color: #f8f9fa;
border-radius: 4px;
font-size: 13px;
min-height: 20px;
border: 1px solid #e9ecef;
}
.lookup-match {
padding: 3px;
background-color: #e8f5e8;
border: 1px solid #c3e6c3;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-weight: bold;
}
.lookup-no-match {
color: #6c757d;
font-style: italic;
}
.lookup-searching {
color: #007bff;
}
.aircraft-list {
max-height: 200px;
overflow-y: auto;
border: 1px solid #dee2e6;
border-radius: 4px;
background-color: white;
}
.aircraft-option {
padding: 5px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: background-color 0.2s ease;
display: flex;
justify-content: space-between;
align-items: center;
}
.aircraft-option:hover {
background-color: #f8f9fa;
}
.aircraft-option:last-child {
border-bottom: none;
}
.aircraft-code {
font-family: 'Courier New', monospace;
font-weight: bold;
color: #495057;
}
.aircraft-details {
color: #6c757d;
font-size: 12px;
}
.recent-regs-section {
background: #f9f9f9;
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 10px;
margin-bottom: 15px;
}
.recent-regs-title {
font-weight: 600;
font-size: 12px;
color: #666;
margin-bottom: 6px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.recent-regs-container {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.recent-reg-btn {
background: white;
border: 1px solid #3498db;
color: #3498db;
padding: 6px 10px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s ease;
font-weight: 500;
}
.recent-reg-btn:hover {
background: #3498db;
color: white;
}
.recent-regs-section.empty {
display: none;
}
@media (max-width: 480px) {
.container {
padding: 15px;
}
.header {
margin: -15px -15px 15px -15px;
padding: 15px;
}
.tabs {
gap: 5px;
}
.tab-btn {
font-size: 12px;
padding: 10px;
min-width: 100px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Swansea - Book Out</h1>
</div>
<div id="successMessage" class="success-message"></div>
<div id="errorMessage" class="error-message"></div>
<div id="successModal" class="modal-overlay">
<div class="modal">
<div class="modal-icon"></div>
<h2>Booking Submitted!</h2>
<p id="modalMessage"></p>
<button class="modal-button" onclick="closeSuccessModal()">Done</button>
</div>
</div>
<div class="info-box">
Booking out really helps our volunteers in the tower, thank you!
</div>
<div class="tabs">
<button class="tab-btn active" onclick="switchTab(this, 'local')">Local</button>
<button class="tab-btn" onclick="switchTab(this, 'circuits')">Circuits</button>
<button class="tab-btn" onclick="switchTab(this, 'departure')">Departure</button>
</div>
<!-- Local Flight Form -->
<div id="local" class="tab-content active">
<div id="localRecentSection" class="recent-regs-section">
<div class="recent-regs-title">⏱️ Recently Used</div>
<div id="localRecent" class="recent-regs-container"></div>
</div>
<form id="localFlightForm" onsubmit="handleSubmit(event, 'local')">
<div class="form-row">
<div class="form-group">
<label for="localReg">Aircraft Registration <span class="required">*</span></label>
<input type="text" id="localReg" name="registration" placeholder="e.g gbamc" required oninput="handleAircraftLookup(this.value, 'local')">
<div id="localReg-lookup-results" class="aircraft-lookup-results"></div>
</div>
<div class="form-group">
<label for="localType">Aircraft Type</label>
<input type="text" id="localType" name="type" placeholder="e.g., Cessna 172">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="localPOB">POB <span class="required">*</span></label>
<input type="number" id="localPOB" name="pob" value="1" min="1" required>
</div>
<div class="form-group">
<label for="localCallsign">Callsign</label>
<input type="text" id="localCallsign" name="callsign" placeholder="Optional">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="localETD">ETD</label>
<input type="text" id="localETD" name="etd" placeholder="HH:MM" pattern="([0-9]{2}:[0-9]{2}|[0-9]{4})" inputmode="decimal" oninput="formatTimeInput(this)">
</div>
<div class="form-group">
<label for="localDuration">Duration</label>
<input type="number" id="localDuration" name="duration" value="45" min="5" inputmode="numeric">
</div>
</div>
<div class="form-group">
<label for="localNotes">Notes</label>
<textarea id="localNotes" name="notes" placeholder="Any additional information..."></textarea>
</div>
<button type="submit" class="btn-submit">📋 Book Local Flight</button>
</form>
</div>
<!-- Circuit Form -->
<div id="circuits" class="tab-content">
<div id="circuitsRecentSection" class="recent-regs-section">
<div class="recent-regs-title">⏱️ Recently Used</div>
<div id="circuitsRecent" class="recent-regs-container"></div>
</div>
<form id="circuitForm" onsubmit="handleSubmit(event, 'circuits')">
<div class="form-row">
<div class="form-group">
<label for="circuitReg">Aircraft Registration <span class="required">*</span></label>
<input type="text" id="circuitReg" name="registration" placeholder="e.g., G-BAMC" required oninput="handleAircraftLookup(this.value, 'circuits')">
<div id="circuitReg-lookup-results" class="aircraft-lookup-results"></div>
</div>
<div class="form-group">
<label for="circuitType">Aircraft Type</label>
<input type="text" id="circuitType" name="type" placeholder="e.g., Cessna 172">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="circuitPOB">POB <span class="required">*</span></label>
<input type="number" id="circuitPOB" name="pob" value="1" min="1" required>
</div>
<div class="form-group">
<label for="circuitCallsign">Callsign</label>
<input type="text" id="circuitCallsign" name="callsign" placeholder="Optional">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="circuitETD">ETD</label>
<input type="text" id="circuitETD" name="etd" placeholder="HH:MM" pattern="([0-9]{2}:[0-9]{2}|[0-9]{4})" inputmode="decimal" oninput="formatTimeInput(this)">
</div>
<div class="form-group">
<label for="circuitDuration">Duration</label>
<input type="number" id="circuitDuration" name="duration" value="30" min="5" inputmode="numeric">
</div>
</div>
<div class="form-group">
<label for="circuitNotes">Notes</label>
<textarea id="circuitNotes" name="notes" placeholder="Any additional information..."></textarea>
</div>
<button type="submit" class="btn-submit">✈️ Book Circuits</button>
</form>
</div>
<!-- Departure Form -->
<div id="departure" class="tab-content">
<div id="departureRecentSection" class="recent-regs-section">
<div class="recent-regs-title">⏱️ Recently Used</div>
<div id="departureRecent" class="recent-regs-container"></div>
</div>
<form id="departureForm" onsubmit="handleSubmit(event, 'departure')">
<div class="form-row">
<div class="form-group">
<label for="depReg">Aircraft Registration <span class="required">*</span></label>
<input type="text" id="depReg" name="registration" placeholder="e.g., N12345" required oninput="handleAircraftLookup(this.value, 'departure')">
<div id="depReg-lookup-results" class="aircraft-lookup-results"></div>
</div>
<div class="form-group">
<label for="depType">Aircraft Type</label>
<input type="text" id="depType" name="type" placeholder="e.g., Cessna 172">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="depPOB">Persons on Board <span class="required">*</span></label>
<input type="number" id="depPOB" name="pob" value="1" min="1" required>
</div>
<div class="form-group">
<label for="depCallsign">Callsign</label>
<input type="text" id="depCallsign" name="callsign" placeholder="Optional">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="depTo">Destination (ICAO) <span class="required">*</span></label>
<input type="text" id="depTo" name="out_to" placeholder="e.g., KJFK" required oninput="handleAirportLookup(this.value, 'depTo')">
</div>
<div class="form-group">
<label for="depETD">Takeoff Time</label>
<input type="text" id="depETD" name="etd" placeholder="HH:MM" pattern="([0-9]{2}:[0-9]{2}|[0-9]{4})" inputmode="decimal" oninput="formatTimeInput(this)">
</div>
</div>
<div id="depTo-lookup-results" class="airport-lookup-results"></div>
<div class="form-group">
<label for="depNotes">Notes</label>
<textarea id="depNotes" name="notes" placeholder="Any additional information..."></textarea>
</div>
<button type="submit" class="btn-submit">✈️ Book Departure</button>
</form>
</div>
<!-- Arrival Form -->
<div id="arrival" class="tab-content">
<div id="arrivalRecentSection" class="recent-regs-section">
<div class="recent-regs-title">⏱️ Recently Used</div>
<div id="arrivalRecent" class="recent-regs-container"></div>
</div>
<form id="arrivalForm" onsubmit="handleSubmit(event, 'arrival')">
<div class="form-row">
<div class="form-group">
<label for="arrReg">Aircraft Registration <span class="required">*</span></label>
<input type="text" id="arrReg" name="registration" placeholder="e.g., N12345" required oninput="handleAircraftLookup(this.value, 'arrival')">
<div id="arrReg-lookup-results" class="aircraft-lookup-results"></div>
</div>
<div class="form-group">
<label for="arrType">Aircraft Type</label>
<input type="text" id="arrType" name="type" placeholder="e.g., Cessna 172">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="arrPOB">Persons on Board <span class="required">*</span></label>
<input type="number" id="arrPOB" name="pob" value="1" min="1" required>
</div>
<div class="form-group">
<label for="arrCallsign">Callsign</label>
<input type="text" id="arrCallsign" name="callsign" placeholder="Optional">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="arrFrom">Origin (ICAO) <span class="required">*</span></label>
<input type="text" id="arrFrom" name="in_from" placeholder="e.g., KJFK" required oninput="handleAirportLookup(this.value, 'arrFrom')">
</div>
<div class="form-group">
<label for="arrETA">Arrival Time</label>
<input type="text" id="arrETA" name="eta" placeholder="HH:MM" pattern="([0-9]{2}:[0-9]{2}|[0-9]{4})" inputmode="decimal" oninput="formatTimeInput(this)">
</div>
</div>
<div id="arrFrom-lookup-results" class="airport-lookup-results"></div>
<div class="form-group">
<label for="arrNotes">Notes</label>
<textarea id="arrNotes" name="notes" placeholder="Any additional information..."></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label for="arrEmail">Pilot Email</label>
<input type="email" id="arrEmail" name="pilot_email">
</div>
<div class="form-group">
<label for="arrName">Pilot Name</label>
<input type="text" id="arrName" name="pilot_name" placeholder="Optional">
</div>
</div>
<button type="submit" class="btn-submit">✈️ Book Arrival</button>
</form>
</div>
<div class="loading" id="loading">
<div class="spinner"></div>
<p style="margin-top: 15px; color: #666;">Processing...</p>
</div>
<div class="footer">
<p>📞 Questions? Tower 01792 687042</p>
<p style="margin-top: 10px; font-size: 12px; color: #999;">Version 1.0</p>
</div>
</div>
<script>
const API_BASE = window.location.origin + '/api/v1';
const STORAGE_RECENT_REGS_KEY = 'bookingPage_recentRegs';
const STORAGE_AIRCRAFT_TYPES_KEY = 'bookingPage_aircraftTypes';
const STORAGE_CALLSIGNS_KEY = 'bookingPage_callsigns';
const MAX_RECENT = 5;
// ==================== localStorage Management ====================
function getRecentRegistrations() {
try {
const stored = localStorage.getItem(STORAGE_RECENT_REGS_KEY);
return stored ? JSON.parse(stored) : [];
} catch (e) {
console.error('Error getting recent registrations:', e);
return [];
}
}
function saveRecentRegistration(registration) {
try {
if (!registration || registration.trim() === '') {
console.log('⚠️ saveRecentRegistration: empty registration');
return;
}
const reg = registration.toUpperCase().trim();
let recentRegs = getRecentRegistrations();
console.log('📝 Saving recent reg:', reg);
// Remove if already exists
recentRegs = recentRegs.filter(r => r !== reg);
// Add to front
recentRegs.unshift(reg);
// Keep only last MAX_RECENT
recentRegs = recentRegs.slice(0, MAX_RECENT);
localStorage.setItem(STORAGE_RECENT_REGS_KEY, JSON.stringify(recentRegs));
console.log('✅ Recent regs updated:', recentRegs);
updateAllRecentRegsUI();
} catch (e) {
console.error('Error saving recent registration:', e);
}
}
function cacheAircraftType(registration, typeCode) {
try {
if (!registration || !typeCode) {
console.log('⚠️ cache skipped - registration:', registration, 'typeCode:', typeCode);
return;
}
const reg = registration.toUpperCase().trim();
const stored = localStorage.getItem(STORAGE_AIRCRAFT_TYPES_KEY);
let aircraftTypes = stored ? JSON.parse(stored) : {};
aircraftTypes[reg] = typeCode;
localStorage.setItem(STORAGE_AIRCRAFT_TYPES_KEY, JSON.stringify(aircraftTypes));
console.log('✅ Cached aircraft type:', reg, '=', typeCode);
console.log('All cached types:', aircraftTypes);
} catch (e) {
console.error('Error caching aircraft type:', e);
}
}
function getCachedAircraftType(registration) {
try {
const reg = registration.toUpperCase().trim();
const stored = localStorage.getItem(STORAGE_AIRCRAFT_TYPES_KEY);
if (!stored) {
console.log('⚠️ No cached types found for:', reg);
return null;
}
const aircraftTypes = JSON.parse(stored);
const typeCode = aircraftTypes[reg] || null;
console.log('🔍 Retrieved cached type for', reg, '=', typeCode);
return typeCode;
} catch (e) {
console.error('Error getting cached aircraft type:', e);
return null;
}
}
function cacheCallsign(registration, callsign) {
try {
if (!registration || !callsign || callsign.trim() === '') {
return;
}
const reg = registration.toUpperCase().trim();
const cs = callsign.toUpperCase().trim();
const stored = localStorage.getItem(STORAGE_CALLSIGNS_KEY);
let callsigns = stored ? JSON.parse(stored) : {};
callsigns[reg] = cs;
localStorage.setItem(STORAGE_CALLSIGNS_KEY, JSON.stringify(callsigns));
console.log('✅ Cached callsign:', reg, '=', cs);
} catch (e) {
console.error('Error caching callsign:', e);
}
}
function getCachedCallsign(registration) {
try {
const reg = registration.toUpperCase().trim();
const stored = localStorage.getItem(STORAGE_CALLSIGNS_KEY);
if (!stored) {
return null;
}
const callsigns = JSON.parse(stored);
const callsign = callsigns[reg] || null;
console.log('🔍 Retrieved cached callsign for', reg, '=', callsign);
return callsign;
} catch (e) {
console.error('Error retrieving cached callsign:', e);
return null;
}
}
function applyRecentReg(registration, formType) {
const registerIdMap = {
'local': 'localReg',
'circuits': 'circuitReg',
'departure': 'depReg',
'arrival': 'arrReg'
};
const typeIdMap = {
'local': 'localType',
'circuits': 'circuitType',
'departure': 'depType',
'arrival': 'arrType'
};
const callsignIdMap = {
'local': 'localCallsign',
'circuits': 'circuitCallsign',
'departure': 'depCallsign',
'arrival': 'arrCallsign'
};
const regId = registerIdMap[formType];
const typeId = typeIdMap[formType];
const callsignId = callsignIdMap[formType];
console.log('📍 applyRecentReg called - reg:', registration, 'form:', formType);
document.getElementById(regId).value = registration;
// Restore cached type if available (always overwrite)
const cachedType = getCachedAircraftType(registration);
if (cachedType) {
console.log('✔️ Applying cached type:', cachedType);
document.getElementById(typeId).value = cachedType;
} else {
console.log('️ No cached type found');
}
// Restore cached callsign if available
const cachedCallsign = getCachedCallsign(registration);
if (cachedCallsign) {
console.log('✔️ Applying cached callsign:', cachedCallsign);
document.getElementById(callsignId).value = cachedCallsign;
}
document.getElementById(regId).focus();
// Trigger the lookup
handleAircraftLookup(registration, formType);
}
function updateRecentRegsUI(formType) {
const sectionIdMap = {
'local': 'localRecentSection',
'circuits': 'circuitsRecentSection',
'departure': 'departureRecentSection',
'arrival': 'arrivalRecentSection'
};
const containerIdMap = {
'local': 'localRecent',
'circuits': 'circuitsRecent',
'departure': 'departureRecent',
'arrival': 'arrivalRecent'
};
const recentRegs = getRecentRegistrations();
const section = document.getElementById(sectionIdMap[formType]);
const container = document.getElementById(containerIdMap[formType]);
if (recentRegs.length === 0) {
section.classList.add('empty');
return;
}
section.classList.remove('empty');
container.innerHTML = recentRegs.map(reg => `
<button type="button" class="recent-reg-btn" onclick="applyRecentReg('${reg}', '${formType}')" title="Use ${reg}">
${reg}
</button>
`).join('');
}
function updateAllRecentRegsUI() {
['local', 'circuits', 'departure', 'arrival'].forEach(formType => {
updateRecentRegsUI(formType);
});
}
// ==================== End localStorage Management ====================
function switchTab(button, tabName) {
// Hide all tabs
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('active');
});
// Show selected tab and activate button
document.getElementById(tabName).classList.add('active');
button.classList.add('active');
}
function showMessage(message, isError = false) {
if (isError) {
const errorEl = document.getElementById('errorMessage');
errorEl.textContent = message;
errorEl.style.display = 'block';
// Auto-hide error after 5 seconds
setTimeout(() => {
errorEl.style.display = 'none';
}, 5000);
} else {
// Show success modal instead
const modalMessage = document.getElementById('modalMessage');
modalMessage.textContent = message;
document.getElementById('successModal').classList.add('active');
}
}
function closeSuccessModal() {
document.getElementById('successModal').classList.remove('active');
}
async function handleSubmit(event, formType) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
const data = Object.fromEntries(formData);
// Save the registration to localStorage
if (data.registration) {
saveRecentRegistration(data.registration);
// Also cache the aircraft type if provided
if (data.type) {
console.log('💾 Caching type on submit - reg:', data.registration, 'type:', data.type);
cacheAircraftType(data.registration, data.type);
}
// Cache the callsign if provided
if (data.callsign) {
cacheCallsign(data.registration, data.callsign);
}
}
// Set flight_type based on form type
if (formType === 'local' && !data.flight_type) {
data.flight_type = 'LOCAL';
} else if (formType === 'circuits' && !data.flight_type) {
data.flight_type = 'CIRCUITS';
}
// Convert time inputs to ISO datetime (combine with today's date)
const today = new Date().toISOString().split('T')[0];
const timeFields = ['etd', 'circuit_timestamp', 'eta'];
timeFields.forEach(field => {
if (data[field]) {
let timeValue = data[field];
// Normalize 4-digit format (HHMM) to HH:MM
if (/^[0-9]{4}$/.test(timeValue)) {
timeValue = timeValue.slice(0, 2) + ':' + timeValue.slice(2);
}
data[field] = `${today}T${timeValue}:00Z`;
}
});
let endpoint = '';
switch(formType) {
case 'local':
endpoint = '/public-book/local-flights';
break;
case 'circuits':
endpoint = '/public-book/local-flights';
break;
case 'departure':
endpoint = '/public-book/departures';
break;
case 'arrival':
endpoint = '/public-book/arrivals';
break;
}
document.getElementById('loading').style.display = 'block';
try {
const response = await fetch(`${API_BASE}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || `Error: ${response.status}`);
}
const result = await response.json();
showMessage(`Thanks - Your booking has been submitted. Have a great flight!`);
form.reset();
} catch (error) {
console.error('Error:', error);
showMessage(`❌ Error: ${error.message}`, true);
} finally {
document.getElementById('loading').style.display = 'none';
}
}
// Set current time as default for time inputs
function formatTimeInput(input) {
let value = input.value.replace(/[^0-9]/g, '');
// Auto-format: if user types 1430, it becomes 14:30
if (value.length === 4) {
input.value = value.slice(0, 2) + ':' + value.slice(2);
} else if (value.length <= 2) {
input.value = value;
} else if (value.length === 3) {
input.value = value.slice(0, 2) + ':' + value.slice(2);
}
}
function setDefaultTimes() {
const now = new Date();
const futureTime = new Date(now.getTime() + 10 * 60000); // 10 minutes from now
const futureHours = String(futureTime.getUTCHours()).padStart(2, '0');
const futureMinutes = String(futureTime.getUTCMinutes()).padStart(2, '0');
const futureTimeValue = `${futureHours}:${futureMinutes}`;
const etdFieldIds = ['localETD', 'circuitETD', 'depETD', 'arrETA'];
etdFieldIds.forEach(id => {
const input = document.getElementById(id);
if (input && !input.value) {
input.value = futureTimeValue;
}
});
}
// Aircraft lookup functionality
let aircraftLookupTimeouts = {};
function formatAircraftRegistration(input) {
const reg = input.trim().toUpperCase();
// If 5 alpha characters, add hyphen: GIVYY -> G-IVYY
if (/^[A-Z]{5}$/.test(reg)) {
return reg[0] + '-' + reg.slice(1);
}
// Otherwise just capitalize
return reg;
}
async function handleAircraftLookup(registration, formType) {
if (aircraftLookupTimeouts[formType]) {
clearTimeout(aircraftLookupTimeouts[formType]);
}
const registerIdMap = {
'local': 'localReg',
'circuits': 'circuitReg',
'departure': 'depReg',
'arrival': 'arrReg'
};
const typeIdMap = {
'local': 'localType',
'circuits': 'circuitType',
'departure': 'depType',
'arrival': 'arrType'
};
const regId = registerIdMap[formType];
const typeId = typeIdMap[formType];
const resultsDiv = document.getElementById(`${regId}-lookup-results`);
if (!registration || registration.length < 4) {
resultsDiv.innerHTML = '';
return;
}
console.log('🔎 Aircraft lookup:', registration, 'formType:', formType);
resultsDiv.innerHTML = '<span class="lookup-searching">Searching...</span>';
aircraftLookupTimeouts[formType] = setTimeout(async () => {
try {
const response = await fetch(`${API_BASE}/aircraft/public/lookup/${registration.toUpperCase()}`);
if (response.ok) {
const data = await response.json();
console.log('📡 API Response:', data, 'Length:', data ? data.length : 'null');
if (data && data.length > 0) {
if (data.length === 1) {
const aircraft = data[0];
const regValue = aircraft.registration || registration.toUpperCase();
console.log('✨ Single match found:', regValue);
console.log('📋 Full aircraft object:', aircraft);
console.log('🔤 aircraft.type_code:', aircraft.type_code);
document.getElementById(regId).value = regValue;
// Cache the aircraft type
if (aircraft.type_code) {
console.log('💾 About to cache - reg:', regValue, 'type:', aircraft.type_code);
cacheAircraftType(regValue, aircraft.type_code);
} else {
console.log('⚠️ type_code is empty or null, not caching');
}
resultsDiv.innerHTML = `
<div class="lookup-match">
${regValue} - ${aircraft.type_code || 'Unknown'} ${aircraft.model ? `(${aircraft.model})` : ''}
</div>
`;
if (!document.getElementById(typeId).value) {
document.getElementById(typeId).value = aircraft.type_code || '';
}
} else if (data.length <= 10) {
console.log('🎲 Multiple matches found:', data.length);
resultsDiv.innerHTML = `
<div class="aircraft-list">
${data.map(aircraft => `
<div class="aircraft-option" onclick="selectAircraft('${formType}', '${aircraft.registration || registration.toUpperCase()}', '${aircraft.type_code || ''}')">
<div class="aircraft-code">${aircraft.registration || registration.toUpperCase()}</div>
<div class="aircraft-details">${aircraft.type_code || 'Unknown'} ${aircraft.model ? `(${aircraft.model})` : ''}</div>
</div>
`).join('')}
</div>
`;
} else {
resultsDiv.innerHTML = '<span class="lookup-no-match">Too many matches, please be more specific</span>';
}
} else {
// No aircraft found - auto-format and apply the registration
const formattedReg = formatAircraftRegistration(registration);
document.getElementById(regId).value = formattedReg;
console.log('❌ No match - formatted:', registration, '→', formattedReg);
resultsDiv.innerHTML = `<span class="lookup-no-match">No match found - formatted as ${formattedReg}</span>`;
}
} else {
console.log('⚠️ Lookup response not ok:', response.status);
resultsDiv.innerHTML = '';
}
} catch (error) {
console.error('Aircraft lookup error:', error);
resultsDiv.innerHTML = '';
}
}, 500);
}
function selectAircraft(formType, registration, typeCode) {
const registerIdMap = {
'local': 'localReg',
'circuits': 'circuitReg',
'departure': 'depReg',
'arrival': 'arrReg'
};
const typeIdMap = {
'local': 'localType',
'circuits': 'circuitType',
'departure': 'depType',
'arrival': 'arrType'
};
const regId = registerIdMap[formType];
const typeId = typeIdMap[formType];
console.log('🎯 selectAircraft called:', registration, typeCode);
document.getElementById(regId).value = registration;
document.getElementById(typeId).value = typeCode;
// Cache the aircraft type
if (typeCode) {
cacheAircraftType(registration, typeCode);
}
document.getElementById(`${regId}-lookup-results`).innerHTML = '';
document.getElementById(regId).blur();
}
// Airport lookup functionality
let airportLookupTimeouts = {};
async function handleAirportLookup(query, fieldType) {
if (airportLookupTimeouts[fieldType]) {
clearTimeout(airportLookupTimeouts[fieldType]);
}
const fieldIdMap = {
'depTo': 'depTo',
'arrFrom': 'arrFrom'
};
const fieldId = fieldIdMap[fieldType];
const resultsDiv = document.getElementById(`${fieldId}-lookup-results`);
if (!query || query.length < 2) {
resultsDiv.innerHTML = '';
return;
}
resultsDiv.innerHTML = '<span class="lookup-searching">Searching...</span>';
airportLookupTimeouts[fieldType] = setTimeout(async () => {
try {
const response = await fetch(`${API_BASE}/airport/public/lookup/${query.toUpperCase()}`);
if (response.ok) {
const data = await response.json();
if (data && data.length > 0) {
if (data.length === 1) {
const airport = data[0];
resultsDiv.innerHTML = `
<div class="aircraft-list">
<div class="aircraft-option" onclick="selectAirport('${fieldId}', '${airport.icao}')">
<div class="aircraft-code">${airport.icao}/${airport.iata || ''}</div>
<div class="aircraft-details">${airport.name}, ${airport.country}</div>
</div>
</div>
`;
} else if (data.length <= 10) {
resultsDiv.innerHTML = `
<div class="aircraft-list">
${data.map(airport => `
<div class="aircraft-option" onclick="selectAirport('${fieldId}', '${airport.icao}')">
<div class="aircraft-code">${airport.icao}/${airport.iata || ''}</div>
<div class="aircraft-details">${airport.name}, ${airport.country}</div>
</div>
`).join('')}
</div>
`;
} else {
resultsDiv.innerHTML = '<span class="lookup-no-match">Too many matches, please be more specific</span>';
}
} else {
resultsDiv.innerHTML = '<span class="lookup-no-match">No airport found</span>';
}
} else {
resultsDiv.innerHTML = '';
}
} catch (error) {
console.error('Airport lookup error:', error);
resultsDiv.innerHTML = '';
}
}, 500);
}
function selectAirport(fieldId, icaoCode) {
document.getElementById(fieldId).value = icaoCode;
document.getElementById(`${fieldId}-lookup-results`).innerHTML = '';
document.getElementById(fieldId).blur();
}
// Clear lookup results on blur
document.addEventListener('DOMContentLoaded', function() {
console.log('🚀 Page loaded - initializing...');
['localReg', 'depReg', 'arrReg', 'depTo', 'arrFrom'].forEach(fieldId => {
const field = document.getElementById(fieldId);
if (field) {
field.addEventListener('blur', function() {
setTimeout(() => {
document.getElementById(`${fieldId}-lookup-results`).innerHTML = '';
}, 150);
});
}
});
// Initialize recent registrations UI
updateAllRecentRegsUI();
console.log('✅ Recent regs UI initialized');
console.log('🗄️ Cached aircraft types:', localStorage.getItem(STORAGE_AIRCRAFT_TYPES_KEY));
// Set default times
setDefaultTimes();
});
// Initialize on page load
document.addEventListener('DOMContentLoaded', setDefaultTimes);
</script>
</body>
</html>