Added achievement data

This commit is contained in:
2026-05-10 02:28:30 +10:00
parent f2b2eaaabe
commit 217a971360
23 changed files with 479 additions and 470 deletions
@@ -2,21 +2,14 @@
import { Aircraft } from "@/Types/types";
import GlassTooltip from "@/Components/FlightsGoneBy/GlassTooltip.vue";
import InlineBadge from "@/Components/FlightsGoneBy/InlineBadge.vue";
import WakeTurbulence from "@/Components/FlightsGoneBy/WakeTurbulence.vue";
defineProps<{
aircraft: Aircraft
showTooltips?: boolean
}>()
function formatWtc(wtc: string): string {
switch (wtc.toUpperCase()) {
case 'L': return 'Light'
case 'M': return 'Medium'
case 'H': return 'Heavy'
case 'J': return 'Super'
default: return wtc
}
}
function formatEngineType(type: string): string {
return type.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())
@@ -41,7 +34,7 @@ function formatEngineType(type: string): string {
{{aircraft.designator }}
</InlineBadge>
<InlineBadge variant="generic">
{{ formatWtc(aircraft.wtc) }}
<WakeTurbulence :value="aircraft.wtc" />
</InlineBadge>
</div>
@@ -17,7 +17,7 @@ const size = computed(() => props.size ? props.size + 'px' : '30px');
</script>
<template>
<span class="alliance-logo"></span>
<span :title="alliance.name" class="alliance-logo"></span>
</template>
<style scoped>
@@ -2,7 +2,7 @@
import FlightClassBadge from "@/Components/FlightsGoneBy/FlightClassBadge.vue";
import AirlineLogo from "@/Components/FlightsGoneBy/AirlineLogo.vue";
import {Flight} 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";
@@ -21,11 +21,17 @@ const props = defineProps<{
flightId?: number | null
}>()
console.log(props.user)
function editRoute(id: number) {
return route('flights.edit', { flight: id })
}
const showDeleteDialog = ref(false)
const flightToDelete = ref<Flight | null>(null)
const showDeleteDialog = computed({
get: () => flightToDelete.value !== null,
set: (val) => { if (!val) flightToDelete.value = null }
})
const ITEMS_PER_PAGE = 25
@@ -263,7 +269,6 @@ watch(
</td>
<td class="v-data-table__td actions-cell">
<template>
<v-menu>
<template #activator="{ props: menuProps }">
<v-btn
@@ -275,8 +280,8 @@ watch(
/>
</template>
<v-list density="compact" bg-color="#1a1e2e">
<v-list-item v-if="canEdit"
prepend-icon="mdi-pencil-outline"
<v-list-item
prepend-icon="mdi-magnify"
title="View Details"
:href="`/u/${user.name}/flight/${(item as Flight).id}`"
/>
@@ -286,30 +291,32 @@ watch(
:href="editRoute((item as Flight).id)"
/>
<v-list-item v-if="canEdit"
prepend-icon="mdi-trash-can-outline"
title="Delete"
@click="showDeleteDialog = true"
prepend-icon="mdi-trash-can-outline"
title="Delete"
@click="flightToDelete = (item as Flight)"
/>
</v-list>
</v-menu>
<v-dialog v-model="showDeleteDialog" max-width="400">
<v-dialog v-if="canEdit" v-model="showDeleteDialog" max-width="400">
<v-card title="Delete Flight">
<v-card-text>Are you sure you want to delete this flight?</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn v-if="!deleting" @click="showDeleteDialog = false">Cancel</v-btn>
<v-btn v-if="!deleting" @click="flightToDelete = null">Cancel</v-btn>
<v-btn
color="error"
:loading="deleting"
@click="deleting = true; router.delete(route('flights.delete', { flight: (item as Flight).id }), { onFinish: () => deleting = false })"
@click="deleting = true; router.delete(
route('flights.delete', { flight: flightToDelete!.id }),
{ onFinish: () => { deleting = false; flightToDelete = null } }
)"
>
Delete
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
</td>
</tr>
@@ -31,7 +31,6 @@ defineProps<{
.glass-tooltip {
background: var(--surface);
border: 1px solid var(--table-border);
border-radius: 8px;
padding: 10px 14px;
min-width: 180px;
display: flex;
@@ -26,7 +26,6 @@ onUnmounted(() => document.removeEventListener('click', handleClickOutside))
<header class="glass">
<Link href="/" class="brand">FlightsGoneBy</Link>
<!-- Notification icon (always visible) -->
<button v-if="props.auth.user" class="notif-btn" aria-label="Notifications">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
@@ -47,9 +46,8 @@ onUnmounted(() => document.removeEventListener('click', handleClickOutside))
<Link :href="route('profile.view', { user: props.auth.user.name })" class="nav-link">Profile</Link>
<Link href="/feed" class="nav-link">Feed</Link>
<!-- User dropdown -->
<div class="dropdown" ref="dropdownRef">
<button class="nav-link dropdown-trigger" @click="dropdownOpen = !dropdownOpen">
<button class="nav-link dropdown-trigger" @click.stop="dropdownOpen = !dropdownOpen">
{{ props.auth.user.name }}
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@@ -66,23 +64,23 @@ onUnmounted(() => document.removeEventListener('click', handleClickOutside))
</nav>
<!-- Hamburger (mobile only) -->
<button class="hamburger" :class="{ open: menuOpen }" @click="menuOpen = !menuOpen" aria-label="Toggle menu">
<button class="hamburger" :class="{ open: menuOpen }" @click.stop="menuOpen = !menuOpen" aria-label="Toggle menu">
<span /><span /><span />
</button>
<!-- Mobile drawer -->
<Transition name="slide">
<nav v-if="menuOpen" class="nav-mobile" @click="menuOpen = false">
<nav v-if="menuOpen" class="nav-mobile" @click.stop>
<template v-if="!props.auth.user">
<Link :href="route('login')" class="nav-link">Log In</Link>
<Link :href="route('register')" class="nav-link nav-link--highlight">Register</Link>
<Link :href="route('login')" class="nav-link" @click="menuOpen = false">Log In</Link>
<Link :href="route('register')" class="nav-link nav-link--highlight" @click="menuOpen = false">Register</Link>
</template>
<template v-else>
<span class="nav-greeting">Welcome, {{ props.auth.user.name }}</span>
<Link :href="route('flights.add')" class="nav-link">Add Flight</Link>
<Link :href="route('profile.view', { username: props.auth.user.name })" class="nav-link">Profile</Link>
<Link href="/feed" class="nav-link nav-link--highlight">Feed</Link>
<Link :href="route('import.fr24')" class="nav-link">Import from FR24</Link>
<Link :href="route('flights.add')" class="nav-link" @click="menuOpen = false">Add Flight</Link>
<Link :href="route('profile.view', { user: props.auth.user.name })" class="nav-link" @click="menuOpen = false">Profile</Link>
<Link href="/feed" class="nav-link nav-link--highlight" @click="menuOpen = false">Feed</Link>
<Link :href="route('import.fr24')" class="nav-link" @click="menuOpen = false">Import from FR24</Link>
<div class="dropdown-divider" />
<button class="nav-link nav-link--danger" @click="logout">Log Out</button>
</template>
@@ -0,0 +1,36 @@
<script setup lang="ts">
import Panel from "@/Components/FlightsGoneBy/Panels/Panel.vue";
import DetailRows from "@/Components/FlightsGoneBy/Panels/DetailRows.vue";
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";
defineProps<{
flight: Flight
}>()
</script>
<template>
<Panel label="Aircraft">
<div class="livery" v-if="flight.livery_url" :style="{ backgroundImage: `url('${flight.livery_url}')` }">
</div>
<PanelHeader>{{ flight.aircraft?.display_name_short }}</PanelHeader>
<PanelSubHeader v-if="flight.aircraft?.manufacturer_code">{{ flight.aircraft.manufacturer_code }}</PanelSubHeader>
<DetailRows>
<DetailRow v-if="flight.aircraft?.designator" label="Designator" :value="flight.aircraft.designator" variant="Badge" />
<DetailRow v-if="flight.aircraft_registration" label="Registration" :value="flight.aircraft_registration" />
<DetailRow class="detail-row" v-if="flight.aircraft?.engine_type" label="Engine Type" :value="flight.aircraft?.engine_type" />
<DetailRow class="detail-row" v-if="flight.aircraft?.engine_count" label="No. of Engines" :value="flight.aircraft?.engine_count.toString()" />
<DetailRow class="detail-row" v-if="flight.aircraft?.wtc" label="Wake Turbulence Category" variant="WTC" :value="flight.aircraft?.wtc" />
</DetailRows>
</Panel>
</template>
<style scoped>
.livery{
width: 100%;
aspect-ratio: 16/9;
background-size: cover;
}
</style>
@@ -0,0 +1,65 @@
<script setup lang="ts">
import AllianceLogo from "@/Components/FlightsGoneBy/AllianceLogo.vue";
import AirlineLogo from "@/Components/FlightsGoneBy/AirlineLogo.vue";
import Panel from "@/Components/FlightsGoneBy/Panels/Panel.vue";
import {Airline} from "@/Types/types";
import DetailRows from "@/Components/FlightsGoneBy/Panels/DetailRows.vue";
import DetailRow from "@/Components/FlightsGoneBy/Panels/DetailRow.vue";
import InlineBadge from "@/Components/FlightsGoneBy/InlineBadge.vue";
import PanelHeader from "@/Components/FlightsGoneBy/Panels/PanelHeader.vue";
import PanelSubHeader from "@/Components/FlightsGoneBy/Panels/PanelSubHeader.vue";
defineProps<{
airline: Airline
}>()
</script>
<template>
<Panel label="Airline">
<div class="airline-header">
<AirlineLogo :airline="airline" class="airline-logo" />
<div>
<PanelHeader>{{ airline.name }}</PanelHeader>
<PanelSubHeader v-if="airline.country?.name"><span class="fi" :class="`fi-${airline.country.code.toLowerCase()}`"></span> {{ airline.country.name }}</PanelSubHeader>
</div>
</div>
<DetailRows>
<DetailRow v-if="airline.alliance?.name" label="Alliance" :value="airline.alliance.name" variant="Alliance" :alliance="airline.alliance" />
<DetailRow label="IATA" v-if="airline.IATA_code" :value="airline.IATA_code" variant="Badge" />
<DetailRow label="ICAO" v-if="airline.IATA_code" :value="airline.ICAO_code" variant="Badge" />
</DetailRows>
</Panel>
</template>
<style scoped>
.airline-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.25rem;
}
.airline-logo-placeholder {
width: 42px;
height: 42px;
border-radius: 8px;
background: var(--accent-glow);
border: 1px solid rgba(56, 189, 248, 0.2);
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-family: 'Share Tech Mono', monospace;
color: var(--accent);
flex-shrink: 0;
}
.airline-name {
font-size: 1rem;
font-weight: 600;
color: var(--text);
}
</style>
@@ -0,0 +1,37 @@
<script setup lang="ts">
import Panel from "@/Components/FlightsGoneBy/Panels/Panel.vue";
import { Airport } from "@/Types/types";
import InlineBadge from "@/Components/FlightsGoneBy/InlineBadge.vue";
import DetailRows from "@/Components/FlightsGoneBy/Panels/DetailRows.vue";
import DetailRow from "@/Components/FlightsGoneBy/Panels/DetailRow.vue";
import PanelSubHeader from "@/Components/FlightsGoneBy/Panels/PanelSubHeader.vue";
import PanelHeader from "@/Components/FlightsGoneBy/Panels/PanelHeader.vue";
defineProps<{
label?: string
airport: Airport
}>()
</script>
<template>
<Panel :label="label">
<PanelHeader class="airport-name">{{ airport.name }}</PanelHeader>
<PanelSubHeader>
{{ airport.municipality }},
{{ airport.region?.country?.name }} <span :class="`fi fi-${airport.region?.country.code.toLowerCase()}`"></span>
</PanelSubHeader>
<DetailRows>
<DetailRow v-if="airport.iata_code" label="IATA" variant="Badge" :value="airport.iata_code" />
<DetailRow v-if="airport.icao_code" label="ICAO" variant="Badge" :value="airport.icao_code" />
<DetailRow label="City Served" :value="airport.municipality" />
<DetailRow label="Region" v-if="airport.region?.name" :value="airport.region.name" />
<DetailRow label="Country" v-if="airport.region?.country" variant="Country" :country="airport.region.country" :value="airport.region.country.name" />
</DetailRows>
</Panel>
</template>
<style scoped>
</style>
@@ -0,0 +1,48 @@
<script setup lang="ts">
import {Alliance, Country} from "@/Types/types";
import InlineBadge from "@/Components/FlightsGoneBy/InlineBadge.vue";
import AllianceLogo from "@/Components/FlightsGoneBy/AllianceLogo.vue";
import WakeTurbulence from "@/Components/FlightsGoneBy/WakeTurbulence.vue";
defineProps<{
label: string
value: string | null
variant?: "Country" | "Badge" | "Alliance" | "WTC"
country?: Country
alliance?: Alliance
}>()
</script>
<template>
<div class="detail-row">
<span class="detail-key">{{label}}</span>
<span class="detail-val" v-if="!variant">{{value ?? ''}}</span>
<span class="detail-val" v-if="variant == 'Badge'"><InlineBadge variant="generic">{{value ?? ''}}</InlineBadge></span>
<span class="detail-val" v-if="variant == 'Country' && country">{{country.name}} <span :class="`fi fi-${country.code.toLowerCase()}`"></span></span>
<span class="detail-val" v-if="variant == 'Alliance' && alliance">{{alliance.name}} <AllianceLogo :alliance="alliance"/> </span>
<span class="detail-val" v-if="variant == 'WTC'"><WakeTurbulence :value="value" /> </span>
</div>
</template>
<style scoped>
.detail-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.8rem;
}
.detail-key {
color: var(--muted);
}
.detail-val {
color: var(--text);
text-align: right;
display:inline-flex;
align-items: center;
justify-content: center;
gap: 0.5em;
}
</style>
@@ -0,0 +1,20 @@
<script setup lang="ts">
</script>
<template>
<div class="detail-rows">
<slot />
</div>
</template>
<style scoped>
.detail-rows {
display: flex;
flex-direction: column;
gap: 0.35rem;
margin-top: 0.5rem;
border-top: 1px solid var(--border);
padding-top: 0.75rem;
}
</style>
@@ -0,0 +1,35 @@
<script setup lang="ts">
defineProps<{
label?: string
}>()
</script>
<template>
<div class="panel glass glass-border">
<div v-if="label" class="panel-label">{{label}}</div>
<slot />
</div>
</template>
<style scoped>
/* Panels */
.panel {
padding: 1.25rem 1.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.panel-label {
font-size: 0.65rem;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 0.25rem;
opacity: 0.8;
}
</style>
@@ -0,0 +1,17 @@
<script setup lang="ts">
</script>
<template>
<div class="panel-header"><slot /></div>
</template>
<style scoped>
.panel-header {
font-size: 1rem;
font-weight: 600;
color: var(--text);
line-height: 1.3;
}
</style>
@@ -0,0 +1,15 @@
<script setup lang="ts">
</script>
<template>
<div class="panel-sub-header"><slot/></div>
</template>
<style scoped>
.panel-sub-header {
font-size: 0.8rem;
color: var(--muted);
margin-bottom: 0.5rem;
}
</style>
@@ -0,0 +1,26 @@
<script setup lang="ts">
import {Flight} from "@/Types/types";
import FlightMap from "@/Components/FlightsGoneBy/FlightMap.vue";
import Panel from "@/Components/FlightsGoneBy/Panels/Panel.vue";
import DetailRows from "@/Components/FlightsGoneBy/Panels/DetailRows.vue";
import DetailRow from "@/Components/FlightsGoneBy/Panels/DetailRow.vue";
defineProps<{
flight: Flight
}>()
</script>
<template>
<Panel label="Route">
<div class="map-placeholder">
<FlightMap :flights="[flight]" />
</div>
<DetailRows>
<DetailRow label="Distance" :value="flight.distance + 'km'" />
</DetailRows>
</Panel>
</template>
<style scoped>
</style>
@@ -0,0 +1,22 @@
<script setup lang="ts">
defineProps<{
value: string | null
}>()
function formatWtc(wtc: string): string {
switch (wtc.toUpperCase()) {
case 'L': return 'Light'
case 'M': return 'Medium'
case 'H': return 'Heavy'
case 'J': return 'Super'
default: return wtc
}
}
</script>
<template>
{{formatWtc(value ?? '')}}
</template>
<style scoped>
</style>