Refine admin panel workflows

This commit is contained in:
AzuTear
2026-06-18 14:29:40 +02:00
parent f4512eba2e
commit d015fcbe6f
14 changed files with 481 additions and 598 deletions
+17 -56
View File
@@ -12,11 +12,9 @@ const riskSaving = ref<number | null>(null)
const adminMessage = ref('')
const adminError = ref('')
const riskFilter = ref('')
const auditFilter = ref('')
const severityFilter = ref<'all' | 'high' | 'medium' | 'low'>('all')
const riskFlags = computed(() => store.admin.riskFlags)
const auditEntries = computed(() => store.admin.auditEntries)
const filteredRiskFlags = computed(() => {
const query = riskFilter.value.trim().toLowerCase()
return riskFlags.value.filter((flag) =>
@@ -27,16 +25,6 @@ const filteredRiskFlags = computed(() => {
.includes(query)),
)
})
const filteredAuditEntries = computed(() => {
const query = auditFilter.value.trim().toLowerCase()
if (!query) return auditEntries.value
return auditEntries.value.filter((entry) =>
[entry.summary, entry.adminTwitchUserId, entry.actionType, entry.entityType, entry.entityId]
.join(' ')
.toLowerCase()
.includes(query),
)
})
const riskStats = computed(() => [
{ label: 'Offen', value: riskFlags.value.length },
{ label: 'High', value: riskFlags.value.filter((flag) => flag.severity.toLowerCase() === 'high').length },
@@ -68,13 +56,13 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
<template>
<div class="space-y-6">
<AdminPageHeader
eyebrow="Risiko & Audit"
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."
eyebrow="Risiko"
title="Auffällige Muster entscheiden"
description="Dieser Bereich ist nur für operative Risiko-Sichtung zuständig: Voting-, Login- und Einreichungsmuster prüfen, verwerfen oder erledigt markieren. Audit-Logs liegen separat im Team-Audit."
:icon="ShieldAlert"
/>
<div class="grid gap-6 xl:grid-cols-[0.95fr_1.05fr]">
<div class="grid gap-6 xl:grid-cols-[0.82fr_1.18fr]">
<Card class="p-7">
<div class="flex items-center justify-between gap-4">
<div>
@@ -167,48 +155,21 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
</Card>
<Card class="p-7">
<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 für Kategorie-, Kandidaten- und Review-Änderungen.</p>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-violet-500">Review-Protokoll</p>
<h2 class="mt-2 font-[Cormorant_Garamond] text-4xl text-violet-800">Risk Playbook</h2>
<div class="mt-6 space-y-3">
<div class="rounded-[22px] border border-violet-100 bg-white/90 p-4">
<p class="font-semibold text-slate-900">1. Quelle prüfen</p>
<p class="mt-1 text-sm leading-6 text-slate-500">Vote-Flags vor Ergebnisfreigabe priorisieren, Clip-Flags vor Public-Einbindung, Login-Flags bei wiederholten IP-Mustern.</p>
</div>
<span class="text-sm uppercase tracking-[0.2em] text-slate-500">
{{ filteredAuditEntries.length }} / {{ auditEntries.length }} Einträge
</span>
</div>
<div class="mt-6">
<input
v-model="auditFilter"
type="text"
class="w-full rounded-2xl border border-violet-200 px-4 py-3"
placeholder="Audit-Einträge nach Aktion, Admin oder Objekt filtern"
/>
</div>
<div class="mt-6 space-y-4">
<div
v-for="entry in filteredAuditEntries"
:key="entry.id"
class="rounded-[26px] border border-violet-100 bg-violet-50/60 px-5 py-5"
>
<div class="flex flex-wrap items-start justify-between gap-4">
<div>
<p class="font-semibold text-slate-800">{{ entry.summary }}</p>
<p class="mt-1 text-sm text-slate-500">
{{ entry.adminTwitchUserId }} · {{ entry.actionType }} · {{ entry.entityType }} {{ entry.entityId }}
</p>
</div>
<p class="text-sm text-slate-500">{{ new Date(entry.createdAt).toLocaleString('de-DE') }}</p>
</div>
<div class="rounded-[22px] border border-violet-100 bg-white/90 p-4">
<p class="font-semibold text-slate-900">2. Entscheidung dokumentieren</p>
<p class="mt-1 text-sm leading-6 text-slate-500">Erledigt bedeutet geprüft und relevant; Verwerfen bedeutet false positive oder kein Award-Risiko.</p>
</div>
<div class="rounded-[22px] border border-violet-100 bg-white/90 p-4">
<p class="font-semibold text-slate-900">3. Audit separat lesen</p>
<p class="mt-1 text-sm leading-6 text-slate-500">Admin-Aktionen findest du im Team-Audit, damit Risikoentscheidungen nicht mit normalen Bearbeitungen vermischt werden.</p>
</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-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-Einträge passen zum aktuellen Filter.
</p>
</div>
</Card>
</div>