Added Crew and General Aviation Filters

This commit is contained in:
2026-04-20 22:30:34 +10:00
parent e007824fa9
commit a57775e141
26 changed files with 559 additions and 98 deletions
@@ -22,7 +22,7 @@ const chartOptions = computed(() => ({
},
theme: { mode: 'dark' },
labels: props.labels,
colors: ['#4da6ff', '#ffc107', '#a150d5', '#22c55e', '#f97316', '#e11d48', '#06b6d4'],
colors: ['#4da6ff', '#ffc107', '#a150d5', '#22c55e', '#f97316', '#e11d48', '#06b6d4', 'pink'],
dataLabels: {
enabled: true,
formatter: (val: number) => `${Math.round(val)}%`,
@@ -1,12 +1,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import PlaneLoader from "@/Components/FlightsGoneBy/PlaneLoader.vue"
import VueApexCharts from "vue3-apexcharts"
interface TooltipItem {
[key: string]: unknown
}
const props = defineProps<{
title: string
series: { name: string; data: number[] }[]
@@ -17,18 +12,36 @@ const props = defineProps<{
barHeight?: number
colors?: string[]
options?: object
events?: object
}>()
const BAR_HEIGHT = computed(() => props.barHeight ?? 32)
const BAR_HEIGHT = computed(() => props.barHeight ?? 32)
const MAX_VISIBLE = computed(() => props.maxVisible ?? 12)
const chartHeight = computed(() => props.categories.length * BAR_HEIGHT.value + 40)
const chartHeight = computed(() => props.categories.length * BAR_HEIGHT.value + 40)
const scrollHeight = computed(() => {
const visible = Math.min(props.categories.length, MAX_VISIBLE.value)
return `${visible * BAR_HEIGHT.value + 40}px`
})
// ── Tooltip state (exposed to parent via scoped slot) ─────────────────────────
const tooltipVisible = ref(false)
const tooltipX = ref(0)
const tooltipY = ref(0)
const hoveredIndex = ref<number | null>(null)
function onMouseMove(e: MouseEvent) {
tooltipX.value = e.clientX + 14
tooltipY.value = e.clientY + 14
}
function onMouseLeave() {
tooltipVisible.value = false
hoveredIndex.value = null
}
// ── Chart options ─────────────────────────────────────────────────────────────
const chartOptions = computed(() => ({
chart: {
type: 'bar',
@@ -38,6 +51,16 @@ const chartOptions = computed(() => ({
animations: { enabled: false },
stacked: true,
animation: { enabled: false },
events: {
dataPointMouseEnter: (_e: unknown, _ctx: unknown, config: { dataPointIndex: number }) => {
tooltipVisible.value = true
hoveredIndex.value = config.dataPointIndex
},
dataPointMouseLeave: () => {
tooltipVisible.value = false
hoveredIndex.value = null
},
},
},
theme: { mode: 'dark' },
plotOptions: {
@@ -68,12 +91,11 @@ const chartOptions = computed(() => ({
markers: { width: 8, height: 8, radius: 2 },
itemMargin: { horizontal: 8 },
},
tooltip: { theme: 'dark', shared: true, intersect: false },
tooltip: { enabled: false },
states: {
hover: { filter: { type: 'lighten', value: 0.1 } },
},
}))
</script>
<template>
@@ -81,7 +103,12 @@ const chartOptions = computed(() => ({
<div class="chart-title">{{ title }}</div>
<div v-if="categories.length" class="chart-outer">
<div class="chart-scroll" :style="{ height: scrollHeight }">
<div
class="chart-scroll"
:style="{ height: scrollHeight }"
@mousemove="onMouseMove"
@mouseleave="onMouseLeave"
>
<VueApexCharts
type="bar"
:height="chartHeight"
@@ -90,8 +117,13 @@ const chartOptions = computed(() => ({
/>
</div>
<!-- Optional slot for custom tooltip -->
<slot name="tooltip" />
<slot
name="tooltip"
:visible="tooltipVisible"
:x="tooltipX"
:y="tooltipY"
:index="hoveredIndex"
/>
<div v-if="footerValue !== undefined" class="chart-footer">
<span class="total-count">{{ footerValue }}</span>
@@ -166,8 +198,4 @@ const chartOptions = computed(() => ({
:deep(svg) {
outline: none;
}
.chart-hidden {
visibility: hidden;
}
</style>
@@ -42,19 +42,19 @@ const chartEvents = computed(() => ({
@mousemove="onMouseMove"
@mouseleave="onMouseLeave"
>
<template #tooltip>
<ChartTooltip :visible="!!tooltipItem" :x="tooltipX" :y="tooltipY">
<template #tooltip="{ visible, x, y, index }">
<ChartTooltip :visible="visible" :x="x" :y="y">
<div class="ct-name">
<span :class="`fi fi-${tooltipItem?.code.toLowerCase()}`" />
{{ tooltipItem?.name }}
<span v-if="index !== null" :class="`fi fi-${countries[index].code.toLowerCase()}`" />
{{ index !== null ? countries[index].name : '' }}
</div>
<div class="ct-row">
<span class="ct-label">Flights</span>
<span class="ct-val">{{ tooltipItem?.past }}</span>
<span class="ct-val">{{ index !== null ? countries[index].past : '' }}</span>
</div>
<div class="ct-row" v-if="tooltipItem?.upcoming">
<div v-if="index !== null && countries[index].upcoming" class="ct-row">
<span class="ct-label">Upcoming</span>
<span class="ct-val">{{ tooltipItem?.upcoming }}</span>
<span class="ct-val">{{ countries[index].upcoming }}</span>
</div>
</ChartTooltip>
</template>
@@ -43,19 +43,19 @@ const chartEvents = computed(() => ({
@mousemove="onMouseMove"
@mouseleave="onMouseLeave"
>
<template #tooltip>
<ChartTooltip :visible="!!tooltipItem" :x="tooltipX" :y="tooltipY">
<template #tooltip="{ visible, x, y, index }">
<ChartTooltip :visible="visible" :x="x" :y="y">
<div class="ct-name">
<img :src="`${page.logo_api_url}/airlines/logos/tail/id/${tooltipItem?.id}`" width="24" height="24"/>
{{ tooltipItem?.name }}
<img :src="`${page.logo_api_url}/airlines/logos/tail/id/${index !== null ? airlines[index].id : ''}`" width="24" height="24"/>
{{ index !== null ? airlines[index].name : '' }}
</div>
<div class="ct-row">
<span class="ct-label">Flights</span>
<span class="ct-val">{{ tooltipItem?.past }}</span>
<span class="ct-val">{{ index !== null ? airlines[index].past : '' }}</span>
</div>
<div class="ct-row" v-if="tooltipItem?.upcoming">
<div v-if="index !== null && airlines[index].upcoming" class="ct-row">
<span class="ct-label">Upcoming</span>
<span class="ct-val">{{ tooltipItem?.upcoming }}</span>
<span class="ct-val">{{ airlines[index].upcoming }}</span>
</div>
</ChartTooltip>
</template>
@@ -4,6 +4,7 @@ import { FlightStats } from "@/Composables/useFlightStats"
import { useChartTooltip } from '@/Composables/useChartTooltip'
import ScrollingHorizontalBarChart from "@/Components/FlightsGoneBy/Charts/ChartTypes/ScrollingHorizontalBarChart.vue"
import ChartTooltip from "@/Components/FlightsGoneBy/Charts/ChartTooltip.vue"
import InlineBadge from "@/Components/FlightsGoneBy/InlineBadge.vue";
interface AirportItem {
label: string
@@ -42,21 +43,21 @@ const chartEvents = computed(() => ({
@mousemove="onMouseMove"
@mouseleave="onMouseLeave"
>
<template #tooltip>
<ChartTooltip :visible="!!tooltipItem" :x="tooltipX" :y="tooltipY">
<div class="ct-name">{{ tooltipItem?.fullName }}</div>
<div class="ct-sub">{{ tooltipItem?.label }}</div>
<template #tooltip="{ visible, x, y, index }">
<ChartTooltip :visible="visible" :x="x" :y="y">
<div class="ct-name">{{ index !== null ? airports[index].fullName : '' }}</div>
<div class="ct-sub"><InlineBadge variant="generic">{{ index !== null ? airports[index].label : '' }}</InlineBadge></div>
<div class="ct-row">
<span class="ct-label">Departures</span>
<span class="ct-val">{{ tooltipItem?.departures }}</span>
<span class="ct-val">{{ index !== null ? airports[index].departures : '' }}</span>
</div>
<div class="ct-row">
<span class="ct-label">Arrivals</span>
<span class="ct-val">{{ tooltipItem?.arrivals }}</span>
<span class="ct-val">{{ index !== null ? airports[index].arrivals : '' }}</span>
</div>
<div v-if="tooltipItem?.upcoming" class="ct-row">
<div v-if="index !== null && airports[index].upcoming" class="ct-row">
<span class="ct-label">Upcoming</span>
<span class="ct-val">{{ tooltipItem?.upcoming }}</span>
<span class="ct-val">{{ airports[index].upcoming }}</span>
</div>
</ChartTooltip>
</template>
@@ -1,11 +1,49 @@
<script setup lang="ts">
import { computed } from 'vue'
import { FlightStats } from "@/Composables/useFlightStats"
import ScrollingHorizontalBarChart from "@/Components/FlightsGoneBy/Charts/ChartTypes/ScrollingHorizontalBarChart.vue"
import ChartTooltip from "@/Components/FlightsGoneBy/Charts/ChartTooltip.vue"
import { useChartTooltip } from "@/Composables/useChartTooltip"
interface RouteItem {
label: string
depLabel: string
arrLabel: string
past: number
upcoming: number
}
const props = defineProps<{
flightStats: FlightStats
}>()
const { tooltipItem, tooltipX, tooltipY, onMouseMove, onMouseLeave } = useChartTooltip<RouteItem>()
const routes = computed(() => props.flightStats.topRoutes.value.routes)
const series = computed(() => props.flightStats.topRoutes.value.series)
</script>
<template>
<ScrollingHorizontalBarChart
title="Top Routes"
:series="series"
:categories="routes.map(r => r.label)"
:footer-value="routes.length"
footer-label="total routes"
:max-visible="18"
>
<template #tooltip="{ visible, x, y, index }">
<ChartTooltip :visible="visible" :x="x" :y="y">
<div class="ct-name">{{ index !== null ? routes[index].label : '' }}</div>
<div class="ct-row">
<span class="ct-label">Flights</span>
<span class="ct-val">{{ index !== null ? routes[index].past : '' }}</span>
</div>
<div v-if="index !== null && routes[index].upcoming" class="ct-row">
<span class="ct-label">Upcoming</span>
<span class="ct-val">{{ routes[index].upcoming }}</span>
</div>
</ChartTooltip>
</template>
</ScrollingHorizontalBarChart>
</template>
<style scoped>
</style>