Clean up repository hygiene
- Expand .gitignore with editor state, secrets, logs, and archive patterns - Remove AdminCategoriesViewV2.vue (replaced, no remaining references) - Exclude accidental AdminContentView duplicate from tsconfig Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+31
@@ -3,7 +3,38 @@ frontend/dist/
|
|||||||
Backend/bin/
|
Backend/bin/
|
||||||
Backend/obj/
|
Backend/obj/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Editor and local machine state
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Environment and secrets
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
!.env.template
|
||||||
|
*.local
|
||||||
|
*.secret
|
||||||
|
*.secrets
|
||||||
|
*.key
|
||||||
|
*.pem
|
||||||
|
*.p12
|
||||||
|
*.pfx
|
||||||
|
|
||||||
|
# Logs and diagnostics
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
log/
|
||||||
|
|
||||||
*.zip
|
*.zip
|
||||||
|
*.tar
|
||||||
|
*.tar.gz
|
||||||
|
*.tgz
|
||||||
|
*.rar
|
||||||
|
*.7z
|
||||||
*.docx
|
*.docx
|
||||||
|
|
||||||
# Local Claude Code config (settings, preview launch configs)
|
# Local Claude Code config (settings, preview launch configs)
|
||||||
|
|||||||
@@ -1,462 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed, ref } from 'vue'
|
|
||||||
import {
|
|
||||||
ChevronRight,
|
|
||||||
ExternalLink,
|
|
||||||
Layers3,
|
|
||||||
PlusCircle,
|
|
||||||
Search,
|
|
||||||
Tags,
|
|
||||||
Trash2,
|
|
||||||
TriangleAlert,
|
|
||||||
Users,
|
|
||||||
X,
|
|
||||||
} from '@lucide/vue'
|
|
||||||
import { RouterLink } from 'vue-router'
|
|
||||||
|
|
||||||
import AdminPageHeader from '../../components/admin/AdminPageHeader.vue'
|
|
||||||
import AdminSeasonToolbar from '../../components/admin/AdminSeasonToolbar.vue'
|
|
||||||
import { useAdminCategoryManager } from '../../components/admin/useAdminCategoryManager'
|
|
||||||
import { watchAdminToast } from '../../composables/useAdminToast'
|
|
||||||
import Button from '../../components/ui/Button.vue'
|
|
||||||
import Card from '../../components/ui/Card.vue'
|
|
||||||
import Modal from '../../components/ui/Modal.vue'
|
|
||||||
|
|
||||||
const {
|
|
||||||
selectedSeasonId,
|
|
||||||
query,
|
|
||||||
statusFilter,
|
|
||||||
selectedCategoryId,
|
|
||||||
saving,
|
|
||||||
adminMessage,
|
|
||||||
adminError,
|
|
||||||
editForms,
|
|
||||||
newCategoryForm,
|
|
||||||
filteredCategories,
|
|
||||||
selectedCategory,
|
|
||||||
categoryStats,
|
|
||||||
statusFilters,
|
|
||||||
categoryToDelete,
|
|
||||||
deleting,
|
|
||||||
saveCategory,
|
|
||||||
createCategory,
|
|
||||||
fillNewSlug,
|
|
||||||
confirmDeleteCategory,
|
|
||||||
} = useAdminCategoryManager()
|
|
||||||
|
|
||||||
watchAdminToast(adminMessage, adminError)
|
|
||||||
|
|
||||||
// ---------- "Neu" panel toggle ----------
|
|
||||||
const showCreatePanel = ref(false)
|
|
||||||
|
|
||||||
// ---------- Grouped list ----------
|
|
||||||
const groupedCategories = computed(() => {
|
|
||||||
const groups = new Map<string, typeof filteredCategories.value>()
|
|
||||||
for (const cat of filteredCategories.value) {
|
|
||||||
const key = cat.groupName || 'Ohne Gruppe'
|
|
||||||
if (!groups.has(key)) groups.set(key, [])
|
|
||||||
groups.get(key)!.push(cat)
|
|
||||||
}
|
|
||||||
return [...groups.entries()].map(([name, cats]) => ({ name, cats }))
|
|
||||||
})
|
|
||||||
|
|
||||||
// ---------- Stats für aktuell gewählte Kategorie ----------
|
|
||||||
const selectedCandidateCount = computed(() =>
|
|
||||||
selectedCategory.value
|
|
||||||
? filteredCategories.value.find((c) => c.id === selectedCategory.value!.id)?.candidates ?? 0
|
|
||||||
: 0,
|
|
||||||
)
|
|
||||||
const selectedPendingCount = computed(() =>
|
|
||||||
selectedCategory.value
|
|
||||||
? filteredCategories.value.find((c) => c.id === selectedCategory.value!.id)?.pending ?? 0
|
|
||||||
: 0,
|
|
||||||
)
|
|
||||||
|
|
||||||
// ---------- Top-level stats ----------
|
|
||||||
const topStats = computed(() => [
|
|
||||||
{
|
|
||||||
label: 'Kategorien',
|
|
||||||
value: categoryStats.value.find((s) => s.label === 'Kategorien')?.value ?? 0,
|
|
||||||
tone: 'text-violet-700 bg-violet-50 border-violet-100',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Kandidaten',
|
|
||||||
value: categoryStats.value.find((s) => s.label === 'Kandidaten')?.value ?? 0,
|
|
||||||
tone: 'text-cyan-700 bg-cyan-50 border-cyan-100',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Offene Reviews',
|
|
||||||
value: categoryStats.value.find((s) => s.label === 'Reviews')?.value ?? 0,
|
|
||||||
tone: 'text-amber-700 bg-amber-50 border-amber-100',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Leer',
|
|
||||||
value: statusFilters.value.find((f) => f.key === 'empty')?.count ?? 0,
|
|
||||||
tone: 'text-rose-700 bg-rose-50 border-rose-100',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="space-y-6">
|
|
||||||
<AdminPageHeader
|
|
||||||
eyebrow="Kategorien"
|
|
||||||
description="Kategorien verwalten, Gruppen pflegen und Kandidatenbasis prüfen."
|
|
||||||
:icon="Tags"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AdminSeasonToolbar />
|
|
||||||
|
|
||||||
<!-- Top stats -->
|
|
||||||
<section class="grid grid-cols-2 gap-4 xl:grid-cols-4">
|
|
||||||
<Card v-for="stat in topStats" :key="stat.label" class="flex items-center gap-4 p-5">
|
|
||||||
<span class="grid h-11 w-11 shrink-0 place-items-center rounded-2xl border text-sm" :class="stat.tone">
|
|
||||||
<span class="text-lg font-black">{{ stat.value }}</span>
|
|
||||||
</span>
|
|
||||||
<span class="text-sm font-semibold text-slate-600">{{ stat.label }}</span>
|
|
||||||
</Card>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="grid gap-6 xl:grid-cols-[minmax(340px,0.82fr)_minmax(0,1.18fr)]">
|
|
||||||
<!-- ═══ Linke Spalte: Gruppierte Liste ═══ -->
|
|
||||||
<div class="flex flex-col gap-3">
|
|
||||||
<Card class="overflow-hidden">
|
|
||||||
<!-- Search + Filter -->
|
|
||||||
<div class="space-y-3 border-b border-violet-100 p-4">
|
|
||||||
<label class="relative block">
|
|
||||||
<Search class="pointer-events-none absolute left-4 top-1/2 h-4 w-4 -translate-y-1/2 text-violet-400" />
|
|
||||||
<input
|
|
||||||
v-model="query"
|
|
||||||
class="h-11 w-full rounded-2xl border border-violet-200 bg-white pl-11 pr-4 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100"
|
|
||||||
placeholder="Kategorie, Gruppe oder Slug"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div class="flex flex-wrap gap-1.5">
|
|
||||||
<button
|
|
||||||
v-for="filter in statusFilters"
|
|
||||||
:key="filter.key"
|
|
||||||
type="button"
|
|
||||||
class="rounded-full border px-3 py-1 text-xs font-semibold transition"
|
|
||||||
:class="statusFilter === filter.key
|
|
||||||
? 'border-violet-200 bg-violet-100 text-violet-800'
|
|
||||||
: 'border-violet-100 bg-white text-slate-600 hover:bg-violet-50'"
|
|
||||||
@click="statusFilter = filter.key"
|
|
||||||
>
|
|
||||||
{{ filter.label }} · {{ filter.count }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Grouped category list -->
|
|
||||||
<div class="max-h-[640px] overflow-y-auto">
|
|
||||||
<template v-if="groupedCategories.length > 0">
|
|
||||||
<div v-for="group in groupedCategories" :key="group.name">
|
|
||||||
<!-- Group header -->
|
|
||||||
<div class="sticky top-0 z-10 flex items-center gap-2 border-b border-violet-50 bg-violet-50/90 px-4 py-2 backdrop-blur-sm">
|
|
||||||
<Layers3 class="h-3.5 w-3.5 text-violet-400" />
|
|
||||||
<span class="text-[11px] font-bold uppercase tracking-[0.18em] text-violet-500">{{ group.name }}</span>
|
|
||||||
<span class="ml-auto rounded-full border border-violet-100 bg-white px-2 py-0.5 text-[10px] font-semibold text-violet-600">{{ group.cats.length }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Categories in group -->
|
|
||||||
<div class="divide-y divide-violet-50/80">
|
|
||||||
<button
|
|
||||||
v-for="cat in group.cats"
|
|
||||||
:key="cat.id"
|
|
||||||
type="button"
|
|
||||||
class="group w-full px-4 py-3 text-left transition"
|
|
||||||
:class="selectedCategory?.id === cat.id
|
|
||||||
? 'bg-[linear-gradient(135deg,#ece2ff30,#fff2dd20)] outline-none ring-1 ring-inset ring-violet-200'
|
|
||||||
: 'hover:bg-violet-50/50'"
|
|
||||||
@click="selectedCategoryId = cat.id; showCreatePanel = false"
|
|
||||||
>
|
|
||||||
<div class="flex items-start justify-between gap-3">
|
|
||||||
<div class="min-w-0 flex-1">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<p class="truncate text-sm font-semibold text-slate-900">{{ cat.name }}</p>
|
|
||||||
<ChevronRight
|
|
||||||
class="h-3.5 w-3.5 shrink-0 text-violet-400 opacity-0 transition group-hover:opacity-100"
|
|
||||||
:class="selectedCategory?.id === cat.id ? 'opacity-100' : ''"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p class="mt-0.5 truncate text-xs text-slate-400">/{{ cat.slug }}</p>
|
|
||||||
<!-- Badges -->
|
|
||||||
<div class="mt-2 flex flex-wrap gap-1">
|
|
||||||
<span
|
|
||||||
class="rounded-full px-2 py-0.5 text-[10px] font-semibold"
|
|
||||||
:class="cat.candidates > 0 ? 'bg-emerald-50 text-emerald-700' : 'bg-rose-50 text-rose-600'"
|
|
||||||
>
|
|
||||||
{{ cat.candidates }} Kandidaten
|
|
||||||
</span>
|
|
||||||
<span v-if="cat.pending > 0" class="rounded-full bg-amber-50 px-2 py-0.5 text-[10px] font-semibold text-amber-700">
|
|
||||||
{{ cat.pending }} Reviews
|
|
||||||
</span>
|
|
||||||
<span class="rounded-full bg-slate-50 px-2 py-0.5 text-[10px] font-semibold text-slate-500">
|
|
||||||
Limit {{ cat.maxNomineesPerUser }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class="mt-0.5 shrink-0 rounded-xl border border-violet-100 bg-white px-2 py-0.5 text-[11px] font-bold text-violet-600">
|
|
||||||
#{{ cat.sortOrder }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<p v-else class="px-5 py-10 text-center text-sm text-slate-500">
|
|
||||||
Keine Kategorien passen zu diesem Filter.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Add button -->
|
|
||||||
<div class="border-t border-violet-100 p-3">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="flex w-full items-center justify-center gap-2 rounded-2xl border py-2.5 text-sm font-semibold transition"
|
|
||||||
:class="showCreatePanel
|
|
||||||
? 'border-violet-200 bg-violet-100 text-violet-800'
|
|
||||||
: 'border-violet-100 bg-white text-violet-700 hover:bg-violet-50'"
|
|
||||||
@click="showCreatePanel = !showCreatePanel; selectedCategoryId = null"
|
|
||||||
>
|
|
||||||
<component :is="showCreatePanel ? X : PlusCircle" class="h-4 w-4" />
|
|
||||||
{{ showCreatePanel ? 'Abbrechen' : 'Kategorie anlegen' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ═══ Rechte Spalte: Edit / Create ═══ -->
|
|
||||||
<div class="space-y-0">
|
|
||||||
|
|
||||||
<!-- Create panel -->
|
|
||||||
<Card v-if="showCreatePanel" class="p-6">
|
|
||||||
<div class="flex items-start gap-4">
|
|
||||||
<span class="grid h-11 w-11 shrink-0 place-items-center rounded-2xl bg-violet-100 text-violet-700">
|
|
||||||
<PlusCircle class="h-5 w-5" />
|
|
||||||
</span>
|
|
||||||
<div>
|
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-violet-500">Neue Kategorie</p>
|
|
||||||
<h2 class="mt-1 text-xl font-bold text-slate-900">Kategorie anlegen</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-5 grid gap-4 sm:grid-cols-2">
|
|
||||||
<label class="space-y-1.5">
|
|
||||||
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Gruppe</span>
|
|
||||||
<input
|
|
||||||
v-model="newCategoryForm.groupName"
|
|
||||||
placeholder="z. B. Streaming"
|
|
||||||
class="h-12 w-full rounded-2xl border border-violet-200 px-4 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label class="space-y-1.5">
|
|
||||||
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Name</span>
|
|
||||||
<input
|
|
||||||
v-model="newCategoryForm.name"
|
|
||||||
placeholder="Bester Streamer"
|
|
||||||
class="h-12 w-full rounded-2xl border border-violet-200 px-4 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label class="space-y-1.5">
|
|
||||||
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Slug</span>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<input
|
|
||||||
v-model="newCategoryForm.slug"
|
|
||||||
placeholder="bester-streamer"
|
|
||||||
class="h-12 min-w-0 flex-1 rounded-2xl border border-violet-200 px-4 text-sm font-mono outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="h-12 rounded-2xl border border-violet-100 bg-violet-50 px-4 text-xs font-semibold text-violet-700 transition hover:bg-violet-100"
|
|
||||||
@click="fillNewSlug"
|
|
||||||
>
|
|
||||||
Auto
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<div class="grid grid-cols-2 gap-3">
|
|
||||||
<label class="space-y-1.5">
|
|
||||||
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Reihenfolge</span>
|
|
||||||
<input
|
|
||||||
v-model.number="newCategoryForm.sortOrder"
|
|
||||||
type="number"
|
|
||||||
class="h-12 w-full rounded-2xl border border-violet-200 px-4 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label class="space-y-1.5">
|
|
||||||
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Limit</span>
|
|
||||||
<input
|
|
||||||
v-model.number="newCategoryForm.maxNomineesPerUser"
|
|
||||||
type="number"
|
|
||||||
class="h-12 w-full rounded-2xl border border-violet-200 px-4 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<label class="mt-4 block space-y-1.5">
|
|
||||||
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Beschreibung</span>
|
|
||||||
<textarea
|
|
||||||
v-model="newCategoryForm.description"
|
|
||||||
rows="3"
|
|
||||||
placeholder="Kurze Beschreibung für Team und Kandidaten-Seite"
|
|
||||||
class="w-full rounded-2xl border border-violet-200 px-4 py-3 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div class="mt-5 flex justify-end gap-3">
|
|
||||||
<Button variant="ghost" @click="showCreatePanel = false">Abbrechen</Button>
|
|
||||||
<Button :disabled="saving === 'new' || !selectedSeasonId" @click="createCategory">
|
|
||||||
{{ saving === 'new' ? 'Erstellt …' : 'Kategorie anlegen' }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<!-- Edit panel -->
|
|
||||||
<Card v-else-if="selectedCategory && editForms[selectedCategory.id]" class="overflow-hidden">
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="border-b border-violet-100 bg-gradient-to-br from-white via-violet-50/40 to-[#f7eef8] px-6 py-5">
|
|
||||||
<div class="flex items-start justify-between gap-4">
|
|
||||||
<div class="min-w-0">
|
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-violet-500">
|
|
||||||
{{ editForms[selectedCategory.id].groupName || 'Ohne Gruppe' }}
|
|
||||||
</p>
|
|
||||||
<h2 class="mt-1 truncate text-xl font-bold text-slate-900">{{ selectedCategory.name }}</h2>
|
|
||||||
<p class="mt-1 font-mono text-sm text-slate-400">/{{ selectedCategory.slug }}</p>
|
|
||||||
</div>
|
|
||||||
<span class="shrink-0 rounded-2xl border border-violet-200 bg-white px-3 py-2 text-sm font-bold text-violet-700">
|
|
||||||
#{{ selectedCategory.sortOrder }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quick stats row -->
|
|
||||||
<div class="mt-4 flex flex-wrap gap-3">
|
|
||||||
<RouterLink
|
|
||||||
to="/admin/candidates"
|
|
||||||
class="flex items-center gap-1.5 rounded-full border border-violet-100 bg-white px-3 py-1.5 text-xs font-semibold text-slate-600 transition hover:border-violet-200 hover:bg-violet-50"
|
|
||||||
>
|
|
||||||
<Users class="h-3.5 w-3.5 text-violet-500" />
|
|
||||||
{{ selectedCandidateCount }} Kandidaten
|
|
||||||
<ExternalLink class="h-3 w-3 text-slate-400" />
|
|
||||||
</RouterLink>
|
|
||||||
<span
|
|
||||||
v-if="selectedPendingCount > 0"
|
|
||||||
class="flex items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-3 py-1.5 text-xs font-semibold text-amber-700"
|
|
||||||
>
|
|
||||||
{{ selectedPendingCount }} Reviews offen
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="selectedCandidateCount === 0"
|
|
||||||
class="flex items-center gap-1.5 rounded-full border border-rose-200 bg-rose-50 px-3 py-1.5 text-xs font-semibold text-rose-600"
|
|
||||||
>
|
|
||||||
Keine Kandidaten
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Form body -->
|
|
||||||
<div class="p-6">
|
|
||||||
<div class="grid gap-4 sm:grid-cols-2">
|
|
||||||
<label class="space-y-1.5">
|
|
||||||
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Gruppe</span>
|
|
||||||
<input
|
|
||||||
v-model="editForms[selectedCategory.id].groupName"
|
|
||||||
class="h-12 w-full rounded-2xl border border-violet-200 px-4 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label class="space-y-1.5">
|
|
||||||
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Name</span>
|
|
||||||
<input
|
|
||||||
v-model="editForms[selectedCategory.id].name"
|
|
||||||
class="h-12 w-full rounded-2xl border border-violet-200 px-4 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label class="space-y-1.5">
|
|
||||||
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Slug</span>
|
|
||||||
<input
|
|
||||||
v-model="editForms[selectedCategory.id].slug"
|
|
||||||
class="h-12 w-full rounded-2xl border border-violet-200 px-4 font-mono text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div class="grid grid-cols-2 gap-3">
|
|
||||||
<label class="space-y-1.5">
|
|
||||||
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Reihenfolge</span>
|
|
||||||
<input
|
|
||||||
v-model.number="editForms[selectedCategory.id].sortOrder"
|
|
||||||
type="number"
|
|
||||||
class="h-12 w-full rounded-2xl border border-violet-200 px-4 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label class="space-y-1.5">
|
|
||||||
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Limit</span>
|
|
||||||
<input
|
|
||||||
v-model.number="editForms[selectedCategory.id].maxNomineesPerUser"
|
|
||||||
type="number"
|
|
||||||
class="h-12 w-full rounded-2xl border border-violet-200 px-4 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label class="mt-4 block space-y-1.5">
|
|
||||||
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Beschreibung</span>
|
|
||||||
<textarea
|
|
||||||
v-model="editForms[selectedCategory.id].description"
|
|
||||||
rows="3"
|
|
||||||
class="w-full rounded-2xl border border-violet-200 px-4 py-3 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Actions -->
|
|
||||||
<div class="flex items-center justify-between gap-3 border-t border-violet-100 px-6 py-4">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
class="gap-2 border border-rose-100 bg-rose-50 text-rose-600 hover:bg-rose-100"
|
|
||||||
@click="categoryToDelete = selectedCategory"
|
|
||||||
>
|
|
||||||
<Trash2 class="h-4 w-4" /> Löschen
|
|
||||||
</Button>
|
|
||||||
<Button :disabled="saving === selectedCategory.id" @click="saveCategory(selectedCategory.id)">
|
|
||||||
{{ saving === selectedCategory.id ? 'Speichert …' : 'Speichern' }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<!-- Empty state when nothing selected -->
|
|
||||||
<Card v-else class="flex flex-col items-center justify-center gap-4 py-20 text-center">
|
|
||||||
<span class="grid h-14 w-14 place-items-center rounded-full bg-violet-100 text-violet-400">
|
|
||||||
<Tags class="h-7 w-7" />
|
|
||||||
</span>
|
|
||||||
<div>
|
|
||||||
<p class="font-semibold text-slate-700">Kategorie auswählen</p>
|
|
||||||
<p class="mt-1 text-sm text-slate-500">Links eine Kategorie anklicken oder eine neue anlegen.</p>
|
|
||||||
</div>
|
|
||||||
<Button class="gap-2 mt-2" @click="showCreatePanel = true; selectedCategoryId = null">
|
|
||||||
<PlusCircle class="h-4 w-4" /> Neue Kategorie
|
|
||||||
</Button>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Delete modal -->
|
|
||||||
<Modal :open="!!categoryToDelete" title="Kategorie löschen?" @close="categoryToDelete = null">
|
|
||||||
<div class="flex items-start gap-4">
|
|
||||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-full bg-rose-50 text-rose-500">
|
|
||||||
<TriangleAlert class="h-6 w-6" />
|
|
||||||
</span>
|
|
||||||
<p class="text-sm leading-7 text-slate-600">
|
|
||||||
„<strong class="text-slate-800">{{ categoryToDelete?.name }}</strong>" und alle zugehörigen Kandidaten werden
|
|
||||||
aus diesem Award-Jahr entfernt. Das lässt sich nicht rückgängig machen.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<template #footer>
|
|
||||||
<Button variant="ghost" @click="categoryToDelete = null">Abbrechen</Button>
|
|
||||||
<Button class="!bg-rose-600 hover:!bg-rose-500" :disabled="deleting" @click="confirmDeleteCategory">
|
|
||||||
{{ deleting ? 'Löscht …' : 'Endgültig löschen' }}
|
|
||||||
</Button>
|
|
||||||
</template>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -10,5 +10,6 @@
|
|||||||
"erasableSyntaxOnly": true,
|
"erasableSyntaxOnly": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
|
"exclude": ["src/views/admin/AdminContentView 2.vue"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user