Added Notifications

This commit is contained in:
2026-06-14 16:04:01 +10:00
parent e24d3ceaec
commit a753bffaf8
49 changed files with 1118 additions and 381 deletions
+54
View File
@@ -0,0 +1,54 @@
<script setup lang="ts">
import MainLayout from "@/Layouts/MainLayout.vue";
import AdminSidebar from "@/Pages/Admin/AdminSidebar.vue";
import GlassBox from "@/Components/FlightsGoneBy/GlassBox.vue";
import {Head} from "@inertiajs/vue3";
defineProps<{
title: string;
missingLiveryCount: number;
}>()
</script>
<template>
<MainLayout>
<Head :title="title" />
<div class="admin-container">
<AdminSidebar :missingLiveryCount="missingLiveryCount" />
<div class="admin-content">
<GlassBox class="admin-page" :title="title">
<slot />
</GlassBox>
</div>
</div>
</MainLayout>
</template>
<style scoped>
.admin-container {
display: flex;
flex-direction: row;
width: 100%;
min-height: 90dvh;
/* Override MainLayout's centred main — fill the full height */
align-self: stretch;
align-items: stretch;
}
.admin-content {
display: flex;
flex-direction: column;
align-items: center;;
flex: 1 1 auto;
padding: 2rem;
overflow-y: auto;
min-width: 0;
}
.admin-page {
width: 100%;
max-width: 1200px;
min-height: 50%;
}
</style>
+69
View File
@@ -0,0 +1,69 @@
<script setup lang="ts">
import {Link} from "@inertiajs/vue3";
import {number} from "echarts";
defineProps<{
missingLiveryCount: number;
}>()
</script>
<template>
<aside class="glass admin-sidebar">
<div class="sidebar-title">Admin</div>
<Link :href="route('admin.dashboard')" class="sidebar-link">
<v-icon icon="mdi-chart-line" size="18" />
Dashboard
</Link>
<Link :href="route('admin.reconcile-missing-liveries')" class="sidebar-link">
<v-icon icon="mdi-airplane-takeoff" size="18" />
Reconcile Missing Liveries
<v-chip size="x-small" color="error" class="ml-1">{{ missingLiveryCount }}</v-chip>
</Link>
</aside>
</template>
<style scoped>
.admin-sidebar {
width: 400px;
flex-shrink: 0;
padding: 1.5rem 1rem;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.25rem;
/* no height here */
}
.sidebar-title {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text);
opacity: 0.5;
padding: 0 0.5rem 0.75rem;
margin-bottom: 0.5rem;
}
.sidebar-link {
display: flex;
width: 100%;
height: 2.5rem;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
border-radius: 6px;
font-size: 0.875rem;
color: var(--text);
text-decoration: none;
transition: background 0.15s ease;
}
.sidebar-link:hover {
color: var(--accent);
background: rgba(56, 189, 248, 0.07);
}
.sidebar-link.active {
font-weight: 500;
}
</style>
+28
View File
@@ -0,0 +1,28 @@
<script setup lang="ts">
import AdminLayout from "@/Pages/Admin/AdminLayout.vue";
import GrowthCard from "@/Components/Admin/GrowthCard.vue";
defineOptions({ layout: AdminLayout });
defineProps<{
userCount: number,
oneWeekUserGrowth: number,
flightCount: number,
oneWeekFlightGrowth: number,
latestUser:string
}>()
</script>
<template>
<v-container>
<v-row>
<v-col cols="12" sm="6">
<GrowthCard label="Total Users" :count="userCount" :weekly-growth="oneWeekUserGrowth" icon="mdi-account">
Latest User: {{ latestUser }}
</GrowthCard>
</v-col>
<v-col cols="12" sm="6">
<GrowthCard label="Total Flights" :count="flightCount" :weekly-growth="oneWeekFlightGrowth" icon="mdi-airplane" />
</v-col>
</v-row>
</v-container>
</template>
@@ -0,0 +1,18 @@
<script setup lang="ts">
import GlassBox from "@/Components/FlightsGoneBy/GlassBox.vue";
import { Head } from "@inertiajs/vue3";
import {MissingLivery} from "@/Types/types";
import MissingLiveryCard from "@/Components/Admin/MissingLiveryCard.vue";
import AdminLayout from "@/Pages/Admin/AdminLayout.vue";
import Panel from "@/Components/FlightsGoneBy/Panels/Panel.vue";
defineOptions({ layout: AdminLayout });
defineProps<{
missingLiveries: MissingLivery[]
}>()
</script>
<template>
<MissingLiveryCard v-for="livery in missingLiveries" :key="livery.filename" :livery="livery" />
</template>
+1 -1
View File
@@ -10,7 +10,7 @@ import { router } from "@inertiajs/vue3";
defineOptions({ layout: MainLayout });
const page = usePage<SharedProps>();
const name = computed(() => page?.props?.auth?.user?.name || 'there');
const name = computed(() => page?.props?.auth?.user?.name || 'mate');
</script>
<template>
@@ -7,6 +7,8 @@ import PanelHeader from '@/Components/FlightsGoneBy/Panels/PanelHeader.vue'
import BadgeTable from '@/Components/FlightsGoneBy/GenericBadgeTable.vue'
import FlightBadge from '@/Components/FlightsGoneBy/FlightBadge.vue'
defineOptions({ inheritAttrs: false })
const props = defineProps<{
achievement: Achievement
user: User
@@ -7,7 +7,7 @@ import PanelHeader from '@/Components/FlightsGoneBy/Panels/PanelHeader.vue'
import PanelSubHeader from '@/Components/FlightsGoneBy/Panels/PanelSubHeader.vue'
import BadgeTable from '@/Components/FlightsGoneBy/GenericBadgeTable.vue'
import FlightBadge from '@/Components/FlightsGoneBy/FlightBadge.vue'
defineOptions({ inheritAttrs: false })
const props = defineProps<{
achievement: Achievement
user: User
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { Achievement, Airline, Alliance, Flight, User } from '@/Types/types'
import AllianceChallenge from '@/Components/FlightsGoneBy/AllianceChallenge.vue'
defineOptions({ inheritAttrs: false })
defineProps<{
achievement: Achievement
user: User
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { Achievement, Airline, Alliance, Flight, User } from '@/Types/types'
import AllianceChallenge from '@/Components/FlightsGoneBy/AllianceChallenge.vue'
defineOptions({ inheritAttrs: false })
defineProps<{
achievement: Achievement
user: User
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { Achievement, Airline, Alliance, Flight, User } from '@/Types/types'
import AllianceChallenge from '@/Components/FlightsGoneBy/AllianceChallenge.vue'
defineOptions({ inheritAttrs: false })
defineProps<{
achievement: Achievement
user: User
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { Achievement, Airline, Alliance, Flight, User } from '@/Types/types'
import AllianceChallenge from '@/Components/FlightsGoneBy/AllianceChallenge.vue'
defineOptions({ inheritAttrs: false })
defineProps<{
achievement: Achievement
user: User
@@ -7,7 +7,7 @@ import PanelHeader from '@/Components/FlightsGoneBy/Panels/PanelHeader.vue'
import PanelSubHeader from '@/Components/FlightsGoneBy/Panels/PanelSubHeader.vue'
import BadgeTable from '@/Components/FlightsGoneBy/GenericBadgeTable.vue'
import FlightBadge from '@/Components/FlightsGoneBy/FlightBadge.vue'
defineOptions({ inheritAttrs: false })
const props = defineProps<{
achievement: Achievement
user: User
@@ -6,7 +6,7 @@ import Panel from '@/Components/FlightsGoneBy/Panels/Panel.vue'
import PanelHeader from '@/Components/FlightsGoneBy/Panels/PanelHeader.vue'
import BadgeTable from '@/Components/FlightsGoneBy/GenericBadgeTable.vue'
import FlightBadge from '@/Components/FlightsGoneBy/FlightBadge.vue'
defineOptions({ inheritAttrs: false })
const props = defineProps<{
achievement: Achievement
user: User
@@ -6,7 +6,9 @@ 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'
import {copyToClipboard} from "@/Composables/useClipboard";
import CopyButton from "@/Components/FlightsGoneBy/CopyButton.vue";
defineOptions({ inheritAttrs: false })
const props = defineProps<{
achievement: Achievement
@@ -47,28 +49,7 @@ const selectedYear = ref<number | 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>
@@ -133,13 +114,10 @@ async function copyBBCode() {
class="year-select"
/>
<v-btn
:icon="copied ? 'mdi-check' : 'mdi-content-copy'"
:color="copied ? 'success' : undefined"
density="compact"
variant="text"
<CopyButton
title="Copy as BBCode"
@click="copyBBCode"
size="large"
:text="airlineTable?.bbCode ?? ''"
/>
</div>
</div>
@@ -7,7 +7,8 @@ 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
@@ -45,27 +46,6 @@ const selectedYear = ref<number | null>(
const alphabetTable = ref<InstanceType<typeof AlphabetTable> | null>(null)
const copied = ref(false)
async function copyBBCode() {
const text = alphabetTable.value?.toBBCode()
if (text == null) return
try {
await navigator.clipboard.writeText(text)
} catch {
// Fallback for non-HTTPS or browsers that block clipboard API
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>
@@ -124,13 +104,10 @@ async function copyBBCode() {
class="year-select"
/>
<v-btn
:icon="copied ? 'mdi-check' : 'mdi-content-copy'"
:color="copied ? 'success' : undefined"
density="compact"
variant="text"
<CopyButton
title="Copy as BBCode"
@click="copyBBCode"
size="large"
:text="alphabetTable?.bbCode ?? ''"
/>
</div>
</div>
@@ -12,7 +12,7 @@ import {useRegionFlights} from "@/Composables/useRegionFlights";
import RegionLegend from "@/Components/FlightsGoneBy/Panels/RegionLegend.vue";
defineOptions({ layout: MainLayout })
defineOptions({ inheritAttrs: false })
const props = defineProps<{
achievement: Achievement
user: User
@@ -12,7 +12,7 @@ import RegionLegend from "@/Components/FlightsGoneBy/Panels/RegionLegend.vue";
import Brazil from "@/Components/Maps/Brazil.vue";
defineOptions({ layout: MainLayout })
defineOptions({ inheritAttrs: false })
const props = defineProps<{
achievement: Achievement
user: User
@@ -13,7 +13,7 @@ import RegionLegend from "@/Components/FlightsGoneBy/Panels/RegionLegend.vue";
import Canada from "@/Components/Maps/Canada.vue";
defineOptions({ layout: MainLayout })
defineOptions({ inheritAttrs: false })
const props = defineProps<{
achievement: Achievement
user: User
@@ -12,7 +12,7 @@ import China from "@/Components/Maps/China.vue";
import RegionLegend from "@/Components/FlightsGoneBy/Panels/RegionLegend.vue";
defineOptions({ layout: MainLayout })
defineOptions({ inheritAttrs: false })
const props = defineProps<{
achievement: Achievement
user: User
@@ -11,9 +11,8 @@ import FlightRegionTable from "@/Components/FlightsGoneBy/FlightRegionTable.vue"
import {useRegionFlights} from "@/Composables/useRegionFlights";
import RegionLegend from "@/Components/FlightsGoneBy/Panels/RegionLegend.vue";
import USA from "@/Components/Maps/USA.vue";
defineOptions({ inheritAttrs: false })
defineOptions({ layout: MainLayout })
const props = defineProps<{
achievement: Achievement
user: User