Align backend with frontend, make clips end-to-end, polish admin

Backend
- Add ClipSubmission entity + table (runtime bootstrapper) and
  POST /api/public/clips (server-derives platform from the link)
- Surface clip submissions in the admin season detail
- Add DELETE candidate/category/clip endpoints with audit entries

Frontend
- Clips admin: real moderation view (list, open link, delete) instead
  of placeholder; wired clipSubmissions through types/api/store
- Categories admin: add delete with confirm modal (matches Candidates)
- Voting ranking bar uses the unified purple gradient
- Fix ASCII transliterations to proper German umlauts across admin
- Remove orphan AdminView 2.vue

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
AzuTear
2026-06-18 07:53:19 +02:00
parent cca297a4eb
commit 24f9a69022
22 changed files with 504 additions and 145 deletions
+10 -10
View File
@@ -69,8 +69,8 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
<div class="space-y-6">
<AdminPageHeader
eyebrow="Risiko & Audit"
title="Auffaellige Muster und Admin-Aktionen verfolgen"
description="Dieser Bereich trennt operative Risiko-Sichtung von der Nachvollziehbarkeit. So findest du sowohl offene Flags als auch bereits ausgefuehrte Eingriffe deutlich schneller."
title="Auffällige Muster und Admin-Aktionen verfolgen"
description="Dieser Bereich trennt operative Risiko-Sichtung von der Nachvollziehbarkeit. So findest du sowohl offene Flags als auch bereits ausgeführte Eingriffe deutlich schneller."
:icon="ShieldAlert"
/>
@@ -78,8 +78,8 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
<Card class="p-7">
<div class="flex items-center justify-between gap-4">
<div>
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Risikopruefung</h2>
<p class="mt-2 text-sm text-slate-500">Auffaellige Login-, Nominierungs- und Voting-Muster fuer die manuelle Sichtung.</p>
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Risikoprüfung</h2>
<p class="mt-2 text-sm text-slate-500">Auffällige Login-, Nominierungs- und Voting-Muster für die manuelle Sichtung.</p>
</div>
<span class="text-sm uppercase tracking-[0.2em] text-slate-500">
{{ filteredRiskFlags.length }} / {{ riskFlags.length }} offen
@@ -110,7 +110,7 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
/>
</label>
<div class="rounded-2xl border border-violet-100 bg-violet-50/60 px-4 py-3 text-sm text-slate-600">
Tipp: Filtere erst auf den Problemtyp und markiere dann nur den geprueften Fall.
Tipp: Filtere erst auf den Problemtyp und markiere dann nur den geprüften Fall.
</div>
</div>
<div class="mt-3 flex flex-wrap gap-2">
@@ -170,10 +170,10 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
<div class="flex items-center justify-between gap-4">
<div>
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Audit-Protokoll</h2>
<p class="mt-2 text-sm text-slate-500">Nachvollziehbare Admin-Aktionen fuer Kategorie-, Kandidaten- und Review-Aenderungen.</p>
<p class="mt-2 text-sm text-slate-500">Nachvollziehbare Admin-Aktionen für Kategorie-, Kandidaten- und Review-Änderungen.</p>
</div>
<span class="text-sm uppercase tracking-[0.2em] text-slate-500">
{{ filteredAuditEntries.length }} / {{ auditEntries.length }} Eintraege
{{ filteredAuditEntries.length }} / {{ auditEntries.length }} Einträge
</span>
</div>
@@ -182,7 +182,7 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
v-model="auditFilter"
type="text"
class="w-full rounded-2xl border border-violet-200 px-4 py-3"
placeholder="Audit-Eintraege nach Aktion, Admin oder Objekt filtern"
placeholder="Audit-Einträge nach Aktion, Admin oder Objekt filtern"
/>
</div>
@@ -204,10 +204,10 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
</div>
<p v-if="auditEntries.length === 0" class="rounded-[26px] border border-dashed border-violet-100 px-5 py-6 text-sm text-slate-500">
Noch keine Audit-Eintraege vorhanden.
Noch keine Audit-Einträge vorhanden.
</p>
<p v-else-if="filteredAuditEntries.length === 0" class="rounded-[26px] border border-dashed border-violet-100 px-5 py-6 text-sm text-slate-500">
Keine Audit-Eintraege passen zum aktuellen Filter.
Keine Audit-Einträge passen zum aktuellen Filter.
</p>
</div>
</Card>