import { computed, reactive, ref, watch } from 'vue' import { CheckCircle2, Clock3, ListChecks, Trophy } from '@lucide/vue' import { useAwardsStore } from '../../stores/awards' const allFilter = 'all' export function useAdminWinnersManager() { const store = useAwardsStore() const savingResultForCategory = ref(null) const deletingResultId = ref(null) const adminMessage = ref('') const adminError = ref('') const query = ref('') const statusFilter = ref(allFilter) const winnerSelections = reactive>({}) const seasonDetail = computed(() => store.adminSeasonDetail) const resultMap = computed(() => new Map(seasonDetail.value.results.map((result) => [result.categoryId, result]))) const resultRows = computed(() => seasonDetail.value.categories .map((category) => { const candidates = seasonDetail.value.candidates .filter((candidate) => candidate.categoryId === category.id) .sort((a, b) => a.displayName.localeCompare(b.displayName)) const existing = resultMap.value.get(category.id) ?? null const openReviews = seasonDetail.value.pendingNominations.filter((nomination) => nomination.categoryId === category.id).length const approvedClips = seasonDetail.value.clipSubmissions.filter((clip) => clip.categoryId === category.id && clip.status === 'approved').length return { category, candidates, existing, openReviews, approvedClips, hasPendingReviews: openReviews > 0, isEmpty: candidates.length === 0, isComplete: Boolean(existing), } }) .sort((a, b) => a.category.sortOrder - b.category.sortOrder), ) const completedCount = computed(() => resultRows.value.filter((row) => row.isComplete).length) const missingCount = computed(() => Math.max(0, resultRows.value.length - completedCount.value)) const reviewWarningCount = computed(() => resultRows.value.filter((row) => row.hasPendingReviews).length) const emptyCount = computed(() => resultRows.value.filter((row) => row.isEmpty).length) const completionPct = computed(() => resultRows.value.length === 0 ? 0 : Math.round((completedCount.value / resultRows.value.length) * 100)) const statusFilters = computed(() => [ { value: allFilter, label: 'Alle', count: resultRows.value.length }, { value: 'open', label: 'Offen', count: missingCount.value }, { value: 'set', label: 'Gesetzt', count: completedCount.value }, { value: 'review', label: 'Reviews offen', count: reviewWarningCount.value }, { value: 'empty', label: 'Ohne Kandidaten', count: emptyCount.value }, ]) const visibleResultRows = computed(() => { const normalizedQuery = query.value.trim().toLowerCase() return resultRows.value.filter((row) => { const matchesStatus = statusFilter.value === allFilter || (statusFilter.value === 'open' && !row.isComplete) || (statusFilter.value === 'set' && row.isComplete) || (statusFilter.value === 'review' && row.hasPendingReviews) || (statusFilter.value === 'empty' && row.isEmpty) if (!matchesStatus) return false if (!normalizedQuery) return true return [ row.category.name, row.category.groupName, row.existing?.candidateDisplayName ?? '', ...row.candidates.map((candidate) => `${candidate.displayName} ${candidate.channelSlug} ${candidate.platform}`), ].some((value) => value.toLowerCase().includes(normalizedQuery)) }) }) const summaryCards = computed(() => [ { label: 'Fortschritt', value: `${completionPct.value}%`, note: `${completedCount.value} von ${resultRows.value.length} Kategorien final.`, icon: Trophy, tone: 'border-violet-100 bg-violet-50 text-violet-800', }, { label: 'Offen', value: String(missingCount.value), note: missingCount.value === 0 ? 'Keine Gewinner fehlen.' : 'Diese Kategorien brauchen eine Auswahl.', icon: ListChecks, tone: missingCount.value > 0 ? 'border-sky-100 bg-sky-50 text-sky-800' : 'border-emerald-100 bg-emerald-50 text-emerald-800', }, { label: 'Reviews', value: String(reviewWarningCount.value), note: reviewWarningCount.value === 0 ? 'Keine Review-Warnungen.' : 'Vor finaler Freigabe pruefen.', icon: Clock3, tone: reviewWarningCount.value > 0 ? 'border-amber-100 bg-amber-50 text-amber-800' : 'border-emerald-100 bg-emerald-50 text-emerald-800', }, { label: 'Gesetzt', value: String(completedCount.value), note: 'Speist Archiv und Gewinneransicht.', icon: CheckCircle2, tone: 'border-emerald-100 bg-emerald-50 text-emerald-800', }, ]) watch(resultRows, (rows) => { const knownCategoryIds = new Set(rows.map((row) => row.category.id)) for (const key of Object.keys(winnerSelections)) { if (!knownCategoryIds.has(Number(key))) { delete winnerSelections[Number(key)] } } for (const row of rows) { winnerSelections[row.category.id] = row.existing?.candidateId ? String(row.existing.candidateId) : '' } }, { immediate: true }) async function saveWinner(categoryId: number) { const candidateId = Number(winnerSelections[categoryId]) if (!candidateId || !seasonDetail.value.id) return savingResultForCategory.value = categoryId adminMessage.value = '' adminError.value = '' try { await store.setAdminResult(seasonDetail.value.id, { categoryId, candidateId }) adminMessage.value = 'Gewinner wurde gespeichert.' } catch (error) { adminError.value = error instanceof Error ? error.message : 'Gewinner konnte nicht gespeichert werden.' } finally { savingResultForCategory.value = null } } async function clearWinner(resultId: number) { if (!seasonDetail.value.id) return deletingResultId.value = resultId adminMessage.value = '' adminError.value = '' try { await store.deleteAdminResult(resultId, seasonDetail.value.id) adminMessage.value = 'Gewinner wurde entfernt.' } catch (error) { adminError.value = error instanceof Error ? error.message : 'Gewinner konnte nicht entfernt werden.' } finally { deletingResultId.value = null } } return { adminError, adminMessage, completionPct, deletingResultId, query, savingResultForCategory, statusFilter, statusFilters, summaryCards, visibleResultRows, winnerSelections, clearWinner, saveWinner, } }