1 line
15 KiB
JSON
1 line
15 KiB
JSON
{"file":"index-kkjYgRlj.js","mappings":"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","names":["_unref","_ssrInterpolate","_push","_parent","_createBlock","_createVNode","_createTextVNode","_ssrRenderList","_ssrRenderClass","_toDisplayString"],"sources":["../../../../pages/gamification/index.vue"],"sourcesContent":["<template>\n <div>\n <!-- Page Header -->\n <div class=\"mb-8\">\n <h1 class=\"text-2xl font-bold text-white\">{{ t('gamification.dashboard.title') }}</h1>\n <p class=\"text-slate-400 mt-1\">{{ t('gamification.dashboard.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.dashboard.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.dashboard.load_error') }}</p>\n </div>\n\n <!-- Dashboard Content -->\n <template v-else>\n <!-- Stat Tiles -->\n <div class=\"grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-6 mb-8\">\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6 hover:border-amber-500/50 transition group\">\n <div class=\"flex items-center justify-between mb-4\">\n <div class=\"p-2 rounded-lg bg-amber-500/10 text-amber-400 group-hover:bg-amber-500/20 transition\">\n <svg class=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n </div>\n </div>\n <h3 class=\"text-sm font-medium text-slate-400 uppercase tracking-wider\">{{ t('gamification.dashboard.total_users') }}</h3>\n <p class=\"text-3xl font-bold text-white mt-2\">{{ stats.total_users }}</p>\n </div>\n\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6 hover:border-emerald-500/50 transition group\">\n <div class=\"flex items-center justify-between mb-4\">\n <div class=\"p-2 rounded-lg bg-emerald-500/10 text-emerald-400 group-hover:bg-emerald-500/20 transition\">\n <svg class=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13 7h8m0 0v8m0-8l-8 8-4-4-6 6\" />\n </svg>\n </div>\n </div>\n <h3 class=\"text-sm font-medium text-slate-400 uppercase tracking-wider\">{{ t('gamification.dashboard.active_season') }}</h3>\n <p class=\"text-3xl font-bold text-white mt-2\">{{ stats.active_season || t('gamification.dashboard.none') }}</p>\n </div>\n\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6 hover:border-violet-500/50 transition group\">\n <div class=\"flex items-center justify-between mb-4\">\n <div class=\"p-2 rounded-lg bg-violet-500/10 text-violet-400 group-hover:bg-violet-500/20 transition\">\n <svg class=\"w-6 h-6\" 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 </div>\n </div>\n <h3 class=\"text-sm font-medium text-slate-400 uppercase tracking-wider\">{{ t('gamification.dashboard.pending_contributions') }}</h3>\n <p class=\"text-3xl font-bold text-white mt-2\">{{ stats.pending_contributions }}</p>\n </div>\n\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6 hover:border-cyan-500/50 transition group\">\n <div class=\"flex items-center justify-between mb-4\">\n <div class=\"p-2 rounded-lg bg-cyan-500/10 text-cyan-400 group-hover:bg-cyan-500/20 transition\">\n <svg class=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z\" />\n </svg>\n </div>\n </div>\n <h3 class=\"text-sm font-medium text-slate-400 uppercase tracking-wider\">{{ t('gamification.dashboard.today_xp') }}</h3>\n <p class=\"text-3xl font-bold text-white mt-2\">{{ stats.today_xp }}</p>\n </div>\n </div>\n\n <!-- Quick Links -->\n <div class=\"grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8\">\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\">{{ t('gamification.dashboard.quick_actions') }}</h2>\n <div class=\"space-y-3\">\n <NuxtLink to=\"/gamification/point-rules\" class=\"flex items-center gap-3 px-4 py-3 bg-slate-700/50 hover:bg-slate-700 rounded-lg text-sm text-slate-300 hover:text-white transition\">\n <svg class=\"w-5 h-5 text-amber-400 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 {{ t('gamification.dashboard.manage_point_rules') }}\n </NuxtLink>\n <NuxtLink to=\"/gamification/badges\" class=\"flex items-center gap-3 px-4 py-3 bg-slate-700/50 hover:bg-slate-700 rounded-lg text-sm text-slate-300 hover:text-white transition\">\n <svg class=\"w-5 h-5 text-violet-400 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-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z\" />\n </svg>\n {{ t('gamification.dashboard.manage_badges') }}\n </NuxtLink>\n <NuxtLink to=\"/gamification/seasons\" class=\"flex items-center gap-3 px-4 py-3 bg-slate-700/50 hover:bg-slate-700 rounded-lg text-sm text-slate-300 hover:text-white transition\">\n <svg class=\"w-5 h-5 text-emerald-400 flex-shrink-0\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z\" />\n </svg>\n {{ t('gamification.dashboard.manage_seasons') }}\n </NuxtLink>\n </div>\n </div>\n\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\">{{ t('gamification.dashboard.recent_ledger') }}</h2>\n <div v-if=\"recentLedger.length === 0\" class=\"text-sm text-slate-500 text-center py-8\">\n {{ t('gamification.dashboard.no_ledger_entries') }}\n </div>\n <div v-else class=\"space-y-3\">\n <div v-for=\"entry in recentLedger\" :key=\"entry.id\" class=\"flex items-center justify-between py-2 border-b border-slate-700/50 last:border-0\">\n <div>\n <p class=\"text-sm text-slate-300\">{{ entry.reason }}</p>\n <p class=\"text-xs text-slate-500\">{{ entry.created_at }}</p>\n </div>\n <span class=\"text-sm font-semibold\" :class=\"entry.points >= 0 ? 'text-emerald-400' : 'text-rose-400'\">\n {{ entry.points >= 0 ? '+' : '' }}{{ entry.points }}\n </span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Top 5 Leaderboard -->\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-6\">\n <div class=\"flex items-center justify-between mb-4\">\n <h2 class=\"text-lg font-semibold text-white\">{{ t('gamification.dashboard.top_5_leaderboard') }}</h2>\n <NuxtLink to=\"/gamification/leaderboard\" class=\"text-sm text-indigo-400 hover:text-indigo-300 transition\">\n {{ t('gamification.dashboard.full_leaderboard') }} →\n </NuxtLink>\n </div>\n <div v-if=\"topLeaderboard.length === 0\" class=\"text-sm text-slate-500 text-center py-8\">\n {{ t('gamification.dashboard.no_leaderboard_data') }}\n </div>\n <div v-else class=\"space-y-2\">\n <div v-for=\"(entry, idx) in topLeaderboard\" :key=\"idx\" class=\"flex items-center gap-4 px-4 py-3 rounded-lg\" :class=\"idx < 3 ? 'bg-slate-700/50' : ''\">\n <span class=\"w-8 text-center text-lg font-bold\" :class=\"idx === 0 ? 'text-amber-400' : idx === 1 ? 'text-slate-300' : idx === 2 ? 'text-amber-700' : 'text-slate-500'\">\n #{{ idx + 1 }}\n </span>\n <div class=\"flex-1\">\n <p class=\"text-sm font-medium text-white\">{{ entry.user_name }}</p>\n <p class=\"text-xs text-slate-500\">{{ t('gamification.dashboard.level') }} {{ entry.level }} · {{ entry.xp }} {{ t('gamification.dashboard.xp_abbr') }}</p>\n </div>\n <span class=\"text-sm font-semibold text-emerald-400\">{{ entry.total_points }} {{ t('gamification.dashboard.points') }}</span>\n </div>\n </div>\n </div>\n </template>\n </div>\n</template>\n\n<script setup lang=\"ts\">\ndefinePageMeta({\n middleware: 'auth',\n})\n\nconst { t } = useI18n()\n\ninterface DashboardStats {\n total_users: number\n active_season: string | null\n pending_contributions: number\n today_xp: number\n}\n\ninterface LedgerEntry {\n id: number\n user_name: string\n points: number\n reason: string\n created_at: string\n}\n\ninterface LeaderboardEntry {\n user_name: string\n level: number\n xp: number\n total_points: number\n}\n\nconst loading = ref(true)\nconst error = ref(false)\nconst stats = ref<DashboardStats>({\n total_users: 0,\n active_season: null,\n pending_contributions: 0,\n today_xp: 0,\n})\nconst recentLedger = ref<LedgerEntry[]>([])\nconst topLeaderboard = ref<LeaderboardEntry[]>([])\n\nonMounted(async () => {\n try {\n const tokenCookie = useCookie('access_token')\n const token = tokenCookie.value\n const headers = token ? { Authorization: `Bearer ${token}` } : {}\n\n const [statsData, ledgerData, leaderboardData] = await Promise.all([\n $fetch('/api/v1/admin/gamification/dashboard-summary', { headers }).catch(() => ({ total: 0 })),\n $fetch('/api/v1/admin/gamification/points-ledger?limit=5', { headers }).catch(() => []),\n $fetch('/api/v1/gamification/leaderboard?limit=5', { headers }).catch(() => []),\n ])\n\n stats.value = {\n total_users: statsData.total ?? 0,\n active_season: statsData.active_season ?? null,\n pending_contributions: statsData.pending_contributions ?? 0,\n today_xp: statsData.today_xp ?? 0,\n }\n recentLedger.value = Array.isArray(ledgerData) ? ledgerData.slice(0, 5) : []\n topLeaderboard.value = Array.isArray(leaderboardData) ? leaderboardData.slice(0, 5).map((entry: any) => ({\n user_name: entry.username || entry.user_name || `User #${entry.user_id}`,\n level: entry.current_level || entry.level || 0,\n xp: entry.total_xp || entry.xp || 0,\n total_points: entry.total_points || entry.total_xp || 0,\n })) : []\n } catch (e) {\n console.error('Failed to load gamification dashboard:', e)\n error.value = true\n } finally {\n loading.value = false\n }\n})\n</script>\n"],"version":3} |