Files
FlightsAPI/resources/js/Components/FlightsGoneBy/BoardingPasses.vue
T

186 lines
5.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed } from "vue";
import { Flight } from "@/Types/types";
import BoardingPass from "@/Components/FlightsGoneBy/BoardingPass.vue";
import { FlightStats } from "@/Composables/useFlightStats";
const props = defineProps<{
flightStats: FlightStats
canEdit: boolean
}>()
const upcomingFlights = computed(() =>
[...props.flightStats.upcomingFlights.value]
.sort((a, b) => new Date(a.departure_date).getTime() - new Date(b.departure_date).getTime())
)
const departedFlights = computed(() =>
[...props.flightStats.pastFlights.value]
.sort((a, b) => new Date(b.departure_date).getTime() - new Date(a.departure_date).getTime())
)
const allFlights = computed(() => [...upcomingFlights.value, ...departedFlights.value])
function isFirstDeparted(flight: Flight, pageItems: Flight[]): boolean {
const firstDeparted = pageItems.find(f => departedFlights.value.includes(f))
return flight === firstDeparted
}
function isUpcoming(flight: Flight): boolean {
return upcomingFlights.value.includes(flight)
}
</script>
<template>
<v-data-iterator :items="allFlights" :items-per-page="25">
<template #default="{ items }">
<div class="passes-root">
<template v-if="items.some(i => isUpcoming(i.raw))">
<div class="section-header upcoming">
<span class="section-icon"></span>
<span class="section-label">UPCOMING FLIGHTS</span>
<span class="section-count">{{ upcomingFlights.length }} scheduled</span>
<div class="section-line" />
</div>
</template>
<div class="passes-grid">
<template v-for="{ raw: flight } in items" :key="flight.id">
<template v-if="isFirstDeparted(flight, items.map(i => i.raw))">
<div class="section-header departed grid-span-full">
<span class="section-icon"></span>
<span class="section-label">DEPARTED FLIGHTS</span>
<span class="section-count">{{ departedFlights.length }} logged</span>
<div class="section-line" />
</div>
</template>
<BoardingPass :flight="flight" />
</template>
</div>
<template v-if="!items.some(i => isUpcoming(i.raw)) && items[0]?.raw === departedFlights[0]">
<div class="section-header departed departed-only">
<span class="section-icon"></span>
<span class="section-label">DEPARTED FLIGHTS</span>
<span class="section-count">{{ departedFlights.length }} logged</span>
<div class="section-line" />
</div>
</template>
</div>
</template>
<template #footer="{ page, pageCount, prevPage, nextPage }">
<div class="iterator-footer">
<span class="footer-info">Page {{ page }} of {{ pageCount }}</span>
<div class="footer-nav">
<button class="nav-btn" :disabled="page === 1" @click="prevPage"></button>
<button class="nav-btn" :disabled="page === pageCount" @click="nextPage"></button>
</div>
</div>
</template>
</v-data-iterator>
</template>
<style scoped>
.passes-root {
display: flex;
flex-direction: column;
}
.departed-only {
order: -1;
}
.section-header {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 1.5rem 0 0.75rem;
}
.section-icon { font-size: 0.7rem; flex-shrink: 0; }
.section-label {
font-family: 'Share Tech Mono', monospace;
font-size: 0.72rem;
letter-spacing: 0.22em;
font-weight: 400;
flex-shrink: 0;
text-transform: uppercase;
}
.section-count {
font-family: 'Share Tech Mono', monospace;
font-size: 0.65rem;
letter-spacing: 0.1em;
color: #445566;
flex-shrink: 0;
text-transform: uppercase;
}
.section-line { flex: 1; height: 1px; min-width: 2rem; }
.section-header.upcoming .section-icon,
.section-header.upcoming .section-label { color: #ffc107; }
.section-header.upcoming .section-line { background: linear-gradient(to right, rgba(255,193,7,0.4), transparent); }
.section-header.departed .section-icon,
.section-header.departed .section-label { color: #778899; }
.section-header.departed .section-line { background: linear-gradient(to right, rgba(119,136,153,0.35), transparent); }
.passes-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: 1.25rem;
padding-bottom: 1.5rem;
}
.grid-span-full {
grid-column: 1 / -1;
padding: 0.75rem 0 0;
}
.iterator-footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 1rem;
padding: 1rem 0 2rem;
border-top: 1px solid rgba(255,255,255,0.06);
}
.footer-info {
font-family: 'Share Tech Mono', monospace;
font-size: 0.72rem;
letter-spacing: 0.08em;
color: #445566;
}
.footer-nav {
display: flex;
gap: 0.25rem;
}
.nav-btn {
width: 2rem;
height: 2rem;
background: transparent;
border: 1px solid rgba(255,255,255,0.08);
border-radius: 2px;
color: #778;
font-size: 1rem;
cursor: pointer;
transition: background 0.15s, color 0.15s, border-color 0.15s;
display: flex;
align-items: center;
justify-content: center;
}
.nav-btn:hover:not(:disabled) {
background: rgba(255,193,7,0.05);
border-color: rgba(255,193,7,0.2);
color: #ffc107;
}
.nav-btn:disabled {
opacity: 0.25;
cursor: default;
}
</style>