Refactor admin panel into categorized navigation
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
|
||||
import AdminSeasonToolbar from '../../components/admin/AdminSeasonToolbar.vue'
|
||||
import Button from '../../components/ui/Button.vue'
|
||||
import Card from '../../components/ui/Card.vue'
|
||||
import { useAwardsStore } from '../../stores/awards'
|
||||
|
||||
const store = useAwardsStore()
|
||||
const seasonSaving = ref(false)
|
||||
const categorySaving = ref<number | 'new' | null>(null)
|
||||
const adminMessage = ref('')
|
||||
const adminError = ref('')
|
||||
|
||||
const seasonForm = reactive({
|
||||
currentPhase: '',
|
||||
isCurrent: false,
|
||||
})
|
||||
|
||||
const newCategoryForm = reactive({
|
||||
groupName: '',
|
||||
name: '',
|
||||
slug: '',
|
||||
description: '',
|
||||
sortOrder: 1,
|
||||
maxNomineesPerUser: 3,
|
||||
})
|
||||
|
||||
const editForms = reactive<Record<number, {
|
||||
groupName: string
|
||||
name: string
|
||||
slug: string
|
||||
description: string
|
||||
sortOrder: number
|
||||
maxNomineesPerUser: number
|
||||
}>>({})
|
||||
|
||||
const seasonDetail = computed(() => store.adminSeasonDetail)
|
||||
const selectedSeasonId = computed(() => store.adminSelectedSeasonId)
|
||||
|
||||
watch(
|
||||
seasonDetail,
|
||||
(detail) => {
|
||||
seasonForm.currentPhase = detail.currentPhase
|
||||
seasonForm.isCurrent = detail.isCurrent
|
||||
|
||||
for (const category of detail.categories) {
|
||||
editForms[category.id] = {
|
||||
groupName: category.groupName,
|
||||
name: category.name,
|
||||
slug: category.slug,
|
||||
description: category.description,
|
||||
sortOrder: category.sortOrder,
|
||||
maxNomineesPerUser: category.maxNomineesPerUser,
|
||||
}
|
||||
}
|
||||
|
||||
newCategoryForm.sortOrder = detail.categories.length + 1
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
async function saveSeason() {
|
||||
if (!selectedSeasonId.value) return
|
||||
|
||||
seasonSaving.value = true
|
||||
adminMessage.value = ''
|
||||
adminError.value = ''
|
||||
|
||||
try {
|
||||
await store.updateAdminSeason(selectedSeasonId.value, {
|
||||
currentPhase: seasonForm.currentPhase,
|
||||
isCurrent: seasonForm.isCurrent,
|
||||
})
|
||||
adminMessage.value = 'Season-Einstellungen gespeichert.'
|
||||
} catch (error) {
|
||||
adminError.value = error instanceof Error ? error.message : 'Season konnte nicht gespeichert werden.'
|
||||
} finally {
|
||||
seasonSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveCategory(categoryId: number) {
|
||||
if (!selectedSeasonId.value) return
|
||||
|
||||
categorySaving.value = categoryId
|
||||
adminMessage.value = ''
|
||||
adminError.value = ''
|
||||
|
||||
try {
|
||||
await store.updateAdminCategory(categoryId, selectedSeasonId.value, editForms[categoryId])
|
||||
adminMessage.value = 'Kategorie gespeichert.'
|
||||
} catch (error) {
|
||||
adminError.value = error instanceof Error ? error.message : 'Kategorie konnte nicht gespeichert werden.'
|
||||
} finally {
|
||||
categorySaving.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function createCategory() {
|
||||
if (!selectedSeasonId.value) return
|
||||
|
||||
categorySaving.value = 'new'
|
||||
adminMessage.value = ''
|
||||
adminError.value = ''
|
||||
|
||||
try {
|
||||
await store.createAdminCategory(selectedSeasonId.value, newCategoryForm)
|
||||
adminMessage.value = 'Neue Kategorie angelegt.'
|
||||
newCategoryForm.groupName = ''
|
||||
newCategoryForm.name = ''
|
||||
newCategoryForm.slug = ''
|
||||
newCategoryForm.description = ''
|
||||
newCategoryForm.sortOrder = seasonDetail.value.categories.length + 1
|
||||
newCategoryForm.maxNomineesPerUser = 3
|
||||
} catch (error) {
|
||||
adminError.value = error instanceof Error ? error.message : 'Kategorie konnte nicht angelegt werden.'
|
||||
} finally {
|
||||
categorySaving.value = null
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<AdminSeasonToolbar />
|
||||
|
||||
<div class="grid gap-6 xl:grid-cols-[0.95fr_1.05fr]">
|
||||
<Card class="p-7">
|
||||
<div class="flex flex-col gap-6">
|
||||
<div>
|
||||
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Season Setup</h2>
|
||||
<p class="mt-2 text-sm text-slate-500">Phase, Current-Status und Basiskontext fuer die aktive Awards-Season.</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<label class="text-sm font-semibold text-slate-600">Phase</label>
|
||||
<input
|
||||
v-model="seasonForm.currentPhase"
|
||||
type="text"
|
||||
class="w-full rounded-2xl border border-violet-200 bg-white px-4 py-3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label class="flex items-center gap-3 rounded-2xl border border-violet-100 bg-violet-50/60 px-4 py-4 text-sm text-slate-700">
|
||||
<input v-model="seasonForm.isCurrent" type="checkbox" class="h-4 w-4 accent-violet-600" />
|
||||
Diese Season ist die aktuelle Public Season
|
||||
</label>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<Button :disabled="seasonSaving || !selectedSeasonId" @click="saveSeason">
|
||||
{{ seasonSaving ? 'Speichert ...' : 'Season speichern' }}
|
||||
</Button>
|
||||
<span class="text-sm text-slate-500">
|
||||
{{ seasonDetail.year }} · {{ seasonDetail.name }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p v-if="adminMessage" class="rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700">
|
||||
{{ adminMessage }}
|
||||
</p>
|
||||
<p v-if="adminError" class="rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700">
|
||||
{{ adminError }}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card class="p-7">
|
||||
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Neue Kategorie</h2>
|
||||
<div class="mt-6 space-y-4">
|
||||
<input v-model="newCategoryForm.groupName" type="text" class="w-full rounded-2xl border border-violet-200 px-4 py-3" placeholder="Group Name" />
|
||||
<input v-model="newCategoryForm.name" type="text" class="w-full rounded-2xl border border-violet-200 px-4 py-3" placeholder="Kategorie-Name" />
|
||||
<input v-model="newCategoryForm.slug" type="text" class="w-full rounded-2xl border border-violet-200 px-4 py-3" placeholder="Slug" />
|
||||
<textarea v-model="newCategoryForm.description" class="min-h-28 w-full rounded-2xl border border-violet-200 px-4 py-3" placeholder="Beschreibung" />
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<input v-model="newCategoryForm.sortOrder" type="number" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Sortierung" />
|
||||
<input v-model="newCategoryForm.maxNomineesPerUser" type="number" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Max Nominees" />
|
||||
</div>
|
||||
<Button :disabled="categorySaving === 'new' || !selectedSeasonId" @click="createCategory">
|
||||
{{ categorySaving === 'new' ? 'Erstellt ...' : 'Kategorie anlegen' }}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card class="p-7">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Kategorien der Season</h2>
|
||||
<p class="mt-2 text-sm text-slate-500">Sortierung, Slugs und Limits werden hier pro Jahr gepflegt.</p>
|
||||
</div>
|
||||
<span class="text-sm uppercase tracking-[0.2em] text-slate-500">
|
||||
{{ seasonDetail.categories.length }} Kategorien
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 space-y-4">
|
||||
<div
|
||||
v-for="category in seasonDetail.categories"
|
||||
:key="category.id"
|
||||
class="rounded-[26px] border border-violet-100 bg-white/90 p-5"
|
||||
>
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<input v-model="editForms[category.id].groupName" type="text" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Group" />
|
||||
<input v-model="editForms[category.id].name" type="text" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Name" />
|
||||
<input v-model="editForms[category.id].slug" type="text" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Slug" />
|
||||
<input v-model="editForms[category.id].sortOrder" type="number" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Sortierung" />
|
||||
<input v-model="editForms[category.id].maxNomineesPerUser" type="number" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Limit" />
|
||||
<div class="flex items-center rounded-2xl border border-violet-100 bg-violet-50/60 px-4 py-3 text-sm text-slate-600">
|
||||
{{ category.candidateCount }} Kandidaten in dieser Kategorie
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
v-model="editForms[category.id].description"
|
||||
class="mt-4 min-h-28 w-full rounded-2xl border border-violet-200 px-4 py-3"
|
||||
placeholder="Beschreibung"
|
||||
/>
|
||||
|
||||
<div class="mt-4 flex justify-end">
|
||||
<Button :disabled="categorySaving === category.id" @click="saveCategory(category.id)">
|
||||
{{ categorySaving === category.id ? 'Speichert ...' : 'Kategorie speichern' }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user