Drone flights and Bulk Logging WIPs
This commit is contained in:
+204
-11
@@ -266,6 +266,85 @@
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.match-strips {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.65rem;
|
||||
margin-top: 0.55rem;
|
||||
}
|
||||
|
||||
.match-strip-preview {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
display: block;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.match-strip {
|
||||
display: inline-block;
|
||||
min-width: 760px;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.match-strip .bulk-field {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.match-strip .strip-value {
|
||||
background: rgba(255,255,255,0.35);
|
||||
border-radius: 4px;
|
||||
color: #111;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
min-height: 2rem;
|
||||
padding: 0.45rem 0.55rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.match-strip .callsign-preview {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.match-strip .callsign-preview label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.match-strip .callsign-preview .strip-value {
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
font-size: 1.2rem;
|
||||
justify-content: center;
|
||||
min-height: 100%;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.match-strip.strip-kind-local {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.match-strip.strip-kind-local .strip-value {
|
||||
background: rgba(255,255,255,0.28);
|
||||
border-radius: 0;
|
||||
min-height: 0;
|
||||
padding: 0.2rem;
|
||||
}
|
||||
|
||||
.match-strip.strip-kind-local .strip-registration .strip-value {
|
||||
background: rgba(255,255,255,0.55);
|
||||
outline: 2px solid rgba(0,0,0,0.2);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.match-strip.strip-kind-local .strip-registration.callsign-preview .strip-value {
|
||||
background: transparent;
|
||||
font-size: 1.35rem;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.movement-table table {
|
||||
min-width: 1050px;
|
||||
}
|
||||
@@ -640,6 +719,10 @@
|
||||
function applySuggestion(suggestion) {
|
||||
if (!suggestion.source) return;
|
||||
|
||||
if (document.getElementById('flight-kind').value === 'LOCAL') {
|
||||
fillIfEmpty('aircraft-type', suggestion.aircraft_type);
|
||||
return;
|
||||
}
|
||||
if (suggestion.movement_id) document.getElementById('matched-movement-id').value = suggestion.movement_id;
|
||||
if (suggestion.ppr_id) document.getElementById('matched-ppr-id').value = suggestion.ppr_id;
|
||||
fillSuggestedTime(suggestion.movement_time);
|
||||
@@ -675,30 +758,138 @@
|
||||
function renderContext(context) {
|
||||
const panel = document.getElementById('context-panel');
|
||||
const pprs = context.pprs || [];
|
||||
const localFlights = context.local_flights || [];
|
||||
const movements = context.movements || [];
|
||||
if (!pprs.length && !movements.length) {
|
||||
if (!pprs.length && !localFlights.length) {
|
||||
hideContext();
|
||||
return;
|
||||
}
|
||||
|
||||
panel.className = `context-panel ${movements.length ? 'warning' : ''}`;
|
||||
panel.className = 'context-panel';
|
||||
const parts = [];
|
||||
if (movements.length) {
|
||||
parts.push(`<strong>Existing movement found.</strong> Saving will update it instead of adding another.`);
|
||||
parts.push(movements.map(m => `<span class="match-pill">${formatTime(m.timestamp)} ${m.movement_type} #${m.id}</span>`).join(''));
|
||||
}
|
||||
if (pprs.length) {
|
||||
parts.push(`<strong>Matching PPR${pprs.length > 1 ? 's' : ''}:</strong>`);
|
||||
parts.push(pprs.map(p => {
|
||||
const encoded = encodeURIComponent(JSON.stringify(p));
|
||||
return `<button type="button" class="match-pill" onclick='selectPPR("${encoded}")'>#${p.id} ${p.aircraft_registration} ${p.from_location || ''}${p.to_location ? ' to ' + p.to_location : ''}</button>`;
|
||||
}).join(''));
|
||||
parts.push(renderPPRMatchStrips(pprs));
|
||||
}
|
||||
if (localFlights.length) {
|
||||
parts.push(`<strong>Existing local flight${localFlights.length > 1 ? 's' : ''}:</strong>`);
|
||||
parts.push(renderLocalFlightMatchStrips(localFlights));
|
||||
}
|
||||
|
||||
panel.innerHTML = parts.join('<br>');
|
||||
panel.style.display = 'block';
|
||||
}
|
||||
|
||||
function renderPPRMatchStrips(pprs) {
|
||||
const kind = document.getElementById('flight-kind').value;
|
||||
return `<div class="match-strips">${pprs.map(ppr => renderPPRMatchStrip(ppr, kind)).join('')}</div>`;
|
||||
}
|
||||
|
||||
function renderLocalFlightMatchStrips(localFlights) {
|
||||
return `<div class="match-strips">${localFlights.map(local => renderLocalFlightMatchStrip(local)).join('')}</div>`;
|
||||
}
|
||||
|
||||
function renderPPRMatchStrip(ppr, kind) {
|
||||
return `
|
||||
<div class="match-strip-preview" aria-label="Matching PPR ${escapeHtml(ppr.id)} for ${escapeHtml(ppr.aircraft_registration || 'aircraft')}">
|
||||
<div class="virtual-strip match-strip ${stripClassForKind(kind)} strip-kind-${kind.toLowerCase()}">
|
||||
${kind === 'LOCAL' ? renderLocalPPRStrip(ppr) : renderStandardPPRStrip(ppr, kind)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderLocalFlightMatchStrip(local) {
|
||||
return `
|
||||
<div class="match-strip-preview" aria-label="Existing local flight ${escapeHtml(local.id)} for ${escapeHtml(local.aircraft_registration || 'aircraft')}">
|
||||
<div class="virtual-strip match-strip strip-pink strip-kind-local">
|
||||
${renderExistingLocalStrip(local)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderStandardPPRStrip(ppr, kind) {
|
||||
const isArrival = kind === 'ARRIVAL';
|
||||
const isDeparture = kind === 'DEPARTURE';
|
||||
const isOverflight = kind === 'OVERFLIGHT';
|
||||
const pob = kind === 'ARRIVAL' ? ppr.pob_in : (ppr.pob_out || ppr.pob_in);
|
||||
return `
|
||||
<div class="bulk-grid">
|
||||
${stripField('Registration', ppr.aircraft_registration)}
|
||||
${isArrival ? stripField('LDG', compactIsoTime(ppr.eta)) : ''}
|
||||
${isDeparture ? stripField('T/O', compactIsoTime(ppr.etd)) : ''}
|
||||
${isOverflight ? stripField('Contact time', '') : ''}
|
||||
${isOverflight ? stripField('QSY time', '') : ''}
|
||||
${stripField('Type', ppr.aircraft_type)}
|
||||
${stripField('C/SIGN', ppr.callsign || ppr.aircraft_registration, 'callsign-preview')}
|
||||
${['ARRIVAL', 'OVERFLIGHT'].includes(kind) ? stripField('From', ppr.from_location) : ''}
|
||||
${['DEPARTURE', 'OVERFLIGHT'].includes(kind) ? stripField('To', ppr.to_location) : ''}
|
||||
${stripField('POB', pob)}
|
||||
${['ARRIVAL', 'DEPARTURE'].includes(kind) ? stripField('Runway', '') : ''}
|
||||
${stripField('Wind', '')}
|
||||
${stripField('Pressure', '')}
|
||||
${stripField('Notes', ppr.notes, 'notes-field')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderLocalPPRStrip(ppr) {
|
||||
return `
|
||||
<div class="bulk-grid">
|
||||
<div class="bulk-field strip-field local-slash-cell"></div>
|
||||
${stripField('LOC / CCTS', 'Local', 'local-nature-cell')}
|
||||
${stripField('Type', ppr.aircraft_type, 'type-cell')}
|
||||
${stripField('C/SIGN', ppr.callsign || ppr.aircraft_registration, 'strip-registration callsign-preview')}
|
||||
${stripField('T/O', compactIsoTime(ppr.etd), 'local-to-cell')}
|
||||
${stripField('LDG', compactIsoTime(ppr.eta), 'local-ldg-cell')}
|
||||
${stripField('CCTS', '', 'local-circuit-cell')}
|
||||
${stripField('POB', ppr.pob_out || ppr.pob_in, 'pob-cell')}
|
||||
${stripField('Notes', ppr.notes, 'notes-field')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderExistingLocalStrip(local) {
|
||||
return `
|
||||
<div class="bulk-grid">
|
||||
<div class="bulk-field strip-field local-slash-cell"></div>
|
||||
${stripField('LOC / CCTS', local.flight_type || 'Local', 'local-nature-cell')}
|
||||
${stripField('Type', local.aircraft_type, 'type-cell')}
|
||||
${stripField('C/SIGN', local.callsign || local.aircraft_registration, 'strip-registration callsign-preview')}
|
||||
${stripField('T/O', compactIsoTime(local.takeoff_time || local.departed_time || local.etd), 'local-to-cell')}
|
||||
${stripField('LDG', compactIsoTime(local.landing_time), 'local-ldg-cell')}
|
||||
${stripField('CCTS', local.circuits, 'local-circuit-cell')}
|
||||
${stripField('POB', local.pob, 'pob-cell')}
|
||||
${stripField('Notes', local.notes, 'notes-field')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function stripField(label, value, extraClass = '') {
|
||||
return `
|
||||
<div class="bulk-field ${extraClass}">
|
||||
<label>${escapeHtml(label)}</label>
|
||||
<div class="strip-value">${escapeHtml(value || '')}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function compactIsoTime(value) {
|
||||
if (!value) return '';
|
||||
return compactTime(value.slice(11, 16));
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? '').replace(/[&<>"']/g, char => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
}[char]));
|
||||
}
|
||||
|
||||
function hideContext() {
|
||||
const panel = document.getElementById('context-panel');
|
||||
panel.style.display = 'none';
|
||||
@@ -717,6 +908,8 @@
|
||||
document.getElementById('pob').value = kind === 'ARRIVAL' ? (ppr.pob_in || '') : (ppr.pob_out || ppr.pob_in || '');
|
||||
if (kind === 'ARRIVAL' && ppr.eta) document.getElementById('landing-time').value = compactTime(ppr.eta.slice(11, 16));
|
||||
if (kind === 'DEPARTURE' && ppr.etd) document.getElementById('takeoff-time').value = compactTime(ppr.etd.slice(11, 16));
|
||||
if (kind === 'LOCAL' && ppr.etd) document.getElementById('local-takeoff-time').value = compactTime(ppr.etd.slice(11, 16));
|
||||
if (kind === 'LOCAL' && ppr.eta) document.getElementById('local-landing-time').value = compactTime(ppr.eta.slice(11, 16));
|
||||
document.getElementById('notes').value = ppr.notes || '';
|
||||
}
|
||||
|
||||
@@ -745,7 +938,7 @@
|
||||
pressure_setting: nullableValue('pressure-setting'),
|
||||
notes: nullableValue('notes'),
|
||||
ppr_id: nullableNumber('matched-ppr-id'),
|
||||
movement_id: nullableNumber('matched-movement-id')
|
||||
movement_id: kind === 'LOCAL' ? null : nullableNumber('matched-movement-id')
|
||||
};
|
||||
|
||||
const response = await authFetch(`${API_BASE}/movements/bulk-log`, {
|
||||
|
||||
Reference in New Issue
Block a user