Refactor app architecture and clean local artifacts
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { RouterLink, RouterView, useRoute } from 'vue-router'
|
||||
import {
|
||||
AlertTriangle,
|
||||
@@ -8,30 +8,33 @@ import {
|
||||
ClipboardList,
|
||||
Film,
|
||||
LayoutDashboard,
|
||||
FileClock,
|
||||
Settings,
|
||||
Sparkles,
|
||||
Tags,
|
||||
UserCog,
|
||||
Trophy,
|
||||
Users,
|
||||
Vote,
|
||||
FileText,
|
||||
} from '@lucide/vue'
|
||||
|
||||
import Card from '../../components/ui/Card.vue'
|
||||
import { getVoteMetricValue } from '../../lib/adminMetrics'
|
||||
import { getRiskMetricValue } from '../../lib/adminMetrics'
|
||||
import { useAwardsStore } from '../../stores/awards'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
|
||||
const route = useRoute()
|
||||
const store = useAwardsStore()
|
||||
const authStore = useAuthStore()
|
||||
const openRiskCount = computed(() => getRiskMetricValue(store.admin.metrics))
|
||||
const pendingClipCount = computed(() => store.adminSeasonDetail.clipSubmissions.filter((clip) => clip.status === 'pending').length)
|
||||
const adminWorkspaceLoading = ref(false)
|
||||
const adminWorkspaceLoaded = ref(false)
|
||||
|
||||
const navGroups = [
|
||||
const fullNavGroups = computed(() => [
|
||||
{
|
||||
label: 'Betrieb',
|
||||
items: [
|
||||
{ label: 'Dashboard', to: '/admin/dashboard', description: 'Live-Lage und Aufgaben', icon: LayoutDashboard, badge: () => null },
|
||||
{ label: 'Nominierungen', to: '/admin/nominations', description: 'Eingang und Backlog', icon: ClipboardList, badge: () => `${store.adminSeasonDetail.pendingNominations.length}` },
|
||||
{ label: 'Voting', to: '/admin/voting', description: 'Readiness und Sperren', icon: Vote, badge: () => `${getVoteMetricValue(store.admin.metrics)}` },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -40,25 +43,40 @@ const navGroups = [
|
||||
{ label: 'Jahre', to: '/admin/years', description: 'Jahresstatus und Setup', icon: CalendarCog, badge: () => `${store.adminSeasons.length}` },
|
||||
{ label: 'Kategorien', to: '/admin/categories', description: 'Struktur und Limits', icon: Tags, badge: () => `${store.adminSeasonDetail.categories.length}` },
|
||||
{ label: 'Kandidaten', to: '/admin/candidates', description: 'Kandidatenbasis pflegen', icon: Users, badge: () => `${store.adminSeasonDetail.candidates.length}` },
|
||||
{ label: 'Clips', to: '/admin/clips', description: 'Clip-Kategorien prüfen', icon: Film, badge: () => `${store.adminSeasonDetail.categories.filter((category) => `${category.groupName} ${category.name}`.toLowerCase().includes('clip')).length}` },
|
||||
{ label: 'Clips', to: '/admin/clips', description: 'Clip-Einreichungen prüfen', icon: Film, badge: () => `${pendingClipCount.value}` },
|
||||
{ label: 'Landingpage', to: '/admin/content', description: 'FAQ, Footer und Datenschutz', icon: FileText, badge: () => null },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Kontrolle',
|
||||
items: [
|
||||
{ label: 'Reviews', to: '/admin/reviews', description: 'Freitext-Fälle entscheiden', icon: Sparkles, badge: () => `${store.adminSeasonDetail.pendingNominations.length}` },
|
||||
{ label: 'Risiko', to: '/admin/risk', description: 'Flags entscheiden', icon: AlertTriangle, badge: () => `${store.admin.riskFlags.length}` },
|
||||
{ label: 'Team-Audit', to: '/admin/users-logs', description: 'Admin-Spuren', icon: UserCog, badge: () => `${store.admin.auditEntries.length}` },
|
||||
{ label: 'Risiko', to: '/admin/risk', description: 'Flags entscheiden', icon: AlertTriangle, badge: () => `${openRiskCount.value}` },
|
||||
{ label: 'Audit-Log', to: '/admin/users-logs', description: 'Admin-Aktionen', icon: FileClock, badge: () => null },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Auswertung',
|
||||
items: [
|
||||
{ label: 'Analytics', to: '/admin/analytics', description: 'Metriken und Rankings', icon: BarChart3, badge: () => `${store.admin.topCategories.length}` },
|
||||
{ label: 'Einstellungen', to: '/admin/settings', description: 'Public-Status und Checks', icon: Settings, badge: () => null },
|
||||
{ label: 'Analytics', to: '/admin/analytics', description: 'Metriken und Rankings', icon: BarChart3, badge: () => null },
|
||||
{ label: 'Gewinner', to: '/admin/winners', description: 'Finale Ergebnisse freigeben', icon: Trophy, badge: () => `${store.adminSeasonDetail.results.length}` },
|
||||
{ label: 'Einstellungen', to: '/admin/settings', description: 'Systemchecks und Status', icon: Settings, badge: () => null },
|
||||
],
|
||||
},
|
||||
]
|
||||
])
|
||||
|
||||
const navGroups = computed(() => {
|
||||
if (authStore.canManageAdminWorkspace) {
|
||||
return fullNavGroups.value
|
||||
}
|
||||
|
||||
const allowedRoutes = new Set(['/admin/content', '/admin/settings'])
|
||||
return fullNavGroups.value
|
||||
.map((group) => ({
|
||||
...group,
|
||||
items: group.items.filter((item) => allowedRoutes.has(item.to)),
|
||||
}))
|
||||
.filter((group) => group.items.length > 0)
|
||||
})
|
||||
|
||||
const currentSeason = computed(() => store.adminSeasonDetail)
|
||||
const seasonSummary = computed(() => [
|
||||
@@ -71,16 +89,40 @@ function isActive(to: string) {
|
||||
return route.path === to
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (!authStore.isAdmin) return
|
||||
await store.initializeAdminWorkspace()
|
||||
})
|
||||
async function ensureAdminWorkspace() {
|
||||
if (!authStore.hydrated || !authStore.canAccessAdmin || adminWorkspaceLoading.value) return
|
||||
if (adminWorkspaceLoaded.value && store.apiMode === 'api') return
|
||||
|
||||
adminWorkspaceLoading.value = true
|
||||
try {
|
||||
if (authStore.canManageAdminWorkspace) {
|
||||
await store.initializeAdminWorkspace()
|
||||
} else {
|
||||
await store.loadAdminContentWorkspace()
|
||||
}
|
||||
adminWorkspaceLoaded.value = store.apiMode === 'api'
|
||||
} finally {
|
||||
adminWorkspaceLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [authStore.hydrated, authStore.canAccessAdmin, authStore.canManageAdminWorkspace, route.path] as const,
|
||||
() => {
|
||||
void ensureAdminWorkspace()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pb-10">
|
||||
<div class="grid gap-6 xl:grid-cols-[292px_minmax(0,1fr)]">
|
||||
<aside class="space-y-3 xl:sticky xl:top-4 xl:h-fit">
|
||||
<main class="order-1 min-w-0 xl:order-2">
|
||||
<RouterView />
|
||||
</main>
|
||||
|
||||
<aside class="order-2 space-y-3 xl:order-1 xl:sticky xl:top-4 xl:h-fit">
|
||||
<Card class="p-3">
|
||||
<nav class="space-y-4">
|
||||
<section v-for="group in navGroups" :key="group.label" class="space-y-1.5">
|
||||
@@ -133,8 +175,6 @@ onMounted(async () => {
|
||||
</div>
|
||||
</Card>
|
||||
</aside>
|
||||
|
||||
<RouterView />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user