Refactor app architecture and clean local artifacts
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
<script setup lang="ts">
|
||||
import { RefreshCw, Search } from '@lucide/vue'
|
||||
|
||||
import Button from '../ui/Button.vue'
|
||||
|
||||
defineProps<{
|
||||
riskFilter: string
|
||||
severityFilter: 'all' | 'high' | 'medium' | 'low'
|
||||
filteredCount: number
|
||||
totalOpen: number
|
||||
loadedLabel: string
|
||||
loading: boolean
|
||||
message: string
|
||||
error: string
|
||||
stats: Array<{ label: string; value: number; tone: string }>
|
||||
severityFilters: Array<{ key: 'all' | 'high' | 'medium' | 'low'; label: string; count: number }>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:riskFilter': [value: string]
|
||||
'update:severityFilter': [value: 'all' | 'high' | 'medium' | 'low']
|
||||
refresh: []
|
||||
}>()
|
||||
|
||||
function statToneClass(tone: string) {
|
||||
if (tone === 'danger') return 'border-rose-100 bg-rose-50 text-rose-800'
|
||||
if (tone === 'warning') return 'border-amber-100 bg-amber-50 text-amber-800'
|
||||
if (tone === 'ok') return 'border-emerald-100 bg-emerald-50 text-emerald-800'
|
||||
return 'border-violet-100 bg-violet-50 text-violet-800'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="border-b border-violet-100 bg-white/70 p-6">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-slate-900">Risiko-Zentrale</h2>
|
||||
<p class="mt-2 max-w-2xl text-sm leading-6 text-slate-500">
|
||||
Offene Hinweise priorisieren, Entscheidungsnotizen setzen und geprüfte Fälle nachvollziehbar protokollieren.
|
||||
</p>
|
||||
<p class="mt-1 text-xs font-semibold text-slate-400">
|
||||
Geladen: {{ loadedLabel }} · Quelle: /api/admin/risk-flags
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<span class="rounded-2xl border border-violet-100 bg-violet-50 px-4 py-2 text-sm font-semibold text-violet-800">
|
||||
{{ filteredCount }} / {{ totalOpen }} sichtbar
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="gap-2 rounded-2xl border border-violet-200 bg-violet-50 px-4 text-violet-700 shadow-none hover:bg-violet-100"
|
||||
:disabled="loading"
|
||||
@click="emit('refresh')"
|
||||
>
|
||||
<RefreshCw class="h-4 w-4" :class="{ 'animate-spin': loading }" />
|
||||
{{ loading ? 'Lädt ...' : 'Aktualisieren' }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<div
|
||||
v-for="stat in stats"
|
||||
:key="stat.label"
|
||||
class="rounded-2xl border px-4 py-3"
|
||||
:class="statToneClass(stat.tone)"
|
||||
>
|
||||
<p class="text-xs font-semibold text-slate-500">{{ stat.label }}</p>
|
||||
<strong class="mt-1 block text-2xl">{{ stat.value }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 grid gap-4 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-center">
|
||||
<label class="relative block">
|
||||
<Search class="pointer-events-none absolute left-4 top-1/2 h-4 w-4 -translate-y-1/2 text-violet-400" />
|
||||
<input
|
||||
:value="riskFilter"
|
||||
type="text"
|
||||
class="h-12 w-full rounded-2xl border border-violet-200 bg-white pl-11 pr-4 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100"
|
||||
placeholder="Typ, User, IP, Summary oder Metadaten filtern"
|
||||
@input="emit('update:riskFilter', ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="filter in severityFilters"
|
||||
:key="filter.key"
|
||||
type="button"
|
||||
class="rounded-full border px-3 py-2 text-xs font-semibold transition"
|
||||
:class="severityFilter === filter.key ? 'border-violet-200 bg-violet-100 text-violet-800' : 'border-violet-100 bg-white text-slate-600 hover:bg-violet-50'"
|
||||
@click="emit('update:severityFilter', filter.key)"
|
||||
>
|
||||
{{ filter.label }} · {{ filter.count }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="message" class="mt-5 rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700">
|
||||
{{ message }}
|
||||
</p>
|
||||
<p v-if="error" class="mt-5 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700">
|
||||
{{ error }}
|
||||
</p>
|
||||
</section>
|
||||
</template>
|
||||
Reference in New Issue
Block a user