Added Notifications

This commit is contained in:
2026-05-26 23:02:37 +10:00
parent 57b015eb18
commit 05c16147ee
19 changed files with 455 additions and 363 deletions
+1 -1
View File
@@ -239,7 +239,7 @@ body {
color: #df8fdf;
}
.difficulty-near-impossible-global {
.difficulty-near_impossible-global {
background: rgba(160, 20, 20, 0.25);
border: 1px solid rgba(220, 60, 60, 0.3);
color: #e87070;
@@ -42,18 +42,6 @@ const unlocked = computed(() => {
return true
})
const difficultyVariant = computed(() => {
switch (props.achievement.difficulty?.internal_name) {
case 'easy': return 'easy'
case 'moderate': return 'moderate'
case 'hard': return 'hard'
case 'expensive': return 'expensive'
case 'near_impossible': return 'near-impossible'
case 'impossible': return 'impossible'
default: return 'economy'
}
})
</script>
<template>
@@ -79,7 +67,7 @@ const difficultyVariant = computed(() => {
<GlassTooltip>
<template #activator="{ props: tooltipProps }">
<span v-bind="tooltipProps" style="display:inline-flex">
<InlineBadge type="difficulty" :variant="difficultyVariant">
<InlineBadge type="difficulty" :variant="props.achievement.difficulty.internal_name">
{{ achievement.difficulty?.name }}&nbsp;
<v-icon icon="mdi-help-circle-outline" size="12" />
</InlineBadge>
@@ -87,7 +75,7 @@ const difficultyVariant = computed(() => {
</template>
<div class="difficulty-tooltip">
<InlineBadge type="difficulty" :variant="difficultyVariant">
<InlineBadge type="difficulty" :variant="props.achievement.difficulty.internal_name">
{{ achievement.difficulty?.name }}
</InlineBadge>
<p class="difficulty-description">{{ achievement.difficulty?.description }}</p>
@@ -1,11 +1,13 @@
<script setup lang="ts">
import { Aircraft } from "@/Types/types";
import {Aircraft, Airline, Flight} from "@/Types/types";
import GlassTooltip from "@/Components/FlightsGoneBy/GlassTooltip.vue";
import InlineBadge from "@/Components/FlightsGoneBy/InlineBadge.vue";
import WakeTurbulence from "@/Components/FlightsGoneBy/WakeTurbulence.vue";
import LiveryImage from "@/Components/FlightsGoneBy/LiveryImage.vue";
defineProps<{
aircraft: Aircraft
flight?: Flight
showTooltips?: boolean
}>()
@@ -46,6 +48,11 @@ function formatEngineType(type: string): string {
<span class="tooltip-value">{{ aircraft.engine_count }}x {{ formatEngineType(aircraft.engine_type) }}</span>
</div>
</div>
<div class="tooltip-divider" v-if="flight?.livery_url" />
<div class="tooltip-rows" v-if="flight?.livery_url">
<LiveryImage :flight="flight" />
</div>
</GlassTooltip>
</template>
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed } from "vue";
import {Flight, User} from "@/Types/types";
import BoardingPass from "@/Components/FlightsGoneBy/BoardingPass.vue";
import BoardingPass from "@/Components/FlightsGoneBy/BoardingPasses/BoardingPass.vue";
import { FlightStats } from "@/Composables/useFlightStats";
const props = defineProps<{
@@ -1,11 +1,12 @@
<script setup lang="ts">
import {Flight, SharedProps, User} from "@/Types/types";
import AirlineLogo from "@/Components/FlightsGoneBy/AirlineLogo.vue";
import AirportToolTip from "@/Components/FlightsGoneBy/AirportToolTip.vue";
import AircraftToolTip from "@/Components/FlightsGoneBy/AircraftToolTip.vue";
import Distance from "@/Components/Distance.vue";
import UserFlightContextMenu from "@/Components/FlightsGoneBy/UserFlightContextMenu.vue";
import {usePage} from "@inertiajs/vue3";
import Mono from "@/Components/FlightsGoneBy/Mono.vue";
import BoardingPassEndpoint from "@/Components/FlightsGoneBy/BoardingPasses/BoardingPassEndpoint.vue";
import BoardingPassStat from "@/Components/FlightsGoneBy/BoardingPasses/BoardingPassStat.vue";
defineProps<{
flight: Flight
@@ -21,64 +22,38 @@ const page = usePage<SharedProps>().props
<template>
<div class="boarding-pass">
<div class="pass-header" :class="`class-${flight.flight_class?.internal_name}-global`">
<span v-if="flight.flight_class?.name !== 'Unspecified'" class="pass-header-class">
<Mono v-if="flight.flight_class?.name !== 'Unspecified'" class="pass-header-class" uppercase smallest largeSpacing>
{{ flight.flight_class?.name }}
</span>
</Mono>
<span v-else></span>
<UserFlightContextMenu v-if="user" :profile-user="user" :flight="flight" :can-edit="canEdit ?? false" referrer="boarding-passes"/>
</div>
<div class="pass-body">
<div class="pass-route">
<div class="pass-endpoint">
<AirportToolTip :airport="flight.departure_airport">
<div class="pass-iata">{{ flight.departure_airport.display_code}}</div>
</AirportToolTip>
<div class="pass-endpoint-city">{{ flight.departure_airport.municipality }}</div>
<div class="pass-endpoint-date">{{ flight.departure_date_display }}</div>
<div class="pass-endpoint-time">{{ flight.departure_time_display }}</div>
</div>
<BoardingPassEndpoint :airport="flight.departure_airport" :date="flight.departure_date_display" :time="flight.departure_time_display" />
<div class="pass-centre">
<AirlineLogo :airline="flight.airline" size="44" class="pass-logo" />
<div class="pass-flight-number">{{ flight.flight_number }}</div>
<Mono class="pass-flight-number">{{ flight.flight_number }}</Mono>
<div class="pass-airline-name">{{ flight.airline?.name }}</div>
<AircraftToolTip v-if="flight.aircraft?.designator" :aircraft="flight.aircraft">
<div v-if="flight.aircraft?.designator" class="pass-aircraft">{{ flight.aircraft.manufacturer_code}} {{flight.aircraft.model_full_name}}</div>
</AircraftToolTip>
</div>
<div class="pass-endpoint pass-endpoint--right">
<AirportToolTip :airport="flight.arrival_airport">
<div class="pass-iata">{{ flight.arrival_airport.display_code}}</div>
</AirportToolTip>
<div class="pass-endpoint-city">{{ flight.arrival_airport.municipality }}</div>
<div class="pass-endpoint-date">{{ flight.arrival_date_display ?? flight.departure_date_display }}</div>
<div class="pass-endpoint-time">
{{ flight.arrival_time_display }}
<span v-if="flight.arrival_day_difference" class="pass-daydiff">+{{ flight.arrival_day_difference }}</span>
</div>
</div>
<BoardingPassEndpoint :dayDifference="flight.arrival_day_difference" right :airport="flight.arrival_airport" :date="flight.arrival_date_display" :time="flight.arrival_time_display" />
</div>
<div v-if="flight.duration_display || flight.distance || flight.seat_number" class="pass-tear">
<div class="pass-tear-notch pass-tear-notch--left" />
<div class="pass-tear-notch pass-tear-notch--right" />
<div v-if="flight.duration_display || flight.distance " class="pass-stats-row">
<span v-if="flight.seat_number" class="pass-stat">
<span class="pass-stat-label">SEAT</span>
<span class="pass-stat-value">{{ flight.seat_number }}</span>
</span>
<BoardingPassStat v-if="flight.seat_number" label="Seat" :value="flight.seat_number" />
<span class="pass-stat-divider" v-if="flight.duration_display && flight.distance">·</span>
<span v-if="flight.duration_display" class="pass-stat">
<span class="pass-stat-label">DURATION</span>
<span class="pass-stat-value">{{ flight.duration_display }}</span>
</span>
<BoardingPassStat label="Duration" :value="flight.duration_display" />
<span class="pass-stat-divider" v-if="flight.duration_display && flight.distance">·</span>
<span v-if="flight.distance" class="pass-stat">
<span class="pass-stat-label">DISTANCE</span>
<span class="pass-stat-value"><Distance :unit="page.auth?.user?.distance_unit" :value="Math.round(flight.distance)" /></span>
</span>
<BoardingPassStat label="Distance" :value="flight.distance" isDistance />
</div>
</div>
</div>
@@ -126,28 +101,6 @@ const page = usePage<SharedProps>().props
.pass-tear-notch--left { left: -1.6rem; } /* bleeds into the padding */
.pass-tear-notch--right { right: -1.6rem; }
.pass-stat {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.15rem;
}
.pass-stat-label {
font-family: 'Share Tech Mono', monospace;
font-size: 0.55rem;
letter-spacing: 0.18em;
color: #445566;
text-transform: uppercase;
}
.pass-stat-value {
font-family: 'Share Tech Mono', monospace;
font-size: 0.82rem;
color: #9aa;
letter-spacing: 0.06em;
}
.pass-stat-divider {
color: #334;
font-size: 1rem;
@@ -172,12 +125,7 @@ const page = usePage<SharedProps>().props
border-bottom: 1px solid rgba(255,255,255,0.06);
}
.pass-header-class {
font-family: 'Share Tech Mono', monospace;
font-size: 0.62rem;
letter-spacing: 0.18em;
text-transform: uppercase;
}
.pass-body {
padding: 1.25rem 1.25rem 1rem;
@@ -190,47 +138,6 @@ const page = usePage<SharedProps>().props
gap: 0.5rem;
}
.pass-endpoint {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.pass-endpoint--right {
text-align: right;
align-items: flex-end;
}
.pass-iata {
font-family: 'Barlow Condensed', sans-serif;
font-size: 3rem;
font-weight: 700;
color: #f0f2f5;
letter-spacing: 0.02em;
line-height: 1;
}
.pass-endpoint-city {
font-family: 'Barlow', sans-serif;
font-size: 0.8rem;
color: #778899;
font-weight: 500;
white-space: nowrap;
}
.pass-endpoint-date {
font-family: 'Share Tech Mono', monospace;
font-size: 0.7rem;
color: #445566;
letter-spacing: 0.04em;
}
.pass-endpoint-time {
font-family: 'Share Tech Mono', monospace;
font-size: 1rem;
color: #c8cdd8;
letter-spacing: 0.06em;
}
.pass-centre {
display: flex;
@@ -240,20 +147,10 @@ const page = usePage<SharedProps>().props
padding: 0 0.5rem;
}
.pass-plane-icon {
font-size: 0.9rem;
color: #445566;
margin-bottom: 0.1rem;
}
.pass-logo { opacity: 0.9; }
.pass-flight-number {
font-family: 'Share Tech Mono', monospace;
font-size: 0.85rem;
font-weight: 700;
color: #e8eaf0;
letter-spacing: 0.08em;
}
.pass-airline-name {
@@ -269,11 +166,4 @@ const page = usePage<SharedProps>().props
letter-spacing: 0.06em;
white-space: nowrap
}
.pass-daydiff {
font-size: 0.65rem;
color: #ffc107;
margin-left: 0.15rem;
vertical-align: super;
}
</style>
@@ -0,0 +1,56 @@
<script setup lang="ts">
import AirportToolTip from "@/Components/FlightsGoneBy/AirportToolTip.vue";
import Mono from "@/Components/FlightsGoneBy/Mono.vue";
import {Airport, Flight} from "@/Types/types";
import DayDifference from "@/Components/FlightsGoneBy/DayDifference.vue";
defineProps<{
airport: Airport
right?: boolean
dayDifference?: number
date: string
time: string
}>()
</script>
<template>
<div class="pass-endpoint" :class="{'pass-endpoint--right': right}">
<AirportToolTip :airport="airport">
<div class="pass-iata">{{ airport.display_code}}</div>
</AirportToolTip>
<div class="pass-endpoint-city">{{ airport.municipality }}</div>
<Mono dark smaller>{{ date }}</Mono>
<Mono>{{ time }}<DayDifference v-if="right" :value="dayDifference ?? 0" /></Mono>
</div>
</template>
<style scoped>
.pass-endpoint {
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.pass-endpoint--right {
text-align: right;
align-items: flex-end;
}
.pass-iata {
font-family: 'Barlow Condensed', sans-serif;
font-size: 3rem;
font-weight: 700;
color: #f0f2f5;
letter-spacing: 0.02em;
line-height: 1;
}
.pass-endpoint-city {
font-family: 'Barlow', sans-serif;
font-size: 0.8rem;
color: #778899;
font-weight: 500;
white-space: nowrap;
}
</style>
@@ -0,0 +1,30 @@
<script setup lang="ts">
import Mono from "@/Components/FlightsGoneBy/Mono.vue";
import Distance from "@/Components/Distance.vue";
defineProps<{
label: string,
value: any
isDistance?: boolean
}>()
</script>
<template>
<span class="pass-stat">
<Mono smallest dark uppercase largeSpacing>{{label}}</Mono>
<Mono smaller muted v-if="!isDistance">{{ value?.toString() }}</Mono>
<Mono smaller muted v-if="isDistance">
<Distance :value="parseInt(value)" />
</Mono>
</span>
</template>
<style scoped>
.pass-stat {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.15rem;
}
</style>
@@ -0,0 +1,26 @@
<script setup lang="ts">
import {computed} from "vue";
import Mono from "@/Components/FlightsGoneBy/Mono.vue";
const props = defineProps<{
value: number
}>()
const signedValue = computed(() => {
return props.value >= 0 ? `+${props.value}` : `${props.value}`
})
</script>
<template>
<sup v-if="value != 0" class="day-diff">
<Mono>{{signedValue}}</Mono>
</sup>
</template>
<style scoped>
.day-diff {
font-size: 0.65rem;
color: #ffc107;
margin-left: 0.15rem;
}
</style>
@@ -1,18 +1,9 @@
<script setup lang="ts">
import FlightClassBadge from "@/Components/FlightsGoneBy/FlightClassBadge.vue";
import AirlineLogo from "@/Components/FlightsGoneBy/AirlineLogo.vue";
import {Flight, SharedProps, User} from "@/Types/types";
import {Flight, User} from "@/Types/types";
import { computed, ref, watch, nextTick } from "vue";
import type { DataTableSortItem } from 'vuetify';
import InlineBadge from "@/Components/FlightsGoneBy/InlineBadge.vue";
import AirportToolTip from "@/Components/FlightsGoneBy/AirportToolTip.vue";
import AircraftToolTip from "@/Components/FlightsGoneBy/AircraftToolTip.vue";
import {FlightStats} from "@/Composables/useFlightStats";
import CrewTooltip from "@/Components/FlightsGoneBy/CrewTooltip.vue";
import Distance from "@/Components/Distance.vue";
import UserFlightContextMenu from "@/Components/FlightsGoneBy/UserFlightContextMenu.vue";
import {usePage} from "@inertiajs/vue3";
import DepartureBoardTableRow from "@/Components/FlightsGoneBy/DepartureBoardTableRow.vue";
const props = defineProps<{
flightStats: FlightStats
@@ -21,7 +12,6 @@ const props = defineProps<{
flightId?: number | null
}>()
const ITEMS_PER_PAGE = 25
const headers = [
@@ -65,14 +55,9 @@ const customKeySort = {
},
}
const page = usePage<SharedProps>()
const sortBy = ref<DataTableSortItem[]>([])
const currentPage = ref(1)
const today = new Date()
today.setHours(0, 0, 0, 0)
const isSorting = computed(() => sortBy.value.length > 0)
const upcomingFlights = computed(() =>
@@ -117,7 +102,6 @@ watch(
async ([id]) => {
if (!id) return
// Count the flight's position among data rows (skipping group headers)
const items = tableItems.value
let dataIndex = 0
let found = false
@@ -175,92 +159,14 @@ watch(
</template>
<template v-else-if="!(item as any)._groupHeader">
<tr
class="v-data-table__tr"
:class="[
(item as any)._group && !isSorting ? `group-row--${(item as any)._group}` : '',
(item as any).id === flightId ? 'flight-row--highlighted' : '',
]"
:data-flight-id="(item as any).id"
>
<td class="v-data-table__td airline-logo-cell">
<AirlineLogo size="32" :airline="(item as any).airline" class="airline-logo-img" />
</td>
<td class="v-data-table__td flight-number-cell">
<div class="flight-cell">
<span class="flight-number">{{ (item as any).flight_number }}</span>
</div>
</td>
<td class="v-data-table__td">
<AirportToolTip :airport="(item as Flight).departure_airport">
<span class="iata">{{ (item as Flight).departure_airport.display_code }}</span><br/>
</AirportToolTip>
<span class="city-name">{{ (item as Flight).departure_airport.municipality }}</span>
</td>
<td class="v-data-table__td">
<AirportToolTip :airport="(item as Flight).arrival_airport">
<span class="iata">{{ (item as Flight).arrival_airport.display_code }}</span><br/>
</AirportToolTip>
<span class="city-name">{{ (item as Flight).arrival_airport.municipality }}</span>
</td>
<td class="v-data-table__td">
<span class="date-cell">{{ (item as Flight).departure_date_display }}</span>
</td>
<td class="v-data-table__td">
<span class="time-cell">{{ (item as Flight).departure_time_display }}</span>
</td>
<td class="v-data-table__td">
<span class="time-cell">{{ (item as Flight).arrival_time_display }}</span>
<sup v-if="(item as Flight).arrival_day_difference" class="day-diff">
+{{ (item as Flight).arrival_day_difference }}
</sup>
</td>
<td class="v-data-table__td">
<span class="mono-tag">{{ (item as Flight).duration_display ?? '—' }}</span>
</td>
<td class="v-data-table__td">
<span class="mono-tag distance-cell">
<Distance :unit="page.props.auth.user?.distance_unit" :value="Math.round((item as Flight).distance)" />
</span>
</td>
<td class="v-data-table__td">
<AircraftToolTip v-if="(item as any).aircraft" :aircraft="(item as any).aircraft">
<span class="mono-tag">{{ (item as any).aircraft?.designator }}</span>
</AircraftToolTip>
</td>
<td class="v-data-table__td ">
<span class="mono-tag">
{{(item as Flight).aircraft_registration}}
</span>
</td>
<td class="v-data-table__td ">
<span class="class-cell">
<CrewTooltip v-if="(item as Flight).flight_reason?.name == 'Crew'" :crew-type="(item as Flight).crew_type!">
<FlightClassBadge v-if="(item as Flight).flight_class?.internal_name === 'crew'" :flight="(item as Flight)" />
<InlineBadge v-else variant="crew">Crew</InlineBadge>
</CrewTooltip>
<FlightClassBadge v-if="(item as Flight).flight_reason?.name !== 'Crew' || (item as Flight).flight_class?.internal_name !== 'crew'" :flight="(item as Flight)" />
<InlineBadge v-if="(item as Flight).seat_number" variant="economy">{{ (item as Flight).seat_number }}</InlineBadge>
<InlineBadge v-if="(item as Flight).seat_type?.name && (item as Flight).seat_type?.name !== 'Unassigned'" variant="economy">{{ (item as Flight).seat_type?.name }}</InlineBadge>
</span>
</td>
<td class="v-data-table__td actions-cell">
<UserFlightContextMenu :profile-user="user" :can-edit="canEdit" :flight="(item as Flight)" />
</td>
</tr>
<DepartureBoardTableRow
:flight="(item as Flight)"
:user="user"
:can-edit="canEdit"
:highlighted="(item as any).id === flightId"
:group="(item as any)._group"
:is-sorting="isSorting"
/>
</template>
</template>
@@ -408,95 +314,6 @@ watch(
padding-left: 0.5rem !important;
}
.airline-logo-cell {
width: 50px !important;
min-width: 50px !important;
max-width: 50px !important;
padding-right: 0 !important;
}
.flight-number-cell {
padding-left: 0.5rem !important;
}
.flight-cell {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
}
.airline-logo-img {
opacity: 0.85;
flex-shrink: 0;
}
.flight-number {
font-family: 'Share Tech Mono', monospace;
font-size: 0.9rem;
color: #e8eaf0;
letter-spacing: 0.06em;
}
.iata {
display: inline-block;
font-family: 'Share Tech Mono', monospace;
font-size: 1rem;
font-weight: 600;
color: #e8eaf0;
letter-spacing: 0.08em;
margin-right: 0.35rem;
}
.city-name {
font-size: 0.75rem;
color: #556;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.date-cell {
font-family: 'Share Tech Mono', monospace;
font-size: 0.82rem;
color: #9aa;
letter-spacing: 0.04em;
}
.time-cell {
font-family: 'Share Tech Mono', monospace;
font-size: 0.82rem;
color: #c8cdd8;
letter-spacing: 0.04em;
}
.day-diff {
font-family: 'Share Tech Mono', monospace;
font-size: 0.65rem;
color: #ffc107;
letter-spacing: 0.04em;
margin-left: 0.15rem;
}
.mono-tag {
font-family: 'Share Tech Mono', monospace;
font-size: 0.8rem;
color: #778899;
letter-spacing: 0.06em;
}
.seat-cell {
font-family: 'Share Tech Mono', monospace;
font-size: 0.85rem;
color: #c8cdd8;
}
.class-cell {
display: flex;
align-items: center;
vertical-align: middle;
gap: 0.5rem;
}
.no-data {
padding: 3rem;
text-align: center;
@@ -506,26 +323,10 @@ watch(
color: #445;
}
.actions-cell {
width: 40px;
padding: 0 0.5rem !important;
text-align: right;
}
:deep(.v-list-item-title) {
font-family: 'Share Tech Mono', monospace !important;
font-size: 0.8rem !important;
letter-spacing: 0.08em !important;
color: #c8cdd8 !important;
}
/* ── Highlighted flight row ── */
.flight-row--highlighted {
box-shadow: inset 3px 0 0 #ffc107;
}
.flight-row--highlighted td {
background: rgba(255, 193, 7, 0.08) !important;
transition: background 0.3s ease;
}
</style>
@@ -0,0 +1,214 @@
<script setup lang="ts">
import FlightClassBadge from "@/Components/FlightsGoneBy/FlightClassBadge.vue";
import AirlineLogo from "@/Components/FlightsGoneBy/AirlineLogo.vue";
import { Flight, SharedProps } from "@/Types/types";
import InlineBadge from "@/Components/FlightsGoneBy/InlineBadge.vue";
import AirportToolTip from "@/Components/FlightsGoneBy/AirportToolTip.vue";
import AircraftToolTip from "@/Components/FlightsGoneBy/AircraftToolTip.vue";
import CrewTooltip from "@/Components/FlightsGoneBy/CrewTooltip.vue";
import Distance from "@/Components/Distance.vue";
import UserFlightContextMenu from "@/Components/FlightsGoneBy/UserFlightContextMenu.vue";
import { usePage } from "@inertiajs/vue3";
import { User } from "@/Types/types";
import Mono from "@/Components/FlightsGoneBy/Mono.vue";
import DayDifference from "@/Components/FlightsGoneBy/DayDifference.vue";
const props = defineProps<{
flight: Flight
user: User
canEdit: boolean
highlighted?: boolean
group?: 'upcoming' | 'departed'
isSorting?: boolean
}>()
const page = usePage<SharedProps>()
</script>
<template>
<tr
class="v-data-table__tr"
:class="[
group && !isSorting ? `group-row--${group}` : '',
highlighted ? 'flight-row--highlighted' : '',
]"
:data-flight-id="flight.id"
>
<td class="v-data-table__td airline-logo-cell">
<AirlineLogo size="32" :airline="flight.airline" class="airline-logo-img" />
</td>
<td class="v-data-table__td flight-number-cell">
<div class="flight-cell">
<Mono class="flight-number">
{{ flight.flight_number }}
</Mono>
</div>
</td>
<td class="v-data-table__td">
<AirportToolTip :airport="flight.departure_airport">
<Mono class="iata">{{ flight.departure_airport.display_code }}</Mono><br/>
</AirportToolTip>
<span class="city-name">{{ flight.departure_airport.municipality }}</span>
</td>
<td class="v-data-table__td">
<AirportToolTip :airport="flight.arrival_airport">
<span class="iata"><Mono>{{ flight.arrival_airport.display_code }}</Mono></span><br/>
</AirportToolTip>
<span class="city-name" >{{ flight.arrival_airport.municipality }}</span>
</td>
<td class="v-data-table__td">
<span class="date-cell"><Mono muted smaller>{{ flight.departure_date_display }}</Mono></span>
</td>
<td class="v-data-table__td">
<span class="time-cell">
<Mono small>{{ flight.departure_time_display }}</Mono>
</span>
</td>
<td class="v-data-table__td arrival-time-cell">
<Mono small>{{ flight.arrival_time_display }}</Mono>
<DayDifference :value="flight.arrival_day_difference" />
</td>
<td class="v-data-table__td duration-cell">
<Mono muted small>{{ flight.duration_display ?? '—' }}</Mono>
</td>
<td class="v-data-table__td distance-cell">
<Mono muted small>
<Distance :unit="page.props.auth.user?.distance_unit" :value="Math.round(flight.distance)" />
</Mono>
</td>
<td class="v-data-table__td aircraft-cell">
<AircraftToolTip v-if="flight.aircraft" :aircraft="flight.aircraft" :flight="flight">
<Mono muted small>{{ flight.aircraft?.designator }}</Mono>
</AircraftToolTip>
</td>
<td class="v-data-table__td registration-cell">
<Mono muted smaller v-if="flight.aircraft_registration">{{ flight.aircraft_registration }}</Mono>
</td>
<td class="v-data-table__td class-badges-cell">
<span class="class-cell">
<CrewTooltip v-if="flight.flight_reason?.name == 'Crew'" :crew-type="flight.crew_type!">
<FlightClassBadge v-if="flight.flight_class?.internal_name === 'crew'" :flight="flight" />
<InlineBadge v-else variant="crew">Crew</InlineBadge>
</CrewTooltip>
<FlightClassBadge v-if="flight.flight_reason?.name !== 'Crew' || flight.flight_class?.internal_name !== 'crew'" :flight="flight" />
<InlineBadge v-if="flight.seat_number" variant="economy">{{ flight.seat_number }}</InlineBadge>
<InlineBadge v-if="flight.seat_type?.name && flight.seat_type?.name !== 'Unassigned'" variant="economy">{{ flight.seat_type?.name }}</InlineBadge>
</span>
</td>
<td class="v-data-table__td actions-cell">
<UserFlightContextMenu :profile-user="user" :can-edit="canEdit" :flight="flight" />
</td>
</tr>
</template>
<style scoped>
.airline-logo-cell {
width: 50px !important;
min-width: 50px !important;
max-width: 50px !important;
padding-right: 0 !important;
}
.city-name{
color: #556;
text-transform: uppercase;
letter-spacing: .05em;
font-size: .75rem;
}
.flight-number-cell {
padding-left: 0.5rem !important;
}
.flight-cell {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
}
.arrival-time-cell {
white-space: nowrap;
}
.class-cell {
display: flex;
align-items: center;
vertical-align: middle;
gap: 0.5rem;
overflow-x: auto;
max-width: 160px;
/* Hide scrollbar but keep functionality */
scrollbar-width: none;
&::-webkit-scrollbar { display: none; }
}
.iata {
font-weight: 600;
}
.class-cell {
display: flex;
align-items: center;
vertical-align: middle;
gap: 0.5rem;
}
.actions-cell {
width: 40px;
padding: 0 0.5rem !important;
text-align: right;
}
.flight-row--highlighted td {
background: rgba(255, 193, 7, 0.08) !important;
transition: background 0.3s ease;
}
@media (max-width: 1180px) {
:deep(.v-data-table__td) {
padding: 0 4px !important;
font-size: 0.8rem;
}
.arrival-time-cell {
white-space: nowrap;
}
.flight-number-cell,
.duration-cell,
.distance-cell,
.aircraft-cell,
.registration-cell {
white-space: nowrap;
}
.class-badges-cell {
max-width: 140px;
overflow: hidden;
display:none;
}
.class-cell {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: nowrap;
overflow-x: auto;
scrollbar-width: none;
&::-webkit-scrollbar { display: none; }
}
}
</style>
@@ -1,6 +1,6 @@
<script setup lang="ts">
import {Flight} from "@/Types/types";
import BoardingPass from "@/Components/FlightsGoneBy/BoardingPass.vue";
import BoardingPass from "@/Components/FlightsGoneBy/BoardingPasses/BoardingPass.vue";
const props = defineProps<{
flight: Flight
@@ -4,7 +4,7 @@ import AirportToolTip from "@/Components/FlightsGoneBy/AirportToolTip.vue";
import AirlineLogo from "@/Components/FlightsGoneBy/AirlineLogo.vue";
import FlightClassBadge from "@/Components/FlightsGoneBy/FlightClassBadge.vue";
import AircraftToolTip from "@/Components/FlightsGoneBy/AircraftToolTip.vue";
import BoardingPass from "@/Components/FlightsGoneBy/BoardingPass.vue";
import BoardingPass from "@/Components/FlightsGoneBy/BoardingPasses/BoardingPass.vue";
const props = defineProps<{
data: UserActionFlightCancelledData
@@ -4,7 +4,7 @@ import AirportToolTip from "@/Components/FlightsGoneBy/AirportToolTip.vue";
import AirlineLogo from "@/Components/FlightsGoneBy/AirlineLogo.vue";
import FlightClassBadge from "@/Components/FlightsGoneBy/FlightClassBadge.vue";
import AircraftToolTip from "@/Components/FlightsGoneBy/AircraftToolTip.vue";
import BoardingPass from "@/Components/FlightsGoneBy/BoardingPass.vue";
import BoardingPass from "@/Components/FlightsGoneBy/BoardingPasses/BoardingPass.vue";
const props = defineProps<{
flight: Flight
@@ -1,6 +1,6 @@
<script setup lang="ts">
import {Flight} from "@/Types/types";
import BoardingPass from "@/Components/FlightsGoneBy/BoardingPass.vue";
import BoardingPass from "@/Components/FlightsGoneBy/BoardingPasses/BoardingPass.vue";
import FlightBadge from "@/Components/FlightsGoneBy/FlightBadge.vue";
import {computed} from "vue";
@@ -0,0 +1,22 @@
<script setup lang="ts">
import {Flight} from "@/Types/types";
defineProps<{
flight: Flight
}>()
</script>
<template>
<div class="livery" v-if="flight.livery_url" :style="{ backgroundImage: `url('${flight.livery_url}')` }">
</div>
</template>
<style scoped>
.livery{
width: 100%;
aspect-ratio: 16/9;
background-size: cover;
min-width: 300px;
}
</style>
@@ -0,0 +1,62 @@
<script setup lang="ts">
withDefaults(defineProps<{
muted?: boolean
dark?: boolean
small?: boolean
smaller?: boolean,
smallest?: boolean,
uppercase?: boolean
smallSpacing?: boolean
largeSpacing?: boolean
}>(), {
size: 'medium',
muted: false,
uppercase: false
})
</script>
<template>
<span class="mono-tag" :class="{ 'muted': muted, 'dark' : dark ,'text-sm': small, 'text-smaller': smaller, 'text-smallest': smallest, 'uppercase': uppercase, 'small-spacing': smallSpacing, 'large-spacing': largeSpacing}">
<slot />
</span>
</template>
<style scoped>
.mono-tag {
font-family: 'Share Tech Mono', monospace;
letter-spacing: 0.06em;
}
.dark{
color: rgb(85, 85, 102);
letter-spacing: 0.05em;
}
.text-sm{
font-size: 0.82rem;
}
.text-smaller{
font-size: 0.72rem;
}
.text-smallest{
font-size: 0.62rem;
}
.muted{
color: rgb(119, 136, 153);;
}
.uppercase{
text-transform: uppercase;
}
.small-spacing{
letter-spacing: 0.04em;
}
.large-spacing{
letter-spacing: 0.18em;
}
</style>
@@ -5,6 +5,7 @@ import DetailRow from "@/Components/FlightsGoneBy/Panels/DetailRow.vue";
import type {Flight} from "@/Types/types";
import PanelHeader from "@/Components/FlightsGoneBy/Panels/PanelHeader.vue";
import PanelSubHeader from "@/Components/FlightsGoneBy/Panels/PanelSubHeader.vue";
import LiveryImage from "@/Components/FlightsGoneBy/LiveryImage.vue";
defineProps<{
flight: Flight
@@ -13,8 +14,7 @@ defineProps<{
<template>
<Panel label="Aircraft">
<div class="livery" v-if="flight.livery_url" :style="{ backgroundImage: `url('${flight.livery_url}')` }">
</div>
<LiveryImage :flight="flight" />
<PanelHeader>{{ flight.aircraft?.display_name_short }}</PanelHeader>
<PanelSubHeader v-if="flight.aircraft?.manufacturer_code">{{ flight.aircraft.manufacturer_code }}</PanelSubHeader>
<PanelSubHeader v-if="!flight.aircraft && !flight.aircraft_registration">No Aircraft Information Found</PanelSubHeader>
@@ -29,9 +29,5 @@ defineProps<{
</template>
<style scoped>
.livery{
width: 100%;
aspect-ratio: 16/9;
background-size: cover;
}
</style>
+1 -1
View File
@@ -3,7 +3,7 @@ import MainLayout from "@/Layouts/MainLayout.vue";
import {Head, Link} from "@inertiajs/vue3";
import ProfileLayout from "@/Components/FlightsGoneBy/ProfileLayout.vue";
import {Achievement, Flight, User, UserAchievement} from "@/Types/types";
import BoardingPass from "@/Components/FlightsGoneBy/BoardingPass.vue";
import BoardingPass from "@/Components/FlightsGoneBy/BoardingPasses/BoardingPass.vue";
import Panel from "@/Components/FlightsGoneBy/Panels/Panel.vue";
import AirportPanel from "@/Components/FlightsGoneBy/Panels/AirportPanel.vue";
import AirlinePanel from "@/Components/FlightsGoneBy/Panels/AirlinePanel.vue";
+3 -3
View File
@@ -10,7 +10,7 @@ declare module '@vue/runtime-core' {
export type ProfileView = 'map' | 'board' | 'passes' | 'achievements' ;
export type ChartType = "line" | "area" | "bar" | "pie" | "donut" | "radialBar" | "scatter" | "bubble" | "heatmap" | "candlestick" | "boxPlot" | "radar" | "polarArea" | "rangeBar" | "rangeArea" | "treemap" | undefined
export type BadgeVariant = 'first' | 'business' | 'premium' | 'economy' | 'private' | 'unspecified' | 'generic' | 'general_aviation' | 'crew' | "easy" | "moderate" | "hard" | "expensive" | "near-impossible" | "impossible"
export type BadgeVariant = 'first' | 'business' | 'premium' | 'economy' | 'private' | 'unspecified' | 'generic' | 'general_aviation' | 'crew' | "easy" | "moderate" | "hard" | "expensive" | "near_impossible" | "impossible"
export interface User {
id: number
@@ -69,7 +69,7 @@ export type SharedProps = import('@inertiajs/core').PageProps & {
}
export interface AchievementDifficulty {
id: number
internal_name: string
internal_name: "easy" | "moderate" | "hard" | "expensive" | "near_impossible" | "impossible"
name: string
description: string
created_at: string | null
@@ -98,7 +98,7 @@ export interface Achievement {
achievement_category_id: number
achievement_difficulty_id: number
category?: AchievementCategory
difficulty?: AchievementDifficulty
difficulty: AchievementDifficulty
has_page: boolean
sort_order: number
}