Refactor app architecture and clean local artifacts

This commit is contained in:
AzuTear
2026-06-24 23:43:14 +02:00
parent 17134b3b82
commit fef1d36fe8
274 changed files with 37724 additions and 6065 deletions
@@ -0,0 +1,160 @@
<script setup lang="ts">
import { onBeforeUnmount, onMounted, watch } from 'vue'
import { Sparkles, X } from '@lucide/vue'
import AdminReviewDecisionPanel from './AdminReviewDecisionPanel.vue'
import AdminReviewsHistorySection from './AdminReviewsHistorySection.vue'
import AdminReviewsQueueHeader from './AdminReviewsQueueHeader.vue'
import AdminReviewsQueueList from './AdminReviewsQueueList.vue'
import { useAdminReviewsManager } from './useAdminReviewsManager'
const props = defineProps<{
open: boolean
}>()
const emit = defineEmits<{
close: []
}>()
const {
reviewSaving,
adminMessage,
adminError,
reviewForms,
seasonDetail,
reviewFilter,
categoryFilter,
selectedNominationId,
candidatePlatformOptions,
filteredNominations,
selectedNomination,
reviewStats,
reviewedNominations,
categoryOptions,
selectedCandidateCollision,
selectedRelatedPendingNominations,
selectedNominationSignalSummary,
canApproveSelected,
approveNomination,
rejectNomination,
selectedPlatformValue,
handlePlatformSelection,
extractNominationStreamUrl,
} = useAdminReviewsManager()
function closeModal() {
emit('close')
}
function onKey(event: KeyboardEvent) {
if (event.key === 'Escape' && props.open) {
closeModal()
}
}
watch(
() => props.open,
(open) => {
if (typeof document !== 'undefined') {
document.body.style.overflow = open ? 'hidden' : ''
}
},
{ immediate: true },
)
onMounted(() => window.addEventListener('keydown', onKey))
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey)
if (typeof document !== 'undefined') document.body.style.overflow = ''
})
</script>
<template>
<Teleport to="body">
<div
v-if="open"
class="fixed inset-0 z-50 flex items-center justify-center p-3 sm:p-6"
@click.self="closeModal"
>
<div class="absolute inset-0 bg-violet-950/35 backdrop-blur-sm" @click="closeModal" />
<section class="relative z-10 flex max-h-[calc(100vh-2rem)] w-full max-w-6xl flex-col overflow-hidden rounded-[28px] border border-violet-200/80 bg-white shadow-[0_40px_90px_rgba(76,40,160,0.28)]">
<header class="flex items-start justify-between gap-4 border-b border-violet-100 bg-[linear-gradient(135deg,#f7f2ff,#fff7ed)] px-5 py-4 sm:px-6">
<div class="min-w-0">
<div class="flex items-center gap-2">
<span class="grid h-9 w-9 shrink-0 place-items-center rounded-2xl bg-violet-100 text-violet-700">
<Sparkles class="h-4.5 w-4.5" />
</span>
<p class="text-xs font-semibold uppercase tracking-[0.22em] text-violet-500">Review-Fokus</p>
</div>
<h2 class="mt-3 text-2xl font-bold text-slate-950">Nominierung entscheiden</h2>
<p class="mt-1 max-w-2xl text-sm leading-6 text-slate-500">
Die Nominierungsliste bleibt scanbar; Entscheidungen laufen hier konzentriert mit Queue, Prüfung und Kandidatenübernahme.
</p>
</div>
<button
type="button"
class="grid h-10 w-10 shrink-0 place-items-center rounded-full text-slate-400 transition hover:bg-white/80 hover:text-violet-700"
aria-label="Review-Dialog schließen"
@click="closeModal"
>
<X class="h-5 w-5" />
</button>
</header>
<div class="min-h-0 overflow-y-auto bg-white">
<AdminReviewsQueueHeader
v-model:review-filter="reviewFilter"
v-model:category-filter="categoryFilter"
:total-pending="seasonDetail.pendingNominations.length"
:visible-count="filteredNominations.length"
:review-stats="reviewStats"
:category-options="categoryOptions"
/>
<div class="space-y-4 p-4 sm:p-6">
<p v-if="adminMessage" class="rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700">{{ adminMessage }}</p>
<p v-if="adminError" class="rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700">{{ adminError }}</p>
<div class="grid gap-5 xl:grid-cols-[minmax(300px,0.8fr)_minmax(0,1.2fr)]">
<AdminReviewsQueueList
:nominations="filteredNominations"
:total-pending="seasonDetail.pendingNominations.length"
:selected-nomination-id="selectedNominationId"
@select="selectedNominationId = $event"
/>
<AdminReviewDecisionPanel
:nomination="selectedNomination"
:review-saving="reviewSaving"
:review-form="selectedNomination ? reviewForms[selectedNomination.id] : null"
:candidate-platform-options="candidatePlatformOptions"
:selected-candidate-collision="selectedCandidateCollision"
:selected-related-pending-nominations="selectedRelatedPendingNominations"
:signal-summary="selectedNominationSignalSummary"
:stream-url="selectedNomination ? extractNominationStreamUrl(selectedNomination) : ''"
:can-approve-selected="canApproveSelected"
:selected-platform-value="selectedPlatformValue"
@platform-change="handlePlatformSelection"
@approve="approveNomination"
@reject="rejectNomination"
/>
</div>
<details class="rounded-[24px] border border-violet-100 bg-violet-50/40">
<summary class="cursor-pointer px-5 py-4 text-sm font-semibold text-violet-900">
Letzte Entscheidungen anzeigen
</summary>
<div class="border-t border-violet-100 p-5">
<AdminReviewsHistorySection
:reviewed-nominations="reviewedNominations"
:reviewed-total="seasonDetail.reviewedNominations.length"
/>
</div>
</details>
</div>
</div>
</section>
</div>
</Teleport>
</template>