Files
service-finder/frontend_admin/.nuxt/dist/server/_nuxt/index-B9tmK_Km.js.map.json
2026-06-30 08:56:55 +00:00

1 line
71 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":"index-B9tmK_Km.js","mappings":"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","names":["$t","_ssrRenderList","_ssrInterpolate","_ssrRenderAttr","_ssrIncludeBooleanAttr","_ssrLooseEqual","_ssrRenderClass","_ssrLooseContain","_unref"],"sources":["../../../../pages/packages/index.vue"],"sourcesContent":["<template>\n <div>\n <!-- Page Header -->\n <div class=\"mb-8 flex items-center justify-between\">\n <div>\n <h1 class=\"text-2xl font-bold text-white\">{{ $t('packages.title') }}</h1>\n <p class=\"text-slate-400 mt-1\">{{ $t('packages.subtitle') }}</p>\n </div>\n <button\n @click=\"openCreateModal\"\n class=\"px-5 py-2.5 bg-emerald-600 hover:bg-emerald-500 text-white font-medium rounded-lg transition text-sm flex items-center gap-2\"\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=\"M12 4v16m8-8H4\" />\n </svg>\n {{ $t('packages.create_new') }}\n </button>\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=\"animate-spin h-8 w-8 text-indigo-400\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\"></path>\n </svg>\n <span class=\"text-slate-400 text-sm\">{{ $t('packages.loading') }}</span>\n </div>\n </div>\n\n <!-- Error State -->\n <div v-else-if=\"error\" class=\"bg-red-900/30 border border-red-700 rounded-xl p-6 text-center\">\n <p class=\"text-red-300 mb-3\">{{ error }}</p>\n <button\n @click=\"fetchPackages\"\n class=\"px-4 py-2 bg-red-700 hover:bg-red-600 text-white rounded-lg transition text-sm\"\n >\n {{ $t('packages.retry') }}\n </button>\n </div>\n\n <!-- Empty State -->\n <div v-else-if=\"packages.length === 0\" class=\"bg-slate-800/50 border border-slate-700 rounded-xl p-12 text-center\">\n <svg class=\"w-16 h-16 mx-auto 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=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\" />\n </svg>\n <h3 class=\"text-lg font-medium text-slate-300 mb-2\">{{ $t('packages.no_packages') }}</h3>\n <p class=\"text-slate-500 mb-6\">{{ $t('packages.no_packages_desc') }}</p>\n <button\n @click=\"openCreateModal\"\n class=\"px-5 py-2.5 bg-emerald-600 hover:bg-emerald-500 text-white font-medium rounded-lg transition text-sm\"\n >\n {{ $t('packages.create_first') }}\n </button>\n </div>\n\n <!-- Package Content (shown when packages exist) -->\n <div v-else class=\"space-y-6 mb-8\">\n <!-- Package Type Tabs (Base vs Add-on) -->\n <div class=\"mb-6\">\n <div class=\"flex gap-2 border-b border-slate-700\">\n <button\n @click=\"listTab = 'base'\"\n class=\"px-5 py-3 text-sm font-medium border-b-2 transition\"\n :class=\"listTab === 'base'\n ? 'border-indigo-500 text-indigo-300'\n : 'border-transparent text-slate-400 hover:text-slate-200 hover:border-slate-500'\"\n >\n <span class=\"flex items-center gap-2\">\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=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\" />\n </svg>\n Alapcsomagok\n </span>\n </button>\n <button\n @click=\"listTab = 'addon'\"\n class=\"px-5 py-3 text-sm font-medium border-b-2 transition\"\n :class=\"listTab === 'addon'\n ? 'border-amber-500 text-amber-300'\n : 'border-transparent text-slate-400 hover:text-slate-200 hover:border-slate-500'\"\n >\n <span class=\"flex items-center gap-2\">\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=\"M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n Kiegészítők\n </span>\n </button>\n </div>\n </div>\n\n <!-- Package Tier Cards Grid -->\n <div class=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6\">\n <div\n v-for=\"pkg in listTab === 'base' ? basePackages : addonPackages\"\n :key=\"pkg.id\"\n class=\"bg-slate-800 rounded-xl border border-slate-700 p-6 flex flex-col transition-all hover:border-slate-600\"\n >\n <!-- Card Header -->\n <div class=\"flex items-center justify-between mb-4\">\n <div class=\"flex items-center gap-3\">\n <div\n class=\"p-2 rounded-lg\"\n :class=\"getTierColorClass(pkg.tier_level)\"\n >\n <span class=\"w-5 h-5 text-lg\" v-html=\"getTierIcon(pkg.tier_level)\" />\n </div>\n <div>\n <h2 class=\"text-lg font-semibold text-white\">{{ getDisplayName(pkg) }}</h2>\n <span class=\"text-xs text-slate-500 font-mono\">{{ pkg.name }}</span>\n </div>\n </div>\n <div class=\"flex items-center gap-1.5 flex-wrap\">\n <span\n v-if=\"pkg.tier_level >= 1\"\n class=\"px-2 py-0.5 text-xs font-semibold rounded-full bg-indigo-500/20 text-indigo-300\"\n >\n {{ $t('packages.popular') }}\n </span>\n <span\n v-if=\"pkg.is_default_fallback\"\n class=\"px-2 py-0.5 text-xs font-semibold rounded-full bg-amber-500/20 text-amber-300\"\n title=\"Default fallback package for expired subscriptions\"\n >\n 🔒 {{ $t('packages.fallback_badge') }}\n </span>\n <span\n v-if=\"pkg.trial_days_on_signup > 0\"\n class=\"px-2 py-0.5 text-xs font-semibold rounded-full bg-emerald-500/20 text-emerald-300\"\n :title=\"`${pkg.trial_days_on_signup} days trial on signup`\"\n >\n 🎯 {{ $t('packages.trial_badge') }}: {{ pkg.trial_days_on_signup }}d\n </span>\n </div>\n </div>\n\n <!-- Price -->\n <div class=\"mb-4\">\n <span class=\"text-3xl font-bold text-white\">{{ getFormattedPrice(pkg) }}</span>\n <span class=\"text-sm text-slate-400 ml-1\">/ {{ $t('packages.month') }}</span>\n </div>\n\n <!-- Description / Subtitle -->\n <p class=\"text-sm text-slate-400 mb-4 flex-1\">{{ getDescription(pkg) }}</p>\n\n <!-- Allowances -->\n <div class=\"border-t border-slate-700 pt-4 mb-4\">\n <h3 class=\"text-xs font-semibold uppercase tracking-wider text-slate-500 mb-3\">\n {{ $t('packages.allowances') }}\n </h3>\n <div class=\"space-y-2\">\n <div class=\"flex items-center justify-between text-sm\">\n <span class=\"text-slate-400\">{{ $t('packages.max_vehicles') }}</span>\n <span class=\"text-slate-200 font-medium\">{{ getAllowance(pkg, 'max_vehicles') }}</span>\n </div>\n <div class=\"flex items-center justify-between text-sm\">\n <span class=\"text-slate-400\">{{ $t('packages.max_garages') }}</span>\n <span class=\"text-slate-200 font-medium\">{{ getAllowance(pkg, 'max_garages') }}</span>\n </div>\n <div class=\"flex items-center justify-between text-sm\">\n <span class=\"text-slate-400\">{{ $t('packages.max_users') }}</span>\n <span class=\"text-slate-200 font-medium\">{{ getAllowance(pkg, 'max_users') }}</span>\n </div>\n <div class=\"flex items-center justify-between text-sm\">\n <span class=\"text-slate-400\">{{ $t('packages.monthly_credits') }}</span>\n <span class=\"text-slate-200 font-medium\">{{ getAllowance(pkg, 'monthly_free_credits') }}</span>\n </div>\n </div>\n </div>\n\n <!-- Tier Level Badge -->\n <div class=\"mb-4\">\n <span class=\"text-xs text-slate-500\">{{ $t('packages.tier_level') }}: </span>\n <span class=\"text-xs font-mono text-slate-400\">{{ pkg.tier_level }}</span>\n </div>\n\n <!-- Actions -->\n <div class=\"flex gap-2\">\n <button\n @click=\"openEditModal(pkg)\"\n class=\"flex-1 px-4 py-2.5 bg-indigo-600 hover:bg-indigo-500 text-white font-medium rounded-lg transition text-sm\"\n >\n {{ $t('packages.edit') }}\n </button>\n <button\n @click=\"duplicatePackage(pkg)\"\n class=\"px-3 py-2.5 bg-emerald-700 hover:bg-emerald-600 text-emerald-200 rounded-lg transition\"\n :title=\"$t('packages.duplicate')\"\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=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\" />\n </svg>\n </button>\n <button\n @click=\"confirmDelete(pkg)\"\n class=\"px-3 py-2.5 bg-red-700 hover:bg-red-600 text-red-200 rounded-lg transition\"\n :title=\"$t('packages.delete')\"\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=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </div>\n </div> <!-- /grid -->\n </div> <!-- /package-content-wrapper -->\n\n <!-- ── Create / Edit Modal (Tabbed Layout) ──────────────────────── -->\n <div\n v-if=\"showModal\"\n class=\"fixed inset-0 z-50 flex items-center justify-center bg-black/60\"\n @click.self=\"showModal = false\"\n >\n <div class=\"bg-slate-800 border border-slate-700 rounded-xl w-full max-w-3xl mx-4 shadow-2xl max-h-[90vh] flex flex-col\">\n <!-- Modal Header -->\n <div class=\"flex items-center justify-between px-6 py-4 border-b border-slate-700\">\n <h3 class=\"text-lg font-semibold text-white\">\n {{ isCreating ? $t('packages.create_title') : ($t('packages.edit_title') + ': ' + getDisplayName(editingPackage!)) }}\n </h3>\n <button\n @click=\"showModal = false\"\n class=\"p-1 rounded-lg text-slate-400 hover:text-white hover:bg-slate-700 transition\"\n >\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 <!-- 🎯 GLOBAL TYPE SELECTOR (above tabs, below title) -->\n <div class=\"px-6 pt-4 pb-2 mb-6\">\n <div class=\"bg-slate-700/40 border border-slate-600 rounded-xl p-4\">\n <label class=\"block text-sm font-semibold text-slate-200 mb-3\">{{ $t('packages.form_type') }}</label>\n <div class=\"flex gap-4\">\n <label\n class=\"flex-1 flex items-center gap-3 px-4 py-3 rounded-lg border-2 cursor-pointer transition\"\n :class=\"form.type === 'base' || form.type === 'private' || form.type === 'corporate'\n ? 'border-indigo-500 bg-indigo-500/10'\n : 'border-slate-600 bg-slate-700/50 hover:border-slate-500'\"\n >\n <input\n type=\"radio\"\n name=\"packageType\"\n value=\"private\"\n v-model=\"form.type\"\n class=\"sr-only\"\n />\n <div\n class=\"w-5 h-5 rounded-full border-2 flex items-center justify-center flex-shrink-0\"\n :class=\"form.type === 'private' || form.type === 'corporate' || form.type === 'base'\n ? 'border-indigo-500'\n : 'border-slate-500'\"\n >\n <div\n v-if=\"form.type === 'private' || form.type === 'corporate' || form.type === 'base'\"\n class=\"w-2.5 h-2.5 rounded-full bg-indigo-500\"\n ></div>\n </div>\n <div>\n <span class=\"text-sm font-medium text-white\">📦 Alapcsomag (Base Tier)</span>\n <p class=\"text-xs text-slate-400 mt-0.5\">Teljes jogú csomag járművekkel, garázsokkal, felhasználókkal</p>\n </div>\n </label>\n <label\n class=\"flex-1 flex items-center gap-3 px-4 py-3 rounded-lg border-2 cursor-pointer transition\"\n :class=\"form.type === 'addon'\n ? 'border-amber-500 bg-amber-500/10'\n : 'border-slate-600 bg-slate-700/50 hover:border-slate-500'\"\n >\n <input\n type=\"radio\"\n name=\"packageType\"\n value=\"addon\"\n v-model=\"form.type\"\n class=\"sr-only\"\n />\n <div\n class=\"w-5 h-5 rounded-full border-2 flex items-center justify-center flex-shrink-0\"\n :class=\"form.type === 'addon' ? 'border-amber-500' : 'border-slate-500'\"\n >\n <div\n v-if=\"form.type === 'addon'\"\n class=\"w-2.5 h-2.5 rounded-full bg-amber-500\"\n ></div>\n </div>\n <div>\n <span class=\"text-sm font-medium text-white\"> Kiegészítő (Add-on)</span>\n <p class=\"text-xs text-slate-400 mt-0.5\">Kiegészítő modul meglévő csomaghoz, korlátozott opciókkal</p>\n </div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Tab Navigation -->\n <div class=\"flex border-b border-slate-700 px-6\">\n <button\n v-for=\"tab in tabs\"\n :key=\"tab.key\"\n @click=\"activeTab = tab.key\"\n class=\"px-4 py-3 text-sm font-medium border-b-2 transition\"\n :class=\"activeTab === tab.key\n ? 'border-indigo-500 text-indigo-300'\n : 'border-transparent text-slate-400 hover:text-slate-200 hover:border-slate-500'\"\n >\n {{ tab.label }}\n </button>\n </div>\n\n <!-- Tab Content (scrollable) -->\n <div class=\"flex-1 overflow-y-auto px-6 py-5\">\n <!-- ═══ TAB 1: Basic Info ═══ -->\n <div v-if=\"activeTab === 'basic'\" class=\"space-y-4\">\n <!-- System Name: free-text input for base, dropdown for addon -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ $t('packages.form_name') }}</label>\n <input\n v-model=\"form.name\"\n type=\"text\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n :placeholder=\"$t('packages.form_name_placeholder')\"\n />\n </div>\n\n <!-- Display Name -->\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ $t('packages.form_display_name') }}</label>\n <input\n v-model=\"form.display_name\"\n type=\"text\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n :placeholder=\"$t('packages.form_display_name_placeholder')\"\n />\n </div>\n\n <!-- Tier Level (hidden for add-on) -->\n <div v-if=\"form.type !== 'addon'\">\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ $t('packages.form_tier_level') }}</label>\n <input\n v-model.number=\"form.tier_level\"\n type=\"number\"\n min=\"0\"\n max=\"10\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n />\n <p class=\"text-xs text-slate-500 mt-1\">{{ $t('packages.form_tier_level_hint') }}</p>\n </div>\n\n <!-- Subtitle & Badge (hidden for add-on) -->\n <div v-if=\"form.type !== 'addon'\" class=\"grid grid-cols-2 gap-4\">\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ $t('packages.form_subtitle') }}</label>\n <input\n v-model=\"form.subtitle\"\n type=\"text\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n :placeholder=\"$t('packages.form_subtitle_placeholder')\"\n />\n </div>\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ $t('packages.form_badge') }}</label>\n <input\n v-model=\"form.badge\"\n type=\"text\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n :placeholder=\"$t('packages.form_badge_placeholder')\"\n />\n </div>\n </div>\n\n <!-- Is Custom -->\n <div class=\"flex items-center gap-3\">\n <input\n v-model=\"form.is_custom\"\n type=\"checkbox\"\n id=\"is_custom\"\n class=\"rounded bg-slate-700 border-slate-600 text-indigo-600 focus:ring-indigo-500\"\n />\n <label for=\"is_custom\" class=\"text-sm text-slate-300\">{{ $t('packages.form_is_custom') }}</label>\n </div>\n\n <!-- Singleton Rules (hidden for add-on) -->\n <div v-if=\"form.type !== 'addon'\" class=\"border-t border-slate-700 pt-4\">\n <h4 class=\"text-sm font-semibold text-slate-300 mb-3\">{{ $t('packages.singleton_rules') }}</h4>\n <div class=\"space-y-4\">\n <div class=\"flex items-center gap-3\">\n <input\n v-model=\"form.is_default_fallback\"\n type=\"checkbox\"\n id=\"is_default_fallback\"\n class=\"rounded bg-slate-700 border-slate-600 text-amber-500 focus:ring-amber-500\"\n />\n <label for=\"is_default_fallback\" class=\"text-sm text-slate-300\">\n {{ $t('packages.form_is_default_fallback') }}\n </label>\n </div>\n <div>\n <label class=\"block text-sm text-slate-300 mb-1\">{{ $t('packages.form_trial_days') }}</label>\n <input\n v-model.number=\"form.trial_days_on_signup\"\n type=\"number\"\n min=\"0\"\n max=\"365\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-emerald-500 focus:border-transparent\"\n />\n <p class=\"text-xs text-slate-500 mt-1\">{{ $t('packages.form_trial_days_hint') }}</p>\n </div>\n </div>\n </div>\n </div>\n\n <!-- ═══ TAB 2: Pricing (Per-Region + Smart Calculator) ═══ -->\n <div v-if=\"activeTab === 'pricing'\" class=\"space-y-6\">\n <!-- Per-Region Pricing Grid -->\n <div>\n <h4 class=\"text-sm font-semibold text-slate-300 mb-3\">{{ $t('packages.per_region_pricing') }}</h4>\n <div class=\"overflow-x-auto\">\n <table class=\"w-full text-sm\">\n <thead>\n <tr class=\"border-b border-slate-700\">\n <th class=\"text-left py-2 px-3 text-slate-400 font-medium\">Region</th>\n <th class=\"text-left py-2 px-3 text-slate-400 font-medium\">{{ $t('packages.form_monthly_price') }}</th>\n <th class=\"text-left py-2 px-3 text-slate-400 font-medium\">{{ $t('packages.form_yearly_price') }}</th>\n <th class=\"text-left py-2 px-3 text-slate-400 font-medium\">Kredit Ár</th>\n <th class=\"text-left py-2 px-3 text-slate-400 font-medium\">{{ $t('packages.form_currency') }}</th>\n <th class=\"text-left py-2 px-3 text-slate-400 font-medium\"></th>\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"region in regionStore.regions\" :key=\"region.country_code\" class=\"border-b border-slate-700/50\">\n <td class=\"py-2 px-3 text-slate-200 font-medium\">\n <span class=\"inline-flex items-center gap-1.5\">\n <span>{{ region.country_code }}</span>\n <span class=\"text-xs text-slate-500\">({{ region.currency }}, {{ region.default_vat_rate }}% VAT)</span>\n </span>\n </td>\n <td class=\"py-2 px-3\">\n <input\n :value=\"getZonePrice(region.country_code, 'monthly')\"\n @input=\"setZonePrice(region.country_code, 'monthly', ($event.target as HTMLInputElement).value)\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n class=\"w-full px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n />\n </td>\n <td class=\"py-2 px-3\">\n <input\n :value=\"getZonePrice(region.country_code, 'yearly')\"\n @input=\"setZonePrice(region.country_code, 'yearly', ($event.target as HTMLInputElement).value)\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n class=\"w-full px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n />\n </td>\n <td class=\"py-2 px-3\">\n <input\n :value=\"getZoneCreditPrice(region.country_code)\"\n @input=\"setZoneCreditPrice(region.country_code, ($event.target as HTMLInputElement).value)\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n class=\"w-full px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n />\n </td>\n <td class=\"py-2 px-3\">\n <input\n :value=\"getZoneCurrency(region.country_code)\"\n @input=\"setZoneCurrency(region.country_code, ($event.target as HTMLInputElement).value)\"\n type=\"text\"\n maxlength=\"3\"\n class=\"w-full px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent uppercase\"\n />\n </td>\n <td class=\"py-2 px-3 text-center\">\n <div class=\"flex items-center gap-1\">\n <button\n @click=\"applyDefaultPricingToZone(region.country_code)\"\n class=\"p-1.5 rounded-lg text-emerald-400 hover:text-emerald-300 hover:bg-emerald-500/10 transition\"\n :title=\"'Apply DEFAULT pricing to ' + region.country_code\"\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=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n </button>\n <button\n @click=\"removeZone(region.country_code)\"\n class=\"p-1.5 rounded-lg text-red-400 hover:text-red-300 hover:bg-red-500/10 transition\"\n :title=\"'Remove ' + region.country_code\"\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=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Add Zone Dropdown + Button -->\n <div class=\"flex items-center gap-3 mt-4\">\n <select\n v-model=\"selectedAddZoneCode\"\n class=\"px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n >\n <option value=\"\" disabled>{{ $t('packages.add_zone_select') }}</option>\n <option\n v-for=\"region in availableRegionsToAdd\"\n :key=\"region.country_code\"\n :value=\"region.country_code\"\n >\n {{ region.country_code }} — {{ region.name }} ({{ region.currency }}, {{ region.default_vat_rate }}% VAT)\n </option>\n </select>\n <button\n @click=\"addZone\"\n :disabled=\"!selectedAddZoneCode\"\n class=\"px-4 py-2 bg-indigo-600 hover:bg-indigo-500 disabled:bg-slate-600 disabled:cursor-not-allowed text-white text-sm font-medium rounded-lg transition flex items-center gap-2\"\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=\"M12 4v16m8-8H4\" />\n </svg>\n {{ $t('packages.add_zone') }}\n </button>\n </div>\n </div>\n\n <!-- Smart Pricing Calculator -->\n <div class=\"border-t border-slate-700 pt-5\">\n <h4 class=\"text-sm font-semibold text-slate-300 mb-3\">🧮 {{ $t('packages.smart_calculator') }}</h4>\n <div class=\"bg-slate-700/30 rounded-xl p-4 space-y-4\">\n <!-- Calculator Inputs -->\n <div class=\"grid grid-cols-3 gap-4\">\n <div>\n <label class=\"block text-xs text-slate-400 mb-1\">{{ $t('packages.calc_net_price') }}</label>\n <input\n v-model.number=\"calcNetPrice\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-emerald-500 focus:border-transparent\"\n placeholder=\"0.00\"\n />\n </div>\n <div>\n <label class=\"block text-xs text-slate-400 mb-1\">{{ $t('packages.calc_vat_rate') }}</label>\n <select\n v-model=\"calcVatRate\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-emerald-500 focus:border-transparent\"\n >\n <option v-for=\"region in regionStore.regions\" :key=\"region.country_code\" :value=\"region.default_vat_rate\">\n {{ region.country_code }} — {{ region.default_vat_rate }}%\n </option>\n </select>\n </div>\n <div>\n <label class=\"block text-xs text-slate-400 mb-1\">{{ $t('packages.calc_currency') }}</label>\n <select\n v-model=\"calcCurrency\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-emerald-500 focus:border-transparent\"\n >\n <option v-for=\"region in regionStore.regions\" :key=\"region.country_code\" :value=\"region.currency\">\n {{ region.currency }} ({{ region.country_code }})\n </option>\n </select>\n </div>\n </div>\n\n <!-- Calculator Results -->\n <div class=\"grid grid-cols-3 gap-4 pt-2\">\n <div class=\"bg-slate-800 rounded-lg p-3 text-center\">\n <p class=\"text-xs text-slate-400 mb-1\">{{ $t('packages.calc_net') }}</p>\n <p class=\"text-lg font-bold text-white\">{{ formatCurrency(calcNetPrice, calcCurrency) }}</p>\n </div>\n <div class=\"bg-slate-800 rounded-lg p-3 text-center\">\n <p class=\"text-xs text-slate-400 mb-1\">{{ $t('packages.calc_vat') }} ({{ calcVatRate }}%)</p>\n <p class=\"text-lg font-bold text-amber-400\">{{ formatCurrency(calcNetPrice * (calcVatRate / 100), calcCurrency) }}</p>\n </div>\n <div class=\"bg-slate-800 rounded-lg p-3 text-center ring-2 ring-emerald-500/50\">\n <p class=\"text-xs text-slate-400 mb-1\">{{ $t('packages.calc_gross') }}</p>\n <p class=\"text-lg font-bold text-emerald-400\">{{ formatCurrency(calcNetPrice * (1 + calcVatRate / 100), calcCurrency) }}</p>\n </div>\n </div>\n\n <!-- Quick Apply Button -->\n <div class=\"flex justify-end pt-1\">\n <button\n @click=\"applyCalculatorToActiveRegion\"\n class=\"px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white text-sm font-medium rounded-lg transition flex items-center gap-2\"\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=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n {{ $t('packages.calc_apply') }}\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <!-- ═══ TAB 3: Features ═══ -->\n <div v-if=\"activeTab === 'features'\" class=\"space-y-5\">\n <!-- Allowances -->\n <div>\n <h4 class=\"text-sm font-semibold text-slate-300 mb-3\">{{ $t('packages.allowances') }}</h4>\n <div class=\"grid grid-cols-3 gap-4\">\n <div>\n <label class=\"block text-xs text-slate-400 mb-1\">{{ $t('packages.max_vehicles') }}</label>\n <input\n v-model.number=\"form.max_vehicles\"\n type=\"number\"\n min=\"0\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n />\n </div>\n <div>\n <label class=\"block text-xs text-slate-400 mb-1\">{{ $t('packages.max_garages') }}</label>\n <input\n v-model.number=\"form.max_garages\"\n type=\"number\"\n min=\"0\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n />\n </div>\n <div>\n <label class=\"block text-xs text-slate-400 mb-1\">{{ $t('packages.max_users') }}</label>\n <input\n v-model.number=\"form.max_users\"\n type=\"number\"\n min=\"1\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n />\n </div>\n <div>\n <label class=\"block text-xs text-slate-400 mb-1\">{{ $t('packages.monthly_credits') }}</label>\n <input\n v-model.number=\"form.monthly_free_credits\"\n type=\"number\"\n min=\"0\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n />\n </div>\n </div>\n </div>\n\n <!-- SaaS Allowances (Feature Capabilities) -->\n <div class=\"border-t border-slate-700 pt-4\">\n <h4 class=\"text-sm font-semibold text-slate-300 mb-3\">SaaS Allowances</h4>\n <div class=\"grid grid-cols-3 gap-4\">\n <div>\n <label class=\"block text-xs text-slate-400 mb-1\">Költségkategória mélység (max_cost_category_depth)</label>\n <input\n v-model.number=\"form.max_cost_category_depth\"\n type=\"number\"\n min=\"0\"\n max=\"10\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n />\n </div>\n <div>\n <label class=\"block text-xs text-slate-400 mb-1\">Statisztika (advanced_reports)</label>\n <div class=\"flex items-center h-[38px]\">\n <label class=\"relative inline-flex items-center cursor-pointer\">\n <input\n v-model=\"form.advanced_reports\"\n type=\"checkbox\"\n class=\"sr-only peer\"\n />\n <div class=\"w-11 h-6 bg-slate-600 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-indigo-500 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-600\"></div>\n <span class=\"ms-3 text-sm text-slate-300\">{{ form.advanced_reports ? 'Enabled' : 'Disabled' }}</span>\n </label>\n </div>\n </div>\n <div>\n <label class=\"block text-xs text-slate-400 mb-1\">PDF/CSV Export (export_data)</label>\n <div class=\"flex items-center h-[38px]\">\n <label class=\"relative inline-flex items-center cursor-pointer\">\n <input\n v-model=\"form.export_data\"\n type=\"checkbox\"\n class=\"sr-only peer\"\n />\n <div class=\"w-11 h-6 bg-slate-600 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-indigo-500 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-600\"></div>\n <span class=\"ms-3 text-sm text-slate-300\">{{ form.export_data ? 'Enabled' : 'Disabled' }}</span>\n </label>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Feature Capabilities (JSON editor) -->\n <div class=\"border-t border-slate-700 pt-4\">\n <h4 class=\"text-sm font-semibold text-slate-300 mb-3\">{{ $t('packages.feature_capabilities') }}</h4>\n <textarea\n v-model=\"form.feature_capabilities_json\"\n rows=\"8\"\n class=\"w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm font-mono focus:ring-2 focus:ring-indigo-500 focus:border-transparent\"\n placeholder='{\\n \"ai_analysis\": true,\\n \"priority_support\": false,\\n \"api_access\": true\\n}'\n ></textarea>\n <p class=\"text-xs text-slate-500 mt-1\">{{ $t('packages.feature_capabilities_hint') }}</p>\n <p v-if=\"featureCapabilitiesError\" class=\"text-xs text-red-400 mt-1\">{{ featureCapabilitiesError }}</p>\n </div>\n </div>\n </div>\n\n <!-- Modal Footer -->\n <div class=\"flex items-center justify-between px-6 py-4 border-t border-slate-700\">\n <div class=\"text-xs text-slate-500\">\n {{ activeTabLabel }}\n </div>\n <div class=\"flex items-center gap-3\">\n <button\n @click=\"showModal = false\"\n class=\"px-4 py-2 text-sm text-slate-400 hover:text-white transition\"\n >\n {{ $t('packages.cancel') }}\n </button>\n <button\n @click=\"savePackage\"\n :disabled=\"saving\"\n class=\"px-6 py-2 bg-emerald-600 hover:bg-emerald-500 disabled:bg-slate-600 disabled:cursor-not-allowed text-white font-medium rounded-lg transition text-sm flex items-center gap-2\"\n >\n <svg v-if=\"saving\" class=\"animate-spin h-4 w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\"></path>\n </svg>\n {{ isCreating ? $t('packages.create') : $t('packages.save') }}\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Delete Confirmation Modal -->\n <div\n v-if=\"showDeleteConfirm && deletingPackage\"\n class=\"fixed inset-0 z-50 flex items-center justify-center bg-black/60\"\n @click.self=\"showDeleteConfirm = false\"\n >\n <div class=\"bg-slate-800 border border-slate-700 rounded-xl w-full max-w-md mx-4 p-6 shadow-2xl\">\n <h3 class=\"text-lg font-semibold text-white mb-2\">{{ $t('packages.delete_title') }}</h3>\n <p class=\"text-slate-400 text-sm mb-6\">\n {{ $t('packages.delete_confirm', { name: getDisplayName(deletingPackage) }) }}\n </p>\n <div class=\"flex items-center justify-end gap-3\">\n <button\n @click=\"showDeleteConfirm = false\"\n class=\"px-4 py-2 text-sm text-slate-400 hover:text-white transition\"\n >\n {{ $t('packages.cancel') }}\n </button>\n <button\n @click=\"deletePackage\"\n :disabled=\"saving\"\n class=\"px-6 py-2 bg-red-600 hover:bg-red-500 disabled:bg-slate-600 disabled:cursor-not-allowed text-white font-medium rounded-lg transition text-sm\"\n >\n {{ $t('packages.delete_btn') }}\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, reactive, computed, onMounted } from 'vue'\nimport { useRegionStore } from '~/stores/region'\nimport { useFormatter } from '~/composables/useFormatter'\n\ndefinePageMeta({\n middleware: 'auth',\n})\n\n// ── Region Store & Formatter ──────────────────────────────────────\nconst regionStore = useRegionStore()\nconst { formatCurrency } = useFormatter()\n\n// ── Types ──────────────────────────────────────────────────────────\n\ninterface SubscriptionTier {\n id: number\n name: string\n rules: {\n type: string\n display_name?: string | null\n pricing?: {\n monthly_price: number\n yearly_price: number\n currency: string\n credit_price?: number | null\n } | null\n pricing_zones?: Record<string, {\n monthly_price: number\n yearly_price: number\n currency: string\n credit_price?: number | null\n }> | null\n allowances?: {\n max_vehicles: number\n max_garages: number\n max_users: number\n monthly_free_credits: number\n } | null\n marketing?: {\n subtitle?: string | null\n badge?: string | null\n highlight_color?: string | null\n } | null\n lifecycle?: {\n is_public?: boolean\n available_until?: string | null\n } | null\n }\n is_custom: boolean\n tier_level: number\n feature_capabilities: Record<string, any>\n is_default_fallback: boolean\n trial_days_on_signup: number\n}\n\ninterface PackageForm {\n name: string\n display_name: string\n type: string\n tier_level: number\n monthly_price: number\n yearly_price: number\n currency: string\n max_vehicles: number\n max_garages: number\n max_users: number\n monthly_free_credits: number\n max_cost_category_depth: number\n advanced_reports: boolean\n export_data: boolean\n subtitle: string\n badge: string\n is_custom: boolean\n is_default_fallback: boolean\n trial_days_on_signup: number\n feature_capabilities_json: string\n pricing_zones: Record<string, { monthly_price: number; yearly_price: number; credit_price: number; currency: string }>\n}\n\n// ── Tab Configuration ──────────────────────────────────────────────\n\nconst tabs = [\n { key: 'basic', label: '📋 Basic Info' },\n { key: 'pricing', label: '💰 Pricing' },\n { key: 'features', label: '⚡ Features' },\n]\n\nconst activeTab = ref('basic')\n\nconst activeTabLabel = computed(() => {\n const tab = tabs.find(t => t.key === activeTab.value)\n return tab ? tab.label : ''\n})\n\n// ── List Tab State (Base vs Add-on) ────────────────────────────────\n\nconst listTab = ref<'base' | 'addon'>('base')\n\nconst basePackages = computed(() => packages.value.filter(p => p.rules?.type !== 'addon'))\nconst addonPackages = computed(() => packages.value.filter(p => p.rules?.type === 'addon'))\n\n// ── State ──────────────────────────────────────────────────────────\n\nconst packages = ref<SubscriptionTier[]>([])\nconst loading = ref(true)\nconst error = ref<string | null>(null)\nconst showModal = ref(false)\nconst showDeleteConfirm = ref(false)\nconst editingPackage = ref<SubscriptionTier | null>(null)\nconst deletingPackage = ref<SubscriptionTier | null>(null)\nconst isCreating = ref(false)\nconst saving = ref(false)\nconst saveNotification = ref<{ type: 'success' | 'error'; message: string } | null>(null)\nconst featureCapabilitiesError = ref<string | null>(null)\n\n// ── Add Zone State ─────────────────────────────────────────────────\n\nconst selectedAddZoneCode = ref('')\n\nconst availableRegionsToAdd = computed(() => {\n return regionStore.regions.filter(r => {\n return r.country_code !== 'DEFAULT' && !form.pricing_zones[r.country_code]\n })\n})\n\nfunction addZone() {\n if (!selectedAddZoneCode.value) return\n const code = selectedAddZoneCode.value\n if (form.pricing_zones[code]) {\n showNotification('error', `Region ${code} already exists in pricing zones`)\n return\n }\n const region = regionStore.getRegion(code)\n form.pricing_zones[code] = {\n monthly_price: 0,\n yearly_price: 0,\n credit_price: 0,\n currency: region?.currency || 'EUR',\n }\n selectedAddZoneCode.value = ''\n showNotification('success', `Added pricing zone for ${code}`)\n}\n\nfunction removeZone(countryCode: string) {\n if (countryCode === 'DEFAULT') return\n delete form.pricing_zones[countryCode]\n showNotification('success', `Removed pricing zone for ${countryCode}`)\n}\n\n// ── Smart Pricing Calculator State ─────────────────────────────────\n\nconst calcNetPrice = ref(0)\nconst calcVatRate = ref(27)\nconst calcCurrency = ref('EUR')\n\n// ── Default Form ───────────────────────────────────────────────────\n\nconst defaultForm: PackageForm = {\n name: '',\n display_name: '',\n type: 'private',\n tier_level: 0,\n monthly_price: 0,\n yearly_price: 0,\n currency: 'EUR',\n max_vehicles: 1,\n max_garages: 1,\n max_users: 1,\n monthly_free_credits: 0,\n max_cost_category_depth: 3,\n advanced_reports: false,\n export_data: false,\n subtitle: '',\n badge: '',\n is_custom: false,\n is_default_fallback: false,\n trial_days_on_signup: 0,\n feature_capabilities_json: '{\\n \"ai_analysis\": false,\\n \"priority_support\": false,\\n \"api_access\": false\\n}',\n pricing_zones: {}\n}\n\nconst form = reactive<PackageForm>({ ...defaultForm })\n\n// ── Pricing Zone Helpers ───────────────────────────────────────────\n\nfunction getZonePrice(countryCode: string, period: 'monthly' | 'yearly'): number {\n const zone = form.pricing_zones[countryCode]\n if (!zone) return 0\n return period === 'monthly' ? zone.monthly_price : zone.yearly_price\n}\n\nfunction setZonePrice(countryCode: string, period: 'monthly' | 'yearly', value: string) {\n const num = parseFloat(value) || 0\n if (!form.pricing_zones[countryCode]) {\n form.pricing_zones[countryCode] = { monthly_price: 0, yearly_price: 0, credit_price: 0, currency: 'EUR' }\n }\n if (period === 'monthly') {\n form.pricing_zones[countryCode].monthly_price = num\n } else {\n form.pricing_zones[countryCode].yearly_price = num\n }\n}\n\nfunction getZoneCreditPrice(countryCode: string): number {\n const zone = form.pricing_zones[countryCode]\n if (!zone) return 0\n return zone.credit_price ?? 0\n}\n\nfunction setZoneCreditPrice(countryCode: string, value: string) {\n const num = parseFloat(value) || 0\n if (!form.pricing_zones[countryCode]) {\n form.pricing_zones[countryCode] = { monthly_price: 0, yearly_price: 0, credit_price: 0, currency: 'EUR' }\n }\n form.pricing_zones[countryCode].credit_price = num\n}\n\nfunction getZoneCurrency(countryCode: string): string {\n const zone = form.pricing_zones[countryCode]\n if (!zone) return 'EUR'\n return zone.currency\n}\n\nfunction setZoneCurrency(countryCode: string, value: string) {\n if (!form.pricing_zones[countryCode]) {\n form.pricing_zones[countryCode] = { monthly_price: 0, yearly_price: 0, credit_price: 0, currency: 'EUR' }\n }\n form.pricing_zones[countryCode].currency = value.toUpperCase()\n}\n\n/**\n * Apply the Smart Pricing Calculator result to the currently selected region's monthly price.\n */\nfunction applyCalculatorToActiveRegion() {\n const targetRegion = regionStore.regions.find(r => r.default_vat_rate === calcVatRate)\n if (!targetRegion) {\n showNotification('error', 'No region found with the selected VAT rate')\n return\n }\n const code = targetRegion.country_code\n if (!form.pricing_zones[code]) {\n form.pricing_zones[code] = { monthly_price: 0, yearly_price: 0, credit_price: 0, currency: targetRegion.currency }\n }\n form.pricing_zones[code].monthly_price = calcNetPrice\n form.pricing_zones[code].currency = calcCurrency\n showNotification('success', `Applied net price ${formatCurrency(calcNetPrice, calcCurrency)} to ${code} region`)\n}\n\n/**\n * Copy DEFAULT zone pricing values to the specified region.\n * Uses Object.assign for Vue reactivity to ensure the UI updates immediately.\n */\nfunction applyDefaultPricingToZone(countryCode: string) {\n const defaultZone = form.pricing_zones['DEFAULT']\n if (!defaultZone) {\n showNotification('error', 'No DEFAULT pricing zone exists. Set DEFAULT prices first.')\n return\n }\n if (countryCode === 'DEFAULT') return\n // Ensure the target zone object exists\n if (!form.pricing_zones[countryCode]) {\n form.pricing_zones[countryCode] = { monthly_price: 0, yearly_price: 0, credit_price: 0, currency: 'EUR' }\n }\n // Copy DEFAULT values into the target zone using Object.assign for reactivity\n const target = form.pricing_zones[countryCode]\n Object.assign(target, {\n monthly_price: defaultZone.monthly_price,\n yearly_price: defaultZone.yearly_price,\n credit_price: defaultZone.credit_price ?? 0,\n currency: defaultZone.currency,\n })\n showNotification('success', `Applied DEFAULT pricing to ${countryCode} region`)\n}\n\n// ── API Helpers ────────────────────────────────────────────────────\n\nfunction getAuthHeaders() {\n const tokenCookie = useCookie('access_token')\n const token = tokenCookie.value\n return token ? { Authorization: `Bearer ${token}` } : {}\n}\n\nasync function fetchPackages() {\n loading.value = true\n error.value = null\n try {\n const res = await $fetch<{ total: number; tiers: SubscriptionTier[] }>('/api/v1/admin/packages?include_hidden=true', {\n headers: getAuthHeaders(),\n })\n packages.value = res.tiers || []\n } catch (err: any) {\n error.value = err?.data?.detail || err?.message || 'Failed to load packages'\n console.error('[Packages] Fetch error:', err)\n } finally {\n loading.value = false\n }\n}\n\n// ── Display Helpers ────────────────────────────────────────────────\n\nfunction getDisplayName(pkg: SubscriptionTier): string {\n return pkg.rules?.display_name || pkg.name\n}\n\nfunction getDescription(pkg: SubscriptionTier): string {\n return pkg.rules?.marketing?.subtitle || pkg.rules?.marketing?.badge || ''\n}\n\nfunction getFormattedPrice(pkg: SubscriptionTier): string {\n const zones = pkg.rules?.pricing_zones\n const pricing = pkg.rules?.pricing\n if (zones && zones['DEFAULT']) {\n const p = zones['DEFAULT']\n const currency = p.currency === 'HUF' ? 'Ft' : '€'\n return `${p.monthly_price} ${currency}`\n }\n if (pricing) {\n const currency = pricing.currency === 'HUF' ? 'Ft' : '€'\n return `${pricing.monthly_price} ${currency}`\n }\n return '—'\n}\n\nfunction getAllowance(pkg: SubscriptionTier, key: string): number | string {\n const allowances = pkg.rules?.allowances\n if (!allowances) return '—'\n return (allowances as any)[key] ?? '—'\n}\n\nfunction getTierColorClass(level: number): string {\n if (level >= 2) return 'bg-purple-500/10 text-purple-400'\n if (level >= 1) return 'bg-amber-500/10 text-amber-400'\n return 'bg-slate-500/10 text-slate-400'\n}\n\nfunction getTierIcon(level: number): string {\n if (level >= 2) return '🏢'\n if (level >= 1) return '⭐'\n return '🔓'\n}\n\n// ── Modal Actions ──────────────────────────────────────────────────\n\nfunction openCreateModal() {\n isCreating.value = true\n editingPackage.value = null\n activeTab.value = 'basic'\n Object.assign(form, defaultForm)\n // Context-aware type: if on the Add-on tab, default to addon type\n if (listTab.value === 'addon') {\n form.type = 'addon'\n }\n showModal.value = true\n}\n\nfunction openEditModal(pkg: SubscriptionTier) {\n isCreating.value = false\n editingPackage.value = pkg\n activeTab.value = 'basic'\n\n const rules = pkg.rules || {} as any\n const pricing = rules.pricing_zones?.DEFAULT || rules.pricing || {}\n const allowances = rules.allowances || {}\n\n form.name = pkg.name\n form.display_name = rules.display_name || ''\n form.type = rules.type || 'private'\n form.tier_level = pkg.tier_level\n form.monthly_price = pricing.monthly_price || 0\n form.yearly_price = pricing.yearly_price || 0\n form.currency = pricing.currency || 'EUR'\n form.max_vehicles = allowances.max_vehicles || 1\n form.max_garages = allowances.max_garages || 1\n form.max_users = allowances.max_users || 1\n form.monthly_free_credits = allowances.monthly_free_credits || 0\n form.subtitle = rules.marketing?.subtitle || ''\n form.badge = rules.marketing?.badge || ''\n form.is_custom = pkg.is_custom\n form.is_default_fallback = pkg.is_default_fallback\n form.trial_days_on_signup = pkg.trial_days_on_signup\n\n // Load SaaS allowances from feature_capabilities\n const fc = pkg.feature_capabilities || {}\n form.max_cost_category_depth = fc.max_cost_category_depth ?? 3\n form.advanced_reports = fc.advanced_reports ?? false\n form.export_data = fc.export_data ?? false\n\n // Load pricing zones\n form.pricing_zones = {}\n if (rules.pricing_zones) {\n for (const [code, zone] of Object.entries(rules.pricing_zones)) {\n form.pricing_zones[code] = {\n monthly_price: zone.monthly_price || 0,\n yearly_price: zone.yearly_price || 0,\n credit_price: zone.credit_price || 0,\n currency: zone.currency || 'EUR',\n }\n }\n }\n\n // Load feature capabilities as JSON string\n form.feature_capabilities_json = JSON.stringify(pkg.feature_capabilities || {}, null, 2)\n\n showModal.value = true\n}\n\nfunction confirmDelete(pkg: SubscriptionTier) {\n deletingPackage.value = pkg\n showDeleteConfirm.value = true\n}\n\n/**\n * Duplicate a package: opens the Create Modal pre-filled with the selected tier's data.\n * Clears the id and appends \"_copy\" to the name to avoid unique constraint violations.\n */\nfunction duplicatePackage(pkg: SubscriptionTier) {\n isCreating.value = true\n editingPackage.value = null\n activeTab.value = 'basic'\n\n const rules = pkg.rules || {} as any\n const pricing = rules.pricing_zones?.DEFAULT || rules.pricing || {}\n const allowances = rules.allowances || {}\n\n form.name = pkg.name + '_copy'\n form.display_name = (rules.display_name || pkg.name) + ' (másolat)'\n form.type = rules.type || 'private'\n form.tier_level = pkg.tier_level\n form.monthly_price = pricing.monthly_price || 0\n form.yearly_price = pricing.yearly_price || 0\n form.currency = pricing.currency || 'EUR'\n form.max_vehicles = allowances.max_vehicles || 1\n form.max_garages = allowances.max_garages || 1\n form.max_users = allowances.max_users || 1\n form.monthly_free_credits = allowances.monthly_free_credits || 0\n form.subtitle = rules.marketing?.subtitle || ''\n form.badge = rules.marketing?.badge || ''\n form.is_custom = pkg.is_custom\n form.is_default_fallback = false // never duplicate fallback status\n form.trial_days_on_signup = pkg.trial_days_on_signup\n\n // Load SaaS allowances from feature_capabilities\n const fc = pkg.feature_capabilities || {}\n form.max_cost_category_depth = fc.max_cost_category_depth ?? 3\n form.advanced_reports = fc.advanced_reports ?? false\n form.export_data = fc.export_data ?? false\n\n // Load pricing zones\n form.pricing_zones = {}\n if (rules.pricing_zones) {\n for (const [code, zone] of Object.entries(rules.pricing_zones)) {\n form.pricing_zones[code] = {\n monthly_price: zone.monthly_price || 0,\n yearly_price: zone.yearly_price || 0,\n credit_price: zone.credit_price || 0,\n currency: zone.currency || 'EUR',\n }\n }\n }\n\n // Load feature capabilities as JSON string\n form.feature_capabilities_json = JSON.stringify(pkg.feature_capabilities || {}, null, 2)\n\n showModal.value = true\n}\n\n// ── Save / Create ──────────────────────────────────────────────────\n\nasync function savePackage() {\n if (!form.name.trim()) {\n showNotification('error', 'Package name is required')\n return\n }\n\n // Validate feature_capabilities JSON\n let featureCapabilities: Record<string, any> = {}\n try {\n featureCapabilities = JSON.parse(form.feature_capabilities_json)\n featureCapabilitiesError.value = null\n } catch {\n featureCapabilitiesError.value = 'Invalid JSON in feature capabilities'\n activeTab.value = 'features'\n return\n }\n\n saving.value = true\n try {\n // Build pricing_zones from form, ensuring DEFAULT is always present\n const pricingZones: Record<string, any> = { ...form.pricing_zones }\n if (!pricingZones['DEFAULT']) {\n pricingZones['DEFAULT'] = {\n monthly_price: form.monthly_price,\n yearly_price: form.yearly_price,\n credit_price: 0,\n currency: form.currency.toUpperCase(),\n }\n }\n\n // Merge SaaS allowances into feature_capabilities\n const mergedFeatureCapabilities = {\n ...featureCapabilities,\n max_cost_category_depth: form.max_cost_category_depth,\n advanced_reports: form.advanced_reports,\n export_data: form.export_data,\n }\n\n const rulesPayload: any = {\n type: form.type,\n display_name: form.display_name || null,\n pricing_zones: pricingZones,\n allowances: {\n max_vehicles: form.max_vehicles,\n max_garages: form.max_garages,\n max_users: form.max_users,\n monthly_free_credits: form.monthly_free_credits,\n },\n marketing: {\n subtitle: form.subtitle || null,\n badge: form.badge || null,\n },\n lifecycle: {\n is_public: true,\n },\n }\n\n if (isCreating.value) {\n // POST - Create new package\n await $fetch('/api/v1/admin/packages', {\n method: 'POST',\n headers: {\n ...getAuthHeaders(),\n 'Content-Type': 'application/json',\n },\n body: {\n name: form.name.trim().toLowerCase(),\n rules: rulesPayload,\n is_custom: form.is_custom,\n tier_level: form.tier_level,\n feature_capabilities: mergedFeatureCapabilities,\n is_default_fallback: form.is_default_fallback,\n trial_days_on_signup: form.trial_days_on_signup,\n },\n })\n showNotification('success', `Package \"${form.name}\" created successfully`)\n } else if (editingPackage.value) {\n // PATCH - Update existing package\n await $fetch(`/api/v1/admin/packages/${editingPackage.value.id}`, {\n method: 'PATCH',\n headers: {\n ...getAuthHeaders(),\n 'Content-Type': 'application/json',\n },\n body: {\n name: form.name.trim().toLowerCase(),\n rules: rulesPayload,\n is_custom: form.is_custom,\n tier_level: form.tier_level,\n feature_capabilities: mergedFeatureCapabilities,\n is_default_fallback: form.is_default_fallback,\n trial_days_on_signup: form.trial_days_on_signup,\n },\n })\n showNotification('success', `Package \"${form.name}\" updated successfully`)\n }\n\n showModal.value = false\n await fetchPackages()\n } catch (err: any) {\n const detail = err?.data?.detail || err?.message || 'Operation failed'\n showNotification('error', detail)\n console.error('[Packages] Save error:', err)\n } finally {\n saving.value = false\n }\n}\n\nasync function deletePackage() {\n if (!deletingPackage.value) return\n\n saving.value = true\n try {\n await $fetch(`/api/v1/admin/packages/${deletingPackage.value.id}`, {\n method: 'DELETE',\n headers: getAuthHeaders(),\n })\n showNotification('success', `Package \"${getDisplayName(deletingPackage.value)}\" deactivated`)\n showDeleteConfirm.value = false\n deletingPackage.value = null\n await fetchPackages()\n } catch (err: any) {\n const detail = err?.data?.detail || err?.message || 'Delete failed'\n showNotification('error', detail)\n console.error('[Packages] Delete error:', err)\n } finally {\n saving.value = false\n }\n}\n\n// ── Notification ───────────────────────────────────────────────────\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 fetchPackages()\n regionStore.fetchRegions()\n})\n</script>"],"version":3}