frontend admin refakctorálás

This commit is contained in:
Roo
2026-07-08 08:03:57 +00:00
parent 07b59032ce
commit 2e0abc62a7
455 changed files with 14428 additions and 21651 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,30 +2,30 @@
<div>
<!-- Page Header -->
<div class="mb-8">
<h1 class="text-2xl font-bold text-white">Szolgáltatók</h1>
<p class="text-slate-400 mt-1">Szolgáltatók listázása, keresése és moderálása</p>
<h1 class="text-2xl font-bold text-white">{{ $t('providers.title') }}</h1>
<p class="text-slate-400 mt-1">{{ $t('providers.subtitle') }}</p>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-8">
<div class="bg-slate-800 rounded-xl border border-slate-700 p-4">
<p class="text-xs text-slate-400 uppercase tracking-wider">Összes</p>
<p class="text-xs text-slate-400 uppercase tracking-wider">{{ $t('providers.stats_total') }}</p>
<p class="text-2xl font-bold text-white mt-1">{{ stats.total }}</p>
</div>
<div class="bg-slate-800 rounded-xl border border-amber-500/30 p-4">
<p class="text-xs text-slate-400 uppercase tracking-wider">Függőben</p>
<p class="text-xs text-slate-400 uppercase tracking-wider">{{ $t('providers.stats_pending') }}</p>
<p class="text-2xl font-bold text-amber-400 mt-1">{{ stats.pending }}</p>
</div>
<div class="bg-slate-800 rounded-xl border border-emerald-500/30 p-4">
<p class="text-xs text-slate-400 uppercase tracking-wider">Jóváhagyott</p>
<p class="text-xs text-slate-400 uppercase tracking-wider">{{ $t('providers.stats_approved') }}</p>
<p class="text-2xl font-bold text-emerald-400 mt-1">{{ stats.approved }}</p>
</div>
<div class="bg-slate-800 rounded-xl border border-rose-500/30 p-4">
<p class="text-xs text-slate-400 uppercase tracking-wider">Elutasított</p>
<p class="text-xs text-slate-400 uppercase tracking-wider">{{ $t('providers.stats_rejected') }}</p>
<p class="text-2xl font-bold text-rose-400 mt-1">{{ stats.rejected }}</p>
</div>
<div class="bg-slate-800 rounded-xl border border-purple-500/30 p-4">
<p class="text-xs text-slate-400 uppercase tracking-wider">Megjelölt</p>
<p class="text-xs text-slate-400 uppercase tracking-wider">{{ $t('providers.stats_flagged') }}</p>
<p class="text-2xl font-bold text-purple-400 mt-1">{{ stats.flagged }}</p>
</div>
</div>
@@ -36,14 +36,14 @@
<svg class="w-5 h-5 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
<span>Szolgáltatók betöltése...</span>
<span>{{ $t('providers.loading') }}</span>
</div>
</div>
<!-- Error State -->
<div v-else-if="error" class="bg-rose-500/10 border border-rose-500/30 rounded-xl p-6 mb-8">
<p class="text-rose-400">Hiba történt a szolgáltatók betöltése közben.</p>
<button @click="fetchProviders" class="mt-3 text-sm text-rose-300 hover:text-rose-200 underline">Újrapróbálkozás</button>
<p class="text-rose-400">{{ $t('providers.error') }}</p>
<button @click="fetchProviders" class="mt-3 text-sm text-rose-300 hover:text-rose-200 underline">{{ $t('providers.retry') }}</button>
</div>
<template v-else>
@@ -55,7 +55,7 @@
<input
v-model="filters.search"
type="text"
placeholder="Keresés név alapján..."
:placeholder="$t('providers.search_placeholder')"
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm"
@input="onSearchInput"
/>
@@ -67,11 +67,11 @@
class="px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm"
@change="fetchProviders"
>
<option value="">Minden státusz</option>
<option value="pending">Függőben</option>
<option value="approved">Jóváhagyott</option>
<option value="rejected">Elutasított</option>
<option value="flagged">Megjelölt</option>
<option value="">{{ $t('providers.filter_all_status') }}</option>
<option value="pending">{{ $t('providers.filter_pending') }}</option>
<option value="approved">{{ $t('providers.filter_approved') }}</option>
<option value="rejected">{{ $t('providers.filter_rejected') }}</option>
<option value="flagged">{{ $t('providers.filter_flagged') }}</option>
</select>
<!-- Source Filter -->
@@ -80,12 +80,12 @@
class="px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm"
@change="fetchProviders"
>
<option value="">Minden forrás</option>
<option value="manual">Kézi</option>
<option value="ocr">OCR</option>
<option value="import">Import</option>
<option value="bot">Bot (Staging)</option>
<option value="verified_org">Szervezet</option>
<option value="">{{ $t('providers.filter_all_sources') }}</option>
<option value="manual">{{ $t('providers.source_manual') }}</option>
<option value="ocr">{{ $t('providers.source_ocr') }}</option>
<option value="import">{{ $t('providers.source_import') }}</option>
<option value="bot">{{ $t('providers.source_bot') }}</option>
<option value="verified_org">{{ $t('providers.source_verified_org') }}</option>
</select>
<!-- Sort -->
@@ -94,9 +94,9 @@
class="px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm"
@change="fetchProviders"
>
<option value="created_at">Létrehozás dátuma</option>
<option value="name">Név</option>
<option value="validation_score">Validációs pontszám</option>
<option value="created_at">{{ $t('providers.sort_created_at') }}</option>
<option value="name">{{ $t('providers.sort_name') }}</option>
<option value="validation_score">{{ $t('providers.sort_validation_score') }}</option>
</select>
<button
@@ -106,7 +106,7 @@
<svg class="w-4 h-4" :class="filters.sort_order === 'asc' ? 'rotate-180' : ''" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
{{ filters.sort_order === 'asc' ? 'Növekvő' : 'Csökkenő' }}
{{ filters.sort_order === 'asc' ? $t('common.ascending') : $t('common.descending') }}
</button>
</div>
</div>
@@ -116,14 +116,14 @@
<table class="w-full">
<thead>
<tr class="border-b border-slate-700">
<th class="text-left px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">ID</th>
<th class="text-left px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">Név</th>
<th class="text-left px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">Cím</th>
<th class="text-left px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">Státusz</th>
<th class="text-left px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">Forrás</th>
<th class="text-left px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">Validáció</th>
<th class="text-left px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">Létrehozva</th>
<th class="text-right px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">Műveletek</th>
<th class="text-left px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">{{ $t('providers.col_id') }}</th>
<th class="text-left px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">{{ $t('providers.col_name') }}</th>
<th class="text-left px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">{{ $t('providers.col_address') }}</th>
<th class="text-left px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">{{ $t('providers.col_status') }}</th>
<th class="text-left px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">{{ $t('providers.col_source') }}</th>
<th class="text-left px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">{{ $t('providers.col_validation') }}</th>
<th class="text-left px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">{{ $t('providers.col_created') }}</th>
<th class="text-right px-6 py-4 text-xs font-medium text-slate-400 uppercase tracking-wider">{{ $t('providers.col_actions') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-700/50">
@@ -134,7 +134,7 @@
{{ provider.name }}
</NuxtLink>
</td>
<td class="px-6 py-4 text-sm text-slate-300 max-w-[200px] truncate">{{ provider.address || '—' }}</td>
<td class="px-6 py-4 text-sm text-slate-300 max-w-[200px] truncate">{{ provider.address_detail?.full_address_text || [provider.address_detail?.zip, provider.address_detail?.city, provider.address_detail?.street_name].filter(Boolean).join(' ') || '—' }}</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium" :class="statusClass(provider.status)">
{{ statusLabel(provider.status) }}
@@ -164,7 +164,7 @@
<NuxtLink
:to="`/providers/${provider.id}`"
class="p-1.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-700 transition"
title="Részletek"
:title="$t('providers.view_details')"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
@@ -176,7 +176,7 @@
</tr>
<tr v-if="providers.length === 0">
<td colspan="8" class="px-6 py-12 text-center text-sm text-slate-500">
Nincs megjeleníthető szolgáltató.
{{ $t('providers.no_providers') }}
</td>
</tr>
</tbody>
@@ -186,7 +186,7 @@
<!-- Pagination -->
<div class="flex items-center justify-between mt-6">
<p class="text-sm text-slate-400">
{{ providers.length }} találat (összesen: {{ stats.total }})
{{ $t('providers.results_count', { count: providers.length, total: stats.total }) }}
</p>
<div class="flex items-center gap-2">
<button
@@ -194,17 +194,17 @@
@click="filters.skip = Math.max(0, filters.skip - filters.limit); fetchProviders()"
class="px-3 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white disabled:opacity-50 disabled:cursor-not-allowed hover:bg-slate-600 transition text-sm"
>
Előző
{{ $t('common.prev') }}
</button>
<span class="text-sm text-slate-400">
{{ Math.floor(filters.skip / filters.limit) + 1 }}. oldal
{{ $t('providers.page', { page: Math.floor(filters.skip / filters.limit) + 1 }) }}
</span>
<button
:disabled="providers.length < filters.limit"
@click="filters.skip += filters.limit; fetchProviders()"
class="px-3 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white disabled:opacity-50 disabled:cursor-not-allowed hover:bg-slate-600 transition text-sm"
>
Következő
{{ $t('common.next') }}
</button>
</div>
</div>
@@ -225,6 +225,7 @@ definePageMeta({
middleware: 'auth',
})
const { t } = useI18n()
const { formatDate } = useFormatter()
interface ProviderListItem {
@@ -232,6 +233,14 @@ interface ProviderListItem {
name: string
address: string | null
city: string | null
address_detail?: {
zip: string | null
city: string | null
street_name: string | null
street_type: string | null
house_number: string | null
full_address_text: string | null
} | null
category: string | null
status: string
source: string
@@ -299,10 +308,10 @@ function statusClass(status: string): string {
function statusLabel(status: string): string {
const map: Record<string, string> = {
pending: 'Függőben',
approved: 'Jóváhagyott',
rejected: 'Elutasított',
flagged: 'Megjelölt',
pending: t('providers.status_pending'),
approved: t('providers.status_approved'),
rejected: t('providers.status_rejected'),
flagged: t('providers.status_flagged'),
}
return map[status] || status
}

View File

@@ -2,8 +2,8 @@
<div>
<!-- Page Header -->
<div class="mb-8">
<h1 class="text-2xl font-bold text-white">Jóváhagyásra váró szolgáltatók</h1>
<p class="text-slate-400 mt-1">A közösség által beküldött szolgáltatók moderációs sora</p>
<h1 class="text-2xl font-bold text-white">{{ $t('providers.pending_title') }}</h1>
<p class="text-slate-400 mt-1">{{ $t('providers.pending_subtitle') }}</p>
</div>
<!-- Loading State -->
@@ -12,14 +12,14 @@
<svg class="w-5 h-5 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
<span>Függőben lévő szolgáltatók betöltése...</span>
<span>{{ $t('providers.pending_loading') }}</span>
</div>
</div>
<!-- Error State -->
<div v-else-if="error" class="bg-rose-500/10 border border-rose-500/30 rounded-xl p-6 mb-8">
<p class="text-rose-400">Hiba történt a szolgáltatók betöltése közben.</p>
<button @click="fetchPendingProviders" class="mt-3 text-sm text-rose-300 hover:text-rose-200 underline">Újrapróbálkozás</button>
<p class="text-rose-400">{{ $t('providers.error') }}</p>
<button @click="fetchPendingProviders" class="mt-3 text-sm text-rose-300 hover:text-rose-200 underline">{{ $t('providers.retry') }}</button>
</div>
<template v-else>
@@ -34,29 +34,31 @@
<!-- Left: Provider Info -->
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 mb-2">
<h3 class="text-lg font-semibold text-white truncate">{{ provider.name }}</h3>
<NuxtLink :to="`/providers/${provider.id}`" class="text-lg font-semibold text-blue-400 hover:text-blue-300 hover:underline cursor-pointer transition-colors truncate">
{{ provider.name }}
</NuxtLink>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-amber-500/10 text-amber-400">
Függőben
{{ $t('providers.status_pending') }}
</span>
<code class="text-xs font-mono text-slate-400 bg-slate-700/50 px-2 py-0.5 rounded">{{ provider.source }}</code>
</div>
<div class="grid grid-cols-2 gap-x-8 gap-y-2 text-sm">
<div v-if="provider.address" class="text-slate-300">
<span class="text-slate-500">Cím:</span> {{ provider.address }}
<span class="text-slate-500">{{ $t('providers.label_address') }}</span> {{ provider.address }}
</div>
<div v-if="provider.city" class="text-slate-300">
<span class="text-slate-500">Város:</span> {{ provider.city }}
<span class="text-slate-500">{{ $t('providers.label_city') }}</span> {{ provider.city }}
</div>
<div v-if="provider.category" class="text-slate-300">
<span class="text-slate-500">Kategória:</span> {{ provider.category }}
<span class="text-slate-500">{{ $t('providers.label_category') }}</span> {{ provider.category }}
</div>
<div class="text-slate-300">
<span class="text-slate-500">Validációs pontszám:</span>
<span class="text-slate-500">{{ $t('providers.label_validation_score') }}</span>
<span class="text-amber-400 font-medium ml-1">{{ provider.validation_score }}</span>
</div>
<div v-if="provider.created_at" class="text-slate-300">
<span class="text-slate-500">Beküldve:</span> {{ formatDate(provider.created_at) }}
<span class="text-slate-500">{{ $t('providers.label_submitted') }}</span> {{ formatDate(provider.created_at) }}
</div>
</div>
</div>
@@ -74,7 +76,7 @@
<svg v-else class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
Jóváhagyás
{{ $t('providers.approve') }}
</button>
<button
@click="openRejectModal(provider)"
@@ -84,7 +86,7 @@
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
Elutasítás
{{ $t('providers.reject') }}
</button>
<NuxtLink
:to="`/providers/${provider.id}`"
@@ -94,7 +96,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
Részletek
{{ $t('providers.details') }}
</NuxtLink>
</div>
</div>
@@ -105,8 +107,8 @@
<svg class="w-12 h-12 mx-auto text-slate-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<h3 class="text-lg font-medium text-slate-400 mb-1">Nincs függőben lévő szolgáltató</h3>
<p class="text-sm text-slate-500">Minden beküldött szolgáltató fel lett dolgozva.</p>
<h3 class="text-lg font-medium text-slate-400 mb-1">{{ $t('providers.no_pending') }}</h3>
<p class="text-sm text-slate-500">{{ $t('providers.no_pending_subtitle') }}</p>
</div>
</div>
</template>
@@ -114,29 +116,29 @@
<!-- Reject Modal -->
<div v-if="showRejectModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" @click.self="showRejectModal = false">
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6 w-full max-w-lg mx-4 shadow-2xl">
<h2 class="text-lg font-semibold text-white mb-2">Szolgáltató elutasítása</h2>
<h2 class="text-lg font-semibold text-white mb-2">{{ $t('providers.reject_modal_title') }}</h2>
<p class="text-sm text-slate-400 mb-6">
Biztosan elutasítod a(z) <strong class="text-white">{{ rejectingProvider?.name }}</strong> szolgáltatót?
{{ $t('providers.reject_modal_body', { name: rejectingProvider?.name }) }}
</p>
<form @submit.prevent="rejectProvider">
<div class="mb-4">
<label class="block text-sm font-medium text-slate-300 mb-1">Indoklás (opcionális)</label>
<label class="block text-sm font-medium text-slate-300 mb-1">{{ $t('providers.reject_reason_label') }}</label>
<textarea
v-model="rejectReason"
rows="3"
placeholder="Add meg az elutasítás okát..."
:placeholder="$t('providers.reject_reason_placeholder')"
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-rose-500/50 focus:border-rose-500 text-sm resize-none"
></textarea>
</div>
<div class="flex justify-end gap-3">
<button type="button" @click="showRejectModal = false" class="px-4 py-2 text-sm text-slate-400 hover:text-white transition">Mégse</button>
<button type="button" @click="showRejectModal = false" class="px-4 py-2 text-sm text-slate-400 hover:text-white transition">{{ $t('common.cancel') }}</button>
<button type="submit" :disabled="rejectLoading" class="px-4 py-2 bg-rose-600 hover:bg-rose-500 disabled:bg-rose-600/50 text-white rounded-lg text-sm font-medium transition flex items-center gap-2">
<svg v-if="rejectLoading" class="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Elutasítás
{{ $t('providers.reject') }}
</button>
</div>
</form>
@@ -158,6 +160,7 @@ definePageMeta({
middleware: 'auth',
})
const { t } = useI18n()
const { formatDate } = useFormatter()
interface ProviderListItem {
@@ -217,11 +220,11 @@ async function approveProvider(provider: ProviderListItem) {
headers: getHeaders(),
body: { reason: 'Admin approved via pending queue' },
})
showToast(`"${provider.name}" sikeresen jóváhagyva`)
showToast(t('providers.approved_toast', { name: provider.name }))
providers.value = providers.value.filter(p => p.id !== provider.id)
} catch (e: any) {
const detail = e?.response?._data?.detail || e?.data?.detail || 'Ismeretlen hiba'
showToast(`Hiba: ${detail}`)
const detail = e?.response?._data?.detail || e?.data?.detail || t('providers.unknown_error')
showToast(t('providers.error_prefix', { detail }))
console.error('Failed to approve provider:', e)
} finally {
actionLoading.value = null
@@ -243,13 +246,13 @@ async function rejectProvider() {
headers: getHeaders(),
body: { reason: rejectReason.value || 'Admin rejected via pending queue' },
})
showToast(`"${rejectingProvider.value.name}" elutasítva`)
showToast(t('providers.rejected_toast', { name: rejectingProvider.value.name }))
providers.value = providers.value.filter(p => p.id !== rejectingProvider.value!.id)
showRejectModal.value = false
rejectingProvider.value = null
} catch (e: any) {
const detail = e?.response?._data?.detail || e?.data?.detail || 'Ismeretlen hiba'
showToast(`Hiba: ${detail}`)
const detail = e?.response?._data?.detail || e?.data?.detail || t('providers.unknown_error')
showToast(t('providers.error_prefix', { detail }))
console.error('Failed to reject provider:', e)
} finally {
rejectLoading.value = false

View File

@@ -2,8 +2,8 @@
<div>
<!-- Page Header -->
<div class="mb-8">
<h1 class="text-2xl font-bold text-white">Elutasított szolgáltatók</h1>
<p class="text-slate-400 mt-1">Elutasított szolgáltatók visszaállítása az aktív kereshető szolgáltatók közé</p>
<h1 class="text-2xl font-bold text-white">{{ $t('providers.rejected_title') }}</h1>
<p class="text-slate-400 mt-1">{{ $t('providers.rejected_subtitle') }}</p>
</div>
<!-- Loading State -->
@@ -12,14 +12,14 @@
<svg class="w-5 h-5 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
<span>Elutasított szolgáltatók betöltése...</span>
<span>{{ $t('providers.rejected_loading') }}</span>
</div>
</div>
<!-- Error State -->
<div v-else-if="error" class="bg-rose-500/10 border border-rose-500/30 rounded-xl p-6 mb-8">
<p class="text-rose-400">Hiba történt a szolgáltatók betöltése közben.</p>
<button @click="fetchRejectedProviders" class="mt-3 text-sm text-rose-300 hover:text-rose-200 underline">Újrapróbálkozás</button>
<p class="text-rose-400">{{ $t('providers.error') }}</p>
<button @click="fetchRejectedProviders" class="mt-3 text-sm text-rose-300 hover:text-rose-200 underline">{{ $t('providers.retry') }}</button>
</div>
<template v-else>
@@ -36,7 +36,7 @@
<div class="flex items-center gap-3 mb-2">
<h3 class="text-lg font-semibold text-white truncate">{{ provider.name }}</h3>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-rose-500/10 text-rose-400">
Elutasított
{{ $t('providers.status_rejected') }}
</span>
<code class="text-xs font-mono text-slate-400 bg-slate-700/50 px-2 py-0.5 rounded">{{ provider.source }}</code>
<span v-if="provider.source_table" class="inline-flex items-center px-2 py-0.5 rounded text-xs font-mono bg-slate-700/50 text-slate-400">
@@ -46,20 +46,20 @@
<div class="grid grid-cols-2 gap-x-8 gap-y-2 text-sm">
<div v-if="provider.address" class="text-slate-300">
<span class="text-slate-500">Cím:</span> {{ provider.address }}
<span class="text-slate-500">{{ $t('providers.label_address') }}</span> {{ provider.address }}
</div>
<div v-if="provider.city" class="text-slate-300">
<span class="text-slate-500">Város:</span> {{ provider.city }}
<span class="text-slate-500">{{ $t('providers.label_city') }}</span> {{ provider.city }}
</div>
<div v-if="provider.category" class="text-slate-300">
<span class="text-slate-500">Kategória:</span> {{ provider.category }}
<span class="text-slate-500">{{ $t('providers.label_category') }}</span> {{ provider.category }}
</div>
<div class="text-slate-300">
<span class="text-slate-500">Validációs pontszám:</span>
<span class="text-slate-500">{{ $t('providers.label_validation_score') }}</span>
<span class="text-rose-400 font-medium ml-1">{{ provider.validation_score }}</span>
</div>
<div v-if="provider.created_at" class="text-slate-300">
<span class="text-slate-500">Létrehozva:</span> {{ formatDate(provider.created_at) }}
<span class="text-slate-500">{{ $t('providers.label_created') }}</span> {{ formatDate(provider.created_at) }}
</div>
</div>
</div>
@@ -77,7 +77,7 @@
<svg v-else class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Visszaállítás
{{ $t('providers.restore') }}
</button>
<NuxtLink
:to="`/providers/${provider.id}`"
@@ -87,7 +87,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
Részletek
{{ $t('providers.details') }}
</NuxtLink>
</div>
</div>
@@ -98,8 +98,8 @@
<svg class="w-12 h-12 mx-auto text-slate-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<h3 class="text-lg font-medium text-slate-400 mb-1">Nincs elutasított szolgáltató</h3>
<p class="text-sm text-slate-500">Minden szolgáltató jóváhagyott státuszban van.</p>
<h3 class="text-lg font-medium text-slate-400 mb-1">{{ $t('providers.no_rejected') }}</h3>
<p class="text-sm text-slate-500">{{ $t('providers.no_rejected_subtitle') }}</p>
</div>
</div>
</template>
@@ -107,30 +107,29 @@
<!-- Restore Modal -->
<div v-if="showRestoreModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" @click.self="showRestoreModal = false">
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6 w-full max-w-lg mx-4 shadow-2xl">
<h2 class="text-lg font-semibold text-white mb-2">Szolgáltató visszaállítása</h2>
<h2 class="text-lg font-semibold text-white mb-2">{{ $t('providers.restore_modal_title') }}</h2>
<p class="text-sm text-slate-400 mb-6">
Biztosan visszaállítod a(z) <strong class="text-white">{{ restoringProvider?.name }}</strong> szolgáltatót?
A visszaállítás után a szolgáltató újra elérhető lesz a keresőben és a listákban.
{{ $t('providers.restore_modal_body', { name: restoringProvider?.name }) }}
</p>
<form @submit.prevent="restoreProvider">
<div class="mb-4">
<label class="block text-sm font-medium text-slate-300 mb-1">Indoklás (opcionális)</label>
<label class="block text-sm font-medium text-slate-300 mb-1">{{ $t('providers.restore_reason_label') }}</label>
<textarea
v-model="restoreReason"
rows="3"
placeholder="Add meg a visszaállítás okát..."
:placeholder="$t('providers.restore_reason_placeholder')"
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-emerald-500/50 focus:border-emerald-500 text-sm resize-none"
></textarea>
</div>
<div class="flex justify-end gap-3">
<button type="button" @click="showRestoreModal = false" class="px-4 py-2 text-sm text-slate-400 hover:text-white transition">Mégse</button>
<button type="button" @click="showRestoreModal = false" class="px-4 py-2 text-sm text-slate-400 hover:text-white transition">{{ $t('common.cancel') }}</button>
<button type="submit" :disabled="restoreLoading" class="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 disabled:bg-emerald-600/50 text-white rounded-lg text-sm font-medium transition flex items-center gap-2">
<svg v-if="restoreLoading" class="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Visszaállítás
{{ $t('providers.restore') }}
</button>
</div>
</form>
@@ -152,6 +151,7 @@ definePageMeta({
middleware: 'auth',
})
const { t } = useI18n()
const { formatDate } = useFormatter()
interface ProviderListItem {
@@ -218,13 +218,13 @@ async function restoreProvider() {
headers: getHeaders(),
body: { reason: restoreReason.value || 'Admin restored via rejected queue' },
})
showToast(`"${restoringProvider.value.name}" sikeresen visszaállítva`)
showToast(t('providers.restored_toast', { name: restoringProvider.value.name }))
providers.value = providers.value.filter(p => p.id !== restoringProvider.value!.id)
showRestoreModal.value = false
restoringProvider.value = null
} catch (e: any) {
const detail = e?.response?._data?.detail || e?.data?.detail || 'Ismeretlen hiba'
showToast(`Hiba: ${detail}`)
const detail = e?.response?._data?.detail || e?.data?.detail || t('providers.unknown_error')
showToast(t('providers.error_prefix', { detail }))
console.error('Failed to restore provider:', e)
} finally {
restoreLoading.value = false