refaktor címjegyzék
This commit is contained in:
@@ -324,49 +324,21 @@
|
||||
═══════════════════════════════════════════════════ -->
|
||||
<div v-if="activeTab === 'location'" class="max-w-3xl">
|
||||
<form @submit.prevent="saveForm">
|
||||
<!-- Smart Address Form (autocomplete zip/city + structured fields) -->
|
||||
<SharedAddressForm v-model="form.address" />
|
||||
|
||||
<!-- Additional location fields -->
|
||||
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6 mb-6">
|
||||
<h2 class="text-lg font-semibold text-white mb-4">Cím adatok</h2>
|
||||
<h2 class="text-lg font-semibold text-white mb-4">További helyszín adatok</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">Teljes cím</label>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">Teljes cím szövege</label>
|
||||
<input
|
||||
v-model="form.address"
|
||||
v-model="form.address.full_address_text"
|
||||
placeholder="Teljes cím szövege"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">Város</label>
|
||||
<input
|
||||
v-model="form.city"
|
||||
placeholder="Budapest"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">Irányítószám</label>
|
||||
<input
|
||||
v-model="form.address_zip"
|
||||
placeholder="1011"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">Utca</label>
|
||||
<input
|
||||
v-model="form.address_street_name"
|
||||
placeholder="Egressy"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">Házszám</label>
|
||||
<input
|
||||
v-model="form.address_house_number"
|
||||
placeholder="4/b"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">Plus Code</label>
|
||||
<input
|
||||
@@ -670,6 +642,21 @@ const form = reactive({
|
||||
} as Record<string, string[]>,
|
||||
opening_hours: {} as Record<string, { open: string; close: string } | null>,
|
||||
is_always_open: false,
|
||||
// Nested address object matching AddressIn schema (used by SharedAddressForm)
|
||||
address: {
|
||||
zip: '',
|
||||
city: '',
|
||||
street_name: '',
|
||||
street_type: '',
|
||||
house_number: '',
|
||||
stairwell: '',
|
||||
floor: '',
|
||||
door: '',
|
||||
parcel_id: '',
|
||||
full_address_text: '',
|
||||
latitude: null as number | null,
|
||||
longitude: null as number | null,
|
||||
},
|
||||
})
|
||||
|
||||
// ── Computed ───────────────────────────────────────────
|
||||
@@ -690,12 +677,14 @@ const hasChanges = computed(() => {
|
||||
// Scalar field comparisons
|
||||
if (form.name !== (p.name || '')) return true
|
||||
if (form.address !== (p.address || '')) return true
|
||||
if (form.city !== (p.city || '')) return true
|
||||
if (form.address_zip !== (p.address_zip || '')) return true
|
||||
if (form.address_street_name !== (p.address_street_name || '')) return true
|
||||
if (form.address_street_type !== (p.address_street_type || '')) return true
|
||||
if (form.address_house_number !== (p.address_house_number || '')) return true
|
||||
if (form.plus_code !== (p.plus_code || '')) return true
|
||||
|
||||
// Nested address comparison (via SharedAddressForm)
|
||||
if (form.address.zip !== (p.address_zip || '')) return true
|
||||
if (form.address.city !== (p.city || '')) return true
|
||||
if (form.address.street_name !== (p.address_street_name || '')) return true
|
||||
if (form.address.street_type !== (p.address_street_type || '')) return true
|
||||
if (form.address.house_number !== (p.address_house_number || '')) return true
|
||||
if (form.contact_phone !== (p.contact_phone || '')) return true
|
||||
if (form.contact_email !== (p.contact_email || '')) return true
|
||||
if (form.website !== (p.website || '')) return true
|
||||
@@ -777,11 +766,20 @@ function mapProviderToForm(p: ProviderDetail) {
|
||||
form.name = p.name || ''
|
||||
form.address = p.address || ''
|
||||
form.city = p.city || ''
|
||||
form.address_zip = p.address_zip || ''
|
||||
form.address_street_name = p.address_street_name || ''
|
||||
form.address_street_type = p.address_street_type || ''
|
||||
form.address_house_number = p.address_house_number || ''
|
||||
form.plus_code = p.plus_code || ''
|
||||
|
||||
// Populate nested address object from address_detail (unified AddressOut)
|
||||
const addr = p.address_detail || {}
|
||||
form.address_zip = addr.zip || ''
|
||||
form.address_street_name = addr.street_name || ''
|
||||
form.address_street_type = addr.street_type || ''
|
||||
form.address_house_number = addr.house_number || ''
|
||||
form.address.zip = addr.zip || ''
|
||||
form.address.city = addr.city || p.city || ''
|
||||
form.address.street_name = addr.street_name || ''
|
||||
form.address.street_type = addr.street_type || ''
|
||||
form.address.house_number = addr.house_number || ''
|
||||
form.address.full_address_text = addr.full_address_text || p.address || ''
|
||||
form.contact_phone = p.contact_phone || ''
|
||||
form.contact_email = p.contact_email || ''
|
||||
form.website = p.website || ''
|
||||
@@ -1050,7 +1048,7 @@ async function saveForm() {
|
||||
|
||||
// CRITICAL BUGFIX (2026-07-01): address is NOT NULL in the DB schema,
|
||||
// so we must never send null for it. Fall back to name if empty.
|
||||
const safeAddress = form.address?.trim() || form.name
|
||||
const safeAddress = form.address?.full_address_text?.trim() || form.name
|
||||
|
||||
const payload: Record<string, any> = {
|
||||
name: form.name,
|
||||
@@ -1059,17 +1057,27 @@ async function saveForm() {
|
||||
contact_phone: form.contact_phone || null,
|
||||
contact_email: form.contact_email || null,
|
||||
website: form.website || null,
|
||||
city: form.city || null,
|
||||
address_zip: form.address_zip || null,
|
||||
address_street_name: form.address_street_name || null,
|
||||
address_street_type: form.address_street_type || null,
|
||||
address_house_number: form.address_house_number || null,
|
||||
plus_code: form.plus_code || null,
|
||||
category_ids: form.category_ids,
|
||||
supported_vehicle_classes: form.supported_vehicle_classes,
|
||||
specializations: form.specializations,
|
||||
opening_hours: form.opening_hours,
|
||||
is_always_open: form.is_always_open,
|
||||
// Send nested address object matching AddressIn schema
|
||||
address_detail: {
|
||||
zip: form.address.zip || null,
|
||||
city: form.address.city || null,
|
||||
street_name: form.address.street_name || null,
|
||||
street_type: form.address.street_type || null,
|
||||
house_number: form.address.house_number || null,
|
||||
stairwell: form.address.stairwell || null,
|
||||
floor: form.address.floor || null,
|
||||
door: form.address.door || null,
|
||||
parcel_id: form.address.parcel_id || null,
|
||||
full_address_text: safeAddress,
|
||||
latitude: form.address.latitude,
|
||||
longitude: form.address.longitude,
|
||||
},
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -280,8 +280,17 @@
|
||||
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6">
|
||||
<h2 class="text-lg font-semibold text-white mb-4">Moderációs műveletek</h2>
|
||||
|
||||
<!-- Pending Actions -->
|
||||
<div v-if="provider.status === 'pending'" class="space-y-3">
|
||||
<!-- Pending / Research In Progress Actions -->
|
||||
<div v-if="provider.status === 'pending' || provider.status === 'research_in_progress'" class="space-y-3">
|
||||
<NuxtLink
|
||||
:to="`/providers/${provider.id}/edit`"
|
||||
class="w-full px-4 py-3 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg text-sm font-medium transition flex items-center justify-center gap-2"
|
||||
>
|
||||
<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="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
Szerkesztés
|
||||
</NuxtLink>
|
||||
<button
|
||||
@click="approveProvider"
|
||||
:disabled="actionLoading"
|
||||
@@ -506,6 +515,62 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Crawler / Nyers Adatok -->
|
||||
<div v-if="activeTab === 'crawler'" class="max-w-4xl">
|
||||
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6">
|
||||
<h2 class="text-lg font-semibold text-white mb-4">Crawler / Nyers Adatok</h2>
|
||||
<p class="text-sm text-slate-400 mb-6">A robot által begyűjtött nyers adatok és metaadatok.</p>
|
||||
|
||||
<!-- Badges Row -->
|
||||
<div class="flex flex-wrap gap-3 mb-6">
|
||||
<!-- Source Badge -->
|
||||
<div class="inline-flex items-center gap-1.5 px-3 py-1.5 bg-slate-700 rounded-lg text-xs font-medium">
|
||||
<span class="text-slate-400">Forrás:</span>
|
||||
<code class="text-cyan-300 font-mono">{{ provider.source || '—' }}</code>
|
||||
</div>
|
||||
|
||||
<!-- Trust Score Badge -->
|
||||
<div
|
||||
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium"
|
||||
:class="trustScoreBadgeClass"
|
||||
>
|
||||
<span class="text-slate-400">Trust Score:</span>
|
||||
<span class="font-semibold">{{ provider.trust_score ?? '—' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rejection Reason Alert -->
|
||||
<div
|
||||
v-if="provider.rejection_reason"
|
||||
class="flex items-start gap-3 p-4 mb-6 bg-rose-500/10 border border-rose-500/30 rounded-lg"
|
||||
>
|
||||
<svg class="w-5 h-5 text-rose-400 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
||||
</svg>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-rose-300">Elutasítás indoklása</p>
|
||||
<p class="text-sm text-rose-200/70 mt-0.5">{{ provider.rejection_reason }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Raw Data Viewer -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-medium text-slate-300 mb-3">Nyers adatok (raw_data)</h3>
|
||||
<div class="bg-slate-900/50 rounded-lg border border-slate-700 p-4 overflow-x-auto">
|
||||
<RawDataViewer :data="provider.raw_data" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Audit Trail -->
|
||||
<div v-if="provider.audit_trail && Object.keys(provider.audit_trail).length > 0">
|
||||
<h3 class="text-sm font-medium text-slate-300 mb-3">Audit Trail</h3>
|
||||
<div class="bg-slate-900/50 rounded-lg border border-slate-700 p-4 overflow-x-auto">
|
||||
<RawDataViewer :data="provider.audit_trail" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: History -->
|
||||
<div v-if="activeTab === 'history'">
|
||||
<div class="bg-slate-800 rounded-xl border border-slate-700 p-6">
|
||||
@@ -687,7 +752,7 @@ const showFlagModal = ref(false)
|
||||
const flagReason = ref('')
|
||||
|
||||
// Tabs
|
||||
const activeTab = ref<'data' | 'status' | 'validations' | 'history'>('data')
|
||||
const activeTab = ref<'data' | 'status' | 'validations' | 'history' | 'crawler'>('data')
|
||||
|
||||
// Validations
|
||||
const validations = ref<any[]>([])
|
||||
@@ -710,6 +775,7 @@ const tabs = [
|
||||
{ key: 'data', label: 'Adatok', icon: 'svg' },
|
||||
{ key: 'status', label: 'Státusz', icon: 'svg' },
|
||||
{ key: 'validations', label: 'Validációk', icon: 'svg' },
|
||||
{ key: 'crawler', label: 'Crawler / Nyers Adatok', icon: 'svg' },
|
||||
{ key: 'history', label: 'Előzmények', icon: 'svg' },
|
||||
]
|
||||
|
||||
@@ -718,6 +784,7 @@ const statusBannerClass = computed(() => {
|
||||
switch (provider.value?.status) {
|
||||
case 'approved': return 'bg-emerald-500/10 border-emerald-500/30'
|
||||
case 'pending': return 'bg-amber-500/10 border-amber-500/30'
|
||||
case 'research_in_progress': return 'bg-blue-500/10 border-blue-500/30'
|
||||
default: return 'bg-rose-500/10 border-rose-500/30'
|
||||
}
|
||||
})
|
||||
@@ -726,6 +793,7 @@ const statusIconClass = computed(() => {
|
||||
switch (provider.value?.status) {
|
||||
case 'approved': return 'bg-emerald-500/20 text-emerald-400'
|
||||
case 'pending': return 'bg-amber-500/20 text-amber-400'
|
||||
case 'research_in_progress': return 'bg-blue-500/20 text-blue-400'
|
||||
default: return 'bg-rose-500/20 text-rose-400'
|
||||
}
|
||||
})
|
||||
@@ -734,6 +802,7 @@ const statusTextClass = computed(() => {
|
||||
switch (provider.value?.status) {
|
||||
case 'approved': return 'text-emerald-400'
|
||||
case 'pending': return 'text-amber-400'
|
||||
case 'research_in_progress': return 'text-blue-400'
|
||||
default: return 'text-rose-400'
|
||||
}
|
||||
})
|
||||
@@ -742,6 +811,7 @@ const statusSubtextClass = computed(() => {
|
||||
switch (provider.value?.status) {
|
||||
case 'approved': return 'text-emerald-400/60'
|
||||
case 'pending': return 'text-amber-400/60'
|
||||
case 'research_in_progress': return 'text-blue-400/60'
|
||||
default: return 'text-rose-400/60'
|
||||
}
|
||||
})
|
||||
@@ -768,6 +838,18 @@ const hasSpecializations = computed(() => {
|
||||
(Array.isArray(spec.propulsion) && spec.propulsion.length > 0)
|
||||
})
|
||||
|
||||
/**
|
||||
* Trust score badge color based on value.
|
||||
* Green if > 80, yellow if > 50, red if <= 50.
|
||||
*/
|
||||
const trustScoreBadgeClass = computed(() => {
|
||||
const score = provider.value?.trust_score
|
||||
if (score === null || score === undefined) return 'bg-slate-700 text-slate-400'
|
||||
if (score > 80) return 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
|
||||
if (score > 50) return 'bg-amber-500/20 text-amber-400 border border-amber-500/30'
|
||||
return 'bg-rose-500/20 text-rose-400 border border-rose-500/30'
|
||||
})
|
||||
|
||||
// ── DAYS constant ──────────────────────────────────────
|
||||
const DAYS = [
|
||||
{ key: 'monday', label: 'Hétfő' },
|
||||
@@ -797,6 +879,7 @@ function statusLabel(status: string): string {
|
||||
switch (status) {
|
||||
case 'approved': return 'Jóváhagyva'
|
||||
case 'pending': return 'Függőben'
|
||||
case 'research_in_progress': return 'Kutatás folyamatban'
|
||||
case 'rejected': return 'Elutasítva'
|
||||
default: return 'Ismeretlen'
|
||||
}
|
||||
@@ -806,6 +889,7 @@ function statusDescription(status: string): string {
|
||||
switch (status) {
|
||||
case 'approved': return 'A szolgáltató aktív és látható a felhasználók számára.'
|
||||
case 'pending': return 'A szolgáltató még nem lett jóváhagyva, moderálásra vár.'
|
||||
case 'research_in_progress': return 'A szolgáltató adatai még feldolgozás alatt állnak, automatikus kutatás folyik.'
|
||||
case 'rejected': return 'A szolgáltató elutasításra került, nem látható a felhasználók számára.'
|
||||
default: return ''
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user