261 lines
9.0 KiB
Vue
261 lines
9.0 KiB
Vue
<script setup lang="ts">
|
|
import { Achievement, Flight, User } from '@/Types/types'
|
|
import Panel from '@/Components/FlightsGoneBy/Panels/Panel.vue'
|
|
import PanelHeader from '@/Components/FlightsGoneBy/Panels/PanelHeader.vue'
|
|
import PanelSubHeader from '@/Components/FlightsGoneBy/Panels/PanelSubHeader.vue'
|
|
import AlphabetTable from '@/Components/FlightsGoneBy/AlphabetTable.vue'
|
|
import { computed, ref } from 'vue'
|
|
import { useAlphabetFlights, type CodeType } from '@/Composables/useAlphabetFlights'
|
|
import InlineBadge from "@/Components/FlightsGoneBy/InlineBadge.vue";
|
|
import CopyButton from "@/Components/FlightsGoneBy/CopyButton.vue";
|
|
defineOptions({ inheritAttrs: false })
|
|
|
|
const props = defineProps<{
|
|
achievement: Achievement
|
|
user: User
|
|
isFollowing: boolean
|
|
flights: Flight[]
|
|
}>()
|
|
|
|
const codeType = ref<CodeType>('iata')
|
|
const flightsRef = computed(() => props.flights)
|
|
|
|
const visited = computed(() => useAlphabetFlights(flightsRef, codeType.value).visitedLetters.value)
|
|
const byLetter = computed(() => useAlphabetFlights(flightsRef, codeType.value).flightsByLetter.value)
|
|
const { allLetters } = useAlphabetFlights(flightsRef, codeType.value)
|
|
|
|
const visitedCount = computed(() => visited.value.size)
|
|
|
|
const availableYears = computed(() => {
|
|
const years = new Set(props.flights.map(f => new Date(f.departure_date).getFullYear()))
|
|
return [...years].sort((a, b) => b - a)
|
|
})
|
|
|
|
// Year select items: "None" option + all flight years
|
|
const yearItems = computed(() => [
|
|
{ title: 'None', value: null },
|
|
...availableYears.value.map(y => ({ title: String(y), value: y })),
|
|
])
|
|
|
|
const currentYear = new Date().getFullYear()
|
|
const selectedYear = ref<number | null>(
|
|
availableYears.value.includes(currentYear) ? currentYear : (availableYears.value[0] ?? null)
|
|
)
|
|
|
|
// Copy to clipboard
|
|
const alphabetTable = ref<InstanceType<typeof AlphabetTable> | null>(null)
|
|
const copied = ref(false)
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<!-- Challenge description -->
|
|
<Panel>
|
|
<PanelHeader centered>The Alphabet Challenge</PanelHeader>
|
|
<PanelSubHeader centered>
|
|
<p>
|
|
Originating from aviation forums, as <b>Land at the Alphabet</b>, this challenge is a tad more lenient
|
|
as it allows for both landing and taking off to count towards a letter.
|
|
</p>
|
|
<p>
|
|
In honor of this challenge's forum roots, you can copy your results of this challenge as BBCode by pressing the copy icon above the table, and select
|
|
a year to highlight airports that are new for that given year.
|
|
</p>
|
|
|
|
</PanelSubHeader>
|
|
</Panel>
|
|
|
|
<!-- Progress grid -->
|
|
<Panel style="display: flex; flex-direction: column; align-items: center; gap: 1rem;">
|
|
<PanelHeader centered>Progress</PanelHeader>
|
|
<div>
|
|
<button :class="['code-toggle', codeType === 'iata' ? 'active' : '']" @click="codeType = 'iata'">IATA</button>
|
|
<button :class="['code-toggle', codeType === 'icao' ? 'active' : '']" @click="codeType = 'icao'">ICAO</button>
|
|
</div>
|
|
<p class="progress-summary">{{ visitedCount }} / {{ allLetters.length }} letters visited</p>
|
|
|
|
<div class="alphabet-grid">
|
|
<div
|
|
v-for="letter in allLetters"
|
|
:key="letter"
|
|
:class="['letter-tile', visited.has(letter) ? 'visited' : 'unvisited']"
|
|
:title="visited.has(letter) ? 'Visited' : 'Not yet visited'"
|
|
>
|
|
{{ letter }}
|
|
</div>
|
|
</div>
|
|
</Panel>
|
|
|
|
<!-- Airports by letter -->
|
|
<Panel>
|
|
<div class="table-toolbar">
|
|
<PanelHeader>Airports By Letter</PanelHeader>
|
|
|
|
<div class="toolbar-right">
|
|
<v-select
|
|
v-model="selectedYear"
|
|
:items="yearItems"
|
|
item-title="title"
|
|
item-value="value"
|
|
label="New For"
|
|
density="compact"
|
|
variant="outlined"
|
|
hide-details
|
|
class="year-select"
|
|
/>
|
|
|
|
<CopyButton
|
|
title="Copy as BBCode"
|
|
size="large"
|
|
:text="alphabetTable?.bbCode ?? ''"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<AlphabetTable
|
|
ref="alphabetTable"
|
|
:letters="allLetters"
|
|
:flightsByLetter="byLetter"
|
|
:codeType="codeType"
|
|
:selectedYear="selectedYear"
|
|
/>
|
|
</Panel>
|
|
|
|
<!-- Requirements -->
|
|
<Panel>
|
|
<PanelHeader centered>Requirements</PanelHeader>
|
|
<p>
|
|
To complete this challenge you must take off from or land at airports whose
|
|
IATA codes begin with each of the 26 letters of the alphabet —
|
|
A through Z. Airports without an IATA code do not count.
|
|
</p>
|
|
<p>
|
|
Both the departure and arrival airport on a single flight can count toward different
|
|
letters simultaneously, so a flight from <strong>ABX</strong> to <strong>BNE</strong>
|
|
would count toward both A and B.
|
|
</p>
|
|
<p>
|
|
ICAO codes do not count towards this challenge (and it is not possible to visit an ICAO code for every letter), but you can
|
|
toggle to ICAO view to see where you're at for fun.
|
|
</p>
|
|
</Panel>
|
|
|
|
<!-- Difficulty -->
|
|
<Panel>
|
|
<PanelHeader centered>Difficulty</PanelHeader>
|
|
<p>
|
|
This is a <b>very</b> difficult challenge no matter how well travelled you are. However it one of the most fun to try and accomplish as it will get you travelling
|
|
to places you might never have otherwise considered.
|
|
</p>
|
|
<p>
|
|
Most letters are possible to visit somewhat organically if you fly enough, but some will be difficult. <b>Q</b> is probably the most difficult letter to obtain -
|
|
there's no rules against having a code starting with Q, but due to many aviation terms starting with Q (such as QNH), very few airports use it.
|
|
</p>
|
|
<p>
|
|
Based on our airport database, it is actually possible to obtain every letter except <b>V</b> without leaving China.
|
|
</p>
|
|
<p>
|
|
At time of writing, there are just 2 major airports that have daily scheduled service by major airlines, and 4 airports with any commercial service at all.
|
|
Others - such as seaplane ports - might have GA or charter service.
|
|
</p>
|
|
<ul>
|
|
<li><b>QRO</b> in Mexico - a fantastic place to visit and with flights from around Mexico and the US.</li>
|
|
<li><b>QSZ</b> in China is also a very interesting place to visit and is served well from Urumqi and Xi'an.</li>
|
|
<li><b>QBC</b> is a small airport in Canada and has daily runs on commuter aircraft from Vancouver.</li>
|
|
<li><b>QSR</b> is probably most promising recently - it did not have commercial service for 10 years and is having good growth now with European low cost carriers.</li>
|
|
</ul>
|
|
<p>
|
|
Other potential candidates do not seem so promising
|
|
</p>
|
|
<ul>
|
|
<li><b>QAH</b> was in India as a secondary Delhi airport, but the code was changed to <b>HDO</b> </li>
|
|
<li><b>QAJ</b> was proposed for Ajman Airport which might have acted as an alternate to Dubai and Sharjah, but construction has not moved forward.</li>
|
|
</ul>
|
|
<p>
|
|
Every other letter usually has a decent selection of options across most continents.
|
|
</p>
|
|
</Panel>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.progress-summary {
|
|
font-size: 1.2rem;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
}
|
|
|
|
.code-toggle {
|
|
padding: 0.15rem 0.5rem;
|
|
border: 1px solid var(--border);
|
|
border-radius: 4px;
|
|
background: transparent;
|
|
color: var(--muted);
|
|
cursor: pointer;
|
|
font-size: inherit;
|
|
transition: color 0.15s, background 0.15s, border-color 0.15s;
|
|
}
|
|
|
|
.code-toggle.active {
|
|
background: var(--accent-glow);
|
|
color: var(--accent);
|
|
border-color: var(--accent);
|
|
}
|
|
|
|
.alphabet-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(13, 1fr);
|
|
gap: 0.4rem;
|
|
width: 100%;
|
|
max-width: 520px;
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.alphabet-grid {
|
|
grid-template-columns: repeat(7, 1fr);
|
|
}
|
|
}
|
|
|
|
.letter-tile {
|
|
aspect-ratio: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 700;
|
|
font-size: 1rem;
|
|
border-radius: 6px;
|
|
user-select: none;
|
|
}
|
|
|
|
.letter-tile.visited {
|
|
background: var(--accent-glow);
|
|
color: var(--accent);
|
|
border: 1px solid var(--accent-soft);
|
|
}
|
|
|
|
.letter-tile.unvisited {
|
|
background: var(--surface-alt);
|
|
color: var(--muted);
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
/* Table toolbar */
|
|
.table-toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 1rem;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.toolbar-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.year-select {
|
|
max-width: 140px;
|
|
}
|
|
|
|
</style>
|