279 lines
8.3 KiB
Vue
279 lines
8.3 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 AirlineAlphabetTable from '@/Components/FlightsGoneBy/AirlineAlphabetTable.vue'
|
||
import { computed, ref } from 'vue'
|
||
import { useAlphabetAirlines, type CodeType } from '@/Composables/useAlphabetAirlines'
|
||
|
||
|
||
const props = defineProps<{
|
||
achievement: Achievement
|
||
user: User
|
||
isFollowing: boolean
|
||
flights: Flight[]
|
||
}>()
|
||
|
||
const codeType = ref<CodeType>('iata')
|
||
const showNumbers = ref(false)
|
||
const flightsRef = computed(() => props.flights)
|
||
|
||
const { flightsByLetter, visitedLetters, allLetters } = useAlphabetAirlines(
|
||
flightsRef,
|
||
codeType,
|
||
showNumbers,
|
||
)
|
||
|
||
// Only count letters (A–Z) towards progress, never digits
|
||
const visitedLetterCount = computed(() =>
|
||
[...visitedLetters.value].filter(k => isNaN(Number(k))).length
|
||
)
|
||
|
||
const availableYears = computed(() => {
|
||
const years = new Set(props.flights.map(f => new Date(f.departure_date).getFullYear()))
|
||
return [...years].sort((a, b) => b - a)
|
||
})
|
||
|
||
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 BBCode
|
||
const airlineTable = ref<InstanceType<typeof AirlineAlphabetTable> | null>(null)
|
||
const copied = ref(false)
|
||
|
||
async function copyBBCode() {
|
||
const text = airlineTable.value?.toBBCode()
|
||
if (text == null) return
|
||
|
||
try {
|
||
await navigator.clipboard.writeText(text)
|
||
} catch {
|
||
const el = document.createElement('textarea')
|
||
el.value = text
|
||
el.style.cssText = 'position:fixed;top:-9999px;left:-9999px;opacity:0'
|
||
document.body.appendChild(el)
|
||
el.focus()
|
||
el.select()
|
||
document.execCommand('copy')
|
||
document.body.removeChild(el)
|
||
}
|
||
|
||
copied.value = true
|
||
setTimeout(() => (copied.value = false), 2000)
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<!-- Challenge description -->
|
||
<Panel>
|
||
<PanelHeader centered>The Airline Alphabet Challenge</PanelHeader>
|
||
<PanelSubHeader centered>
|
||
<p>
|
||
A twist on the classic Alphabet Challenge — instead of airports, you need to fly with
|
||
airlines whose <b>IATA codes</b> begin with each letter of the alphabet.
|
||
</p>
|
||
<p>
|
||
You can copy your results as BBCode using the copy icon above the table, and select
|
||
a year to highlight airlines 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 class="toggle-row">
|
||
<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>
|
||
<button :class="['code-toggle', showNumbers ? 'active' : '']" @click="showNumbers = !showNumbers">
|
||
Show 0–9
|
||
</button>
|
||
</div>
|
||
|
||
<p class="progress-summary">{{ visitedLetterCount }} / {{allLetters.length}} letters visited</p>
|
||
|
||
<div class="alphabet-grid">
|
||
<div
|
||
v-for="letter in allLetters"
|
||
:key="letter"
|
||
:class="['letter-tile', visitedLetters.has(letter) ? 'visited' : 'unvisited']"
|
||
:title="visitedLetters.has(letter) ? 'Visited' : 'Not yet visited'"
|
||
>
|
||
{{ letter }}
|
||
</div>
|
||
</div>
|
||
</Panel>
|
||
|
||
<!-- Airlines by letter -->
|
||
<Panel>
|
||
<div class="table-toolbar">
|
||
<PanelHeader>Airlines 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"
|
||
/>
|
||
|
||
<v-btn
|
||
:icon="copied ? 'mdi-check' : 'mdi-content-copy'"
|
||
:color="copied ? 'success' : undefined"
|
||
density="compact"
|
||
variant="text"
|
||
title="Copy as BBCode"
|
||
@click="copyBBCode"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<AirlineAlphabetTable
|
||
ref="airlineTable"
|
||
:letters="allLetters"
|
||
:flightsByLetter="flightsByLetter"
|
||
:codeType="codeType"
|
||
:selectedYear="selectedYear"
|
||
/>
|
||
</Panel>
|
||
|
||
<!-- Requirements -->
|
||
<Panel>
|
||
<PanelHeader centered>Requirements</PanelHeader>
|
||
<p>
|
||
To complete this challenge you must fly with airlines whose IATA codes begin with each
|
||
of the 26 letters of the alphabet — A through Z. Airlines without an IATA code do not count.
|
||
</p>
|
||
<p>
|
||
A single flight contributes one letter — the operating airline's IATA code. So a flight
|
||
on <strong>QF</strong> (Qantas) counts toward <strong>Q</strong>.
|
||
</p>
|
||
<p>
|
||
ICAO codes do not count towards this challenge, but you can toggle to ICAO view to see
|
||
where you're at for fun. Similarly, numeric IATA codes (such as <strong>3U</strong>) exist
|
||
but do not count towards the challenge — enable "Show 0–9" to see them.
|
||
</p>
|
||
<p>
|
||
It would unlikely to be possible to complete the challenge with both numbers and letters required.
|
||
</p>
|
||
</Panel>
|
||
|
||
<!-- Difficulty -->
|
||
<Panel>
|
||
<PanelHeader centered>Difficulty</PanelHeader>
|
||
<p>
|
||
Like the airport version, this challenge is <b>very</b> difficult, but it is slightly easier and most letters have a fair few options.
|
||
Some difficulty.
|
||
</p>
|
||
<ul>
|
||
<li><b>X</b> — Very few airlines; worth researching regional carriers in your area.</li>
|
||
</ul>
|
||
<p>
|
||
Most other letters have a reasonable selection of options across different continents,
|
||
though some will require deliberate routing to achieve.
|
||
</p>
|
||
</Panel>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.progress-summary {
|
||
font-size: 1.2rem;
|
||
font-weight: 600;
|
||
color: var(--text);
|
||
}
|
||
|
||
.toggle-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.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 {
|
||
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>
|