Improve admin navigation and local API reachability
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import Select from 'primevue/select'
|
||||
|
||||
import AdminPageHeader from '../../components/admin/AdminPageHeader.vue'
|
||||
import AdminSeasonToolbar from '../../components/admin/AdminSeasonToolbar.vue'
|
||||
import Button from '../../components/ui/Button.vue'
|
||||
import Card from '../../components/ui/Card.vue'
|
||||
@@ -28,12 +29,26 @@ const candidateForms = reactive<Record<number, {
|
||||
|
||||
const seasonDetail = computed(() => store.adminSeasonDetail)
|
||||
const selectedSeasonId = computed(() => store.adminSelectedSeasonId)
|
||||
const candidateFilter = ref('')
|
||||
const categoryOptions = computed(() =>
|
||||
seasonDetail.value.categories.map((category) => ({
|
||||
label: `${category.groupName} · ${category.name}`,
|
||||
value: category.id,
|
||||
})),
|
||||
)
|
||||
const categoryLabelMap = computed(() =>
|
||||
Object.fromEntries(seasonDetail.value.categories.map((category) => [category.id, `${category.groupName} · ${category.name}`])),
|
||||
)
|
||||
const filteredCandidates = computed(() => {
|
||||
const query = candidateFilter.value.trim().toLowerCase()
|
||||
if (!query) return seasonDetail.value.candidates
|
||||
return seasonDetail.value.candidates.filter((candidate) =>
|
||||
[candidate.displayName, candidate.channelSlug, candidate.platform, categoryLabelMap.value[candidate.categoryId] ?? '']
|
||||
.join(' ')
|
||||
.toLowerCase()
|
||||
.includes(query),
|
||||
)
|
||||
})
|
||||
|
||||
watch(
|
||||
seasonDetail,
|
||||
@@ -92,6 +107,12 @@ async function createCandidate() {
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<AdminPageHeader
|
||||
eyebrow="Candidates"
|
||||
title="Kandidatenbasis pflegen"
|
||||
description="Hier findest du alle Kandidaten der gewaehlten Season an einem Ort. Filtere zuerst und bearbeite dann nur die relevanten Eintraege."
|
||||
/>
|
||||
|
||||
<AdminSeasonToolbar />
|
||||
|
||||
<div class="grid gap-6 xl:grid-cols-[1.1fr_0.9fr]">
|
||||
@@ -102,13 +123,25 @@ async function createCandidate() {
|
||||
<p class="mt-2 text-sm text-slate-500">Bekannte Kandidaten koennen pro Kategorie gepflegt und fuer Voting und Archiv genutzt werden.</p>
|
||||
</div>
|
||||
<span class="text-sm uppercase tracking-[0.2em] text-slate-500">
|
||||
{{ seasonDetail.candidates.length }} Kandidaten
|
||||
{{ filteredCandidates.length }} / {{ seasonDetail.candidates.length }} Kandidaten
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid gap-4 md:grid-cols-[minmax(0,1fr)_220px]">
|
||||
<input
|
||||
v-model="candidateFilter"
|
||||
type="text"
|
||||
class="rounded-2xl border border-violet-200 px-4 py-3"
|
||||
placeholder="Nach Name, Handle, Plattform oder Kategorie filtern"
|
||||
/>
|
||||
<div class="rounded-2xl border border-violet-100 bg-violet-50/60 px-4 py-3 text-sm text-slate-600">
|
||||
Tipp: Nutze erst den Filter und aendere danach nur den Zielkandidaten.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 space-y-4">
|
||||
<div
|
||||
v-for="candidate in seasonDetail.candidates"
|
||||
v-for="candidate in filteredCandidates"
|
||||
:key="candidate.id"
|
||||
class="rounded-[26px] border border-violet-100 bg-white/90 p-5"
|
||||
>
|
||||
@@ -131,6 +164,10 @@ async function createCandidate() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="filteredCandidates.length === 0" class="rounded-[26px] border border-dashed border-violet-100 px-5 py-6 text-sm text-slate-500">
|
||||
Keine Kandidaten passen zum aktuellen Filter.
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user