1 line
28 KiB
JSON
1 line
28 KiB
JSON
{"file":"config-ChjtE2V1.js","mappings":"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","names":["_unref","_ssrInterpolate","_ssrRenderAttr","_ssrRenderList"],"sources":["../../../../pages/gamification/config.vue"],"sourcesContent":["<template>\n <div>\n <!-- Page Header -->\n <div class=\"mb-8\">\n <h1 class=\"text-2xl font-bold text-white\">{{ t('gamification.config.title') }}</h1>\n <p class=\"text-slate-400 mt-1\">{{ t('gamification.config.subtitle') }}</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>{{ t('gamification.config.loading') }}</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\">{{ t('gamification.config.load_error') }}</p>\n <button @click=\"fetchConfig\" class=\"mt-3 text-sm text-rose-300 hover:text-rose-200 underline\">{{ t('gamification.config.retry') }}</button>\n </div>\n\n <template v-else>\n <!-- Save Button Bar -->\n <div class=\"mb-6 flex items-center justify-between\">\n <div class=\"flex items-center gap-3\">\n <span class=\"text-sm text-slate-500\">{{ t('gamification.config.last_saved') }} {{ lastSaved ? lastSaved : '—' }}</span>\n </div>\n <button @click=\"saveConfig\" :disabled=\"saving\" class=\"px-4 py-2 bg-emerald-600 hover:bg-emerald-500 disabled:bg-emerald-600/50 text-white rounded-lg text-sm font-medium transition flex items-center gap-2\">\n <svg v-if=\"saving\" 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 {{ saving ? t('gamification.config.saving') : t('gamification.config.save_config') }}\n </button>\n </div>\n\n <!-- Config Sections Grid -->\n <div class=\"grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8\">\n <!-- XP Logic -->\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6\">\n <h2 class=\"text-lg font-semibold text-white mb-4 flex items-center gap-2\">\n <svg class=\"w-5 h-5 text-amber-400\" 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('gamification.config.xp_logic') }}\n </h2>\n <div class=\"space-y-4\">\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ t('gamification.config.base_xp') }}</label>\n <div class=\"flex items-center gap-2\">\n <input\n v-model.number=\"xpLogic.baseXp\"\n type=\"range\"\n min=\"0\"\n max=\"10000\"\n step=\"100\"\n class=\"flex-1 accent-amber-500\"\n />\n <input\n v-model.number=\"xpLogic.baseXp\"\n type=\"number\"\n min=\"0\"\n max=\"10000\"\n class=\"w-20 px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm text-center focus:outline-none focus:ring-2 focus:ring-amber-500/50\"\n />\n </div>\n </div>\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ t('gamification.config.exponent') }}</label>\n <div class=\"flex items-center gap-2\">\n <input\n v-model.number=\"xpLogic.exponent\"\n type=\"range\"\n min=\"0.1\"\n max=\"5.0\"\n step=\"0.1\"\n class=\"flex-1 accent-amber-500\"\n />\n <input\n v-model.number=\"xpLogic.exponent\"\n type=\"number\"\n min=\"0.1\"\n max=\"5.0\"\n step=\"0.1\"\n class=\"w-20 px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm text-center focus:outline-none focus:ring-2 focus:ring-amber-500/50\"\n />\n </div>\n </div>\n </div>\n </div>\n\n <!-- Conversion Logic -->\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6\">\n <h2 class=\"text-lg font-semibold text-white mb-4 flex items-center gap-2\">\n <svg class=\"w-5 h-5 text-emerald-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4\" />\n </svg>\n {{ t('gamification.config.conversion_logic') }}\n </h2>\n <div class=\"space-y-4\">\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ t('gamification.config.social_credit_rate') }}</label>\n <div class=\"flex items-center gap-2\">\n <input\n v-model.number=\"conversionLogic.socialToCreditRate\"\n type=\"range\"\n min=\"0\"\n max=\"10000\"\n step=\"10\"\n class=\"flex-1 accent-emerald-500\"\n />\n <input\n v-model.number=\"conversionLogic.socialToCreditRate\"\n type=\"number\"\n min=\"0\"\n max=\"10000\"\n class=\"w-20 px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm text-center focus:outline-none focus:ring-2 focus:ring-emerald-500/50\"\n />\n </div>\n </div>\n </div>\n </div>\n\n <!-- Penalty Logic -->\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6\">\n <h2 class=\"text-lg font-semibold text-white mb-4 flex items-center gap-2\">\n <svg class=\"w-5 h-5 text-rose-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z\" />\n </svg>\n {{ t('gamification.config.penalty_logic') }}\n </h2>\n <div class=\"space-y-4\">\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ t('gamification.config.recovery_rate') }}</label>\n <div class=\"flex items-center gap-2\">\n <input\n v-model.number=\"penaltyLogic.recoveryRate\"\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n class=\"flex-1 accent-rose-500\"\n />\n <input\n v-model.number=\"penaltyLogic.recoveryRate\"\n type=\"number\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n class=\"w-20 px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm text-center focus:outline-none focus:ring-2 focus:ring-rose-500/50\"\n />\n </div>\n </div>\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-2\">{{ t('gamification.config.thresholds') }}</label>\n <div class=\"grid grid-cols-3 gap-2\">\n <div>\n <label class=\"block text-xs text-slate-500 mb-1\">{{ t('gamification.config.level_1') }}</label>\n <input v-model.number=\"penaltyLogic.thresholds.level1\" type=\"number\" min=\"0\" class=\"w-full px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm text-center focus:outline-none focus:ring-2 focus:ring-rose-500/50\" />\n </div>\n <div>\n <label class=\"block text-xs text-slate-500 mb-1\">{{ t('gamification.config.level_2') }}</label>\n <input v-model.number=\"penaltyLogic.thresholds.level2\" type=\"number\" min=\"0\" class=\"w-full px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm text-center focus:outline-none focus:ring-2 focus:ring-rose-500/50\" />\n </div>\n <div>\n <label class=\"block text-xs text-slate-500 mb-1\">{{ t('gamification.config.level_3') }}</label>\n <input v-model.number=\"penaltyLogic.thresholds.level3\" type=\"number\" min=\"0\" class=\"w-full px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm text-center focus:outline-none focus:ring-2 focus:ring-rose-500/50\" />\n </div>\n </div>\n </div>\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-2\">{{ t('gamification.config.multipliers') }}</label>\n <div class=\"grid grid-cols-4 gap-2\">\n <div v-for=\"key in multiplierKeys\" :key=\"key\">\n <label class=\"block text-xs text-slate-500 mb-1\">{{ key }}</label>\n <input v-model.number=\"penaltyLogic.multipliers[key]\" type=\"number\" min=\"0\" max=\"1\" step=\"0.1\" class=\"w-full px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm text-center focus:outline-none focus:ring-2 focus:ring-rose-500/50\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Level Rewards -->\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6\">\n <h2 class=\"text-lg font-semibold text-white mb-4 flex items-center gap-2\">\n <svg class=\"w-5 h-5 text-violet-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z\" />\n </svg>\n {{ t('gamification.config.level_rewards') }}\n </h2>\n <div class=\"space-y-4\">\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-1\">{{ t('gamification.config.credits_per_10_levels') }}</label>\n <div class=\"flex items-center gap-2\">\n <input\n v-model.number=\"levelRewards.creditsPer10Levels\"\n type=\"range\"\n min=\"0\"\n max=\"10000\"\n step=\"10\"\n class=\"flex-1 accent-violet-500\"\n />\n <input\n v-model.number=\"levelRewards.creditsPer10Levels\"\n type=\"number\"\n min=\"0\"\n max=\"10000\"\n class=\"w-20 px-2 py-1.5 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm text-center focus:outline-none focus:ring-2 focus:ring-violet-500/50\"\n />\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Impact Calculator -->\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6 mb-8\">\n <h2 class=\"text-lg font-semibold text-white mb-4 flex items-center gap-2\">\n <svg class=\"w-5 h-5 text-cyan-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z\" />\n </svg>\n {{ t('gamification.config.impact_calculator') }}\n </h2>\n <p class=\"text-sm text-slate-500 mb-6\">{{ t('gamification.config.calculator_hint') }}</p>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <!-- Calculator Inputs -->\n <div class=\"space-y-6\">\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-2\">\n {{ t('gamification.config.user_level') }}: <span class=\"text-cyan-400 font-bold\">{{ calculator.level }}</span>\n </label>\n <input v-model.number=\"calculator.level\" type=\"range\" min=\"1\" max=\"100\" class=\"w-full accent-cyan-500\" />\n <div class=\"flex justify-between text-xs text-slate-600 mt-1\">\n <span>1</span><span>100</span>\n </div>\n </div>\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-2\">\n {{ t('gamification.config.daily_activities') }}: <span class=\"text-cyan-400 font-bold\">{{ calculator.activities }}</span>\n </label>\n <input v-model.number=\"calculator.activities\" type=\"range\" min=\"1\" max=\"20\" class=\"w-full accent-cyan-500\" />\n <div class=\"flex justify-between text-xs text-slate-600 mt-1\">\n <span>1</span><span>20</span>\n </div>\n </div>\n <div>\n <label class=\"block text-sm font-medium text-slate-300 mb-2\">\n {{ t('gamification.config.social_points') }}: <span class=\"text-cyan-400 font-bold\">{{ calculator.socialPoints }}</span>\n </label>\n <input v-model.number=\"calculator.socialPoints\" type=\"range\" min=\"0\" max=\"500\" step=\"10\" class=\"w-full accent-cyan-500\" />\n <div class=\"flex justify-between text-xs text-slate-600 mt-1\">\n <span>0</span><span>500</span>\n </div>\n </div>\n </div>\n\n <!-- Calculator Results -->\n <div class=\"bg-slate-700/50 rounded-xl p-6 space-y-4\">\n <h3 class=\"text-sm font-medium text-slate-400 uppercase tracking-wider\">{{ t('gamification.config.estimated_results') }}</h3>\n <div class=\"space-y-3\">\n <div class=\"flex items-center justify-between py-2 border-b border-slate-600/50\">\n <span class=\"text-sm text-slate-300\">{{ t('gamification.config.daily_xp') }}</span>\n <span class=\"text-lg font-bold text-amber-400\">{{ formatNumber(calculatedDailyXp) }}</span>\n </div>\n <div class=\"flex items-center justify-between py-2 border-b border-slate-600/50\">\n <span class=\"text-sm text-slate-300\">{{ t('gamification.config.level_up_xp') }}</span>\n <span class=\"text-lg font-bold text-emerald-400\">{{ formatNumber(calculatedLevelUpXp) }}</span>\n </div>\n <div class=\"flex items-center justify-between py-2 border-b border-slate-600/50\">\n <span class=\"text-sm text-slate-300\">{{ t('gamification.config.social_credit') }}</span>\n <span class=\"text-lg font-bold text-violet-400\">{{ formatNumber(calculatedSocialCredit) }}</span>\n </div>\n <div class=\"flex items-center justify-between py-2\">\n <span class=\"text-sm text-slate-300\">{{ t('gamification.config.days_to_level_up') }}</span>\n <span class=\"text-lg font-bold text-cyan-400\">{{ calculatedDaysToLevelUp }}</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Raw JSON Editor (Collapsible) -->\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6\">\n <button @click=\"showRawJson = !showRawJson\" class=\"flex items-center justify-between w-full\">\n <h2 class=\"text-lg font-semibold text-white flex items-center gap-2\">\n <svg class=\"w-5 h-5 text-slate-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\" />\n </svg>\n {{ t('gamification.config.raw_json_editor') }}\n </h2>\n <svg class=\"w-5 h-5 text-slate-400 transition-transform\" :class=\"showRawJson ? 'rotate-180' : ''\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </button>\n <div v-if=\"showRawJson\" class=\"mt-4\">\n <textarea\n v-model=\"rawJson\"\n rows=\"20\"\n class=\"w-full px-3 py-2 bg-slate-900 border border-slate-600 rounded-lg text-green-400 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50 resize-none\"\n spellcheck=\"false\"\n ></textarea>\n <div v-if=\"rawJsonError\" class=\"mt-2 bg-rose-500/10 border border-rose-500/30 rounded-lg p-3\">\n <p class=\"text-sm text-rose-400 font-mono\">{{ rawJsonError }}</p>\n </div>\n <button @click=\"applyRawJson\" class=\"mt-3 px-4 py-2 bg-amber-600 hover:bg-amber-500 text-white rounded-lg text-sm font-medium transition\">\n {{ t('gamification.config.apply_json') }}\n </button>\n </div>\n </div>\n </template>\n\n <!-- Form Error -->\n <div v-if=\"formError\" class=\"fixed bottom-6 right-6 z-50 bg-rose-600 text-white px-4 py-3 rounded-lg shadow-lg text-sm flex items-center gap-2\">\n <svg class=\"w-4 h-4 flex-shrink-0\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n {{ formError }}\n </div>\n\n <!-- Success Toast -->\n <div v-if=\"toast\" class=\"fixed bottom-6 right-6 z-50 bg-emerald-600 text-white px-4 py-3 rounded-lg shadow-lg text-sm flex items-center gap-2 animate-slide-up\">\n <svg class=\"w-4 h-4 flex-shrink-0\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n {{ toast }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\ndefinePageMeta({\n middleware: 'auth',\n})\n\nconst { t } = useI18n()\n\ninterface XpLogic {\n baseXp: number\n exponent: number\n}\n\ninterface PenaltyThresholds {\n level1: number\n level2: number\n level3: number\n}\n\ninterface PenaltyMultipliers {\n [key: string]: number\n}\n\ninterface PenaltyLogic {\n recoveryRate: number\n thresholds: PenaltyThresholds\n multipliers: PenaltyMultipliers\n}\n\ninterface ConversionLogic {\n socialToCreditRate: number\n}\n\ninterface LevelRewards {\n creditsPer10Levels: number\n}\n\nconst loading = ref(true)\nconst error = ref(false)\nconst saving = ref(false)\nconst formError = ref('')\nconst toast = ref('')\nconst lastSaved = ref('')\nconst showRawJson = ref(false)\nconst rawJson = ref('')\nconst rawJsonError = ref('')\n\nconst multiplierKeys = ['L0', 'L1', 'L2', 'L3']\n\nconst xpLogic = reactive<XpLogic>({ baseXp: 500, exponent: 1.5 })\nconst penaltyLogic = reactive<PenaltyLogic>({\n recoveryRate: 0.5,\n thresholds: { level1: 100, level2: 500, level3: 1000 },\n multipliers: { L0: 1.0, L1: 0.5, L2: 0.1, L3: 0.0 },\n})\nconst conversionLogic = reactive<ConversionLogic>({ socialToCreditRate: 100 })\nconst levelRewards = reactive<LevelRewards>({ creditsPer10Levels: 50 })\n\nconst calculator = reactive({ level: 5, activities: 3, socialPoints: 50 })\n\nconst calculatedDailyXp = computed(() => {\n return Math.round(xpLogic.baseXp * Math.pow(calculator.activities, xpLogic.exponent))\n})\n\nconst calculatedLevelUpXp = computed(() => {\n return calculator.level * 1000\n})\n\nconst calculatedSocialCredit = computed(() => {\n return conversionLogic.socialToCreditRate * calculator.socialPoints\n})\n\nconst calculatedDaysToLevelUp = computed(() => {\n const daily = calculatedDailyXp.value\n if (daily <= 0) return '∞'\n return Math.ceil(calculatedLevelUpXp.value / daily) + ' ' + t('gamification.config.days')\n})\n\nfunction getHeaders(): Record<string, string> {\n const tokenCookie = useCookie('access_token')\n const token = tokenCookie.value\n return token ? { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } : { 'Content-Type': 'application/json' }\n}\n\nfunction showToast(msg: string) {\n toast.value = msg\n setTimeout(() => { toast.value = '' }, 3000)\n}\n\nfunction buildConfigObject(): Record<string, any> {\n return {\n xp_logic: {\n base_xp: xpLogic.baseXp,\n exponent: xpLogic.exponent,\n },\n penalty_logic: {\n recovery_rate: penaltyLogic.recoveryRate,\n thresholds: {\n level_1: penaltyLogic.thresholds.level1,\n level_2: penaltyLogic.thresholds.level2,\n level_3: penaltyLogic.thresholds.level3,\n },\n multipliers: { ...penaltyLogic.multipliers },\n },\n conversion_logic: {\n social_to_credit_rate: conversionLogic.socialToCreditRate,\n },\n level_rewards: {\n credits_per_10_levels: levelRewards.creditsPer10Levels,\n },\n }\n}\n\nfunction applyConfigToForm(cfg: Record<string, any>) {\n if (cfg.xp_logic) {\n xpLogic.baseXp = cfg.xp_logic.base_xp ?? 500\n xpLogic.exponent = cfg.xp_logic.exponent ?? 1.5\n }\n if (cfg.penalty_logic) {\n penaltyLogic.recoveryRate = cfg.penalty_logic.recovery_rate ?? 0.5\n if (cfg.penalty_logic.thresholds) {\n penaltyLogic.thresholds.level1 = cfg.penalty_logic.thresholds.level_1 ?? 100\n penaltyLogic.thresholds.level2 = cfg.penalty_logic.thresholds.level_2 ?? 500\n penaltyLogic.thresholds.level3 = cfg.penalty_logic.thresholds.level_3 ?? 1000\n }\n if (cfg.penalty_logic.multipliers) {\n for (const key of multiplierKeys) {\n penaltyLogic.multipliers[key] = cfg.penalty_logic.multipliers[key] ?? 1.0\n }\n }\n }\n if (cfg.conversion_logic) {\n conversionLogic.socialToCreditRate = cfg.conversion_logic.social_to_credit_rate ?? 100\n }\n if (cfg.level_rewards) {\n levelRewards.creditsPer10Levels = cfg.level_rewards.credits_per_10_levels ?? 50\n }\n}\n\nasync function fetchConfig() {\n loading.value = true\n error.value = false\n try {\n const data = await $fetch<{ key: string; value: Record<string, any> }>('/api/v1/admin/gamification/master-config', { headers: getHeaders() })\n if (data.value && Object.keys(data.value).length > 0) {\n applyConfigToForm(data.value)\n }\n rawJson.value = JSON.stringify(buildConfigObject(), null, 2)\n } catch (e) {\n console.error('Failed to fetch master config:', e)\n error.value = true\n } finally {\n loading.value = false\n }\n}\n\nasync function saveConfig() {\n saving.value = true\n formError.value = ''\n try {\n const config = buildConfigObject()\n await $fetch('/api/v1/admin/gamification/master-config', {\n method: 'PUT',\n headers: getHeaders(),\n body: { config },\n })\n lastSaved.value = new Date().toLocaleString('hu-HU')\n rawJson.value = JSON.stringify(config, null, 2)\n showToast(t('gamification.config.config_updated'))\n } catch (e: any) {\n formError.value = t('gamification.config.save_error')\n console.error('Failed to save master config:', e)\n setTimeout(() => { formError.value = '' }, 4000)\n } finally {\n saving.value = false\n }\n}\n\nfunction applyRawJson() {\n rawJsonError.value = ''\n try {\n const parsed = JSON.parse(rawJson.value)\n applyConfigToForm(parsed)\n showRawJson.value = false\n showToast(t('gamification.config.json_applied'))\n } catch (e: any) {\n rawJsonError.value = t('gamification.config.invalid_json') + ' ' + (e.message || '')\n }\n}\n\nfunction formatNumber(n: number): string {\n return new Intl.NumberFormat('hu-HU').format(n)\n}\n\nonMounted(() => {\n fetchConfig()\n})\n</script>\n\n<style scoped>\n.animate-slide-up {\n animation: slideUp 0.3s ease-out;\n}\n@keyframes slideUp {\n from {\n opacity: 0;\n transform: translateY(1rem);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n</style>\n"],"version":3} |