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

1 line
58 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-D8qGjmnz.js","mappings":"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","names":["_ssrRenderAttrs","_mergeProps","$t","_ssrRenderAttr","_ssrInterpolate","_ssrRenderList","_ssrIncludeBooleanAttr","_push","_parent","_createTextVNode","_toDisplayString","_ssrRenderClass"],"sources":["../../../../components/charts/UserTrendChart.vue","../../../../pages/users/index.vue"],"sourcesContent":["<template>\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-5\">\n <!-- Header -->\n <div class=\"flex items-center justify-between mb-4\">\n <div>\n <h3 class=\"text-sm font-semibold text-white\">{{ $t('users.trend_title') }}</h3>\n <p class=\"text-xs text-slate-400 mt-0.5\">{{ $t('users.trend_subtitle') }}</p>\n </div>\n <!-- Period selector -->\n <select\n v-model=\"selectedPeriod\"\n class=\"px-2.5 py-1.5 text-xs bg-slate-700 border border-slate-600 rounded-lg text-slate-300 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition\"\n @change=\"updateChart\"\n >\n <option value=\"7\">{{ $t('users.trend_7d') }}</option>\n <option value=\"14\">{{ $t('users.trend_14d') }}</option>\n <option value=\"30\">{{ $t('users.trend_30d') }}</option>\n <option value=\"90\">{{ $t('users.trend_90d') }}</option>\n </select>\n </div>\n\n <!-- Loading State -->\n <div v-if=\"loading\" class=\"flex items-center justify-center py-12\">\n <svg class=\"w-6 h-6 text-indigo-400 animate-spin\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\" />\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\" />\n </svg>\n </div>\n\n <!-- Empty State -->\n <div v-else-if=\"filteredData.length === 0\" class=\"flex flex-col items-center justify-center py-12 text-center\">\n <svg class=\"w-10 h-10 text-slate-600 mb-3\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" 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 <p class=\"text-slate-500 text-xs\">{{ $t('users.trend_no_data') }}</p>\n </div>\n\n <!-- Chart Area -->\n <div v-else class=\"relative\">\n <!-- SVG Bar Chart -->\n <svg\n :viewBox=\"`0 0 ${svgWidth} ${svgHeight}`\"\n class=\"w-full h-auto overflow-visible\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <!-- Grid lines -->\n <line\n v-for=\"(y, yi) in gridLines\"\n :key=\"'grid-' + yi\"\n :x1=\"padding.left\"\n :y1=\"y\"\n :x2=\"svgWidth - padding.right\"\n :y2=\"y\"\n stroke=\"currentColor\"\n class=\"text-slate-700/50\"\n stroke-width=\"1\"\n stroke-dasharray=\"4,4\"\n />\n <!-- Y-axis labels -->\n <text\n v-for=\"(label, li) in yLabels\"\n :key=\"'ylabel-' + li\"\n :x=\"padding.left - 8\"\n :y=\"label.y + 4\"\n text-anchor=\"end\"\n class=\"fill-slate-500\"\n font-size=\"10\"\n >\n {{ label.text }}\n </text>\n\n <!-- Bars -->\n <g\n v-for=\"(item, idx) in visibleBars\"\n :key=\"'bar-' + idx\"\n class=\"chart-bar-group\"\n @mouseenter=\"hoveredBar = idx\"\n @mouseleave=\"hoveredBar = null\"\n >\n <rect\n :x=\"item.x\"\n :y=\"item.y\"\n :width=\"barWidth\"\n :height=\"item.height\"\n :rx=\"3\"\n :ry=\"3\"\n :fill=\"item.color\"\n :opacity=\"hoveredBar === idx ? 0.9 : 0.7\"\n class=\"transition-all duration-200 cursor-pointer\"\n />\n <!-- Tooltip -->\n <g v-if=\"hoveredBar === idx\">\n <rect\n :x=\"item.tooltipX\"\n :y=\"item.tooltipY - 28\"\n :width=\"item.tooltipWidth\"\n height=\"22\"\n rx=\"4\"\n fill=\"#1e293b\"\n stroke=\"#475569\"\n stroke-width=\"1\"\n />\n <text\n :x=\"item.tooltipX + item.tooltipWidth / 2\"\n :y=\"item.tooltipY - 13\"\n text-anchor=\"middle\"\n fill=\"#e2e8f0\"\n font-size=\"11\"\n font-weight=\"600\"\n >\n {{ item.tooltipText }}\n </text>\n </g>\n </g>\n\n <!-- X-axis labels (show every Nth label to avoid crowding) -->\n <text\n v-for=\"(item, xi) in xLabels\"\n :key=\"'xlabel-' + xi\"\n :x=\"item.x + barWidth / 2\"\n :y=\"svgHeight - padding.bottom + 16\"\n text-anchor=\"middle\"\n class=\"fill-slate-500\"\n font-size=\"9\"\n >\n {{ item.label }}\n </text>\n </svg>\n\n <!-- Summary Stats Below Chart -->\n <div class=\"flex items-center justify-between mt-4 pt-3 border-t border-slate-700/50\">\n <div class=\"flex items-center gap-4 text-xs\">\n <div class=\"flex items-center gap-1.5\">\n <span class=\"w-2.5 h-2.5 rounded-sm bg-indigo-500/70\"></span>\n <span class=\"text-slate-400\">{{ $t('users.trend_daily') }}</span>\n </div>\n <div>\n <span class=\"text-slate-400\">{{ $t('users.trend_avg') }}:</span>\n <span class=\"text-white font-semibold ml-1\">{{ averageCount }}</span>\n </div>\n <div>\n <span class=\"text-slate-400\">{{ $t('users.trend_total') }}:</span>\n <span class=\"text-white font-semibold ml-1\">{{ totalCount }}</span>\n </div>\n </div>\n <div v-if=\"trendDirection !== 'stable'\" class=\"flex items-center gap-1 text-xs\">\n <svg\n v-if=\"trendDirection === 'up'\"\n class=\"w-3.5 h-3.5 text-emerald-400\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\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 <svg\n v-else\n class=\"w-3.5 h-3.5 text-red-400\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13 17h8m0 0V9m0 8l-8-8-4 4-6-6\" />\n </svg>\n <span :class=\"trendDirection === 'up' ? 'text-emerald-400' : 'text-red-400'\" class=\"font-medium\">\n {{ trendPercent }}%\n </span>\n <span class=\"text-slate-500\">{{ $t('users.trend_vs_prev') }}</span>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted, watch } from 'vue'\n\ninterface TrendDataPoint {\n date: string\n count: number\n}\n\nconst props = defineProps<{\n data: TrendDataPoint[]\n loading?: boolean\n}>()\n\n// ── State ──────────────────────────────────────────────────────────\n\nconst selectedPeriod = ref('30')\nconst hoveredBar = ref<number | null>(null)\n\n// ── Chart Dimensions ────────────────────────────────────────────────\n\nconst svgWidth = 800\nconst svgHeight = 220\nconst padding = { top: 10, right: 16, bottom: 30, left: 36 }\n\n// ── Computed ───────────────────────────────────────────────────────\n\nconst filteredData = computed(() => {\n const days = parseInt(selectedPeriod.value, 10)\n if (!props.data || props.data.length === 0) return []\n // Sort by date ascending\n const sorted = [...props.data].sort(\n (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()\n )\n const cutoff = new Date()\n cutoff.setDate(cutoff.getDate() - days)\n return sorted.filter((d) => new Date(d.date) >= cutoff)\n})\n\nconst maxCount = computed(() => {\n if (filteredData.value.length === 0) return 1\n return Math.max(...filteredData.value.map((d) => d.count), 1)\n})\n\nconst chartHeight = svgHeight - padding.top - padding.bottom\n\n// Grid lines (5 horizontal lines)\nconst gridLines = computed(() => {\n const lines = []\n for (let i = 0; i <= 4; i++) {\n lines.push(padding.top + (chartHeight / 4) * i)\n }\n return lines\n})\n\nconst yLabels = computed(() => {\n const labels = []\n for (let i = 0; i <= 4; i++) {\n const y = padding.top + (chartHeight / 4) * i\n const value = Math.round(maxCount.value - (maxCount.value / 4) * i)\n labels.push({ y, text: value.toString() })\n }\n return labels\n})\n\nconst barWidth = computed(() => {\n const count = filteredData.value.length\n if (count === 0) return 0\n const availableWidth = svgWidth - padding.left - padding.right\n const maxBarWidth = 32\n const calculatedWidth = (availableWidth / count) * 0.7\n return Math.min(calculatedWidth, maxBarWidth)\n})\n\nconst visibleBars = computed(() => {\n const count = filteredData.value.length\n if (count === 0) return []\n\n const availableWidth = svgWidth - padding.left - padding.right\n const gap = count > 1 ? (availableWidth - barWidth.value * count) / (count - 1) : 0\n\n return filteredData.value.map((item, idx) => {\n const x = padding.left + idx * (barWidth.value + gap)\n const barHeight = maxCount.value > 0 ? (item.count / maxCount.value) * chartHeight : 0\n const y = svgHeight - padding.bottom - barHeight\n\n // Color gradient based on value\n const ratio = maxCount.value > 0 ? item.count / maxCount.value : 0\n let color: string\n if (ratio > 0.8) color = '#6366f1' // indigo-500\n else if (ratio > 0.5) color = '#818cf8' // indigo-400\n else if (ratio > 0.3) color = '#a5b4fc' // indigo-300\n else color = '#c7d2fe' // indigo-200\n\n // Tooltip\n const tooltipText = `${item.count} ${formatDate(item.date)}`\n const tooltipWidth = tooltipText.length * 7 + 16\n let tooltipX = x + barWidth.value / 2 - tooltipWidth / 2\n if (tooltipX < padding.left) tooltipX = padding.left\n if (tooltipX + tooltipWidth > svgWidth - padding.right) tooltipX = svgWidth - padding.right - tooltipWidth\n const tooltipY = y - 4\n\n return {\n x,\n y,\n height: barHeight,\n color,\n tooltipX,\n tooltipY,\n tooltipWidth,\n tooltipText,\n }\n })\n})\n\n// X-axis labels (show every Nth)\nconst xLabels = computed(() => {\n const count = filteredData.value.length\n if (count === 0) return []\n\n const availableWidth = svgWidth - padding.left - padding.right\n const gap = count > 1 ? (availableWidth - barWidth.value * count) / (count - 1) : 0\n\n // Determine label interval based on data density\n let interval = 1\n if (count > 14) interval = 2\n if (count > 30) interval = 5\n if (count > 60) interval = 10\n\n return filteredData.value\n .map((item, idx) => {\n const x = padding.left + idx * (barWidth.value + gap)\n const d = new Date(item.date)\n const label = `${d.getMonth() + 1}/${d.getDate()}`\n return { x, label, idx }\n })\n .filter((_, i) => i % interval === 0 || i === count - 1)\n})\n\n// Summary stats\nconst averageCount = computed(() => {\n if (filteredData.value.length === 0) return 0\n const sum = filteredData.value.reduce((acc, d) => acc + d.count, 0)\n return Math.round(sum / filteredData.value.length)\n})\n\nconst totalCount = computed(() => {\n return filteredData.value.reduce((acc, d) => acc + d.count, 0)\n})\n\nconst trendDirection = computed(() => {\n const data = filteredData.value\n if (data.length < 4) return 'stable'\n const half = Math.floor(data.length / 2)\n const firstHalf = data.slice(0, half).reduce((acc, d) => acc + d.count, 0) / half\n const secondHalf = data.slice(half).reduce((acc, d) => acc + d.count, 0) / (data.length - half)\n if (secondHalf > firstHalf * 1.05) return 'up'\n if (secondHalf < firstHalf * 0.95) return 'down'\n return 'stable'\n})\n\nconst trendPercent = computed(() => {\n const data = filteredData.value\n if (data.length < 4) return 0\n const half = Math.floor(data.length / 2)\n const firstHalf = data.slice(0, half).reduce((acc, d) => acc + d.count, 0) / half\n if (firstHalf === 0) return 100\n const secondHalf = data.slice(half).reduce((acc, d) => acc + d.count, 0) / (data.length - half)\n return Math.round(Math.abs((secondHalf - firstHalf) / firstHalf) * 100)\n})\n\n// ── Helpers ────────────────────────────────────────────────────────\n\nfunction formatDate(dateStr: string): string {\n try {\n const d = new Date(dateStr)\n return `${d.getMonth() + 1}/${d.getDate()}`\n } catch {\n return dateStr\n }\n}\n\nfunction updateChart() {\n // Trigger reactivity by clearing and resetting hover\n hoveredBar.value = null\n}\n\n// ── Watch for data changes ─────────────────────────────────────────\n\nwatch(() => props.data, () => {\n updateChart()\n})\n</script>\n","<template>\n <div>\n <!-- Page Header -->\n <div class=\"mb-8\">\n <h1 class=\"text-2xl font-bold text-white\">{{ $t('users.title') }}</h1>\n <p class=\"text-slate-400 mt-1\">{{ $t('users.subtitle') }}</p>\n </div>\n\n <!-- Search & Filter Bar -->\n <div class=\"flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 mb-6\">\n <div class=\"relative flex-1 max-w-md\">\n <svg class=\"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\" />\n </svg>\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n :placeholder=\"$t('users.search_placeholder')\"\n class=\"w-full pl-10 pr-4 py-2.5 bg-slate-800 border border-slate-700 rounded-lg text-sm text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 transition\"\n />\n </div>\n <div class=\"flex items-center gap-2 flex-wrap\">\n <!-- Status Filter -->\n <select\n v-model=\"statusFilter\"\n class=\"px-3 py-2.5 bg-slate-800 border border-slate-700 rounded-lg text-sm text-slate-300 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition\"\n >\n <option value=\"all\">{{ $t('users.all_statuses') }}</option>\n <option value=\"active\">{{ $t('users.status_active') }}</option>\n <option value=\"inactive\">{{ $t('users.status_inactive') }}</option>\n <option value=\"deleted\">{{ $t('users.status_deleted') }}</option>\n <option value=\"banned\">{{ $t('users.status_banned') }}</option>\n </select>\n <!-- Role Filter -->\n <select\n v-model=\"roleFilter\"\n class=\"px-3 py-2.5 bg-slate-800 border border-slate-700 rounded-lg text-sm text-slate-300 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition\"\n >\n <option value=\"all\">{{ $t('users.all_roles') }}</option>\n <option value=\"user\">{{ $t('users.role_user') }}</option>\n <option value=\"admin\">{{ $t('users.role_admin') }}</option>\n <option value=\"staff\">{{ $t('users.role_staff') }}</option>\n <option value=\"superadmin\">{{ $t('users.role_superadmin') }}</option>\n </select>\n <!-- Plan Filter -->\n <select\n v-model=\"planFilter\"\n class=\"px-3 py-2.5 bg-slate-800 border border-slate-700 rounded-lg text-sm text-slate-300 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition\"\n >\n <option value=\"all\">{{ $t('users.all_plans') }}</option>\n <option value=\"FREE\">{{ $t('users.plan_free') }}</option>\n <option value=\"PREMIUM\">{{ $t('users.plan_premium') }}</option>\n <option value=\"ENTERPRISE\">{{ $t('users.plan_enterprise') }}</option>\n </select>\n </div>\n </div>\n\n <!-- Loading State -->\n <div v-if=\"loading\" class=\"flex items-center justify-center py-20\">\n <div class=\"flex flex-col items-center gap-3\">\n <svg class=\"w-8 h-8 text-indigo-400 animate-spin\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\" />\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\" />\n </svg>\n <p class=\"text-slate-400 text-sm\">{{ $t('users.loading') }}</p>\n </div>\n </div>\n\n <!-- Error State -->\n <div v-else-if=\"error\" class=\"flex flex-col items-center justify-center py-20 text-center\">\n <svg class=\"w-12 h-12 text-red-500 mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z\" />\n </svg>\n <p class=\"text-red-400 text-sm mb-2\">{{ error }}</p>\n <button @click=\"fetchUsers\" class=\"px-4 py-2 text-sm bg-indigo-600/20 text-indigo-300 hover:bg-indigo-600/30 rounded-lg transition\">\n {{ $t('users.retry') }}\n </button>\n </div>\n\n <!-- Content -->\n <template v-else>\n <!-- Stats Cards Row -->\n <div class=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4 mb-8\">\n <!-- Total Users -->\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-4\">\n <div class=\"flex items-center gap-3\">\n <div class=\"p-2 rounded-lg bg-indigo-500/10 text-indigo-400\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z\" />\n </svg>\n </div>\n <div>\n <p class=\"text-xs text-slate-400 uppercase tracking-wider\">{{ $t('users.total_users') }}</p>\n <p class=\"text-2xl font-bold text-white\">{{ stats.total_users }}</p>\n </div>\n </div>\n </div>\n <!-- Active Users -->\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-4\">\n <div class=\"flex items-center gap-3\">\n <div class=\"p-2 rounded-lg bg-emerald-500/10 text-emerald-400\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n </div>\n <div>\n <p class=\"text-xs text-slate-400 uppercase tracking-wider\">{{ $t('users.active_users') }}</p>\n <p class=\"text-2xl font-bold text-emerald-400\">{{ stats.active_users }}</p>\n </div>\n </div>\n </div>\n <!-- Deleted Users -->\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-4\">\n <div class=\"flex items-center gap-3\">\n <div class=\"p-2 rounded-lg bg-red-500/10 text-red-400\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 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 </div>\n <div>\n <p class=\"text-xs text-slate-400 uppercase tracking-wider\">{{ $t('users.deleted_users') }}</p>\n <p class=\"text-2xl font-bold text-red-400\">{{ stats.deleted_users }}</p>\n </div>\n </div>\n </div>\n <!-- Banned Users -->\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-4\">\n <div class=\"flex items-center gap-3\">\n <div class=\"p-2 rounded-lg bg-amber-500/10 text-amber-400\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636\" />\n </svg>\n </div>\n <div>\n <p class=\"text-xs text-slate-400 uppercase tracking-wider\">{{ $t('users.banned_users') }}</p>\n <p class=\"text-2xl font-bold text-amber-400\">{{ stats.banned_users }}</p>\n </div>\n </div>\n </div>\n <!-- New Today -->\n <div class=\"bg-slate-800 rounded-xl border border-slate-700 p-4\">\n <div class=\"flex items-center gap-3\">\n <div class=\"p-2 rounded-lg bg-cyan-500/10 text-cyan-400\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n </div>\n <div>\n <p class=\"text-xs text-slate-400 uppercase tracking-wider\">{{ $t('users.new_today') }}</p>\n <p class=\"text-2xl font-bold text-cyan-400\">{{ stats.new_users_today }}</p>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Registration Trend Chart -->\n <div class=\"mb-8\">\n <UserTrendChart :data=\"stats.registration_trend\" :loading=\"loading\" />\n </div>\n\n <!-- Filtered results indicator -->\n <div v-if=\"isFilterActive\" class=\"mb-4 text-xs text-slate-500\">\n {{ $t('users.filtered_results') }}: <span class=\"text-slate-300 font-semibold\">{{ users.length }}</span> / {{ totalCount }} db\n </div>\n\n <!-- Bulk Actions Bar -->\n <div v-if=\"selectedUserIds.length > 0 && canEditUsers\" class=\"mb-4 px-4 py-3 bg-indigo-900/30 border border-indigo-700/50 rounded-xl flex items-center justify-between\">\n <p class=\"text-sm text-indigo-300\">\n {{ $t('users.selected_count', { count: selectedUserIds.length }) }}\n </p>\n <div class=\"flex items-center gap-2\">\n <button\n @click=\"bulkActivate\"\n class=\"px-3 py-1.5 text-xs font-medium bg-emerald-600/20 text-emerald-300 hover:bg-emerald-600/30 rounded-lg transition\"\n >\n {{ $t('users.bulk_activate') }}\n </button>\n <button\n @click=\"bulkDeactivate\"\n class=\"px-3 py-1.5 text-xs font-medium bg-red-600/20 text-red-300 hover:bg-red-600/30 rounded-lg transition\"\n >\n {{ $t('users.bulk_deactivate') }}\n </button>\n <button\n @click=\"clearSelection\"\n class=\"px-3 py-1.5 text-xs font-medium bg-slate-600/20 text-slate-300 hover:bg-slate-600/30 rounded-lg transition\"\n >\n {{ $t('users.clear_selection') }}\n </button>\n </div>\n </div>\n\n <!-- Data Table -->\n <div class=\"bg-slate-800/50 border border-slate-700 rounded-xl overflow-hidden\">\n <div class=\"overflow-x-auto\">\n <table class=\"w-full text-sm\">\n <thead>\n <tr class=\"border-b border-slate-700 bg-slate-800/80\">\n <th class=\"text-left py-3.5 px-4 w-10\">\n <input\n type=\"checkbox\"\n :checked=\"allSelected\"\n @change=\"toggleSelectAll\"\n class=\"rounded border-slate-600 bg-slate-700 text-indigo-600 focus:ring-indigo-500/50\"\n />\n </th>\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">ID</th>\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">{{ $t('users.email') }}</th>\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">{{ $t('users.name') }}</th>\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">{{ $t('users.role') }}</th>\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">{{ $t('users.status') }}</th>\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">{{ $t('users.package') }}</th>\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">{{ $t('users.registration') }}</th>\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">{{ $t('users.language') }}</th>\n <th class=\"text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400\">{{ $t('users.actions') }}</th>\n </tr>\n </thead>\n <tbody>\n <tr\n v-for=\"user in users\"\n :key=\"user.id\"\n class=\"border-b border-slate-700/50 hover:bg-slate-700/30 transition\"\n >\n <!-- Checkbox -->\n <td class=\"py-3.5 px-4\">\n <input\n type=\"checkbox\"\n :checked=\"selectedUserIds.includes(user.id)\"\n @change=\"toggleUserSelection(user.id)\"\n class=\"rounded border-slate-600 bg-slate-700 text-indigo-600 focus:ring-indigo-500/50\"\n />\n </td>\n <!-- ID -->\n <td class=\"py-3.5 px-4 text-slate-400 font-mono text-xs\">{{ user.id }}</td>\n <!-- Email -->\n <td class=\"py-3.5 px-4\">\n <NuxtLink\n :to=\"'/users/' + user.id\"\n class=\"text-sm font-medium text-white hover:underline cursor-pointer hover:text-indigo-400 transition-colors\"\n >\n {{ user.email }}\n </NuxtLink>\n </td>\n <!-- Name -->\n <td class=\"py-3.5 px-4\">\n <span class=\"text-slate-300\">{{ user.person_name || '—' }}</span>\n </td>\n <!-- Role -->\n <td class=\"py-3.5 px-4\">\n <span\n class=\"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium\"\n :class=\"roleBadgeClass(user.role)\"\n >\n {{ roleLabel(user.role) }}\n </span>\n </td>\n <!-- Status -->\n <td class=\"py-3.5 px-4\">\n <span\n class=\"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium\"\n :class=\"statusBadgeClass(user)\"\n >\n <span\n class=\"w-1.5 h-1.5 rounded-full mr-1.5\"\n :class=\"statusDotClass(user)\"\n ></span>\n {{ statusLabel(user) }}\n </span>\n </td>\n <!-- Package -->\n <td class=\"py-3.5 px-4\">\n <span\n class=\"px-2.5 py-0.5 rounded-full text-xs font-medium\"\n :class=\"planBadgeClass(user.subscription_plan)\"\n >\n {{ user.subscription_plan || '—' }}\n </span>\n </td>\n <!-- Registration -->\n <td class=\"py-3.5 px-4 text-slate-300 text-xs\">\n {{ formatDate(user.created_at) }}\n </td>\n <!-- Language -->\n <td class=\"py-3.5 px-4\">\n <span class=\"text-slate-400 text-xs uppercase\">{{ user.preferred_language || '—' }}</span>\n </td>\n <!-- Actions -->\n <td class=\"py-3.5 px-4\">\n <div class=\"flex items-center gap-2\">\n <NuxtLink\n :to=\"'/users/' + user.id\"\n class=\"px-3 py-1.5 text-xs font-medium bg-slate-600/20 text-slate-300 hover:bg-slate-600/30 rounded-lg transition\"\n >\n {{ $t('users.view') }}\n </NuxtLink>\n <button\n v-if=\"!user.is_deleted && canEditUsers\"\n @click=\"toggleUserActive(user)\"\n class=\"px-3 py-1.5 text-xs font-medium rounded-lg transition\"\n :class=\"user.is_active\n ? 'bg-red-500/10 text-red-300 hover:bg-red-500/20'\n : 'bg-emerald-500/10 text-emerald-300 hover:bg-emerald-500/20'\"\n >\n {{ user.is_active ? $t('users.deactivate') : $t('users.activate') }}\n </button>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Empty State -->\n <div\n v-if=\"users.length === 0 && !loading\"\n class=\"flex flex-col items-center justify-center py-16 text-center\"\n >\n <svg class=\"w-12 h-12 text-slate-600 mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z\" />\n </svg>\n <p class=\"text-slate-400 text-sm\">{{ $t('users.no_results') }}</p>\n </div>\n </div>\n\n <!-- Pagination -->\n <div v-if=\"totalCount > limit\" class=\"flex items-center justify-between mt-6\">\n <p class=\"text-xs text-slate-500\">\n {{ $t('users.showing') }} {{ skip + 1 }}{{ Math.min(skip + limit, totalCount) }} / {{ totalCount }}\n </p>\n <div class=\"flex items-center gap-2\">\n <button\n @click=\"prevPage\"\n :disabled=\"skip === 0\"\n class=\"px-3 py-1.5 text-xs font-medium rounded-lg transition disabled:opacity-30 disabled:cursor-not-allowed\"\n :class=\"skip > 0 ? 'bg-slate-700 text-slate-300 hover:bg-slate-600' : 'bg-slate-800 text-slate-600'\"\n >\n {{ $t('users.prev') }}\n </button>\n <button\n @click=\"nextPage\"\n :disabled=\"skip + limit >= totalCount\"\n class=\"px-3 py-1.5 text-xs font-medium rounded-lg transition disabled:opacity-30 disabled:cursor-not-allowed\"\n :class=\"skip + limit < totalCount ? 'bg-slate-700 text-slate-300 hover:bg-slate-600' : 'bg-slate-800 text-slate-600'\"\n >\n {{ $t('users.next') }}\n </button>\n </div>\n </div>\n </template>\n\n <!-- Notification -->\n <div\n v-if=\"notification\"\n class=\"fixed bottom-6 right-6 px-5 py-3 rounded-xl shadow-xl text-sm font-medium z-50\"\n :class=\"notification.type === 'success'\n ? 'bg-emerald-600 text-white'\n : 'bg-red-600 text-white'\"\n >\n {{ notification.message }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted, onUnmounted, watch } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { useAuthStore } from '~/stores/auth'\nimport UserTrendChart from '~/components/charts/UserTrendChart.vue'\n\nconst router = useRouter()\nconst auth = useAuthStore()\n\ndefinePageMeta({\n middleware: 'auth',\n})\n\n// ── RBAC Permission Check ──────────────────────────────────────────\n\nconst canViewUsers = computed(() => {\n if (auth.isAdmin) return true\n return !!auth.user?.system_capabilities?.['users:view']\n})\n\nconst canEditUsers = computed(() => {\n if (auth.isAdmin) return true\n return !!auth.user?.system_capabilities?.['users:edit']\n})\n\nconst canBanUsers = computed(() => {\n if (auth.isAdmin) return true\n return !!auth.user?.system_capabilities?.['users:ban']\n})\n\nconst canDeleteUsers = computed(() => {\n if (auth.isAdmin) return true\n return !!auth.user?.system_capabilities?.['users:delete']\n})\n\n// Redirect if no view permission\nif (!canViewUsers.value && !auth.isAdmin) {\n router.push('/')\n}\n\n// ── Types ──────────────────────────────────────────────────────────\n\ninterface UserListItem {\n id: number\n email: string\n role: string\n is_active: boolean\n is_deleted: boolean\n is_vip: boolean\n subscription_plan: string\n preferred_language: string\n region_code: string\n created_at: string | null\n person_name: string | null\n person_phone: string | null\n}\n\ninterface UserListResponse {\n total: number\n skip: number\n limit: number\n users: UserListItem[]\n}\n\ninterface UserStats {\n total_users: number\n active_users: number\n deleted_users: number\n banned_users: number\n new_users_today: number\n new_users_this_week: number\n new_users_this_month: number\n users_by_role: Record<string, number>\n users_by_plan: Record<string, number>\n users_by_language: Record<string, number>\n users_with_person: number\n users_without_person: number\n registration_trend: { date: string; count: number }[]\n active_organizations_count: number\n total_memberships: number\n}\n\n// ── State ──────────────────────────────────────────────────────────\n\nconst loading = ref(true)\nconst error = ref<string | null>(null)\nconst users = ref<UserListItem[]>([])\nconst totalCount = ref(0)\nconst skip = ref(0)\nconst limit = ref(50)\n\nconst searchQuery = ref('')\nconst statusFilter = ref('all')\nconst roleFilter = ref('all')\nconst planFilter = ref('all')\n\nconst stats = ref<UserStats>({\n total_users: 0,\n active_users: 0,\n deleted_users: 0,\n banned_users: 0,\n new_users_today: 0,\n new_users_this_week: 0,\n new_users_this_month: 0,\n users_by_role: {},\n users_by_plan: {},\n users_by_language: {},\n users_with_person: 0,\n users_without_person: 0,\n registration_trend: [],\n active_organizations_count: 0,\n total_memberships: 0,\n})\n\n// Bulk selection\nconst selectedUserIds = ref<number[]>([])\n\n// Notification\nconst notification = ref<{ type: 'success' | 'error'; message: string } | null>(null)\n\n// ── Debounced Search Timer ──────────────────────────────────────────\n\nlet searchDebounceTimer: ReturnType<typeof setTimeout> | null = null\n\nfunction debouncedFetchUsers() {\n if (searchDebounceTimer) {\n clearTimeout(searchDebounceTimer)\n }\n searchDebounceTimer = setTimeout(() => {\n skip.value = 0\n fetchUsers()\n }, 400)\n}\n\n// ── Watchers ────────────────────────────────────────────────────────\n\nwatch(searchQuery, () => {\n debouncedFetchUsers()\n})\n\nwatch(statusFilter, () => {\n skip.value = 0\n fetchUsers()\n})\n\nwatch(roleFilter, () => {\n skip.value = 0\n fetchUsers()\n})\n\nwatch(planFilter, () => {\n skip.value = 0\n fetchUsers()\n})\n\n// ── Computed ───────────────────────────────────────────────────────\n\nconst isFilterActive = computed(() => {\n return searchQuery.value !== '' || statusFilter.value !== 'all' || roleFilter.value !== 'all' || planFilter.value !== 'all'\n})\n\nconst allSelected = computed(() => {\n return users.value.length > 0 && selectedUserIds.value.length === users.value.length\n})\n\n// ── Auth Helper ────────────────────────────────────────────────────\n\nfunction getAuthHeaders() {\n const tokenCookie = useCookie('access_token')\n const token = tokenCookie.value\n return token ? { Authorization: `Bearer ${token}` } : {}\n}\n\n// ── Data Fetching ──────────────────────────────────────────────────\n\nasync function fetchUsers() {\n loading.value = true\n error.value = null\n try {\n const params: Record<string, any> = { skip: skip.value, limit: limit.value }\n\n if (searchQuery.value) {\n params.search_term = searchQuery.value\n }\n // Status filter mapping: frontend uses combined status_filter, backend uses is_active/is_deleted\n if (statusFilter.value !== 'all') {\n switch (statusFilter.value) {\n case 'active':\n params.is_active = true\n break\n case 'inactive':\n params.is_active = false\n break\n case 'deleted':\n params.is_deleted = true\n break\n case 'banned':\n params.is_active = false\n break\n }\n }\n if (roleFilter.value !== 'all') {\n params.role = roleFilter.value\n }\n if (planFilter.value !== 'all') {\n params.plan_filter = planFilter.value\n }\n\n const response = await $fetch<UserListResponse>('/api/v1/admin/users', {\n params,\n headers: getAuthHeaders(),\n })\n users.value = response.users\n totalCount.value = response.total\n } catch (err: any) {\n console.error('[Users] Failed to fetch users:', err)\n error.value = err?.data?.detail || err?.message || 'Failed to load users'\n } finally {\n loading.value = false\n }\n}\n\nasync function fetchStats() {\n try {\n const response = await $fetch<UserStats>('/api/v1/admin/users/stats', {\n headers: getAuthHeaders(),\n })\n stats.value = response\n } catch (err: any) {\n console.error('[Users] Failed to fetch stats:', err)\n }\n}\n\n// ── Pagination ─────────────────────────────────────────────────────\n\nfunction nextPage() {\n if (skip.value + limit.value < totalCount.value) {\n skip.value += limit.value\n fetchUsers()\n }\n}\n\nfunction prevPage() {\n if (skip.value > 0) {\n skip.value = Math.max(0, skip.value - limit.value)\n fetchUsers()\n }\n}\n\n// ── Bulk Selection ─────────────────────────────────────────────────\n\nfunction toggleSelectAll() {\n if (allSelected.value) {\n selectedUserIds.value = []\n } else {\n selectedUserIds.value = users.value.map(u => u.id)\n }\n}\n\nfunction toggleUserSelection(userId: number) {\n const idx = selectedUserIds.value.indexOf(userId)\n if (idx === -1) {\n selectedUserIds.value.push(userId)\n } else {\n selectedUserIds.value.splice(idx, 1)\n }\n}\n\nfunction clearSelection() {\n selectedUserIds.value = []\n}\n\nasync function bulkActivate() {\n if (selectedUserIds.value.length === 0) return\n try {\n await $fetch('/api/v1/admin/users/bulk-action', {\n method: 'POST',\n headers: {\n ...getAuthHeaders(),\n 'Content-Type': 'application/json',\n },\n body: {\n user_ids: selectedUserIds.value,\n action: 'activate',\n },\n })\n showNotification('success', `${selectedUserIds.value.length} user(s) activated successfully`)\n selectedUserIds.value = []\n await fetchUsers()\n await fetchStats()\n } catch (err: any) {\n console.error('[Users] Bulk activate failed:', err)\n showNotification('error', err?.data?.detail || err?.message || 'Bulk activate failed')\n }\n}\n\nasync function bulkDeactivate() {\n if (selectedUserIds.value.length === 0) return\n try {\n await $fetch('/api/v1/admin/users/bulk-action', {\n method: 'POST',\n headers: {\n ...getAuthHeaders(),\n 'Content-Type': 'application/json',\n },\n body: {\n user_ids: selectedUserIds.value,\n action: 'deactivate',\n },\n })\n showNotification('success', `${selectedUserIds.value.length} user(s) deactivated successfully`)\n selectedUserIds.value = []\n await fetchUsers()\n await fetchStats()\n } catch (err: any) {\n console.error('[Users] Bulk deactivate failed:', err)\n showNotification('error', err?.data?.detail || err?.message || 'Bulk deactivate failed')\n }\n}\n\n// ── Single User Toggle ─────────────────────────────────────────────\n\nasync function toggleUserActive(user: UserListItem) {\n const newActive = !user.is_active\n try {\n await $fetch(`/api/v1/admin/users/${user.id}`, {\n method: 'PATCH',\n headers: {\n ...getAuthHeaders(),\n 'Content-Type': 'application/json',\n },\n body: { is_active: newActive },\n })\n showNotification('success', `User \"${user.email}\" ${newActive ? 'activated' : 'deactivated'} successfully`)\n await fetchUsers()\n await fetchStats()\n } catch (err: any) {\n console.error('[Users] Toggle status failed:', err)\n showNotification('error', err?.data?.detail || err?.message || 'Failed to toggle user status')\n }\n}\n\n// ── Helpers ────────────────────────────────────────────────────────\n\nfunction roleBadgeClass(role: string): string {\n const r = role.toLowerCase()\n if (r === 'superadmin') return 'bg-purple-500/20 text-purple-300'\n if (r === 'admin') return 'bg-indigo-500/20 text-indigo-300'\n if (r === 'staff') return 'bg-cyan-500/20 text-cyan-300'\n return 'bg-slate-500/20 text-slate-300'\n}\n\nfunction roleLabel(role: string): string {\n const r = role.toLowerCase()\n if (r === 'superadmin') return 'Superadmin'\n if (r === 'admin') return 'Admin'\n if (r === 'staff') return 'Staff'\n return 'User'\n}\n\nfunction statusBadgeClass(user: UserListItem): string {\n if (user.is_deleted) return 'bg-red-500/10 text-red-400'\n if (!user.is_active) return 'bg-amber-500/10 text-amber-400'\n return 'bg-emerald-500/10 text-emerald-400'\n}\n\nfunction statusDotClass(user: UserListItem): string {\n if (user.is_deleted) return 'bg-red-400'\n if (!user.is_active) return 'bg-amber-400'\n return 'bg-emerald-400'\n}\n\nfunction statusLabel(user: UserListItem): string {\n if (user.is_deleted) return 'Deleted'\n if (!user.is_active) return 'Inactive'\n return 'Active'\n}\n\nfunction planBadgeClass(plan: string): string {\n const p = (plan || '').toLowerCase()\n if (p.includes('enterprise') || p.includes('vip')) return 'bg-purple-500/20 text-purple-300'\n if (p.includes('premium') || p.includes('pro')) return 'bg-amber-500/20 text-amber-300'\n return 'bg-slate-500/20 text-slate-300'\n}\n\nfunction formatDate(dateStr: string | null): string {\n if (!dateStr) return '—'\n try {\n const d = new Date(dateStr)\n return d.toLocaleDateString('hu-HU', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n } catch {\n return dateStr\n }\n}\n\nfunction showNotification(type: 'success' | 'error', message: string) {\n notification.value = { type, message }\n setTimeout(() => {\n notification.value = null\n }, 4000)\n}\n\n// ── Lifecycle ──────────────────────────────────────────────────────\n\nonMounted(() => {\n fetchUsers()\n fetchStats()\n})\n</script>\n"],"version":3}