Refine admin workflows and team access

This commit is contained in:
AzuTear
2026-06-26 18:09:29 +02:00
parent b7804e10ea
commit 8b69dfbafb
71 changed files with 5066 additions and 751 deletions
@@ -32,6 +32,7 @@ export function useAdminAnalyticsManager() {
reviews,
hasWinner,
votes,
votePct: totalVotes.value > 0 ? Math.round((votes / totalVotes.value) * 100) : 0,
status,
statusClass: candidates === 0
? 'border-rose-100 bg-rose-50 text-rose-700'
@@ -40,20 +41,37 @@ export function useAdminAnalyticsManager() {
: hasWinner
? 'border-emerald-100 bg-emerald-50 text-emerald-700'
: 'border-sky-100 bg-sky-50 text-sky-700',
statusDot: candidates === 0 ? 'bg-rose-400' : reviews > 0 ? 'bg-amber-400' : hasWinner ? 'bg-emerald-400' : 'bg-sky-400',
}
})
.sort((a, b) => b.reviews - a.reviews || a.candidates - b.candidates || b.votes - a.votes),
)
const categoryGroups = computed(() => {
const groups = new Map<string, typeof categoryHealth.value>()
for (const cat of categoryHealth.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 }))
})
const emptyCategories = computed(() => categoryHealth.value.filter((category) => category.candidates === 0))
const categoriesWithReviews = computed(() => categoryHealth.value.filter((category) => category.reviews > 0))
const categoriesWithoutWinner = computed(() => categoryHealth.value.filter((category) => !category.hasWinner))
const categoriesReady = computed(() => categoryHealth.value.filter((c) => c.status === 'Bereit' || c.status === 'Gewinner gesetzt'))
const pendingClipCount = computed(() => seasonDetail.value.clipSubmissions.filter((clip) => clip.status === 'pending').length)
const winnerCoveragePct = computed(() => {
const categoryCount = seasonDetail.value.categories.length
if (categoryCount === 0) return 0
return Math.round((seasonDetail.value.results.length / categoryCount) * 100)
})
const readinessPct = computed(() => {
const total = categoryHealth.value.length
if (total === 0) return 0
return Math.round((categoriesReady.value.length / total) * 100)
})
const metricCards = computed(() => [
{ label: 'Nominierungen', value: totalNominations.value, note: 'eingereicht im Jahr', icon: Sparkles, tone: 'text-fuchsia-700 bg-fuchsia-50 border-fuchsia-100' },
@@ -140,14 +158,37 @@ export function useAdminAnalyticsManager() {
},
])
// Top categories enriched with vote share %
const topCategoriesEnriched = computed(() =>
topCategories.value.map((cat) => ({
...cat,
pct: totalVotes.value > 0 ? Math.round((cat.votes / totalVotes.value) * 100) : 0,
barWidth: totalVotes.value > 0 ? Math.max(2, Math.round((cat.votes / maxVotes.value) * 100)) : 2,
})),
)
// Health counts for status summary
const healthSummary = computed(() => ({
leer: emptyCategories.value.length,
reviewOffen: categoriesWithReviews.value.length,
bereit: categoryHealth.value.filter((c) => c.status === 'Bereit').length,
gewinner: categoryHealth.value.filter((c) => c.status === 'Gewinner gesetzt').length,
total: categoryHealth.value.length,
}))
return {
categoryHealth,
categoryGroups,
metricCards,
readinessCards,
insightCards,
attentionItems,
topCategories,
topCategoriesEnriched,
maxVotes,
winnerCoveragePct,
readinessPct,
healthSummary,
totalVotes,
}
}