Add viewer-range categories, nomination tracking, dynamic showact form, session timeout, share URLs, and workflow-per-season
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>
This commit is contained in:
@@ -23,6 +23,9 @@ const {
|
||||
winnerSelections,
|
||||
clearWinner,
|
||||
saveWinner,
|
||||
workflowRuleSummary,
|
||||
selectedWinnerIdentitySummary,
|
||||
selectedWinnerClipPreview,
|
||||
winnerRuleNoticeFor,
|
||||
} = useAdminWinnersManager()
|
||||
|
||||
@@ -101,6 +104,25 @@ watchAdminToast(adminMessage, adminError)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
<span
|
||||
class="rounded-full border px-3 py-1.5 text-xs font-semibold"
|
||||
:class="workflowRuleSummary.maxWinnerPlacements?.enabled ? 'border-violet-200 bg-violet-50 text-violet-800' : 'border-slate-200 bg-slate-50 text-slate-600'"
|
||||
>
|
||||
{{ workflowRuleSummary.maxWinnerPlacements?.enabled
|
||||
? `Gewinnerlimit aktiv: ${workflowRuleSummary.maxWinnerPlacements.limit}x pro Person`
|
||||
: 'Gewinnerlimit aus' }}
|
||||
</span>
|
||||
<span
|
||||
class="rounded-full border px-3 py-1.5 text-xs font-semibold"
|
||||
:class="workflowRuleSummary.winnerRequiresClip?.enabled ? 'border-amber-200 bg-amber-50 text-amber-800' : 'border-slate-200 bg-slate-50 text-slate-600'"
|
||||
>
|
||||
{{ workflowRuleSummary.winnerRequiresClip?.enabled
|
||||
? `Clip-Pflicht ${workflowRuleSummary.winnerRequiresClip.mode === 'warn' ? 'als Warnung' : 'als Blocker'}`
|
||||
: 'Clip-Pflicht aus' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="visibleResultRows.length" class="divide-y divide-violet-50">
|
||||
@@ -155,6 +177,69 @@ watchAdminToast(adminMessage, adminError)
|
||||
{{ winnerRuleNoticeFor(row.category.id)?.mode === 'block' ? 'Blockiert' : 'Warnung' }}:
|
||||
{{ winnerRuleNoticeFor(row.category.id)?.message }}
|
||||
</p>
|
||||
|
||||
<div
|
||||
v-if="selectedWinnerIdentitySummary(row.category.id)"
|
||||
class="grid gap-2 sm:grid-cols-4"
|
||||
>
|
||||
<div class="rounded-2xl border border-violet-100 bg-violet-50/40 px-3 py-2">
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-500">Kandidaturen</p>
|
||||
<strong class="mt-1 block text-sm text-violet-900">{{ selectedWinnerIdentitySummary(row.category.id)?.appearances }}</strong>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-violet-100 bg-violet-50/40 px-3 py-2">
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-500">Gewinner</p>
|
||||
<strong class="mt-1 block text-sm text-violet-900">{{ selectedWinnerIdentitySummary(row.category.id)?.winnerPlacements }}</strong>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-violet-100 bg-violet-50/40 px-3 py-2">
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-500">Viewer-Nominierungen</p>
|
||||
<strong class="mt-1 block text-sm text-violet-900">{{ selectedWinnerIdentitySummary(row.category.id)?.nominationTally }}</strong>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-violet-100 bg-violet-50/40 px-3 py-2">
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-500">Clip</p>
|
||||
<strong class="mt-1 block text-sm text-violet-900">{{ selectedWinnerIdentitySummary(row.category.id)?.hasClip ? 'vorhanden' : 'fehlt' }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="selectedWinnerClipPreview(row.category.id)"
|
||||
class="rounded-[22px] border border-violet-100 bg-violet-50/40 p-3"
|
||||
>
|
||||
<div class="mb-2 flex items-center justify-between gap-3">
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.14em] text-slate-500">Winner Preview</p>
|
||||
<span class="rounded-full border border-violet-100 bg-white px-2.5 py-1 text-[10px] font-semibold text-violet-700">
|
||||
{{ selectedWinnerClipPreview(row.category.id)?.clipPlatform }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<iframe
|
||||
v-if="selectedWinnerClipPreview(row.category.id)?.clipEmbedUrl"
|
||||
:src="selectedWinnerClipPreview(row.category.id)?.clipEmbedUrl ?? undefined"
|
||||
:title="selectedWinnerClipPreview(row.category.id)?.clipEmbedTitle"
|
||||
loading="lazy"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
class="w-full rounded-2xl border-0 bg-slate-950"
|
||||
style="aspect-ratio:16/9;"
|
||||
/>
|
||||
|
||||
<a
|
||||
v-else-if="selectedWinnerClipPreview(row.category.id)?.clipUrl"
|
||||
:href="selectedWinnerClipPreview(row.category.id)?.clipUrl ?? undefined"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex aspect-[16/9] items-center justify-center rounded-2xl border border-violet-100 bg-white px-4 text-center text-sm font-semibold text-violet-700 hover:bg-violet-100/60"
|
||||
>
|
||||
{{ selectedWinnerClipPreview(row.category.id)?.clipTitle }}
|
||||
</a>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="flex aspect-[16/9] items-center justify-center rounded-2xl border border-dashed border-amber-200 bg-amber-50 px-4 text-center text-sm font-semibold text-amber-700"
|
||||
>
|
||||
Noch kein Clip-Link gepflegt.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap justify-end gap-2">
|
||||
|
||||
Reference in New Issue
Block a user