Added achievement data
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user