b53c7fb736
Features: - Category viewer ranges + subcategory templates (admin group modal, tree workspace) - Nomination enrichment via TwitchTracker API (NominationEnrichmentService, TwitchTrackerViewerStatsProvider) with admin tracking rules editor - Nomination group tracker: CategoryGroupName as primary identifier, CategoryId stays as nullable legacy field; StreamerIdentity table - Dynamic showact application form builder (AdminShowactFormBuilder, ShowactApplicationSchedule) - Session idle timeout setting (AdminSessionTimeoutCard) - Share URLs for X and Discord (SiteSettings, public extras) - Workflow rules now stored per season (falls back to global SiteSettings) - New admin routes: settings/access, settings/workflows, tracking-rules - New admin review workspace with subcategory tabs - AdminCategoriesView rebuilt with group/subcategory modals Migrations (all additive): - AddShareUrls, AddShowactDynamicForm, AddCategoryViewerRanges, AddSessionIdleTimeoutSettings, AddSeasonSubcategoryTemplates, AddNominationGroupTrackerIdentity, AddShowactApplicationSchedule, AddSeasonWorkflowRulesJson Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
198 lines
7.4 KiB
Vue
198 lines
7.4 KiB
Vue
<script setup lang="ts">
|
|
import { Layers3, UserPlus, Users } from '@lucide/vue'
|
|
|
|
import AdminCandidateDeleteModal from '../../components/admin/AdminCandidateDeleteModal.vue'
|
|
import AdminCandidateEditorModal from '../../components/admin/AdminCandidateEditorModal.vue'
|
|
import AdminCandidatesFiltersBar from '../../components/admin/AdminCandidatesFiltersBar.vue'
|
|
import AdminCandidatesTable from '../../components/admin/AdminCandidatesTable.vue'
|
|
import AdminPageHeader from '../../components/admin/AdminPageHeader.vue'
|
|
import AdminSeasonToolbar from '../../components/admin/AdminSeasonToolbar.vue'
|
|
import { useAdminCandidateManager } from '../../components/admin/useAdminCandidateManager'
|
|
import { watchAdminToast } from '../../composables/useAdminToast'
|
|
import Card from '../../components/ui/Card.vue'
|
|
|
|
const {
|
|
seasonDetail,
|
|
saving,
|
|
deleting,
|
|
adminMessage,
|
|
adminError,
|
|
search,
|
|
categoryFilter,
|
|
readinessFilter,
|
|
page,
|
|
categoryOptions,
|
|
categoryFilterOptions,
|
|
categoryLabelMap,
|
|
duplicateCandidateKeys,
|
|
candidateIdentitySummaries,
|
|
candidateWorkflowNotices,
|
|
duplicateCandidateCount,
|
|
acceptedCandidateCount,
|
|
clipReadyCount,
|
|
actionNeededCount,
|
|
filteredCandidates,
|
|
pagedCandidates,
|
|
totalPages,
|
|
rangeStart,
|
|
rangeEnd,
|
|
modalOpen,
|
|
form,
|
|
modalTitle,
|
|
canSave,
|
|
candidatePlatformOptions,
|
|
selectedPlatformValue,
|
|
editorIdentitySummary,
|
|
editorRuleNotices,
|
|
acceptanceStatusOptions,
|
|
clipEmbedStatusOptions,
|
|
readinessFilterOptions,
|
|
candidateToDelete,
|
|
clearFilters,
|
|
openCreate,
|
|
openEdit,
|
|
handlePlatformSelection,
|
|
saveModal,
|
|
confirmDelete,
|
|
} = useAdminCandidateManager()
|
|
|
|
watchAdminToast(adminMessage, adminError)
|
|
</script>
|
|
|
|
<template>
|
|
<div class="space-y-6">
|
|
<AdminPageHeader
|
|
eyebrow="Kandidaten"
|
|
:icon="Users"
|
|
/>
|
|
|
|
<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">
|
|
<div class="flex items-start justify-between gap-4">
|
|
<div>
|
|
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-emerald-600">Angenommen</p>
|
|
<strong class="mt-3 block text-3xl text-emerald-800">{{ acceptedCandidateCount }}</strong>
|
|
</div>
|
|
<UserPlus class="h-6 w-6 text-emerald-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-sky-600">Clip bereit</p>
|
|
<strong class="mt-3 block text-3xl text-sky-800">{{ clipReadyCount }}</strong>
|
|
</div>
|
|
<Layers3 class="h-6 w-6 text-sky-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>
|
|
<Card class="p-5" :class="actionNeededCount > 0 ? 'border-rose-200 bg-rose-50/60' : ''">
|
|
<div class="flex items-start justify-between gap-4">
|
|
<div>
|
|
<p class="text-xs font-semibold uppercase tracking-[0.18em]" :class="actionNeededCount > 0 ? 'text-rose-600' : 'text-violet-500'">Handlungsbedarf</p>
|
|
<strong class="mt-3 block text-3xl" :class="actionNeededCount > 0 ? 'text-rose-700' : 'text-violet-900'">{{ actionNeededCount }}</strong>
|
|
</div>
|
|
<UserPlus class="h-6 w-6" :class="actionNeededCount > 0 ? 'text-rose-500' : 'text-violet-500'" />
|
|
</div>
|
|
</Card>
|
|
</section>
|
|
|
|
<Card class="overflow-hidden">
|
|
<AdminCandidatesFiltersBar
|
|
:search="search"
|
|
:category-filter="categoryFilter"
|
|
:readiness-filter="readinessFilter"
|
|
:category-filter-options="categoryFilterOptions"
|
|
:readiness-filter-options="readinessFilterOptions"
|
|
@update:search="search = $event"
|
|
@update:category-filter="categoryFilter = $event"
|
|
@update:readiness-filter="readinessFilter = $event"
|
|
@clear-filters="clearFilters"
|
|
@open-create="openCreate"
|
|
/>
|
|
|
|
<AdminCandidatesTable
|
|
:paged-candidates="pagedCandidates"
|
|
:total-count="seasonDetail.candidates.length"
|
|
:filtered-count="filteredCandidates.length"
|
|
:page="page"
|
|
:total-pages="totalPages"
|
|
:range-start="rangeStart"
|
|
:range-end="rangeEnd"
|
|
:category-label-map="categoryLabelMap"
|
|
:duplicate-candidate-keys="duplicateCandidateKeys"
|
|
:candidate-identity-summaries="candidateIdentitySummaries"
|
|
:candidate-workflow-notices="candidateWorkflowNotices"
|
|
:acceptance-status-options="acceptanceStatusOptions"
|
|
@edit="openEdit"
|
|
@delete="candidateToDelete = $event"
|
|
@update:page="page = $event"
|
|
@open-create="openCreate"
|
|
/>
|
|
</Card>
|
|
|
|
<AdminCandidateEditorModal
|
|
:open="modalOpen"
|
|
:title="modalTitle"
|
|
:form="form"
|
|
:category-options="categoryOptions"
|
|
:candidate-platform-options="candidatePlatformOptions"
|
|
:selected-platform-value="selectedPlatformValue"
|
|
:identity-summary="editorIdentitySummary"
|
|
:rule-notices="editorRuleNotices"
|
|
:acceptance-status-options="acceptanceStatusOptions"
|
|
:clip-embed-status-options="clipEmbedStatusOptions"
|
|
:can-save="canSave"
|
|
:saving="saving"
|
|
@close="modalOpen = false"
|
|
@save="saveModal"
|
|
@update:category-id="form.categoryId = $event"
|
|
@update:display-name="form.displayName = $event"
|
|
@update:channel-slug="form.channelSlug = $event"
|
|
@update:platform="form.platform = $event"
|
|
@update:acceptance-status="form.acceptanceStatus = $event"
|
|
@update:acceptance-note="form.acceptanceNote = $event"
|
|
@update:clip-compilation-url="form.clipCompilationUrl = $event"
|
|
@update:clip-compilation-title="form.clipCompilationTitle = $event"
|
|
@update:clip-compilation-platform="form.clipCompilationPlatform = $event"
|
|
@update:clip-embed-status="form.clipEmbedStatus = $event"
|
|
@platform-selection="handlePlatformSelection"
|
|
/>
|
|
|
|
<AdminCandidateDeleteModal
|
|
:candidate="candidateToDelete"
|
|
:deleting="deleting"
|
|
@close="candidateToDelete = null"
|
|
@confirm="confirmDelete"
|
|
/>
|
|
</div>
|
|
</template>
|