Booking form fixes
This commit is contained in:
+224
-84
@@ -102,11 +102,8 @@
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
input[type="time"],
|
||||
input[type="number"] {
|
||||
padding: 12px 8px;
|
||||
text-align: center;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
input:focus, select:focus, textarea:focus {
|
||||
@@ -198,6 +195,65 @@
|
||||
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;
|
||||
@@ -372,6 +428,15 @@
|
||||
<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>
|
||||
@@ -390,16 +455,22 @@
|
||||
</div>
|
||||
|
||||
<form id="localFlightForm" onsubmit="handleSubmit(event, 'local')">
|
||||
<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 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="localType">Aircraft Type</label>
|
||||
<input type="text" id="localType" name="type" placeholder="e.g., Cessna 172">
|
||||
<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>
|
||||
@@ -407,18 +478,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row-3col">
|
||||
<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>
|
||||
<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">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="localETD">ETD</label>
|
||||
<input type="time" id="localETD" name="etd">
|
||||
<input type="number" id="localDuration" name="duration" value="45" min="5" inputmode="numeric">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -439,16 +506,22 @@
|
||||
</div>
|
||||
|
||||
<form id="circuitForm" onsubmit="handleSubmit(event, 'circuits')">
|
||||
<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 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="circuitType">Aircraft Type</label>
|
||||
<input type="text" id="circuitType" name="type" placeholder="e.g., Cessna 172">
|
||||
<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>
|
||||
@@ -456,18 +529,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row-3col">
|
||||
<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>
|
||||
<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">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="circuitETD">ETD</label>
|
||||
<input type="time" id="circuitETD" name="etd">
|
||||
<input type="number" id="circuitDuration" name="duration" value="30" min="5" inputmode="numeric">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -488,16 +557,22 @@
|
||||
</div>
|
||||
|
||||
<form id="departureForm" onsubmit="handleSubmit(event, 'departure')">
|
||||
<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 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="depType">Aircraft Type</label>
|
||||
<input type="text" id="depType" name="type" placeholder="e.g., Cessna 172">
|
||||
<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>
|
||||
@@ -505,18 +580,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row-3col">
|
||||
<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-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="time" id="depETD" name="etd">
|
||||
<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>
|
||||
@@ -540,16 +611,22 @@
|
||||
</div>
|
||||
|
||||
<form id="arrivalForm" onsubmit="handleSubmit(event, 'arrival')">
|
||||
<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 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="arrType">Aircraft Type</label>
|
||||
<input type="text" id="arrType" name="type" placeholder="e.g., Cessna 172">
|
||||
<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>
|
||||
@@ -557,18 +634,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row-3col">
|
||||
<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-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="time" id="arrETA" name="eta">
|
||||
<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>
|
||||
@@ -608,6 +681,7 @@
|
||||
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 ====================
|
||||
@@ -690,6 +764,43 @@
|
||||
}
|
||||
}
|
||||
|
||||
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',
|
||||
@@ -705,8 +816,16 @@
|
||||
'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;
|
||||
@@ -720,6 +839,13 @@
|
||||
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);
|
||||
@@ -780,24 +906,24 @@
|
||||
}
|
||||
|
||||
function showMessage(message, isError = false) {
|
||||
const successEl = document.getElementById('successMessage');
|
||||
const errorEl = document.getElementById('errorMessage');
|
||||
|
||||
if (isError) {
|
||||
const errorEl = document.getElementById('errorMessage');
|
||||
errorEl.textContent = message;
|
||||
errorEl.style.display = 'block';
|
||||
successEl.style.display = 'none';
|
||||
// Auto-hide error after 5 seconds
|
||||
setTimeout(() => {
|
||||
errorEl.style.display = 'none';
|
||||
}, 5000);
|
||||
} else {
|
||||
successEl.textContent = message;
|
||||
successEl.style.display = 'block';
|
||||
errorEl.style.display = 'none';
|
||||
// Show success modal instead
|
||||
const modalMessage = document.getElementById('modalMessage');
|
||||
modalMessage.textContent = message;
|
||||
document.getElementById('successModal').classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-hide after 5 seconds
|
||||
setTimeout(() => {
|
||||
successEl.style.display = 'none';
|
||||
errorEl.style.display = 'none';
|
||||
}, 5000);
|
||||
function closeSuccessModal() {
|
||||
document.getElementById('successModal').classList.remove('active');
|
||||
}
|
||||
|
||||
async function handleSubmit(event, formType) {
|
||||
@@ -816,6 +942,11 @@
|
||||
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
|
||||
@@ -830,8 +961,12 @@
|
||||
const timeFields = ['etd', 'circuit_timestamp', 'eta'];
|
||||
timeFields.forEach(field => {
|
||||
if (data[field]) {
|
||||
// data[field] is in HH:MM format (time input)
|
||||
const datetime = new Date(`${today}T${data[field]}:00`);
|
||||
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);
|
||||
}
|
||||
const datetime = new Date(`${today}T${timeValue}:00`);
|
||||
data[field] = datetime.toISOString();
|
||||
}
|
||||
});
|
||||
@@ -869,7 +1004,7 @@
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
showMessage(`✅ Success! Your booking has been submitted. Booking ID: ${result.id}`);
|
||||
showMessage(`Thanks - Your booking has been submitted. Have a great flight!`);
|
||||
form.reset();
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
@@ -880,27 +1015,32 @@
|
||||
}
|
||||
|
||||
// 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 hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const timeValue = `${hours}:${minutes}`;
|
||||
|
||||
// ETD fields should default to 15 minutes from now
|
||||
const futureTime = new Date(now.getTime() + 15 * 60000);
|
||||
const futureTime = new Date(now.getTime() + 10 * 60000); // 10 minutes from now
|
||||
const futureHours = String(futureTime.getHours()).padStart(2, '0');
|
||||
const futureMinutes = String(futureTime.getMinutes()).padStart(2, '0');
|
||||
const futureTimeValue = `${futureHours}:${futureMinutes}`;
|
||||
|
||||
const etdFieldIds = ['localETD', 'circuitETD', 'depETD'];
|
||||
const etdFieldIds = ['localETD', 'circuitETD', 'depETD', 'arrETA'];
|
||||
|
||||
document.querySelectorAll('input[type="time"]').forEach(input => {
|
||||
if (!input.value) {
|
||||
if (etdFieldIds.includes(input.id)) {
|
||||
input.value = futureTimeValue;
|
||||
} else {
|
||||
input.value = timeValue;
|
||||
}
|
||||
etdFieldIds.forEach(id => {
|
||||
const input = document.getElementById(id);
|
||||
if (input && !input.value) {
|
||||
input.value = futureTimeValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user