Files
service-finder/frontend_admin/.nuxt/dist/server/_nuxt/index-CzKBE-M2.js.map.json

1 line
38 KiB
JSON

{"file":"index-CzKBE-M2.js","mappings":"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","names":["$t","_ssrRenderAttr","_ssrRenderList","_ssrLooseContain","_ssrLooseEqual","_ssrInterpolate","_push","_parent","_createTextVNode","_toDisplayString","_ssrRenderClass","_ssrIncludeBooleanAttr"],"sources":["../../../../pages/garages/index.vue"],"sourcesContent":["<template>\n <div>\n <!-- Page Header -->\n <div class=\"mb-8\">\n <h1 class=\"text-2xl font-bold text-white\">{{ $t('garages.title') }}</h1>\n <p class=\"text-slate-400 mt-1\">{{ $t('garages.subtitle') }}</p>\n </div>\n <!-- Search & Filter Bar (always rendered — stable DOM for input focus) -->\n <div class=\"flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 mb-6\">\n <div class=\"relative flex-1 max-w-md\">\n <svg class=\"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\" />\n </svg>\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n :placeholder=\"$t('garages.search_placeholder')\"\n class=\"w-full pl-10 pr-4 py-2.5 bg-slate-800 border border-slate-700 rounded-lg text-sm text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 transition\"\n />\n </div>\n <div class=\"flex items-center gap-2\">\n <select\n v-model=\"filterType\"\n class=\"px-3 py-2.5 bg-slate-800 border border-slate-700 rounded-lg text-sm text-slate-300 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition\"\n >\n <option value=\"all\">{{ $t('garages.all_types') }}</option>\n <option value=\"individual\">{{ $t('garages.type_individual') }}</option>\n <option value=\"corporate\">{{ $t('garages.type_corporate') }}</option>\n </select>\n <select\n v-model=\"selectedPackage\"\n class=\"px-3 py-2.5 bg-slate-800 border border-slate-700 rounded-lg text-sm text-slate-300 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition\"\n >\n <option value=\"all\">{{ $t('garages.all_tiers') }}</option>\n <option\n v-for=\"tier in availableTierNames\"\n :key=\"tier\"\n :value=\"tier\"\n >\n {{ tier }}\n </option>\n </select>\n </div>\n </div>\n\n <!-- Loading State -->\n <div v-if=\"loading\" class=\"flex items-center justify-center py-20\">\n <div class=\"flex flex-col items-center gap-3\">\n <svg class=\"w-8 h-8 text-indigo-400 animate-spin\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\" />\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\" />\n </svg>\n <p class=\"text-slate-400 text-sm\">{{ $t('garages.loading') }}</p>\n </div>\n </div>\n\n <!-- Error State -->\n <div v-else-if=\"error\" class=\"flex flex-col items-center justify-center py-20 text-center\">\n <svg class=\"w-12 h-12 text-red-500 mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" 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\" />\n </svg>\n <p class=\"text-red-400 text-sm mb-2\">{{ error }}</p>\n <button @click=\"fetchGarages\" class=\"px-4 py-2 text-sm bg-indigo-600/20 text-indigo-300 hover:bg-indigo-600/30 rounded-lg transition\">\n {{ $t('garages.retry') }}\n </button>\n </div>\n\n <!-- Content -->\n <template v-else>\n <!-- P0 HOTFIX: Global Stats Cards - always show unfiltered global counts -->\n <div class=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mb-8\">\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-4\">\n <div class=\"flex items-center gap-3\">\n <div class=\"p-2 rounded-lg bg-indigo-500/10 text-indigo-400\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\" />\n </svg>\n </div>\n <div>\n <p class=\"text-xs text-slate-400 uppercase tracking-wider\">{{ $t('garages.total_garages') }}</p>\n <p class=\"text-2xl font-bold text-white\">{{ globalTotalCount }}</p>\n </div>\n </div>\n </div>\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-4\">\n <div class=\"flex items-center gap-3\">\n <div class=\"p-2 rounded-lg bg-emerald-500/10 text-emerald-400\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z\" />\n </svg>\n </div>\n <div>\n <p class=\"text-xs text-slate-400 uppercase tracking-wider\">{{ $t('garages.private_garages') }}</p>\n <p class=\"text-2xl font-bold text-emerald-400\">{{ globalIndividualCount }}</p>\n </div>\n </div>\n </div>\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-4\">\n <div class=\"flex items-center gap-3\">\n <div class=\"p-2 rounded-lg bg-amber-500/10 text-amber-400\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\" />\n </svg>\n </div>\n <div>\n <p class=\"text-xs text-slate-400 uppercase tracking-wider\">{{ $t('garages.corporate_garages') }}</p>\n <p class=\"text-2xl font-bold text-amber-400\">{{ globalCorporateCount }}</p>\n </div>\n </div>\n </div>\n </div>\n\n <!-- P0 HOTFIX: Filtered results indicator -->\n <div v-if=\"isFilterActive\" class=\"mb-4 text-xs text-slate-500\">\n Szűrt találat: <span class=\"text-slate-300 font-semibold\">{{ garages.length }}</span> db\n </div>\n\n <!-- Data Table -->\n <div class=\"bg-slate-800/50 border border-slate-700 rounded-xl overflow-hidden\">\n <div class=\"overflow-x-auto\">\n <table class=\"w-full text-sm\">\n <thead>\n <tr class=\"border-b border-slate-700 bg-slate-800/80\">\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">ID</th>\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">{{ $t('garages.company_name') }}</th>\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">{{ $t('garages.status') }}</th>\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">{{ $t('garages.current_package') }}</th>\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">{{ $t('garages.expiration_date') }}</th>\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">{{ $t('garages.actions') }}</th>\n </tr>\n </thead>\n <tbody>\n <tr\n v-for=\"garage in filteredGarages\"\n :key=\"garage.id\"\n class=\"border-b border-slate-700/50 hover:bg-slate-700/30 transition\"\n >\n <!-- ID -->\n <td class=\"py-3.5 px-4 text-slate-400 font-mono text-xs\">{{ garage.id }}</td>\n\n <!-- Company Name (display_name priority, fallback to name) -->\n <td class=\"py-3.5 px-4\">\n <div class=\"flex items-center gap-3\">\n <div\n class=\"w-8 h-8 rounded-lg flex items-center justify-center text-sm font-bold text-white\"\n :class=\"getInitialsColor(garageDisplayName(garage))\"\n >\n {{ getInitials(garageDisplayName(garage)) }}\n </div>\n <div>\n <NuxtLink\n :to=\"'/garages/' + garage.id\"\n class=\"text-sm font-medium text-white hover:underline cursor-pointer hover:text-indigo-400 transition-colors\"\n >\n {{ garageDisplayName(garage) }}\n </NuxtLink>\n <p class=\"text-xs text-slate-500\">{{ garage.city || '—' }} · {{ garage.member_count }} {{ $t('garages.members') }}</p>\n </div>\n </div>\n </td>\n\n <!-- Status -->\n <td class=\"py-3.5 px-4\">\n <span\n class=\"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium\"\n :class=\"statusBadgeClass(garage.status)\"\n >\n <span\n class=\"w-1.5 h-1.5 rounded-full mr-1.5\"\n :class=\"statusDotClass(garage.status)\"\n ></span>\n {{ statusLabel(garage.status) }}\n </span>\n </td>\n\n <!-- Current Package -->\n <td class=\"py-3.5 px-4\">\n <span\n class=\"px-2.5 py-0.5 rounded-full text-xs font-medium\"\n :class=\"tierBadgeClass(garage.subscription_tier_name)\"\n >\n {{ garage.subscription_tier_name }}\n </span>\n </td>\n\n <!-- Expiration Date -->\n <td class=\"py-3.5 px-4\">\n <span v-if=\"garage.subscription_expires_at\" class=\"text-slate-300 text-xs\">\n {{ formatDate(garage.subscription_expires_at) }}\n </span>\n <span v-else class=\"text-slate-500 text-xs italic\">{{ $t('garages.indefinite') }}</span>\n </td>\n\n <!-- Actions -->\n <td class=\"py-3.5 px-4\">\n <div class=\"flex items-center gap-2\">\n <button\n @click=\"openDetails(garage)\"\n class=\"px-3 py-1.5 text-xs font-medium bg-slate-600/20 text-slate-300 hover:bg-slate-600/30 rounded-lg transition\"\n :title=\"$t('garages.view_details')\"\n >\n {{ $t('garages.view_details') }}\n </button>\n <button\n @click=\"openSubscriptionModal(garage)\"\n class=\"px-3 py-1.5 text-xs font-medium bg-indigo-600/20 text-indigo-300 hover:bg-indigo-600/30 rounded-lg transition\"\n >\n {{ $t('garages.manage_subscription') }}\n </button>\n <button\n @click=\"toggleStatus(garage)\"\n class=\"px-3 py-1.5 text-xs font-medium rounded-lg transition\"\n :class=\"garage.is_active\n ? 'bg-red-500/10 text-red-300 hover:bg-red-500/20'\n : 'bg-emerald-500/10 text-emerald-300 hover:bg-emerald-500/20'\"\n >\n {{ garage.is_active ? $t('garages.deactivate') : $t('garages.activate') }}\n </button>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Empty State -->\n <div\n v-if=\"filteredGarages.length === 0 && !loading\"\n class=\"flex flex-col items-center justify-center py-16 text-center\"\n >\n <svg class=\"w-12 h-12 text-slate-600 mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\" />\n </svg>\n <p class=\"text-slate-400 text-sm\">{{ $t('garages.no_results') }}</p>\n </div>\n </div>\n </template>\n\n <!-- ================================================================ -->\n <!-- Subscription Management Modal -->\n <!-- ================================================================ -->\n <div\n v-if=\"showSubscriptionModal\"\n class=\"fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm\"\n @click.self=\"closeSubscriptionModal\"\n >\n <div class=\"bg-slate-800 border border-slate-700 rounded-2xl shadow-2xl w-full max-w-lg mx-4 overflow-hidden\">\n <!-- Modal Header -->\n <div class=\"px-6 py-4 border-b border-slate-700 flex items-center justify-between\">\n <div>\n <h3 class=\"text-lg font-semibold text-white\">{{ $t('garages.manage_subscription') }}</h3>\n <p class=\"text-sm text-slate-400 mt-0.5\">{{ selectedGarage ? garageDisplayName(selectedGarage) : '' }}</p>\n </div>\n <button @click=\"closeSubscriptionModal\" class=\"p-1.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-700 transition\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- Modal Body -->\n <div class=\"px-6 py-5 space-y-5\">\n <!-- Current Subscription Info -->\n <div class=\"bg-slate-900/50 rounded-xl p-4 border border-slate-700/50\">\n <p class=\"text-xs text-slate-500 uppercase tracking-wider mb-1\">{{ $t('garages.current_package') }}</p>\n <p class=\"text-sm font-medium text-white\">\n {{ selectedGarage?.subscription_tier_name || $t('garages.free_fallback') }}\n </p>\n <p v-if=\"selectedGarage?.subscription_expires_at\" class=\"text-xs text-slate-400 mt-1\">\n {{ $t('garages.expires') }}: {{ formatDate(selectedGarage.subscription_expires_at) }}\n </p>\n </div>\n\n <!-- Tier Selection -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-2\">{{ $t('garages.select_package') }}</label>\n <select\n v-model=\"selectedTierId\"\n class=\"w-full px-3 py-2.5 bg-slate-900 border border-slate-700 rounded-lg text-sm text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition\"\n >\n <option value=\"\" disabled>{{ $t('garages.select_package_placeholder') }}</option>\n <option\n v-for=\"tier in availableTiers\"\n :key=\"tier.id\"\n :value=\"tier.id\"\n >\n {{ tier.name }} (Level {{ tier.tier_level }})\n </option>\n </select>\n </div>\n\n <!-- Custom Expiration Date -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-2\">\n {{ $t('garages.custom_expiration') }}\n <span class=\"text-xs text-slate-500 ml-1\">({{ $t('garages.optional') }})</span>\n </label>\n <input\n v-model=\"customExpiresAt\"\n type=\"datetime-local\"\n class=\"w-full px-3 py-2.5 bg-slate-900 border border-slate-700 rounded-lg text-sm text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition\"\n />\n <p class=\"text-xs text-slate-500 mt-1.5\">{{ $t('garages.custom_expiration_hint') }}</p>\n </div>\n\n <!-- Save Error -->\n <div\n v-if=\"saveError\"\n class=\"bg-red-500/10 border border-red-500/20 rounded-lg px-4 py-3\"\n >\n <p class=\"text-sm text-red-400\">{{ saveError }}</p>\n </div>\n </div>\n\n <!-- Modal Footer -->\n <div class=\"px-6 py-4 border-t border-slate-700 flex items-center justify-end gap-3\">\n <button\n @click=\"closeSubscriptionModal\"\n class=\"px-4 py-2 text-sm font-medium text-slate-300 hover:text-white transition\"\n >\n {{ $t('garages.cancel') }}\n </button>\n <button\n @click=\"saveSubscription\"\n :disabled=\"saving || !selectedTierId\"\n class=\"px-5 py-2 text-sm font-medium rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed\"\n :class=\"saving\n ? 'bg-indigo-600/50 text-indigo-200 cursor-wait'\n : 'bg-indigo-600 text-white hover:bg-indigo-500'\"\n >\n <span v-if=\"saving\" class=\"flex items-center gap-2\">\n <svg class=\"w-4 h-4 animate-spin\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\" />\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\" />\n </svg>\n {{ $t('garages.saving') }}\n </span>\n <span v-else>{{ $t('garages.save') }}</span>\n </button>\n </div>\n </div>\n </div>\n\n <!-- Save Notification -->\n <div\n v-if=\"saveNotification\"\n class=\"fixed bottom-6 right-6 px-5 py-3 rounded-xl shadow-xl text-sm font-medium z-50\"\n :class=\"saveNotification.type === 'success'\n ? 'bg-emerald-600 text-white'\n : 'bg-red-600 text-white'\"\n >\n {{ saveNotification.message }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted, onUnmounted, watch } from 'vue'\n\ndefinePageMeta({\n middleware: 'auth',\n})\n\n// ── Types ──────────────────────────────────────────────────────────\n\ninterface SubscriptionInfo {\n id: number\n org_id: number\n tier_id: number\n tier_name: string\n valid_from: string | null\n valid_until: string | null\n is_active: boolean\n extra_allowances: Record<string, unknown>\n}\n\ninterface Garage {\n id: number\n name: string\n full_name: string\n display_name: string | null\n email: string | null\n status: string\n is_active: boolean\n is_verified: boolean\n is_deleted: boolean\n org_type: string\n city: string | null\n region: string | null\n segment: string | null\n member_count: number\n created_at: string | null\n subscription: SubscriptionInfo | null\n subscription_tier_name: string\n subscription_expires_at: string | null\n}\n\ninterface Tier {\n id: number\n name: string\n tier_level: number\n rules: Record<string, unknown>\n}\n\n// ── State ──────────────────────────────────────────────────────────\n\nconst loading = ref(true)\nconst error = ref<string | null>(null)\nconst garages = ref<Garage[]>([])\nconst availableTiers = ref<Tier[]>([])\nconst searchQuery = ref('')\nconst filterType = ref('all')\nconst selectedPackage = ref('all')\n\n// P0 HOTFIX: Accumulating tier names — never shrinks, only grows.\n// This prevents the dropdown from losing options when filtering by tier.\nconst availableTierNames = ref<string[]>([])\n\n// Modal state\nconst showSubscriptionModal = ref(false)\nconst selectedGarage = ref<Garage | null>(null)\nconst selectedTierId = ref<number | ''>('')\nconst customExpiresAt = ref<string>('')\nconst saving = ref(false)\nconst saveError = ref<string | null>(null)\n\n// Notification\nconst saveNotification = ref<{ type: 'success' | 'error'; message: string } | null>(null)\n\n// ── Debounced Search Timer ──────────────────────────────────────────\n\nlet searchDebounceTimer: ReturnType<typeof setTimeout> | null = null\n\n/**\n * P0: Debounced search — waits 400ms after the user stops typing\n * before triggering a server-side API call. This prevents excessive\n * requests on every keystroke while keeping the UI responsive.\n */\nfunction debouncedFetchGarages() {\n if (searchDebounceTimer) {\n clearTimeout(searchDebounceTimer)\n }\n searchDebounceTimer = setTimeout(() => {\n fetchGarages()\n }, 400)\n}\n\n// ── Watchers ────────────────────────────────────────────────────────\n\n// Watch searchQuery with debounce — only fires API call after 400ms idle\nwatch(searchQuery, () => {\n debouncedFetchGarages()\n})\n\n// Watch filterType — immediate API call (no debounce needed for dropdowns)\nwatch(filterType, () => {\n fetchGarages()\n})\n\n// Watch selectedPackage — immediate API call\nwatch(selectedPackage, () => {\n fetchGarages()\n})\n\n// ── Computed ───────────────────────────────────────────────────────\n\n// P0 HOTFIX: Global stats stored from initial unfiltered load\n// These NEVER change when filters are applied, so the stat cards\n// always reflect the true database totals.\nconst globalTotalCount = ref(0)\nconst globalIndividualCount = ref(0)\nconst globalCorporateCount = ref(0)\n\n// P0 HOTFIX: Detect if any filter is active\nconst isFilterActive = computed(() => {\n return searchQuery.value !== '' || filterType.value !== 'all' || selectedPackage.value !== 'all'\n})\n\n// P0 HOTFIX: Accumulating tier names — never shrinks, only grows.\n// Watcher on garages adds newly discovered tier names to availableTierNames.\n// This prevents the dropdown from losing options when filtering by tier.\nwatch(garages, (newGarages) => {\n const currentSet = new Set(availableTierNames.value)\n for (const g of newGarages) {\n if (g.subscription_tier_name) {\n currentSet.add(g.subscription_tier_name)\n }\n }\n availableTierNames.value = Array.from(currentSet).sort()\n}, { immediate: true, deep: true })\n\n// P0: Server-side filtered — the API handles all filtering.\n// This computed is now a simple pass-through; the real filtering\n// happens in fetchGarages() via query params.\nconst filteredGarages = computed(() => garages.value)\n\n// ── Auth Helper ────────────────────────────────────────────────────\n\nfunction getAuthHeaders() {\n const tokenCookie = useCookie('access_token')\n const token = tokenCookie.value\n return token ? { Authorization: `Bearer ${token}` } : {}\n}\n\n// ── Data Fetching ──────────────────────────────────────────────────\n\nasync function fetchGarages() {\n loading.value = true\n error.value = null\n try {\n // P0: Server-side search — pass all filter params to the API\n const params: Record<string, any> = { limit: 200 }\n\n // Debounced search query → server-side omni-search\n if (searchQuery.value) {\n params.search_term = searchQuery.value\n }\n\n // Org type filter\n if (filterType.value !== 'all') {\n params.org_type_filter = filterType.value\n }\n\n // Subscription tier filter\n if (selectedPackage.value !== 'all') {\n params.tier_name_filter = selectedPackage.value\n }\n\n const response = await $fetch<{ total: number; skip: number; limit: number; garages: Garage[] }>('/api/v1/admin/organizations', {\n params,\n headers: getAuthHeaders(),\n })\n garages.value = response.garages\n\n // P0 HOTFIX: Store global counts from unfiltered API response.\n // When no filters are active, the API returns the full dataset,\n // so we capture the global totals here. When filters ARE active,\n // we keep the previously captured global counts unchanged.\n if (!isFilterActive.value) {\n globalTotalCount.value = response.total\n globalIndividualCount.value = garages.value.filter(g => g.org_type === 'individual').length\n globalCorporateCount.value = garages.value.filter(g => g.org_type !== 'individual').length\n }\n } catch (err: any) {\n console.error('[Garages] Failed to fetch garages:', err)\n error.value = err?.data?.detail || err?.message || 'Failed to load garages'\n } finally {\n loading.value = false\n }\n}\n\nasync function fetchTiers() {\n try {\n const response = await $fetch<{ total: number; tiers: Tier[] }>('/api/v1/admin/packages', {\n params: { include_hidden: true, limit: 100 },\n headers: getAuthHeaders(),\n })\n availableTiers.value = response.tiers\n } catch (err: any) {\n console.error('[Garages] Failed to fetch tiers:', err)\n }\n}\n\n// ── Subscription Modal ─────────────────────────────────────────────\n\nfunction openSubscriptionModal(garage: Garage) {\n selectedGarage.value = garage\n selectedTierId.value = garage.subscription?.tier_id || ''\n customExpiresAt.value = ''\n saveError.value = null\n showSubscriptionModal.value = true\n}\n\nfunction closeSubscriptionModal() {\n showSubscriptionModal.value = false\n selectedGarage.value = null\n selectedTierId.value = ''\n customExpiresAt.value = ''\n saveError.value = null\n}\n\nasync function saveSubscription() {\n if (!selectedGarage.value || !selectedTierId.value) return\n\n saving.value = true\n saveError.value = null\n\n try {\n const payload: Record<string, unknown> = {\n tier_id: selectedTierId.value,\n }\n\n // If custom expiration date is set, include it\n if (customExpiresAt.value) {\n payload.expires_at = new Date(customExpiresAt.value).toISOString()\n }\n\n await $fetch(`/api/v1/admin/organizations/${selectedGarage.value.id}/subscription`, {\n method: 'PATCH',\n headers: {\n ...getAuthHeaders(),\n 'Content-Type': 'application/json',\n },\n body: payload,\n })\n\n showNotification('success', `\"${garageDisplayName(selectedGarage.value)}\" ${useNuxtApp().$i18n.t('garages.subscription_updated')}`)\n closeSubscriptionModal()\n\n // Refresh the list\n await fetchGarages()\n } catch (err: any) {\n console.error('[Garages] Failed to save subscription:', err)\n saveError.value = err?.data?.detail || err?.message || 'Failed to save subscription'\n } finally {\n saving.value = false\n }\n}\n\n// ── Status Toggle ──────────────────────────────────────────────────\n\nasync function toggleStatus(garage: Garage) {\n // Determine the new status based on current state\n const newStatus = garage.is_active ? 'inactive' : 'active'\n const actionLabel = garage.is_active ? 'deaktiválása' : 'aktiválása'\n\n try {\n await $fetch(`/api/v1/admin/organizations/${garage.id}/status`, {\n method: 'PUT',\n headers: {\n ...getAuthHeaders(),\n 'Content-Type': 'application/json',\n },\n body: { status: newStatus },\n })\n\n showNotification('success', `\"${garageDisplayName(garage)}\" ${actionLabel} sikeresen megtörtént.`)\n\n // Refresh the list to reflect changes\n await fetchGarages()\n } catch (err: any) {\n console.error('[Garages] Failed to toggle status:', err)\n showNotification('error', err?.data?.detail || err?.message || `A garázs ${actionLabel} sikertelen.`)\n }\n}\n\n// ── Helpers ────────────────────────────────────────────────────────\n\n/**\n * Returns the display name for a garage, prioritizing display_name over name.\n * This ensures clean, human-readable names are shown in the UI.\n */\nfunction garageDisplayName(garage: Garage): string {\n return garage.display_name || garage.name || garage.full_name\n}\n\nfunction openDetails(garage: Garage) {\n navigateTo(`/garages/${garage.id}`)\n}\n\nfunction getInitials(name: string): string {\n return name\n .split(' ')\n .map(w => w.charAt(0))\n .join('')\n .toUpperCase()\n .slice(0, 2)\n}\n\nfunction getInitialsColor(name: string): string {\n const colors = [\n 'bg-amber-600', 'bg-purple-600', 'bg-slate-600',\n 'bg-indigo-600', 'bg-emerald-600', 'bg-rose-600',\n 'bg-cyan-600', 'bg-orange-600', 'bg-teal-600',\n ]\n let hash = 0\n for (let i = 0; i < name.length; i++) {\n hash = name.charCodeAt(i) + ((hash << 5) - hash)\n }\n return colors[Math.abs(hash) % colors.length]\n}\n\nfunction statusBadgeClass(status: string): string {\n if (status === 'active') return 'bg-emerald-500/10 text-emerald-400'\n if (status === 'suspended') return 'bg-red-500/10 text-red-400'\n if (status === 'pending_verification') return 'bg-amber-500/10 text-amber-400'\n return 'bg-slate-500/10 text-slate-400'\n}\n\nfunction statusDotClass(status: string): string {\n if (status === 'active') return 'bg-emerald-400'\n if (status === 'suspended') return 'bg-red-400'\n if (status === 'pending_verification') return 'bg-amber-400'\n return 'bg-slate-400'\n}\n\nfunction statusLabel(status: string): string {\n if (status === 'active') return 'Aktív'\n if (status === 'suspended') return 'Felfüggesztve'\n if (status === 'pending_verification') return 'Függőben'\n return 'Inaktív'\n}\n\nfunction tierBadgeClass(tierName: string): string {\n const name = tierName.toLowerCase()\n if (name.includes('enterprise') || name.includes('vip') || name.includes('corp')) return 'bg-purple-500/20 text-purple-300'\n if (name.includes('premium') || name.includes('pro')) return 'bg-amber-500/20 text-amber-300'\n return 'bg-slate-500/20 text-slate-300'\n}\n\nfunction formatDate(dateStr: string): string {\n try {\n const d = new Date(dateStr)\n return d.toLocaleDateString('hu-HU', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n } catch {\n return dateStr\n }\n}\n\nfunction showNotification(type: 'success' | 'error', message: string) {\n saveNotification.value = { type, message }\n setTimeout(() => {\n saveNotification.value = null\n }, 4000)\n}\n\n// ── Lifecycle ──────────────────────────────────────────────────────\n\nonMounted(() => {\n fetchGarages()\n fetchTiers()\n})\n</script>\n"],"version":3}