import { _ as __nuxt_component_0 } from './nuxt-link-1NBbHb-h.mjs'; import { defineComponent, computed, ref, watch, withCtx, createTextVNode, toDisplayString, mergeProps, useSSRContext } from 'vue'; import { ssrRenderAttrs, ssrInterpolate, ssrRenderAttr, ssrIncludeBooleanAttr, ssrLooseContain, ssrLooseEqual, ssrRenderComponent, ssrRenderList, ssrRenderClass } from 'vue/server-renderer'; import { useRouter } from 'vue-router'; import { c as useAuthStore, d as useCookie } from './server.mjs'; import '../nitro/nitro.mjs'; import 'node:http'; import 'node:https'; import 'node:events'; import 'node:buffer'; import 'node:fs'; import 'node:path'; import 'node:crypto'; import 'node:url'; import 'pinia'; import '../routes/renderer.mjs'; import 'vue-bundle-renderer/runtime'; import 'unhead/server'; import 'devalue'; import 'unhead/utils'; import 'unhead/plugins'; const svgWidth = 800; const svgHeight = 220; const _sfc_main$1 = /* @__PURE__ */ defineComponent({ __name: "UserTrendChart", __ssrInlineRender: true, props: { data: {}, loading: { type: Boolean } }, setup(__props) { const props = __props; const selectedPeriod = ref("30"); const hoveredBar = ref(null); const padding = { top: 10, right: 16, bottom: 30, left: 36 }; const filteredData = computed(() => { const days = parseInt(selectedPeriod.value, 10); if (!props.data || props.data.length === 0) return []; const sorted = [...props.data].sort( (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime() ); const cutoff = /* @__PURE__ */ new Date(); cutoff.setDate(cutoff.getDate() - days); return sorted.filter((d) => new Date(d.date) >= cutoff); }); const maxCount = computed(() => { if (filteredData.value.length === 0) return 1; return Math.max(...filteredData.value.map((d) => d.count), 1); }); const chartHeight = svgHeight - padding.top - padding.bottom; const gridLines = computed(() => { const lines = []; for (let i = 0; i <= 4; i++) { lines.push(padding.top + chartHeight / 4 * i); } return lines; }); const yLabels = computed(() => { const labels = []; for (let i = 0; i <= 4; i++) { const y = padding.top + chartHeight / 4 * i; const value = Math.round(maxCount.value - maxCount.value / 4 * i); labels.push({ y, text: value.toString() }); } return labels; }); const barWidth = computed(() => { const count = filteredData.value.length; if (count === 0) return 0; const availableWidth = svgWidth - padding.left - padding.right; const maxBarWidth = 32; const calculatedWidth = availableWidth / count * 0.7; return Math.min(calculatedWidth, maxBarWidth); }); const visibleBars = computed(() => { const count = filteredData.value.length; if (count === 0) return []; const availableWidth = svgWidth - padding.left - padding.right; const gap = count > 1 ? (availableWidth - barWidth.value * count) / (count - 1) : 0; return filteredData.value.map((item, idx) => { const x = padding.left + idx * (barWidth.value + gap); const barHeight = maxCount.value > 0 ? item.count / maxCount.value * chartHeight : 0; const y = svgHeight - padding.bottom - barHeight; const ratio = maxCount.value > 0 ? item.count / maxCount.value : 0; let color; if (ratio > 0.8) color = "#6366f1"; else if (ratio > 0.5) color = "#818cf8"; else if (ratio > 0.3) color = "#a5b4fc"; else color = "#c7d2fe"; const tooltipText = `${item.count} ${formatDate(item.date)}`; const tooltipWidth = tooltipText.length * 7 + 16; let tooltipX = x + barWidth.value / 2 - tooltipWidth / 2; if (tooltipX < padding.left) tooltipX = padding.left; if (tooltipX + tooltipWidth > svgWidth - padding.right) tooltipX = svgWidth - padding.right - tooltipWidth; const tooltipY = y - 4; return { x, y, height: barHeight, color, tooltipX, tooltipY, tooltipWidth, tooltipText }; }); }); const xLabels = computed(() => { const count = filteredData.value.length; if (count === 0) return []; const availableWidth = svgWidth - padding.left - padding.right; const gap = count > 1 ? (availableWidth - barWidth.value * count) / (count - 1) : 0; let interval = 1; if (count > 14) interval = 2; if (count > 30) interval = 5; if (count > 60) interval = 10; return filteredData.value.map((item, idx) => { const x = padding.left + idx * (barWidth.value + gap); const d = new Date(item.date); const label = `${d.getMonth() + 1}/${d.getDate()}`; return { x, label, idx }; }).filter((_, i) => i % interval === 0 || i === count - 1); }); const averageCount = computed(() => { if (filteredData.value.length === 0) return 0; const sum = filteredData.value.reduce((acc, d) => acc + d.count, 0); return Math.round(sum / filteredData.value.length); }); const totalCount = computed(() => { return filteredData.value.reduce((acc, d) => acc + d.count, 0); }); const trendDirection = computed(() => { const data = filteredData.value; if (data.length < 4) return "stable"; const half = Math.floor(data.length / 2); const firstHalf = data.slice(0, half).reduce((acc, d) => acc + d.count, 0) / half; const secondHalf = data.slice(half).reduce((acc, d) => acc + d.count, 0) / (data.length - half); if (secondHalf > firstHalf * 1.05) return "up"; if (secondHalf < firstHalf * 0.95) return "down"; return "stable"; }); const trendPercent = computed(() => { const data = filteredData.value; if (data.length < 4) return 0; const half = Math.floor(data.length / 2); const firstHalf = data.slice(0, half).reduce((acc, d) => acc + d.count, 0) / half; if (firstHalf === 0) return 100; const secondHalf = data.slice(half).reduce((acc, d) => acc + d.count, 0) / (data.length - half); return Math.round(Math.abs((secondHalf - firstHalf) / firstHalf) * 100); }); function formatDate(dateStr) { try { const d = new Date(dateStr); return `${d.getMonth() + 1}/${d.getDate()}`; } catch { return dateStr; } } function updateChart() { hoveredBar.value = null; } watch(() => props.data, () => { updateChart(); }); return (_ctx, _push, _parent, _attrs) => { _push(`

${ssrInterpolate(_ctx.$t("users.trend_title"))}

${ssrInterpolate(_ctx.$t("users.trend_subtitle"))}

`); if (__props.loading) { _push(`
`); } else if (filteredData.value.length === 0) { _push(`

${ssrInterpolate(_ctx.$t("users.trend_no_data"))}

`); } else { _push(`
`); ssrRenderList(gridLines.value, (y, yi) => { _push(``); }); _push(``); ssrRenderList(yLabels.value, (label, li) => { _push(`${ssrInterpolate(label.text)}`); }); _push(``); ssrRenderList(visibleBars.value, (item, idx) => { _push(``); if (hoveredBar.value === idx) { _push(`${ssrInterpolate(item.tooltipText)}`); } else { _push(``); } _push(``); }); _push(``); ssrRenderList(xLabels.value, (item, xi) => { _push(`${ssrInterpolate(item.label)}`); }); _push(`
${ssrInterpolate(_ctx.$t("users.trend_daily"))}
${ssrInterpolate(_ctx.$t("users.trend_avg"))}:${ssrInterpolate(averageCount.value)}
${ssrInterpolate(_ctx.$t("users.trend_total"))}:${ssrInterpolate(totalCount.value)}
`); if (trendDirection.value !== "stable") { _push(`
`); if (trendDirection.value === "up") { _push(``); } else { _push(``); } _push(`${ssrInterpolate(trendPercent.value)}% ${ssrInterpolate(_ctx.$t("users.trend_vs_prev"))}
`); } else { _push(``); } _push(`
`); } _push(``); }; } }); const _sfc_setup$1 = _sfc_main$1.setup; _sfc_main$1.setup = (props, ctx) => { const ssrContext = useSSRContext(); (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("components/charts/UserTrendChart.vue"); return _sfc_setup$1 ? _sfc_setup$1(props, ctx) : void 0; }; const _sfc_main = /* @__PURE__ */ defineComponent({ __name: "index", __ssrInlineRender: true, setup(__props) { const router = useRouter(); const auth = useAuthStore(); const canViewUsers = computed(() => { var _a, _b; if (auth.isAdmin) return true; return !!((_b = (_a = auth.user) == null ? void 0 : _a.system_capabilities) == null ? void 0 : _b["users:view"]); }); const canEditUsers = computed(() => { var _a, _b; if (auth.isAdmin) return true; return !!((_b = (_a = auth.user) == null ? void 0 : _a.system_capabilities) == null ? void 0 : _b["users:edit"]); }); computed(() => { var _a, _b; if (auth.isAdmin) return true; return !!((_b = (_a = auth.user) == null ? void 0 : _a.system_capabilities) == null ? void 0 : _b["users:ban"]); }); computed(() => { var _a, _b; if (auth.isAdmin) return true; return !!((_b = (_a = auth.user) == null ? void 0 : _a.system_capabilities) == null ? void 0 : _b["users:delete"]); }); if (!canViewUsers.value && !auth.isAdmin) { router.push("/"); } const loading = ref(true); const error = ref(null); const users = ref([]); const totalCount = ref(0); const skip = ref(0); const limit = ref(50); const searchQuery = ref(""); const statusFilter = ref("all"); const roleFilter = ref("all"); const planFilter = ref("all"); const stats = ref({ total_users: 0, active_users: 0, deleted_users: 0, banned_users: 0, new_users_today: 0, new_users_this_week: 0, new_users_this_month: 0, users_by_role: {}, users_by_plan: {}, users_by_language: {}, users_with_person: 0, users_without_person: 0, registration_trend: [], active_organizations_count: 0, total_memberships: 0 }); const selectedUserIds = ref([]); const notification = ref(null); let searchDebounceTimer = null; function debouncedFetchUsers() { if (searchDebounceTimer) { clearTimeout(searchDebounceTimer); } searchDebounceTimer = setTimeout(() => { skip.value = 0; fetchUsers(); }, 400); } watch(searchQuery, () => { debouncedFetchUsers(); }); watch(statusFilter, () => { skip.value = 0; fetchUsers(); }); watch(roleFilter, () => { skip.value = 0; fetchUsers(); }); watch(planFilter, () => { skip.value = 0; fetchUsers(); }); const isFilterActive = computed(() => { return searchQuery.value !== "" || statusFilter.value !== "all" || roleFilter.value !== "all" || planFilter.value !== "all"; }); const allSelected = computed(() => { return users.value.length > 0 && selectedUserIds.value.length === users.value.length; }); function getAuthHeaders() { const tokenCookie = useCookie("access_token"); const token = tokenCookie.value; return token ? { Authorization: `Bearer ${token}` } : {}; } async function fetchUsers() { var _a; loading.value = true; error.value = null; try { const params = { skip: skip.value, limit: limit.value }; if (searchQuery.value) { params.search_term = searchQuery.value; } if (statusFilter.value !== "all") { switch (statusFilter.value) { case "active": params.is_active = true; break; case "inactive": params.is_active = false; break; case "deleted": params.is_deleted = true; break; case "banned": params.is_active = false; break; } } if (roleFilter.value !== "all") { params.role = roleFilter.value; } if (planFilter.value !== "all") { params.plan_filter = planFilter.value; } const response = await $fetch("/api/v1/admin/users", { params, headers: getAuthHeaders() }); users.value = response.users; totalCount.value = response.total; } catch (err) { console.error("[Users] Failed to fetch users:", err); error.value = ((_a = err == null ? void 0 : err.data) == null ? void 0 : _a.detail) || (err == null ? void 0 : err.message) || "Failed to load users"; } finally { loading.value = false; } } function roleBadgeClass(role) { const r = role.toLowerCase(); if (r === "superadmin") return "bg-purple-500/20 text-purple-300"; if (r === "admin") return "bg-indigo-500/20 text-indigo-300"; if (r === "staff") return "bg-cyan-500/20 text-cyan-300"; return "bg-slate-500/20 text-slate-300"; } function roleLabel(role) { const r = role.toLowerCase(); if (r === "superadmin") return "Superadmin"; if (r === "admin") return "Admin"; if (r === "staff") return "Staff"; return "User"; } function statusBadgeClass(user) { if (user.is_deleted) return "bg-red-500/10 text-red-400"; if (!user.is_active) return "bg-amber-500/10 text-amber-400"; return "bg-emerald-500/10 text-emerald-400"; } function statusDotClass(user) { if (user.is_deleted) return "bg-red-400"; if (!user.is_active) return "bg-amber-400"; return "bg-emerald-400"; } function statusLabel(user) { if (user.is_deleted) return "Deleted"; if (!user.is_active) return "Inactive"; return "Active"; } function planBadgeClass(plan) { const p = (plan || "").toLowerCase(); if (p.includes("enterprise") || p.includes("vip")) return "bg-purple-500/20 text-purple-300"; if (p.includes("premium") || p.includes("pro")) return "bg-amber-500/20 text-amber-300"; return "bg-slate-500/20 text-slate-300"; } function formatDate(dateStr) { if (!dateStr) return "\u2014"; try { const d = new Date(dateStr); return d.toLocaleDateString("hu-HU", { year: "numeric", month: "short", day: "numeric" }); } catch { return dateStr; } } return (_ctx, _push, _parent, _attrs) => { const _component_NuxtLink = __nuxt_component_0; _push(`

${ssrInterpolate(_ctx.$t("users.title"))}

${ssrInterpolate(_ctx.$t("users.subtitle"))}

`); if (loading.value) { _push(`

${ssrInterpolate(_ctx.$t("common.loading"))}

`); } else if (error.value) { _push(`

${ssrInterpolate(error.value)}

`); } else { _push(`

${ssrInterpolate(_ctx.$t("users.total_users"))}

${ssrInterpolate(stats.value.total_users)}

${ssrInterpolate(_ctx.$t("users.active_users"))}

${ssrInterpolate(stats.value.active_users)}

${ssrInterpolate(_ctx.$t("users.deleted_users"))}

${ssrInterpolate(stats.value.deleted_users)}

${ssrInterpolate(_ctx.$t("users.banned_users"))}

${ssrInterpolate(stats.value.banned_users)}

${ssrInterpolate(_ctx.$t("users.new_today"))}

${ssrInterpolate(stats.value.new_users_today)}

`); _push(ssrRenderComponent(_sfc_main$1, { data: stats.value.registration_trend, loading: loading.value }, null, _parent)); _push(`
`); if (isFilterActive.value) { _push(`
${ssrInterpolate(_ctx.$t("users.filtered_results"))}: ${ssrInterpolate(users.value.length)} / ${ssrInterpolate(totalCount.value)} db
`); } else { _push(``); } if (selectedUserIds.value.length > 0 && canEditUsers.value) { _push(`

${ssrInterpolate(_ctx.$t("users.selected_count", { count: selectedUserIds.value.length }))}

`); } else { _push(``); } _push(`
`); ssrRenderList(users.value, (user) => { _push(``); }); _push(`
ID${ssrInterpolate(_ctx.$t("common.email"))}${ssrInterpolate(_ctx.$t("common.name"))}${ssrInterpolate(_ctx.$t("users.role"))}${ssrInterpolate(_ctx.$t("common.status"))}${ssrInterpolate(_ctx.$t("users.package"))}${ssrInterpolate(_ctx.$t("users.registration"))}${ssrInterpolate(_ctx.$t("users.language"))}${ssrInterpolate(_ctx.$t("common.actions"))}
${ssrInterpolate(user.id)}`); _push(ssrRenderComponent(_component_NuxtLink, { to: "/users/" + user.id, class: "text-sm font-medium text-white hover:underline cursor-pointer hover:text-indigo-400 transition-colors" }, { default: withCtx((_, _push2, _parent2, _scopeId) => { if (_push2) { _push2(`${ssrInterpolate(user.email)}`); } else { return [ createTextVNode(toDisplayString(user.email), 1) ]; } }), _: 2 }, _parent)); _push(`${ssrInterpolate(user.person_name || "\u2014")}${ssrInterpolate(roleLabel(user.role))} ${ssrInterpolate(statusLabel(user))}${ssrInterpolate(user.subscription_plan || "\u2014")}${ssrInterpolate(formatDate(user.created_at))}${ssrInterpolate(user.preferred_language || "\u2014")}
`); _push(ssrRenderComponent(_component_NuxtLink, { to: "/users/" + user.id, 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" }, { default: withCtx((_, _push2, _parent2, _scopeId) => { if (_push2) { _push2(`${ssrInterpolate(_ctx.$t("common.view"))}`); } else { return [ createTextVNode(toDisplayString(_ctx.$t("common.view")), 1) ]; } }), _: 2 }, _parent)); if (!user.is_deleted && canEditUsers.value) { _push(``); } else { _push(``); } _push(`
`); if (users.value.length === 0 && !loading.value) { _push(`

${ssrInterpolate(_ctx.$t("common.no_results"))}

`); } else { _push(``); } _push(`
`); if (totalCount.value > limit.value) { _push(`

${ssrInterpolate(_ctx.$t("users.showing"))} ${ssrInterpolate(skip.value + 1)}\u2013${ssrInterpolate(Math.min(skip.value + limit.value, totalCount.value))} / ${ssrInterpolate(totalCount.value)}

${ssrInterpolate(_ctx.$t("common.prev"))}= totalCount.value) ? " disabled" : ""} class="${ssrRenderClass([skip.value + limit.value < totalCount.value ? "bg-slate-700 text-slate-300 hover:bg-slate-600" : "bg-slate-800 text-slate-600", "px-3 py-1.5 text-xs font-medium rounded-lg transition disabled:opacity-30 disabled:cursor-not-allowed"])}">${ssrInterpolate(_ctx.$t("common.next"))}
`); } else { _push(``); } _push(``); } if (notification.value) { _push(`
${ssrInterpolate(notification.value.message)}
`); } else { _push(``); } _push(``); }; } }); const _sfc_setup = _sfc_main.setup; _sfc_main.setup = (props, ctx) => { const ssrContext = useSSRContext(); (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("pages/users/index.vue"); return _sfc_setup ? _sfc_setup(props, ctx) : void 0; }; export { _sfc_main as default }; //# sourceMappingURL=index-DP7v0fI5.mjs.map