Files
service-finder/frontend_admin/.nuxt/dist/server/_nuxt/edit-DQMBygGu.js.map.json
2026-07-01 19:49:58 +00:00

1 line
59 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"file":"edit-DQMBygGu.js","mappings":"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","names":["_ssrRenderClass","_ssrIncludeBooleanAttr","_ssrInterpolate","$emit","DAYS","$route","_ssrRenderList","_ssrRenderVNode","_createVNode","_resolveDynamicComponent","_ssrRenderAttr","_ssrLooseContain"],"sources":["../../../../components/TreeNode.vue","../../../../pages/providers/[id]/edit.vue"],"sourcesContent":["<template>\n <div>\n <div class=\"flex items-center gap-2 py-1\">\n <button\n @click=\"$emit('toggle-expand', node.id)\"\n class=\"text-slate-500 hover:text-slate-300 transition w-4 h-4 flex items-center justify-center\"\n >\n <svg\n class=\"w-3 h-3 transition-transform\"\n :class=\"expandedIds.has(node.id) ? 'rotate-90' : ''\"\n fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n <label class=\"flex items-center gap-2 cursor-pointer flex-1\">\n <input\n type=\"checkbox\"\n :checked=\"selectedIds.includes(node.id)\"\n @change=\"$emit('toggle', node.id)\"\n class=\"w-4 h-4 rounded border-slate-500 bg-slate-700 text-indigo-500 focus:ring-indigo-500/50\"\n />\n <span class=\"text-sm text-slate-300\">{{ node.name_hu || node.name_en || node.key }}</span>\n <span class=\"text-xs text-slate-500\">(Szint {{ node.level }})</span>\n </label>\n </div>\n <div v-if=\"expandedIds.has(node.id) && node.children?.length\" class=\"ml-6 border-l border-slate-700 pl-3\">\n <div v-for=\"child in node.children\" :key=\"child.id\">\n <TreeNode\n :node=\"child\"\n :selected-ids=\"selectedIds\"\n :expanded-ids=\"expandedIds\"\n @toggle=\"(id: number) => $emit('toggle', id)\"\n @toggle-expand=\"(id: number) => $emit('toggle-expand', id)\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n/**\n * Recursive TreeNode component for the category tree.\n * Renders a checkbox tree with expand/collapse for nested categories.\n */\ninterface CategoryTreeNode {\n id: number\n key: string\n name_hu?: string | null\n name_en?: string | null\n level: number\n path?: string | null\n is_official: boolean\n children: CategoryTreeNode[]\n}\n\ndefineProps<{\n node: CategoryTreeNode\n selectedIds: number[]\n expandedIds: Set<number>\n}>()\n\ndefineEmits<{\n toggle: [id: number]\n 'toggle-expand': [id: number]\n}>()\n</script>\n","<template>\n <div>\n <!-- Back Button & Header -->\n <div class=\"mb-8\">\n <button\n @click=\"goBack\"\n class=\"inline-flex items-center gap-1.5 text-sm text-slate-400 hover:text-white transition mb-4\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\" />\n </svg>\n Vissza a szolgáltatóhoz\n </button>\n <h1 class=\"text-2xl font-bold text-white\">Szolgáltató szerkesztése</h1>\n <p class=\"text-slate-400 mt-1\">Szolgáltató #{{ $route.params.id }}</p>\n </div>\n\n <!-- Loading State -->\n <div v-if=\"loading\" class=\"flex items-center justify-center py-20\">\n <div class=\"text-slate-400 flex items-center gap-3\">\n <svg class=\"w-5 h-5 animate-spin\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n <span>Szolgáltató betöltése...</span>\n </div>\n </div>\n\n <!-- Error State -->\n <div v-else-if=\"error\" class=\"bg-rose-500/10 border border-rose-500/30 rounded-xl p-6 mb-8\">\n <p class=\"text-rose-400\">Hiba történt a szolgáltató betöltése közben.</p>\n <button @click=\"fetchProvider\" class=\"mt-3 text-sm text-rose-300 hover:text-rose-200 underline\">Újrapróbálkozás</button>\n </div>\n\n <!-- Not Found -->\n <div v-else-if=\"!provider\" class=\"bg-slate-800 rounded-xl border border-slate-700 p-6\">\n <p class=\"text-slate-400 text-center py-12\">A szolgáltató nem található.</p>\n </div>\n\n <template v-else>\n <!-- Tab Navigation -->\n <div class=\"border-b border-slate-700 mb-6\">\n <nav class=\"flex gap-6 -mb-px\">\n <button\n v-for=\"tab in tabs\"\n :key=\"tab.key\"\n @click=\"activeTab = tab.key\"\n class=\"pb-3 text-sm font-medium border-b-2 transition flex items-center gap-2\"\n :class=\"activeTab === tab.key ? 'text-indigo-400 border-indigo-400' : 'text-slate-400 border-transparent hover:text-slate-300'\"\n >\n <component :is=\"tab.icon\" class=\"w-4 h-4\" />\n {{ tab.label }}\n </button>\n </nav>\n </div>\n\n <!-- ═══════════════════════════════════════════════════\n TAB 1: Alapadatok & Kapcsolat (Basic Info & Contact)\n ═══════════════════════════════════════════════════ -->\n <div v-if=\"activeTab === 'basic'\" class=\"max-w-3xl\">\n <form @submit.prevent=\"saveForm\">\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6 mb-6\">\n <h2 class=\"text-lg font-semibold text-white mb-4\">Alapadatok</h2>\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <!-- Name -->\n <div class=\"md:col-span-2\">\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">Név *</label>\n <input\n v-model=\"form.name\"\n required\n 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\"\n />\n </div>\n <!-- Category -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">Kategória</label>\n <input\n v-model=\"form.category\"\n placeholder=\"pl. Autószerviz, Gumiszerviz\"\n 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\"\n />\n </div>\n <!-- Source (read-only) -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">Forrás</label>\n <div class=\"w-full px-3 py-2 bg-slate-700/50 border border-slate-600 rounded-lg text-slate-400 text-sm\">\n {{ provider.source }}\n </div>\n </div>\n </div>\n </div>\n\n <!-- Contact Information -->\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6 mb-6\">\n <h2 class=\"text-lg font-semibold text-white mb-4\">Elérhetőség</h2>\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">Telefon</label>\n <input\n v-model=\"form.contact_phone\"\n placeholder=\"+36 30 123 4567\"\n 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\"\n />\n </div>\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">Email</label>\n <input\n v-model=\"form.contact_email\"\n type=\"email\"\n placeholder=\"info@example.com\"\n 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\"\n />\n </div>\n <div class=\"md:col-span-2\">\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">Weboldal</label>\n <input\n v-model=\"form.website\"\n placeholder=\"https://...\"\n 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\"\n />\n </div>\n </div>\n </div>\n </form>\n </div>\n\n <!-- ═══════════════════════════════════════════════════\n TAB 2: Szolgáltatások & Járművek (The 3D Matrix)\n ═══════════════════════════════════════════════════ -->\n <div v-if=\"activeTab === 'matrix'\" class=\"max-w-4xl\">\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6 mb-6\">\n <h2 class=\"text-lg font-semibold text-white mb-2\">3D Képesség Mátrix</h2>\n <p class=\"text-sm text-slate-400 mb-6\">\n Állítsd be, hogy a szolgáltató milyen járműosztályokhoz, szolgáltatási kategóriákhoz\n és specializációkhoz tartozik.\n </p>\n\n <!-- ── Dimension A: Vehicle Classes ── -->\n <div class=\"mb-8\">\n <h3 class=\"text-md font-medium text-white mb-3 flex items-center gap-2\">\n <span class=\"w-6 h-6 rounded-full bg-indigo-500/20 text-indigo-400 flex items-center justify-center text-xs font-bold\">A</span>\n Járműosztályok (Vehicle Classes)\n </h3>\n <p class=\"text-xs text-slate-500 mb-3\">Mely járműtípusokat szervizeli a szolgáltató?</p>\n <div class=\"grid grid-cols-2 md:grid-cols-4 gap-3\">\n <label\n v-for=\"vc in VEHICLE_CLASSES\"\n :key=\"vc.key\"\n class=\"flex items-center gap-3 p-3 rounded-lg border cursor-pointer transition\"\n :class=\"form.supported_vehicle_classes.includes(vc.key)\n ? 'bg-indigo-500/10 border-indigo-500/40 text-indigo-300'\n : 'bg-slate-700/50 border-slate-600 hover:border-slate-500 text-slate-300'\"\n >\n <input\n type=\"checkbox\"\n :value=\"vc.key\"\n :checked=\"form.supported_vehicle_classes.includes(vc.key)\"\n @change=\"toggleVehicleClass(vc.key)\"\n class=\"w-4 h-4 rounded border-slate-500 bg-slate-700 text-indigo-500 focus:ring-indigo-500/50\"\n />\n <span class=\"text-sm\">{{ vc.label }}</span>\n </label>\n </div>\n </div>\n\n <!-- ── Dimension B: Service Categories (Expertise Tags) ── -->\n <div class=\"mb-8\">\n <h3 class=\"text-md font-medium text-white mb-3 flex items-center gap-2\">\n <span class=\"w-6 h-6 rounded-full bg-emerald-500/20 text-emerald-400 flex items-center justify-center text-xs font-bold\">B</span>\n Szolgáltatási Kategóriák (Expertise Tags)\n </h3>\n <p class=\"text-xs text-slate-500 mb-3\">Válaszd ki a szolgáltatóhoz tartozó kategóriákat a hierarchikus fából.</p>\n\n <!-- Loading tree -->\n <div v-if=\"treeLoading\" class=\"flex items-center gap-2 text-sm text-slate-400 py-4\">\n <svg class=\"w-4 h-4 animate-spin\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n <span>Kategóriák betöltése...</span>\n </div>\n\n <!-- Tree -->\n <div v-else class=\"space-y-1 max-h-96 overflow-y-auto pr-2\">\n <div v-for=\"node in categoryTree\" :key=\"node.id\" class=\"ml-0\">\n <!-- Level 0 node (root) -->\n <div class=\"flex items-center gap-2 py-1\">\n <button\n @click=\"toggleTreeNode(node.id)\"\n class=\"text-slate-500 hover:text-slate-300 transition w-4 h-4 flex items-center justify-center\"\n >\n <svg\n class=\"w-3 h-3 transition-transform\"\n :class=\"expandedNodes.has(node.id) ? 'rotate-90' : ''\"\n fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n <label class=\"flex items-center gap-2 cursor-pointer flex-1\">\n <input\n type=\"checkbox\"\n :checked=\"isCategorySelected(node.id)\"\n @change=\"toggleCategory(node.id)\"\n class=\"w-4 h-4 rounded border-slate-500 bg-slate-700 text-indigo-500 focus:ring-indigo-500/50\"\n />\n <span class=\"text-sm font-medium text-white\">{{ node.name_hu || node.name_en || node.key }}</span>\n <span class=\"text-xs text-slate-500\">(Szint {{ node.level }})</span>\n </label>\n </div>\n <!-- Children (recursive) -->\n <div v-if=\"expandedNodes.has(node.id) && node.children?.length\" class=\"ml-6 border-l border-slate-700 pl-3\">\n <div v-for=\"child in node.children\" :key=\"child.id\">\n <TreeNode\n :node=\"child\"\n :selected-ids=\"form.category_ids\"\n :expanded-ids=\"expandedNodes\"\n @toggle=\"toggleCategory\"\n @toggle-expand=\"toggleTreeNode\"\n />\n </div>\n </div>\n </div>\n </div>\n\n <!-- Selected categories summary -->\n <div v-if=\"form.category_ids.length > 0\" class=\"mt-4 flex flex-wrap gap-2\">\n <span\n v-for=\"id in form.category_ids\"\n :key=\"id\"\n class=\"inline-flex items-center gap-1 px-2 py-1 bg-indigo-500/10 text-indigo-300 rounded-full text-xs\"\n >\n {{ getCategoryName(id) }}\n <button @click=\"removeCategory(id)\" class=\"hover:text-white transition\">\n <svg class=\"w-3 h-3\" 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 </span>\n </div>\n </div>\n\n <!-- ── Dimension C: Specializations ── -->\n <div class=\"mb-4\">\n <h3 class=\"text-md font-medium text-white mb-3 flex items-center gap-2\">\n <span class=\"w-6 h-6 rounded-full bg-amber-500/20 text-amber-400 flex items-center justify-center text-xs font-bold\">C</span>\n Specializációk\n </h3>\n <p class=\"text-xs text-slate-500 mb-3\">Adj meg speciális márkákat, hajtáslánc típusokat vagy egyéb címkéket.</p>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <!-- Brands -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">Márkák (Brands)</label>\n <div class=\"flex flex-wrap gap-2 mb-2\">\n <span\n v-for=\"(brand, idx) in form.specializations.brands\"\n :key=\"idx\"\n class=\"inline-flex items-center gap-1 px-2 py-1 bg-amber-500/10 text-amber-300 rounded-full text-xs\"\n >\n {{ brand }}\n <button @click=\"removeBrand(idx)\" class=\"hover:text-white transition\">\n <svg class=\"w-3 h-3\" 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 </span>\n </div>\n <div class=\"flex gap-2\">\n <input\n v-model=\"brandInput\"\n @keydown.enter.prevent=\"addBrand\"\n placeholder=\"pl. Volvo, Scania\"\n class=\"flex-1 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\"\n />\n <button\n @click=\"addBrand\"\n :disabled=\"!brandInput.trim()\"\n class=\"px-3 py-2 bg-indigo-600 hover:bg-indigo-500 disabled:bg-indigo-600/50 text-white rounded-lg text-sm transition\"\n >\n Hozzáad\n </button>\n </div>\n </div>\n\n <!-- Propulsion -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">Hajtáslánc (Propulsion)</label>\n <div class=\"flex flex-wrap gap-2 mb-2\">\n <span\n v-for=\"(prop, idx) in form.specializations.propulsion\"\n :key=\"idx\"\n class=\"inline-flex items-center gap-1 px-2 py-1 bg-emerald-500/10 text-emerald-300 rounded-full text-xs\"\n >\n {{ prop }}\n <button @click=\"removePropulsion(idx)\" class=\"hover:text-white transition\">\n <svg class=\"w-3 h-3\" 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 </span>\n </div>\n <div class=\"flex gap-2\">\n <input\n v-model=\"propulsionInput\"\n @keydown.enter.prevent=\"addPropulsion\"\n placeholder=\"pl. EV, Hybrid, Diesel\"\n class=\"flex-1 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\"\n />\n <button\n @click=\"addPropulsion\"\n :disabled=\"!propulsionInput.trim()\"\n class=\"px-3 py-2 bg-indigo-600 hover:bg-indigo-500 disabled:bg-indigo-600/50 text-white rounded-lg text-sm transition\"\n >\n Hozzáad\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- ═══════════════════════════════════════════════════\n TAB 3: Helyszín & Lokáció (Location)\n ═══════════════════════════════════════════════════ -->\n <div v-if=\"activeTab === 'location'\" class=\"max-w-3xl\">\n <form @submit.prevent=\"saveForm\">\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6 mb-6\">\n <h2 class=\"text-lg font-semibold text-white mb-4\">Cím adatok</h2>\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div class=\"md:col-span-2\">\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">Teljes cím</label>\n <input\n v-model=\"form.address\"\n placeholder=\"Teljes cím szövege\"\n 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\"\n />\n </div>\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">Város</label>\n <input\n v-model=\"form.city\"\n placeholder=\"Budapest\"\n 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\"\n />\n </div>\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">Irányítószám</label>\n <input\n v-model=\"form.address_zip\"\n placeholder=\"1011\"\n 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\"\n />\n </div>\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">Utca</label>\n <input\n v-model=\"form.address_street_name\"\n placeholder=\"Egressy\"\n 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\"\n />\n </div>\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">Házszám</label>\n <input\n v-model=\"form.address_house_number\"\n placeholder=\"4/b\"\n 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\"\n />\n </div>\n <div class=\"md:col-span-2\">\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">Plus Code</label>\n <input\n v-model=\"form.plus_code\"\n placeholder=\"8FVC9X8V+XV\"\n 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\"\n />\n </div>\n </div>\n </div>\n </form>\n </div>\n\n <!-- ═══════════════════════════════════════════════════\n TAB 4: Nyitvatartás (Opening Hours)\n ═══════════════════════════════════════════════════ -->\n <div v-if=\"activeTab === 'hours'\" class=\"max-w-3xl\">\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6 mb-6\">\n <h2 class=\"text-lg font-semibold text-white mb-2\">Nyitvatartás</h2>\n <p class=\"text-sm text-slate-400 mb-6\">\n Állítsd be a szolgáltató nyitvatartási idejét napokra bontva.\n </p>\n\n <!-- Always-open toggle -->\n <div class=\"mb-6 p-4 rounded-lg border bg-indigo-500/5 border-indigo-500/20\">\n <label class=\"flex items-center gap-3 cursor-pointer\">\n <input\n type=\"checkbox\"\n v-model=\"form.is_always_open\"\n @change=\"onAlwaysOpenChange\"\n class=\"w-5 h-5 rounded border-slate-500 bg-slate-700 text-indigo-500 focus:ring-indigo-500/50\"\n />\n <div>\n <span class=\"text-sm font-medium text-white\">Folyamatos (0-24) nyitvatartás</span>\n <p class=\"text-xs text-slate-400 mt-0.5\">A szolgáltató a hét minden napján 0-24 óráig nyitva van</p>\n </div>\n </label>\n </div>\n\n <!-- Quick action buttons (disabled when always-open is active) -->\n <div class=\"flex flex-wrap gap-2 mb-6\" :class=\"{ 'opacity-50 pointer-events-none': form.is_always_open }\">\n <button\n @click=\"setAllDaysOpen('00:00', '23:59')\"\n class=\"px-3 py-1.5 text-xs font-medium rounded-lg border border-emerald-600/30 text-emerald-400 hover:bg-emerald-600/10 transition\"\n >\n 24/7 Folyamatos nyitvatartás\n </button>\n <button\n @click=\"copyWeekdays\"\n class=\"px-3 py-1.5 text-xs font-medium rounded-lg border border-indigo-600/30 text-indigo-400 hover:bg-indigo-600/10\"\n >\n Hétköznapok másolása (Hétfő → K-P)\n </button>\n <button\n @click=\"clearAllHours\"\n class=\"px-3 py-1.5 text-xs font-medium rounded-lg border border-rose-600/30 text-rose-400 hover:bg-rose-600/10\"\n >\n Összes törlése\n </button>\n </div>\n\n <!-- 7-day grid (disabled when always-open is active) -->\n <div class=\"space-y-3\" :class=\"{ 'opacity-50 pointer-events-none': form.is_always_open }\">\n <div\n v-for=\"day in DAYS\"\n :key=\"day.key\"\n class=\"flex items-center gap-4 p-4 rounded-lg border\"\n :class=\"form.opening_hours[day.key]\n ? 'bg-indigo-500/5 border-indigo-500/20'\n : 'bg-slate-700/30 border-slate-600'\"\n >\n <!-- Day name + checkbox -->\n <label class=\"flex items-center gap-2 w-36 flex-shrink-0 cursor-pointer\">\n <input\n type=\"checkbox\"\n :checked=\"!!form.opening_hours[day.key]\"\n @change=\"toggleDay(day.key)\"\n :disabled=\"form.is_always_open\"\n class=\"w-4 h-4 rounded border-slate-500 bg-slate-700 text-indigo-500 focus:ring-indigo-500/50\"\n />\n <span class=\"text-sm font-medium text-white\">{{ day.label }}</span>\n </label>\n\n <!-- Time inputs (visible only when checked) -->\n <template v-if=\"form.opening_hours[day.key]\">\n <div class=\"flex items-center gap-2\">\n <input\n type=\"time\"\n :value=\"form.opening_hours[day.key]?.open || '08:00'\"\n @input=\"updateDayTime(day.key, 'open', ($event.target as HTMLInputElement).value)\"\n :disabled=\"form.is_always_open\"\n class=\"w-28 px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 disabled:opacity-50\"\n />\n <span class=\"text-slate-500 text-sm\"></span>\n <input\n type=\"time\"\n :value=\"form.opening_hours[day.key]?.close || '17:00'\"\n @input=\"updateDayTime(day.key, 'close', ($event.target as HTMLInputElement).value)\"\n :disabled=\"form.is_always_open\"\n class=\"w-28 px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 disabled:opacity-50\"\n />\n </div>\n </template>\n\n <!-- \"Zárva\" label when unchecked -->\n <span v-else class=\"text-sm text-slate-500 italic\">Zárva</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- ═══════════════════════════════════════════════════\n SAVE BUTTON (fixed bottom bar)\n ═══════════════════════════════════════════════════ -->\n <div class=\"sticky bottom-0 bg-slate-900/90 backdrop-blur-sm border-t border-slate-700 -mx-6 px-6 py-4 mt-8\">\n <div class=\"flex items-center justify-between max-w-4xl\">\n <div class=\"text-sm text-slate-400\">\n <span v-if=\"hasChanges\" class=\"text-amber-400\">● Módosítások el nem mentve</span>\n <span v-else class=\"text-slate-500\">Nincs változás</span>\n </div>\n <div class=\"flex gap-3\">\n <button\n @click=\"goBack\"\n class=\"px-4 py-2 text-sm text-slate-400 hover:text-white transition\"\n >\n Mégse\n </button>\n <button\n @click=\"saveForm\"\n :disabled=\"saveLoading || !hasChanges\"\n class=\"px-6 py-2 bg-indigo-600 hover:bg-indigo-500 disabled:bg-indigo-600/50 text-white rounded-lg text-sm font-medium transition flex items-center gap-2\"\n >\n <svg v-if=\"saveLoading\" class=\"w-4 h-4 animate-spin\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <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\" />\n </svg>\n <svg v-else class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 13l4 4L19 7\" />\n </svg>\n Mentés\n </button>\n </div>\n </div>\n </div>\n </template>\n\n <!-- Toast Notification -->\n <div\n v-if=\"toast\"\n class=\"fixed top-4 right-4 z-50 px-4 py-3 rounded-lg shadow-xl text-sm font-medium transition-all duration-300\"\n :class=\"toast.type === 'success' ? 'bg-emerald-600 text-white' : 'bg-rose-600 text-white'\"\n >\n {{ toast.message }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n/**\n * 🏛️ Service Provider Dedicated Edit Page\n * =========================================\n * \n * Architectural Context (P0 - Card #384):\n * ----------------------------------------\n * This page replaces the missing modal-based edit system with a full-page\n * 3-tab editor. It integrates with the existing PATCH /admin/providers/{id}\n * endpoint which supports:\n * - Basic fields: name, category, contact info\n * - 3D Matrix: supported_vehicle_classes, category_ids (expertise tags),\n * specializations (brands + propulsion)\n * - Location: address, city, zip, street, house_number, plus_code\n * \n * Data Flow:\n * ----------\n * 1. On mount: fetch provider detail from GET /admin/providers/{id}\n * 2. On mount: fetch category tree from GET /providers/categories/tree\n * 3. User edits fields across 3 tabs\n * 4. On save: PATCH /admin/providers/{id} with full payload\n * 5. On success: navigate back to /providers/{id}\n * \n * Vehicle Classes (Dimension A):\n * ------------------------------\n * The 8 system vehicle classes matching the backend enum:\n * car, motorcycle, truck, hgv, light_commercial,\n * personal, boat, other\n */\n\nimport { ref, computed, onMounted, reactive } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\n\n// ── Types ──────────────────────────────────────────────\ninterface CategoryTreeNode {\n id: number\n key: string\n name_hu?: string | null\n name_en?: string | null\n level: number\n path?: string | null\n is_official: boolean\n children: CategoryTreeNode[]\n}\n\ninterface ProviderDetail {\n id: number\n name: string\n address: string\n city?: string | null\n address_zip?: string | null\n address_street_name?: string | null\n address_street_type?: string | null\n address_house_number?: string | null\n plus_code?: string | null\n contact_phone?: string | null\n contact_email?: string | null\n website?: string | null\n category?: string | null\n status: string\n source: string\n validation_score: number\n evidence_image_path?: string | null\n added_by_user_id?: number | null\n category_ids?: number[] | null\n supported_vehicle_classes?: string[] | null\n specializations?: Record<string, any> | null\n opening_hours?: Record<string, any> | null\n created_at?: string | null\n}\n\n// ── Constants ──────────────────────────────────────────\n/**\n * The 8 system vehicle classes.\n * These match the backend VehicleClass enum values.\n */\nconst VEHICLE_CLASSES = [\n { key: 'car', label: 'Személyautó' },\n { key: 'motorcycle', label: 'Motorkerékpár' },\n { key: 'truck', label: 'Teherautó' },\n { key: 'hgv', label: 'Nehézgépjármű (HGV)' },\n { key: 'light_commercial', label: 'Kishaszonjármű' },\n { key: 'personal', label: 'Személyes' },\n { key: 'boat', label: 'Csónak/Vízi' },\n { key: 'other', label: 'Egyéb' },\n]\n\n// ── Route ──────────────────────────────────────────────\nconst route = useRoute()\nconst router = useRouter()\nconst providerId = computed(() => Number(route.params.id))\n\n// ── State ──────────────────────────────────────────────\nconst loading = ref(true)\nconst error = ref(false)\nconst provider = ref<ProviderDetail | null>(null)\nconst saveLoading = ref(false)\nconst toast = ref<{ message: string; type: 'success' | 'error' } | null>(null)\n\n// Tab state\nconst activeTab = ref<'basic' | 'matrix' | 'location' | 'hours'>('basic')\nconst tabs = [\n { key: 'basic', label: 'Alapadatok & Kapcsolat', icon: 'svg' },\n { key: 'matrix', label: 'Szolgáltatások & Járművek', icon: 'svg' },\n { key: 'location', label: 'Helyszín & Lokáció', icon: 'svg' },\n { key: 'hours', label: 'Nyitvatartás', icon: 'svg' },\n]\n\n// Category tree state\nconst categoryTree = ref<CategoryTreeNode[]>([])\nconst treeLoading = ref(false)\nconst expandedNodes = ref<Set<number>>(new Set())\n\n// Tag inputs\nconst brandInput = ref('')\nconst propulsionInput = ref('')\n\n/**\n * The reactive form model.\n * Initialized with defaults, then populated from provider data on load.\n * \n * Mapping from backend ProviderUpdateInput schema:\n * - name, city, address_zip, address_street_name, address_street_type,\n * address_house_number, plus_code, contact_phone, contact_email,\n * website, category, category_ids, supported_vehicle_classes, specializations\n */\nconst form = reactive({\n name: '',\n address: '',\n city: '',\n address_zip: '',\n address_street_name: '',\n address_street_type: '',\n address_house_number: '',\n plus_code: '',\n contact_phone: '',\n contact_email: '',\n website: '',\n category: '',\n category_ids: [] as number[],\n supported_vehicle_classes: [] as string[],\n specializations: {\n brands: [] as string[],\n propulsion: [] as string[],\n } as Record<string, string[]>,\n opening_hours: {} as Record<string, { open: string; close: string } | null>,\n is_always_open: false,\n})\n\n// ── Computed ───────────────────────────────────────────\n\n/**\n * Deep comparison between original provider data and current form state.\n * Used to enable/disable the save button and show dirty state indicator.\n *\n * CRITICAL BUGFIX (2026-07-01):\n * - Normalize specializations before comparison to avoid {} vs {brands:[],propulsion:[]} mismatch\n * - Use JSON.stringify with sorted arrays for reliable array comparison\n * - All scalar fields compared with || '' fallback for null-safety\n */\nconst hasChanges = computed(() => {\n if (!provider.value) return false\n const p = provider.value\n\n // Scalar field comparisons\n if (form.name !== (p.name || '')) return true\n if (form.address !== (p.address || '')) return true\n if (form.city !== (p.city || '')) return true\n if (form.address_zip !== (p.address_zip || '')) return true\n if (form.address_street_name !== (p.address_street_name || '')) return true\n if (form.address_street_type !== (p.address_street_type || '')) return true\n if (form.address_house_number !== (p.address_house_number || '')) return true\n if (form.plus_code !== (p.plus_code || '')) return true\n if (form.contact_phone !== (p.contact_phone || '')) return true\n if (form.contact_email !== (p.contact_email || '')) return true\n if (form.website !== (p.website || '')) return true\n if (form.category !== (p.category || '')) return true\n\n // Array comparisons with deep equality (JSON.stringify + sort for order-independence)\n const sortedFormCategories = [...form.category_ids].sort()\n const sortedOrigCategories = [...(p.category_ids || [])].sort()\n if (JSON.stringify(sortedFormCategories) !== JSON.stringify(sortedOrigCategories)) return true\n\n const sortedFormClasses = [...form.supported_vehicle_classes].sort()\n const sortedOrigClasses = [...(p.supported_vehicle_classes || [])].sort()\n if (JSON.stringify(sortedFormClasses) !== JSON.stringify(sortedOrigClasses)) return true\n\n // Specializations comparison: normalize both sides to the same shape\n // to avoid false-positive when API returns {} vs form default {brands:[],propulsion:[]}\n const normalizeSpec = (spec: any) => ({\n brands: Array.isArray(spec?.brands) ? [...spec.brands].sort() : [],\n propulsion: Array.isArray(spec?.propulsion) ? [...spec.propulsion].sort() : [],\n })\n if (JSON.stringify(normalizeSpec(form.specializations)) !== JSON.stringify(normalizeSpec(p.specializations))) return true\n\n // Opening hours comparison: normalize both sides to the same shape\n // Supports both flat format {monday: {...}} and nested {always_open: true, days: {monday: {...}}}\n const DAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']\n const normalizeHours = (hours: any): Record<string, any> => {\n const result: Record<string, any> = {}\n // Detect nested format with always_open metadata\n const daysSource = hours?.days || hours\n for (const day of DAYS) {\n const slot = daysSource?.[day]\n if (slot && typeof slot === 'object' && slot.open && slot.close) {\n result[day] = { open: slot.open, close: slot.close }\n } else {\n result[day] = null\n }\n }\n return result\n }\n if (JSON.stringify(normalizeHours(form.opening_hours)) !== JSON.stringify(normalizeHours(p.opening_hours))) return true\n\n // is_always_open comparison\n if (form.is_always_open !== (p.opening_hours?.always_open === true)) return true\n\n return false\n})\n\n// ── Auth helpers ───────────────────────────────────────\nfunction getHeaders(): Record<string, string> {\n const token = useCookie('access_token').value\n const headers: Record<string, string> = { 'Content-Type': 'application/json' }\n if (token) headers['Authorization'] = `Bearer ${token}`\n return headers\n}\n\n// ── API: Fetch provider ────────────────────────────────\nasync function fetchProvider() {\n loading.value = true\n error.value = false\n try {\n const res = await $fetch(`/api/v1/admin/providers/${providerId.value}`, {\n headers: getHeaders(),\n })\n provider.value = res as ProviderDetail\n mapProviderToForm(res as ProviderDetail)\n } catch (err: any) {\n console.error('Failed to fetch provider:', err)\n error.value = true\n } finally {\n loading.value = false\n }\n}\n\n/**\n * Maps the provider detail response to the reactive form.\n * Handles null/undefined values and ensures arrays/dicts have defaults.\n */\nfunction mapProviderToForm(p: ProviderDetail) {\n form.name = p.name || ''\n form.address = p.address || ''\n form.city = p.city || ''\n form.address_zip = p.address_zip || ''\n form.address_street_name = p.address_street_name || ''\n form.address_street_type = p.address_street_type || ''\n form.address_house_number = p.address_house_number || ''\n form.plus_code = p.plus_code || ''\n form.contact_phone = p.contact_phone || ''\n form.contact_email = p.contact_email || ''\n form.website = p.website || ''\n form.category = p.category || ''\n form.category_ids = p.category_ids || []\n form.supported_vehicle_classes = p.supported_vehicle_classes || []\n\n // Parse specializations - handle both object and string formats\n const spec = p.specializations || {}\n form.specializations = {\n brands: Array.isArray(spec.brands) ? spec.brands : [],\n propulsion: Array.isArray(spec.propulsion) ? spec.propulsion : [],\n }\n\n // Parse opening_hours - support both flat format {monday: {...}, ...}\n // and the new nested format {always_open: true, days: {monday: {...}, ...}}\n const rawHours = p.opening_hours || {}\n const DAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']\n\n // Detect if the backend stored the nested format with always_open metadata\n const isAlwaysOpen = rawHours.always_open === true\n const daysSource = rawHours.days || rawHours\n\n form.is_always_open = isAlwaysOpen\n\n const hours: Record<string, { open: string; close: string } | null> = {}\n for (const day of DAYS) {\n const slot = daysSource[day]\n if (slot && typeof slot === 'object' && slot.open && slot.close) {\n hours[day] = { open: slot.open, close: slot.close }\n } else {\n hours[day] = null\n }\n }\n form.opening_hours = hours\n}\n\n// ── API: Fetch category tree ───────────────────────────\nasync function fetchCategoryTree() {\n treeLoading.value = true\n try {\n const res = await $fetch('/api/v1/providers/categories/tree', {\n headers: getHeaders(),\n })\n categoryTree.value = (res as CategoryTreeNode[]) || []\n } catch (err: any) {\n console.error('Failed to fetch category tree:', err)\n } finally {\n treeLoading.value = false\n }\n}\n\n// ── Category tree helpers ──────────────────────────────\n\n/**\n * Recursively finds a category node by ID in the tree.\n */\nfunction findCategoryById(id: number, nodes: CategoryTreeNode[]): CategoryTreeNode | null {\n for (const node of nodes) {\n if (node.id === id) return node\n if (node.children?.length) {\n const found = findCategoryById(id, node.children)\n if (found) return found\n }\n }\n return null\n}\n\n/**\n * Gets the display name for a category ID (for the selected chips).\n */\nfunction getCategoryName(id: number): string {\n const node = findCategoryById(id, categoryTree.value)\n if (!node) return `#${id}`\n return node.name_hu || node.name_en || node.key\n}\n\n// ── Dimension A: Vehicle Class toggles ─────────────────\n\n/**\n * Toggles a vehicle class in the form.supported_vehicle_classes array.\n * If already selected, removes it; otherwise adds it.\n */\nfunction toggleVehicleClass(key: string) {\n const idx = form.supported_vehicle_classes.indexOf(key)\n if (idx >= 0) {\n form.supported_vehicle_classes.splice(idx, 1)\n } else {\n form.supported_vehicle_classes.push(key)\n }\n}\n\n// ── Dimension B: Category toggles ──────────────────────\n\n/**\n * Toggles a category ID in the form.category_ids array.\n * If already selected, removes it; otherwise adds it.\n */\nfunction toggleCategory(id: number) {\n const idx = form.category_ids.indexOf(id)\n if (idx >= 0) {\n form.category_ids.splice(idx, 1)\n } else {\n form.category_ids.push(id)\n }\n}\n\n/**\n * Toggles the expanded/collapsed state of a tree node.\n */\nfunction toggleTreeNode(id: number) {\n const newSet = new Set(expandedNodes.value)\n if (newSet.has(id)) {\n newSet.delete(id)\n } else {\n newSet.add(id)\n }\n expandedNodes.value = newSet\n}\n\n/**\n * Checks if a category ID is currently selected.\n */\nfunction isCategorySelected(id: number): boolean {\n return form.category_ids.includes(id)\n}\n\n/**\n * Removes a category ID from the selection.\n */\nfunction removeCategory(id: number) {\n const idx = form.category_ids.indexOf(id)\n if (idx >= 0) {\n form.category_ids.splice(idx, 1)\n }\n}\n\n// ── Dimension C: Specialization tag management ─────────\n\n/**\n * Adds the current brandInput value to the brands array.\n */\nfunction addBrand() {\n const val = brandInput.value.trim()\n if (val && !form.specializations.brands.includes(val)) {\n form.specializations.brands.push(val)\n }\n brandInput.value = ''\n}\n\n/**\n * Removes a brand at the given index.\n */\nfunction removeBrand(idx: number) {\n form.specializations.brands.splice(idx, 1)\n}\n\n/**\n * Adds the current propulsionInput value to the propulsion array.\n */\nfunction addPropulsion() {\n const val = propulsionInput.value.trim()\n if (val && !form.specializations.propulsion.includes(val)) {\n form.specializations.propulsion.push(val)\n }\n propulsionInput.value = ''\n}\n\n/**\n * Removes a propulsion entry at the given index.\n */\nfunction removePropulsion(idx: number) {\n form.specializations.propulsion.splice(idx, 1)\n}\n\n// ── DAYS constant ──────────────────────────────────────\nconst DAYS = [\n { key: 'monday', label: 'Hétfő' },\n { key: 'tuesday', label: 'Kedd' },\n { key: 'wednesday', label: 'Szerda' },\n { key: 'thursday', label: 'Csütörtök' },\n { key: 'friday', label: 'Péntek' },\n { key: 'saturday', label: 'Szombat' },\n { key: 'sunday', label: 'Vasárnap' },\n]\n\n// ── Opening Hours helpers ──────────────────────────────\n\n/**\n * Called when the \"Folyamatos (0-24) nyitvatartás\" checkbox is toggled.\n * When checked: sets all 7 days to 00:00-23:59.\n * When unchecked: clears all hours so the user can set custom times.\n */\nfunction onAlwaysOpenChange() {\n if (form.is_always_open) {\n setAllDaysOpen('00:00', '23:59')\n } else {\n clearAllHours()\n }\n}\n\n/**\n * Toggles a day on/off in the opening_hours form.\n * When enabling, defaults to 08:00-17:00.\n */\nfunction toggleDay(dayKey: string) {\n if (form.opening_hours[dayKey]) {\n form.opening_hours[dayKey] = null\n } else {\n form.opening_hours[dayKey] = { open: '08:00', close: '17:00' }\n }\n}\n\n/**\n * Updates the open or close time for a given day.\n */\nfunction updateDayTime(dayKey: string, field: 'open' | 'close', value: string) {\n const slot = form.opening_hours[dayKey]\n if (slot) {\n slot[field] = value\n }\n}\n\n/**\n * Sets all 7 days to the same open/close times (e.g. 00:00-23:59 for 24/7).\n */\nfunction setAllDaysOpen(open: string, close: string) {\n for (const day of DAYS) {\n form.opening_hours[day.key] = { open, close }\n }\n}\n\n/**\n * Copies Monday's opening hours to Tuesday through Friday.\n * If Monday is closed, does nothing.\n */\nfunction copyWeekdays() {\n const monday = form.opening_hours['monday']\n if (!monday) return\n const weekdayKeys = ['tuesday', 'wednesday', 'thursday', 'friday']\n for (const key of weekdayKeys) {\n form.opening_hours[key] = { open: monday.open, close: monday.close }\n }\n}\n\n/**\n * Clears all opening hours (sets all days to null/closed).\n */\nfunction clearAllHours() {\n for (const day of DAYS) {\n form.opening_hours[day.key] = null\n }\n}\n\n// ── Save ───────────────────────────────────────────────\n\n/**\n * Saves the form data via PATCH /admin/providers/{id}.\n * Builds the payload matching the ProviderUpdateInput schema.\n * On success: navigates back to the provider detail page.\n * On error: shows error toast.\n */\nasync function saveForm() {\n if (saveLoading.value) return\n saveLoading.value = true\n\n // CRITICAL BUGFIX (2026-07-01): address is NOT NULL in the DB schema,\n // so we must never send null for it. Fall back to name if empty.\n const safeAddress = form.address?.trim() || form.name\n\n const payload: Record<string, any> = {\n name: form.name,\n address: safeAddress,\n category: form.category || null,\n contact_phone: form.contact_phone || null,\n contact_email: form.contact_email || null,\n website: form.website || null,\n city: form.city || null,\n address_zip: form.address_zip || null,\n address_street_name: form.address_street_name || null,\n address_street_type: form.address_street_type || null,\n address_house_number: form.address_house_number || null,\n plus_code: form.plus_code || null,\n category_ids: form.category_ids,\n supported_vehicle_classes: form.supported_vehicle_classes,\n specializations: form.specializations,\n opening_hours: form.opening_hours,\n is_always_open: form.is_always_open,\n }\n\n try {\n await $fetch(`/api/v1/admin/providers/${providerId.value}`, {\n method: 'PATCH',\n headers: getHeaders(),\n body: payload,\n })\n showToast('success', 'Szolgáltató sikeresen frissítve!')\n setTimeout(() => {\n router.push(`/providers/${providerId.value}`)\n }, 1200)\n } catch (err: any) {\n console.error('Failed to save provider:', err)\n const msg = err?.data?.detail || err?.message || 'Ismeretlen hiba történt mentés közben.'\n showToast('error', `Hiba: ${msg}`)\n } finally {\n saveLoading.value = false\n }\n}\n\n// ── Navigation ─────────────────────────────────────────\n\n/**\n * Navigates back to the provider detail page.\n */\nfunction goBack() {\n router.push(`/providers/${providerId.value}`)\n}\n\n// ── Toast ──────────────────────────────────────────────\n\n/**\n * Shows a temporary toast notification that auto-hides after 3 seconds.\n */\nfunction showToast(type: 'success' | 'error', message: string) {\n toast.value = { type, message }\n setTimeout(() => {\n toast.value = null\n }, 3000)\n}\n\n// ── Lifecycle ──────────────────────────────────────────\n\nonMounted(async () => {\n await fetchProvider()\n await fetchCategoryTree()\n})\n</script>\n\n<style scoped>\n/* Custom scrollbar for the category tree */\n.overflow-y-auto::-webkit-scrollbar {\n width: 6px;\n}\n.overflow-y-auto::-webkit-scrollbar-track {\n background: transparent;\n}\n.overflow-y-auto::-webkit-scrollbar-thumb {\n background-color: rgba(100, 116, 139, 0.5);\n border-radius: 3px;\n}\n.overflow-y-auto::-webkit-scrollbar-thumb:hover {\n background-color: rgba(100, 116, 139, 0.7);\n}\n</style>"],"version":3}