Updated arrival date input on flight form

This commit is contained in:
2026-08-21 21:40:02 +10:00
parent 76118d0bfd
commit 5492cc72d7
+336 -67
View File
@@ -6,7 +6,7 @@ import AirlineSearchBox from '@/Components/FlightsGoneBy/AirlineSearchBox.vue'
import AircraftSearchBox from '@/Components/FlightsGoneBy/AircraftSearchBox.vue' import AircraftSearchBox from '@/Components/FlightsGoneBy/AircraftSearchBox.vue'
import AirportSearchBox from '@/Components/FlightsGoneBy/AirportSearchBox.vue' import AirportSearchBox from '@/Components/FlightsGoneBy/AirportSearchBox.vue'
import type {SeatType, FlightReason, FlightClass, CrewType, SharedProps} from '@/Types/types' import type {SeatType, FlightReason, FlightClass, CrewType, SharedProps} from '@/Types/types'
import { ref, watch, computed } from 'vue' import { ref, watch, computed, onMounted, nextTick } from 'vue'
import { VTimePicker, VDatePicker } from 'vuetify/components' import { VTimePicker, VDatePicker } from 'vuetify/components'
defineOptions({ layout: MainLayout }) defineOptions({ layout: MainLayout })
@@ -49,25 +49,13 @@ const pad = (n: number) => String(n).padStart(2, '0')
const flightNumber = ref(props.flight?.flight_number ?? '') const flightNumber = ref(props.flight?.flight_number ?? '')
const lookupLoading = ref(false) const lookupLoading = ref(false)
const lookupError = ref<string | null>(null) const lookupError = ref<string | null>(null)
const lookupComplete = ref(true)
interface LookupResult {
airline_options: { value: number; title: string, logo_url: string }[]
from_options: { value: number; title: string; country_code: string }[]
to_options: { value: number; title: string; country_code: string }[]
aircraft_options: { value: number; title: string }[]
scheduled_departure_time: string | null
scheduled_arrival_time: string | null
}
const lookupResult = ref<LookupResult | null>(null)
const lookupKey = ref(0) const lookupKey = ref(0)
async function lookupFlight() { async function lookupFlight() {
if (!flightNumber.value.trim()) return if (!flightNumber.value.trim()) return
lookupLoading.value = true lookupLoading.value = true
lookupError.value = null lookupError.value = null
lookupResult.value = null
try { try {
const response = await fetch(`${route('flights.lookup')}?number=${encodeURIComponent(flightNumber.value.trim())}`, { const response = await fetch(`${route('flights.lookup')}?number=${encodeURIComponent(flightNumber.value.trim())}`, {
@@ -78,8 +66,6 @@ async function lookupFlight() {
lookupError.value = data.message ?? 'Lookup failed.' lookupError.value = data.message ?? 'Lookup failed.'
return return
} }
lookupResult.value = data
lookupComplete.value = true
if (data.airline_options?.length) { if (data.airline_options?.length) {
airlineOptionsData.value = data.airline_options airlineOptionsData.value = data.airline_options
if (!form.airline) form.airline = data.airline_options[0] if (!form.airline) form.airline = data.airline_options[0]
@@ -111,6 +97,12 @@ async function lookupFlight() {
} }
suppressArrivalAutoFill = false suppressArrivalAutoFill = false
// NOTE: if the lookup API is ever extended to return a day
// difference for the arrival (e.g. `data.arrival_day_offset`),
// wire it in here: `arrivalOffset.value = data.arrival_day_offset`
// (clamped/mapped to 'custom' if outside -1..2). Left out for now
// since the API doesn't provide it yet.
lookupKey.value++ lookupKey.value++
} catch (e) { } catch (e) {
lookupError.value = String(e) lookupError.value = String(e)
@@ -122,7 +114,6 @@ async function lookupFlight() {
// ── Display form (drives the template) ─────────────────────────────────────── // ── Display form (drives the template) ───────────────────────────────────────
const form = useForm({ const form = useForm({
flight_number: props.flight?.flight_number ?? '',
departure_date: props.flight?.departure_date ?? '', departure_date: props.flight?.departure_date ?? '',
arrival_date: props.flight?.arrival_date ?? '', arrival_date: props.flight?.arrival_date ?? '',
from: props.flight?.from_options[0] ?? null as { value: number; title: string; country_code: string } | null, from: props.flight?.from_options[0] ?? null as { value: number; title: string; country_code: string } | null,
@@ -230,9 +221,15 @@ function combineDateTime(date: Date | null, time: string | null): string {
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${(hh ?? '00').padStart(2, '0')}:${(mm ?? '00').padStart(2, '0')}` return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${(hh ?? '00').padStart(2, '0')}:${(mm ?? '00').padStart(2, '0')}`
} }
function clearDepartureDate() { departureDatePart.value = null } // Whole-day difference between two Date objects (time-of-day ignored).
function diffInDays(from: Date | null, to: Date | null): number | null {
if (!from || !to) return null
const a = new Date(from.getFullYear(), from.getMonth(), from.getDate())
const b = new Date(to.getFullYear(), to.getMonth(), to.getDate())
return Math.round((b.getTime() - a.getTime()) / 86_400_000)
}
function clearDepartureTime() { departureTimePart.value = null } function clearDepartureTime() { departureTimePart.value = null }
function clearArrivalDate() { arrivalDatePart.value = null }
function clearArrivalTime() { arrivalTimePart.value = null } function clearArrivalTime() { arrivalTimePart.value = null }
// Normalizes whatever shape the API sends a scheduled time in ("14:30", // Normalizes whatever shape the API sends a scheduled time in ("14:30",
@@ -278,19 +275,88 @@ watch([arrivalDatePart, arrivalTimePart], () => {
form.arrival_date = combineDateTime(arrivalDatePart.value, arrivalTimePart.value) form.arrival_date = combineDateTime(arrivalDatePart.value, arrivalTimePart.value)
}) })
// Auto-fill arrival (+1hr) the first time a departure TIME is chosen (and a // ── Arrival day offset ───────────────────────────────────────────────────────
// departure date is already set) and arrival is still empty. Picking the // Rather than making the user pick an independent arrival date, arrival date
// departure date alone no longer triggers this — only setting the time does. // is normally *derived* from departure date + a small relative offset
// (-1 / Same day / +1 / +2). "Custom" drops back to a real date picker for
// the rare case that falls outside that range. This avoids needing airport
// time zones to work out the arrival date, which a duration-based input
// would require.
type ArrivalOffset = -1 | 0 | 1 | 2
const arrivalOffsetOptions: { value: ArrivalOffset; label: string }[] = [
{ value: -1, label: '1 Day' },
{ value: 0, label: 'Same Day' },
{ value: 1, label: '+1 Day' },
{ value: 2, label: '+2 Days' },
]
const initialDiff = diffInDays(depInit.date, arrInit.date)
const initialDiffIsQuickOption = initialDiff !== null && ([-1, 0, 1, 2] as number[]).includes(initialDiff)
// 'offset' (the default): arrival date is derived from departure date + the
// selected quick offset. 'custom': the user has dropped into a real date
// picker, e.g. because the actual gap is more than +2 days.
const arrivalDateMode = ref<'offset' | 'custom'>(
initialDiff !== null && !initialDiffIsQuickOption ? 'custom' : 'offset'
)
const arrivalOffset = ref<ArrivalOffset>(
initialDiffIsQuickOption ? (initialDiff as ArrivalOffset) : 0
)
function applyArrivalOffset() {
if (suppressArrivalAutoFill) return
if (arrivalDateMode.value === 'custom') return
if (!departureDatePart.value) {
arrivalDatePart.value = null
return
}
const d = new Date(departureDatePart.value)
d.setDate(d.getDate() + arrivalOffset.value)
arrivalDatePart.value = d
}
// Re-derive arrival date whenever departure date moves, or the offset is
// changed by hand.
watch(departureDatePart, applyArrivalOffset)
watch(arrivalOffset, applyArrivalOffset)
function useCustomArrivalDate() {
arrivalDateMode.value = 'custom'
}
function useQuickArrivalOffset() {
arrivalDateMode.value = 'offset'
applyArrivalOffset()
}
function formatFullDate(d: Date | null): string {
if (!d) return ''
return d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short', year: 'numeric' })
}
// Auto-fill arrival TIME (+1hr) the first time a departure time is chosen
// (and a departure date is already set) and arrival time is still empty.
// If adding the hour rolls over midnight, bump the offset selector to match
// (e.g. departs 23:30 -> offset flips to "+1") so the resolved arrival date
// stays correct without the user having to notice and fix it themselves.
watch(departureTimePart, (newTime) => { watch(departureTimePart, (newTime) => {
if (suppressArrivalAutoFill) return if (suppressArrivalAutoFill) return
if (!newTime || !departureDatePart.value || arrivalDatePart.value) return if (arrivalDateMode.value === 'custom') return
if (!newTime || !departureDatePart.value || arrivalTimePart.value) return
const dep = new Date(departureDatePart.value) const dep = new Date(departureDatePart.value)
const [h, m] = newTime.split(':').map(Number) const [h, m] = newTime.split(':').map(Number)
dep.setHours(h, m) dep.setHours(h, m)
const arr = new Date(dep.getTime() + 60 * 60 * 1000) const arr = new Date(dep.getTime() + 60 * 60 * 1000)
const rawDiff = diffInDays(departureDatePart.value, arr) ?? 0
const clampedDiff = Math.min(Math.max(rawDiff, -1), 2) as -1 | 0 | 1 | 2
suppressArrivalAutoFill = true suppressArrivalAutoFill = true
arrivalOffset.value = clampedDiff
arrivalDatePart.value = new Date(arr.getFullYear(), arr.getMonth(), arr.getDate()) arrivalDatePart.value = new Date(arr.getFullYear(), arr.getMonth(), arr.getDate())
arrivalTimePart.value = `${pad(arr.getHours())}:${pad(arr.getMinutes())}` arrivalTimePart.value = `${pad(arr.getHours())}:${pad(arr.getMinutes())}`
suppressArrivalAutoFill = false suppressArrivalAutoFill = false
@@ -313,11 +379,31 @@ const arrivalMaxDate = computed(() => arrivalMax.value ? new Date(arrivalMax.val
// ── Native date inputs ─────────────────────────────────────────────────────── // ── Native date inputs ───────────────────────────────────────────────────────
// departureDatePart / arrivalDatePart stay the source of truth as Date // departureDatePart / arrivalDatePart stay the source of truth as Date
// objects. These computed get/set pairs are what the native <input type=date> // objects. The <input type="date"> elements below are deliberately left
// elements bind to — the browser handles locale display, typing, and // UNCONTROLLED from Vue's perspective — no v-model, no :model-value, no
// validation itself, so no custom format-aware parsing is needed here. // reactive :value binding of any kind. That's on purpose: any reactive
// The Vuetify v-date-picker is still available via a separate calendar icon // binding through Vuetify's v-text-field — even one that only commits on
// for users who prefer to pick from a calendar rather than type. // blur/change, not every keystroke — still runs through v-text-field's own
// internal echo of the field, which re-renders the wrapped <input> on every
// keystroke regardless of what we bind from outside. For type="date"
// specifically, that internal re-render resets the browser's own
// per-segment editing state mid-type (day/month/year), which is what was
// scrambling what you typed. Using a genuinely uncontrolled native <input>
// (not wrapped in v-text-field at all) sidesteps that entirely.
//
// We grab a direct DOM ref to each native input and manage its .value
// imperatively instead:
// - once on mount, to show the initial value
// - whenever departureDatePart/arrivalDatePart changes from elsewhere
// (the calendar picker, a flight lookup, auto-fill) — but only if the
// field isn't currently focused, so we never fight with active typing
// - the native `change` event (fires once a full date has been entered
// and committed, or cleared) is what flows the typed value back into
// departureDatePart/arrivalDatePart
// This keeps the real native, locale-aware date input and picker UI intact.
//
// The arrival native date input only exists in the DOM while arrivalOffset
// is 'custom' (see template); its value is (re)synced whenever it mounts.
function toIsoDateString(d: Date | null): string { function toIsoDateString(d: Date | null): string {
if (!d) return '' if (!d) return ''
@@ -331,16 +417,42 @@ function fromIsoDateString(value: string): Date | null {
return new Date(y, m - 1, d) return new Date(y, m - 1, d)
} }
const departureDateValue = computed({ const departureDateEl = ref<HTMLInputElement | null>(null)
get: () => toIsoDateString(departureDatePart.value), const arrivalDateEl = ref<HTMLInputElement | null>(null)
set: (value: string) => { departureDatePart.value = fromIsoDateString(value) },
onMounted(() => {
if (departureDateEl.value) departureDateEl.value.value = toIsoDateString(departureDatePart.value)
if (arrivalDateEl.value) arrivalDateEl.value.value = toIsoDateString(arrivalDatePart.value)
}) })
const arrivalDateValue = computed({ watch(departureDatePart, (d) => {
get: () => toIsoDateString(arrivalDatePart.value), const el = departureDateEl.value
set: (value: string) => { arrivalDatePart.value = fromIsoDateString(value) }, if (el && document.activeElement !== el) el.value = toIsoDateString(d)
}) })
watch(arrivalDatePart, (d) => {
const el = arrivalDateEl.value
if (el && document.activeElement !== el) el.value = toIsoDateString(d)
})
// The arrival native input only exists while arrivalDateMode === 'custom',
// so re-sync its value whenever it (re)mounts into the DOM.
watch(arrivalDateMode, async (mode) => {
if (mode !== 'custom') return
await nextTick()
if (arrivalDateEl.value) arrivalDateEl.value.value = toIsoDateString(arrivalDatePart.value)
})
function commitDepartureDateInput(e: Event) {
const value = (e.target as HTMLInputElement).value
departureDatePart.value = value ? fromIsoDateString(value) : null
}
function commitArrivalDateInput(e: Event) {
const value = (e.target as HTMLInputElement).value
arrivalDatePart.value = value ? fromIsoDateString(value) : null
}
// ── Editable, format-aware time text fields ──────────────────────────────── // ── Editable, format-aware time text fields ────────────────────────────────
// departureTimePart / arrivalTimePart stay the source of truth as internal // departureTimePart / arrivalTimePart stay the source of truth as internal
// "HH:mm" (24hr) strings. These input refs are what the text-fields actually // "HH:mm" (24hr) strings. These input refs are what the text-fields actually
@@ -472,20 +584,21 @@ function commitArrivalTimeInput() {
<!-- Departure date + time --> <!-- Departure date + time -->
<v-row> <v-row>
<v-col cols="12" sm="6"> <v-col cols="12" sm="6">
<v-text-field <div class="native-date-field">
v-model="departureDateValue" <v-icon icon="mdi-calendar" size="20" class="native-date-field__icon" />
<label class="native-date-field__label">Departure Date</label>
<input
ref="departureDateEl"
type="date" type="date"
label="Departure Date" class="native-date-field__input"
prepend-inner-icon="mdi-calendar" @change="commitDepartureDateInput"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.departure_date"
> >
<template #append-inner>
<v-menu v-model="depDateMenu" :close-on-content-click="false"> <v-menu v-model="depDateMenu" :close-on-content-click="false">
<template #activator="{ props: menuProps }"> <template #activator="{ props: menuProps }">
<v-icon <v-icon
v-bind="menuProps" v-bind="menuProps"
icon="mdi-calendar-month" icon="mdi-calendar-month"
size="20"
style="cursor: pointer" style="cursor: pointer"
/> />
</template> </template>
@@ -494,8 +607,10 @@ function commitArrivalTimeInput() {
@update:model-value="depDateMenu = false" @update:model-value="depDateMenu = false"
/> />
</v-menu> </v-menu>
</template> </div>
</v-text-field> <div v-if="submitForm.errors.departure_date" class="native-date-field__error text-error">
{{ submitForm.errors.departure_date }}
</div>
</v-col> </v-col>
<v-col cols="12" sm="6"> <v-col cols="12" sm="6">
<v-menu v-model="depTimeMenu" :close-on-content-click="false"> <v-menu v-model="depTimeMenu" :close-on-content-click="false">
@@ -507,7 +622,6 @@ function commitArrivalTimeInput() {
clearable clearable
prepend-inner-icon="mdi-clock-outline" prepend-inner-icon="mdi-clock-outline"
:placeholder="timeFormat === '24hr' ? 'e.g. 1430' : 'e.g. 2:30 PM'" :placeholder="timeFormat === '24hr' ? 'e.g. 1430' : 'e.g. 2:30 PM'"
:disabled="!lookupComplete"
@click:clear="clearDepartureTime" @click:clear="clearDepartureTime"
@blur="commitDepartureTimeInput" @blur="commitDepartureTimeInput"
@keydown.enter="commitDepartureTimeInput" @keydown.enter="commitDepartureTimeInput"
@@ -528,23 +642,59 @@ function commitArrivalTimeInput() {
</v-col> </v-col>
</v-row> </v-row>
<!-- Arrival date + time --> <!-- Arrival -->
<v-row> <v-row>
<v-col cols="12" sm="6"> <v-col cols="12" sm="6">
<v-text-field <label class="arrival-offset__label">Arrival Date</label>
v-model="arrivalDateValue"
<template v-if="arrivalDateMode === 'offset'">
<v-btn-toggle
v-model="arrivalOffset"
mandatory
variant="outlined"
color="primary"
divided
class="arrival-offset__toggle"
>
<v-btn
v-for="opt in arrivalOffsetOptions"
:key="opt.value"
:value="opt.value"
class="arrival-offset__btn"
>
{{ opt.label }}
</v-btn>
</v-btn-toggle>
<div class="arrival-offset__footer">
<span v-if="arrivalDatePart" class="arrival-offset__hint">
Arrives {{ formatFullDate(arrivalDatePart) }}
</span>
<button
type="button"
class="arrival-offset__link"
@click="useCustomArrivalDate"
>
Custom date
</button>
</div>
</template>
<template v-else>
<div class="native-date-field">
<v-icon icon="mdi-calendar" size="20" class="native-date-field__icon" />
<input
ref="arrivalDateEl"
type="date" type="date"
label="Arrival Date" class="native-date-field__input"
prepend-inner-icon="mdi-calendar" @change="commitArrivalDateInput"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.arrival_date"
> >
<template #append-inner>
<v-menu v-model="arrDateMenu" :close-on-content-click="false"> <v-menu v-model="arrDateMenu" :close-on-content-click="false">
<template #activator="{ props: menuProps }"> <template #activator="{ props: menuProps }">
<v-icon <v-icon
v-bind="menuProps" v-bind="menuProps"
icon="mdi-calendar-month" icon="mdi-calendar-month"
size="20"
style="cursor: pointer" style="cursor: pointer"
/> />
</template> </template>
@@ -555,8 +705,19 @@ function commitArrivalTimeInput() {
@update:model-value="arrDateMenu = false" @update:model-value="arrDateMenu = false"
/> />
</v-menu> </v-menu>
</div>
<button
type="button"
class="arrival-offset__link mt-1"
@click="useQuickArrivalOffset"
>
Use quick select instead
</button>
</template> </template>
</v-text-field>
<div v-if="submitForm.errors.arrival_date" class="native-date-field__error text-error">
{{ submitForm.errors.arrival_date }}
</div>
</v-col> </v-col>
<v-col cols="12" sm="6"> <v-col cols="12" sm="6">
<v-menu v-model="arrTimeMenu" :close-on-content-click="false"> <v-menu v-model="arrTimeMenu" :close-on-content-click="false">
@@ -568,7 +729,6 @@ function commitArrivalTimeInput() {
clearable clearable
prepend-inner-icon="mdi-clock-outline" prepend-inner-icon="mdi-clock-outline"
:placeholder="timeFormat === '24hr' ? 'e.g. 1430' : 'e.g. 2:30 PM'" :placeholder="timeFormat === '24hr' ? 'e.g. 1430' : 'e.g. 2:30 PM'"
:disabled="!lookupComplete"
@click:clear="clearArrivalTime" @click:clear="clearArrivalTime"
@blur="commitArrivalTimeInput" @blur="commitArrivalTimeInput"
@keydown.enter="commitArrivalTimeInput" @keydown.enter="commitArrivalTimeInput"
@@ -597,7 +757,6 @@ function commitArrivalTimeInput() {
label="From" label="From"
:prefilled-options="fromOptionsData" :prefilled-options="fromOptionsData"
:error-messages="submitForm.errors.from_id" :error-messages="submitForm.errors.from_id"
:disabled="!lookupComplete"
/> />
</v-col> </v-col>
</v-row> </v-row>
@@ -610,7 +769,6 @@ function commitArrivalTimeInput() {
label="To" label="To"
:prefilled-options="toOptionsData" :prefilled-options="toOptionsData"
:error-messages="submitForm.errors.to_id" :error-messages="submitForm.errors.to_id"
:disabled="!lookupComplete"
/> />
</v-col> </v-col>
</v-row> </v-row>
@@ -623,7 +781,6 @@ function commitArrivalTimeInput() {
v-model="form.airline" v-model="form.airline"
:prefilled-options="airlineOptionsData" :prefilled-options="airlineOptionsData"
:error-messages="submitForm.errors.airline_id" :error-messages="submitForm.errors.airline_id"
:disabled="!lookupComplete"
/> />
</v-col> </v-col>
</v-row> </v-row>
@@ -636,7 +793,6 @@ function commitArrivalTimeInput() {
v-model="form.aircraft" v-model="form.aircraft"
:prefilled-options="aircraftOptionsData" :prefilled-options="aircraftOptionsData"
:error-messages="submitForm.errors.aircraft_id" :error-messages="submitForm.errors.aircraft_id"
:disabled="!lookupComplete"
/> />
</v-col> </v-col>
</v-row> </v-row>
@@ -648,7 +804,6 @@ function commitArrivalTimeInput() {
v-model="form.aircraft_registration" v-model="form.aircraft_registration"
label="Aircraft Registration" label="Aircraft Registration"
placeholder="e.g. VH-OQA" placeholder="e.g. VH-OQA"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.aircraft_registration" :error-messages="submitForm.errors.aircraft_registration"
/> />
</v-col> </v-col>
@@ -660,7 +815,6 @@ function commitArrivalTimeInput() {
item-title="name" item-title="name"
item-value="id" item-value="id"
:return-object="true" :return-object="true"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.flight_class_id" :error-messages="submitForm.errors.flight_class_id"
/> />
</v-col> </v-col>
@@ -673,7 +827,6 @@ function commitArrivalTimeInput() {
v-model="form.seat_number" v-model="form.seat_number"
label="Seat Number" label="Seat Number"
placeholder="e.g. 12A" placeholder="e.g. 12A"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.seat_number" :error-messages="submitForm.errors.seat_number"
/> />
</v-col> </v-col>
@@ -685,7 +838,6 @@ function commitArrivalTimeInput() {
item-title="name" item-title="name"
item-value="id" item-value="id"
:return-object="true" :return-object="true"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.seat_type_id" :error-messages="submitForm.errors.seat_type_id"
/> />
</v-col> </v-col>
@@ -701,7 +853,6 @@ function commitArrivalTimeInput() {
item-title="name" item-title="name"
item-value="id" item-value="id"
:return-object="true" :return-object="true"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.flight_reason_id" :error-messages="submitForm.errors.flight_reason_id"
/> />
</v-col> </v-col>
@@ -713,7 +864,6 @@ function commitArrivalTimeInput() {
item-title="name" item-title="name"
item-value="id" item-value="id"
:return-object="true" :return-object="true"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.crew_type_id" :error-messages="submitForm.errors.crew_type_id"
/> />
</v-col> </v-col>
@@ -726,7 +876,6 @@ function commitArrivalTimeInput() {
v-model="form.note" v-model="form.note"
label="Note" label="Note"
placeholder="Any additional notes…" placeholder="Any additional notes…"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.note" :error-messages="submitForm.errors.note"
rows="3" rows="3"
auto-grow auto-grow
@@ -740,7 +889,6 @@ function commitArrivalTimeInput() {
<v-checkbox <v-checkbox
v-model="form.auto_update" v-model="form.auto_update"
label="Automatically update flight details within 24 hours of flight departure" label="Automatically update flight details within 24 hours of flight departure"
:disabled="!lookupComplete"
hide-details hide-details
density="compact" density="compact"
/> />
@@ -754,7 +902,7 @@ function commitArrivalTimeInput() {
block block
size="large" size="large"
:loading="submitForm.processing" :loading="submitForm.processing"
:disabled="!lookupComplete || submitForm.processing" :disabled="submitForm.processing"
@click="submit" @click="submit"
> >
{{ isEdit ? 'Save Changes' : 'Add Flight' }} {{ isEdit ? 'Save Changes' : 'Add Flight' }}
@@ -768,8 +916,129 @@ function commitArrivalTimeInput() {
</template> </template>
<style scoped> <style scoped>
:deep(input[type="date"]::-webkit-calendar-picker-indicator) { /* Hand-rolled field chrome for the native <input type="date"> fields.
These deliberately bypass v-text-field (see the "Native date inputs"
comment in the script block for why), so we approximate the surrounding
Vuetify outlined-field look here instead of getting it for free. */
.native-date-field {
position: relative;
display: flex;
align-items: center;
gap: 10px;
min-height: 56px;
padding: 8px 12px;
border: 1px solid rgba(255, 255, 255, 0.24);
border-radius: 4px;
background: transparent;
transition: border-color 0.15s ease;
}
.native-date-field:focus-within {
border-color: rgb(var(--surface));
border-width: 2px;
padding: 7px 11px;
}
.native-date-field__icon {
opacity: 0.7;
flex-shrink: 0;
}
.native-date-field__label {
position: absolute;
top: -8px;
left: 38px;
padding: 0 4px;
font-size: 12px;
line-height: 1;
color: rgba(255, 255, 255, 0.6);
pointer-events: none;
}
.native-date-field__input {
flex: 1;
min-width: 0;
background: transparent;
border: none;
outline: none;
color: inherit;
font-size: 16px;
color-scheme: dark;
}
.native-date-field__input::-webkit-calendar-picker-indicator {
display: none; display: none;
-webkit-appearance: none; -webkit-appearance: none;
} }
/* Firefox doesn't support ::-webkit-calendar-picker-indicator; this trims
its built-in indicator/spinner button area instead. */
.native-date-field__input::-moz-calendar-picker-indicator {
display: none;
}
.native-date-field__error {
margin-top: 4px;
padding-left: 12px;
font-size: 12px;
}
/* Arrival day-offset control */
.arrival-offset__label {
display: block;
font-size: 12px;
line-height: 1;
color: rgba(255, 255, 255, 0.6);
margin-bottom: 6px;
padding-left: 2px;
}
.arrival-offset__toggle {
display: grid;
grid-template-columns: repeat(4, 1fr);
width: 100%;
height: 48px;
border-radius: 4px;
}
.arrival-offset__btn {
min-width: 0;
flex: none;
width: 100%;
height: 100%;
font-size: 11px;
letter-spacing: 0;
text-transform: none;
padding: 0 4px;
}
.arrival-offset__footer {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 8px;
margin-top: 6px;
padding: 0 2px;
}
.arrival-offset__hint {
font-size: 13px;
color: rgba(255, 255, 255, 0.7);
}
.arrival-offset__link {
flex-shrink: 0;
font-size: 12px;
color: rgba(255, 255, 255, 0.75);
background: none;
border: none;
padding: 0;
cursor: pointer;
text-decoration: underline;
text-underline-offset: 2px;
}
.arrival-offset__link:hover {
color: rgb(255, 255, 255);
}
</style> </style>