Refine admin panel workflows
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import Select from 'primevue/select'
|
||||
import { ChevronLeft, ChevronRight, Pencil, Search, Trash2, TriangleAlert, UserPlus, Users, X } from '@lucide/vue'
|
||||
import { ChevronLeft, ChevronRight, Layers3, Pencil, Search, Trash2, TriangleAlert, UserPlus, Users, X } from '@lucide/vue'
|
||||
|
||||
import AdminPageHeader from '../../components/admin/AdminPageHeader.vue'
|
||||
import AdminSeasonToolbar from '../../components/admin/AdminSeasonToolbar.vue'
|
||||
@@ -33,6 +33,23 @@ const categoryFilterOptions = computed(() => [{ label: 'Alle Kategorien', value:
|
||||
const categoryLabelMap = computed(() =>
|
||||
Object.fromEntries(seasonDetail.value.categories.map((c) => [c.id, `${c.groupName} · ${c.name}`])),
|
||||
)
|
||||
const duplicateCandidateKeys = computed(() => {
|
||||
const counts = new Map<string, number>()
|
||||
for (const candidate of seasonDetail.value.candidates) {
|
||||
const categoryKey = `${candidate.categoryId}`
|
||||
const nameKey = `${categoryKey}:name:${candidate.displayName.trim().toLowerCase()}`
|
||||
const slugKey = `${categoryKey}:slug:${candidate.channelSlug.trim().toLowerCase()}`
|
||||
counts.set(nameKey, (counts.get(nameKey) ?? 0) + 1)
|
||||
if (candidate.channelSlug.trim()) counts.set(slugKey, (counts.get(slugKey) ?? 0) + 1)
|
||||
}
|
||||
return counts
|
||||
})
|
||||
const duplicateCandidateCount = computed(() =>
|
||||
seasonDetail.value.candidates.filter((candidate) =>
|
||||
(duplicateCandidateKeys.value.get(`${candidate.categoryId}:name:${candidate.displayName.trim().toLowerCase()}`) ?? 0) > 1 ||
|
||||
(duplicateCandidateKeys.value.get(`${candidate.categoryId}:slug:${candidate.channelSlug.trim().toLowerCase()}`) ?? 0) > 1,
|
||||
).length,
|
||||
)
|
||||
|
||||
const filteredCandidates = computed(() => {
|
||||
const query = search.value.trim().toLowerCase()
|
||||
@@ -151,6 +168,36 @@ async function confirmDelete() {
|
||||
|
||||
<AdminSeasonToolbar />
|
||||
|
||||
<section class="grid gap-4 md:grid-cols-3">
|
||||
<Card class="p-5">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-violet-500">Kandidaten</p>
|
||||
<strong class="mt-3 block text-3xl text-violet-900">{{ seasonDetail.candidates.length }}</strong>
|
||||
</div>
|
||||
<Users class="h-6 w-6 text-violet-500" />
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="p-5">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-violet-500">Kategorien</p>
|
||||
<strong class="mt-3 block text-3xl text-violet-900">{{ seasonDetail.categories.length }}</strong>
|
||||
</div>
|
||||
<UserPlus class="h-6 w-6 text-violet-500" />
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="p-5" :class="duplicateCandidateCount > 0 ? 'border-amber-200 bg-amber-50/60' : ''">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.18em]" :class="duplicateCandidateCount > 0 ? 'text-amber-600' : 'text-violet-500'">Mögliche Duplikate</p>
|
||||
<strong class="mt-3 block text-3xl" :class="duplicateCandidateCount > 0 ? 'text-amber-700' : 'text-violet-900'">{{ duplicateCandidateCount }}</strong>
|
||||
</div>
|
||||
<Layers3 class="h-6 w-6" :class="duplicateCandidateCount > 0 ? 'text-amber-500' : 'text-violet-500'" />
|
||||
</div>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
<Card class="overflow-hidden">
|
||||
<!-- Toolbar -->
|
||||
<div class="flex flex-col gap-4 border-b border-violet-100 p-5 lg:flex-row lg:items-center">
|
||||
@@ -203,6 +250,12 @@ async function confirmDelete() {
|
||||
<div class="min-w-0">
|
||||
<p class="truncate font-semibold text-slate-800">{{ candidate.displayName }}</p>
|
||||
<p class="truncate text-xs text-slate-500">{{ candidate.channelSlug }}</p>
|
||||
<p
|
||||
v-if="(duplicateCandidateKeys.get(`${candidate.categoryId}:name:${candidate.displayName.trim().toLowerCase()}`) ?? 0) > 1 || (duplicateCandidateKeys.get(`${candidate.categoryId}:slug:${candidate.channelSlug.trim().toLowerCase()}`) ?? 0) > 1"
|
||||
class="mt-1 text-xs font-semibold text-amber-700"
|
||||
>
|
||||
Mögliches Duplikat in dieser Kategorie
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
|
||||
Reference in New Issue
Block a user