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

495 lines
38 KiB
JavaScript
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.
import { _ as __nuxt_component_0 } from "./nuxt-link-Ci8vU-Yt.js";
import { defineComponent, ref, computed, watch, mergeProps, useSSRContext, withCtx, createTextVNode, toDisplayString } from "vue";
import { ssrRenderAttrs, ssrInterpolate, ssrIncludeBooleanAttr, ssrLooseContain, ssrLooseEqual, ssrRenderAttr, ssrRenderList, ssrRenderClass, ssrRenderComponent } from "vue/server-renderer";
import { useRouter } from "vue-router";
import { c as useAuthStore, d as useCookie } from "../server.mjs";
import "/app/node_modules/hookable/dist/index.mjs";
import "/app/node_modules/ufo/dist/index.mjs";
import "/app/node_modules/defu/dist/defu.mjs";
import "/app/node_modules/ofetch/dist/node.mjs";
import "#internal/nuxt/paths";
import "/app/node_modules/unctx/dist/index.mjs";
import "/app/node_modules/h3/dist/index.mjs";
import "pinia";
import "/app/node_modules/klona/dist/index.mjs";
import "/app/node_modules/cookie-es/dist/index.mjs";
import "/app/node_modules/destr/dist/index.mjs";
import "/app/node_modules/ohash/dist/index.mjs";
import "/app/node_modules/@unhead/vue/dist/index.mjs";
import "@vue/devtools-api";
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(`<div${ssrRenderAttrs(mergeProps({ class: "bg-slate-800 rounded-xl border border-slate-700 p-5" }, _attrs))}><div class="flex items-center justify-between mb-4"><div><h3 class="text-sm font-semibold text-white">${ssrInterpolate(_ctx.$t("users.trend_title"))}</h3><p class="text-xs text-slate-400 mt-0.5">${ssrInterpolate(_ctx.$t("users.trend_subtitle"))}</p></div><select 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"><option value="7"${ssrIncludeBooleanAttr(Array.isArray(selectedPeriod.value) ? ssrLooseContain(selectedPeriod.value, "7") : ssrLooseEqual(selectedPeriod.value, "7")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.trend_7d"))}</option><option value="14"${ssrIncludeBooleanAttr(Array.isArray(selectedPeriod.value) ? ssrLooseContain(selectedPeriod.value, "14") : ssrLooseEqual(selectedPeriod.value, "14")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.trend_14d"))}</option><option value="30"${ssrIncludeBooleanAttr(Array.isArray(selectedPeriod.value) ? ssrLooseContain(selectedPeriod.value, "30") : ssrLooseEqual(selectedPeriod.value, "30")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.trend_30d"))}</option><option value="90"${ssrIncludeBooleanAttr(Array.isArray(selectedPeriod.value) ? ssrLooseContain(selectedPeriod.value, "90") : ssrLooseEqual(selectedPeriod.value, "90")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.trend_90d"))}</option></select></div>`);
if (__props.loading) {
_push(`<div class="flex items-center justify-center py-12"><svg class="w-6 h-6 text-indigo-400 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg></div>`);
} else if (filteredData.value.length === 0) {
_push(`<div class="flex flex-col items-center justify-center py-12 text-center"><svg class="w-10 h-10 text-slate-600 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg><p class="text-slate-500 text-xs">${ssrInterpolate(_ctx.$t("users.trend_no_data"))}</p></div>`);
} else {
_push(`<div class="relative"><svg${ssrRenderAttr("viewBox", `0 0 ${svgWidth} ${svgHeight}`)} class="w-full h-auto overflow-visible" preserveAspectRatio="xMidYMid meet"><!--[-->`);
ssrRenderList(gridLines.value, (y, yi) => {
_push(`<line${ssrRenderAttr("x1", padding.left)}${ssrRenderAttr("y1", y)}${ssrRenderAttr("x2", svgWidth - padding.right)}${ssrRenderAttr("y2", y)} stroke="currentColor" class="text-slate-700/50" stroke-width="1" stroke-dasharray="4,4"></line>`);
});
_push(`<!--]--><!--[-->`);
ssrRenderList(yLabels.value, (label, li) => {
_push(`<text${ssrRenderAttr("x", padding.left - 8)}${ssrRenderAttr("y", label.y + 4)} text-anchor="end" class="fill-slate-500" font-size="10">${ssrInterpolate(label.text)}</text>`);
});
_push(`<!--]--><!--[-->`);
ssrRenderList(visibleBars.value, (item, idx) => {
_push(`<g class="chart-bar-group"><rect${ssrRenderAttr("x", item.x)}${ssrRenderAttr("y", item.y)}${ssrRenderAttr("width", barWidth.value)}${ssrRenderAttr("height", item.height)}${ssrRenderAttr("rx", 3)}${ssrRenderAttr("ry", 3)}${ssrRenderAttr("fill", item.color)}${ssrRenderAttr("opacity", hoveredBar.value === idx ? 0.9 : 0.7)} class="transition-all duration-200 cursor-pointer"></rect>`);
if (hoveredBar.value === idx) {
_push(`<g><rect${ssrRenderAttr("x", item.tooltipX)}${ssrRenderAttr("y", item.tooltipY - 28)}${ssrRenderAttr("width", item.tooltipWidth)} height="22" rx="4" fill="#1e293b" stroke="#475569" stroke-width="1"></rect><text${ssrRenderAttr("x", item.tooltipX + item.tooltipWidth / 2)}${ssrRenderAttr("y", item.tooltipY - 13)} text-anchor="middle" fill="#e2e8f0" font-size="11" font-weight="600">${ssrInterpolate(item.tooltipText)}</text></g>`);
} else {
_push(`<!---->`);
}
_push(`</g>`);
});
_push(`<!--]--><!--[-->`);
ssrRenderList(xLabels.value, (item, xi) => {
_push(`<text${ssrRenderAttr("x", item.x + barWidth.value / 2)}${ssrRenderAttr("y", svgHeight - padding.bottom + 16)} text-anchor="middle" class="fill-slate-500" font-size="9">${ssrInterpolate(item.label)}</text>`);
});
_push(`<!--]--></svg><div class="flex items-center justify-between mt-4 pt-3 border-t border-slate-700/50"><div class="flex items-center gap-4 text-xs"><div class="flex items-center gap-1.5"><span class="w-2.5 h-2.5 rounded-sm bg-indigo-500/70"></span><span class="text-slate-400">${ssrInterpolate(_ctx.$t("users.trend_daily"))}</span></div><div><span class="text-slate-400">${ssrInterpolate(_ctx.$t("users.trend_avg"))}:</span><span class="text-white font-semibold ml-1">${ssrInterpolate(averageCount.value)}</span></div><div><span class="text-slate-400">${ssrInterpolate(_ctx.$t("users.trend_total"))}:</span><span class="text-white font-semibold ml-1">${ssrInterpolate(totalCount.value)}</span></div></div>`);
if (trendDirection.value !== "stable") {
_push(`<div class="flex items-center gap-1 text-xs">`);
if (trendDirection.value === "up") {
_push(`<svg class="w-3.5 h-3.5 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path></svg>`);
} else {
_push(`<svg class="w-3.5 h-3.5 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 17h8m0 0V9m0 8l-8-8-4 4-6-6"></path></svg>`);
}
_push(`<span class="${ssrRenderClass([trendDirection.value === "up" ? "text-emerald-400" : "text-red-400", "font-medium"])}">${ssrInterpolate(trendPercent.value)}% </span><span class="text-slate-500">${ssrInterpolate(_ctx.$t("users.trend_vs_prev"))}</span></div>`);
} else {
_push(`<!---->`);
}
_push(`</div></div>`);
}
_push(`</div>`);
};
}
});
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(() => {
if (auth.isAdmin) return true;
return !!auth.user?.system_capabilities?.["users:view"];
});
const canEditUsers = computed(() => {
if (auth.isAdmin) return true;
return !!auth.user?.system_capabilities?.["users:edit"];
});
computed(() => {
if (auth.isAdmin) return true;
return !!auth.user?.system_capabilities?.["users:ban"];
});
computed(() => {
if (auth.isAdmin) return true;
return !!auth.user?.system_capabilities?.["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() {
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 = err?.data?.detail || 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 "—";
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(`<div${ssrRenderAttrs(_attrs)}><div class="mb-8"><h1 class="text-2xl font-bold text-white">${ssrInterpolate(_ctx.$t("users.title"))}</h1><p class="text-slate-400 mt-1">${ssrInterpolate(_ctx.$t("users.subtitle"))}</p></div><div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 mb-6"><div class="relative flex-1 max-w-md"><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"><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"></path></svg><input${ssrRenderAttr("value", searchQuery.value)} type="text"${ssrRenderAttr("placeholder", _ctx.$t("users.search_placeholder"))} 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"></div><div class="flex items-center gap-2 flex-wrap"><select 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"><option value="all"${ssrIncludeBooleanAttr(Array.isArray(statusFilter.value) ? ssrLooseContain(statusFilter.value, "all") : ssrLooseEqual(statusFilter.value, "all")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.all_statuses"))}</option><option value="active"${ssrIncludeBooleanAttr(Array.isArray(statusFilter.value) ? ssrLooseContain(statusFilter.value, "active") : ssrLooseEqual(statusFilter.value, "active")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.status_active"))}</option><option value="inactive"${ssrIncludeBooleanAttr(Array.isArray(statusFilter.value) ? ssrLooseContain(statusFilter.value, "inactive") : ssrLooseEqual(statusFilter.value, "inactive")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.status_inactive"))}</option><option value="deleted"${ssrIncludeBooleanAttr(Array.isArray(statusFilter.value) ? ssrLooseContain(statusFilter.value, "deleted") : ssrLooseEqual(statusFilter.value, "deleted")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.status_deleted"))}</option><option value="banned"${ssrIncludeBooleanAttr(Array.isArray(statusFilter.value) ? ssrLooseContain(statusFilter.value, "banned") : ssrLooseEqual(statusFilter.value, "banned")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.status_banned"))}</option></select><select 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"><option value="all"${ssrIncludeBooleanAttr(Array.isArray(roleFilter.value) ? ssrLooseContain(roleFilter.value, "all") : ssrLooseEqual(roleFilter.value, "all")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.all_roles"))}</option><option value="user"${ssrIncludeBooleanAttr(Array.isArray(roleFilter.value) ? ssrLooseContain(roleFilter.value, "user") : ssrLooseEqual(roleFilter.value, "user")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.role_user"))}</option><option value="admin"${ssrIncludeBooleanAttr(Array.isArray(roleFilter.value) ? ssrLooseContain(roleFilter.value, "admin") : ssrLooseEqual(roleFilter.value, "admin")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.role_admin"))}</option><option value="staff"${ssrIncludeBooleanAttr(Array.isArray(roleFilter.value) ? ssrLooseContain(roleFilter.value, "staff") : ssrLooseEqual(roleFilter.value, "staff")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.role_staff"))}</option><option value="superadmin"${ssrIncludeBooleanAttr(Array.isArray(roleFilter.value) ? ssrLooseContain(roleFilter.value, "superadmin") : ssrLooseEqual(roleFilter.value, "superadmin")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.role_superadmin"))}</option></select><select 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"><option value="all"${ssrIncludeBooleanAttr(Array.isArray(planFilter.value) ? ssrLooseContain(planFilter.value, "all") : ssrLooseEqual(planFilter.value, "all")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.all_plans"))}</option><option value="FREE"${ssrIncludeBooleanAttr(Array.isArray(planFilter.value) ? ssrLooseContain(planFilter.value, "FREE") : ssrLooseEqual(planFilter.value, "FREE")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.plan_free"))}</option><option value="PREMIUM"${ssrIncludeBooleanAttr(Array.isArray(planFilter.value) ? ssrLooseContain(planFilter.value, "PREMIUM") : ssrLooseEqual(planFilter.value, "PREMIUM")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.plan_premium"))}</option><option value="ENTERPRISE"${ssrIncludeBooleanAttr(Array.isArray(planFilter.value) ? ssrLooseContain(planFilter.value, "ENTERPRISE") : ssrLooseEqual(planFilter.value, "ENTERPRISE")) ? " selected" : ""}>${ssrInterpolate(_ctx.$t("users.plan_enterprise"))}</option></select></div></div>`);
if (loading.value) {
_push(`<div class="flex items-center justify-center py-20"><div class="flex flex-col items-center gap-3"><svg class="w-8 h-8 text-indigo-400 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg><p class="text-slate-400 text-sm">${ssrInterpolate(_ctx.$t("users.loading"))}</p></div></div>`);
} else if (error.value) {
_push(`<div class="flex flex-col items-center justify-center py-20 text-center"><svg class="w-12 h-12 text-red-500 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg><p class="text-red-400 text-sm mb-2">${ssrInterpolate(error.value)}</p><button class="px-4 py-2 text-sm bg-indigo-600/20 text-indigo-300 hover:bg-indigo-600/30 rounded-lg transition">${ssrInterpolate(_ctx.$t("users.retry"))}</button></div>`);
} else {
_push(`<!--[--><div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4 mb-8"><div class="bg-slate-800 rounded-xl border border-slate-700 p-4"><div class="flex items-center gap-3"><div class="p-2 rounded-lg bg-indigo-500/10 text-indigo-400"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></div><div><p class="text-xs text-slate-400 uppercase tracking-wider">${ssrInterpolate(_ctx.$t("users.total_users"))}</p><p class="text-2xl font-bold text-white">${ssrInterpolate(stats.value.total_users)}</p></div></div></div><div class="bg-slate-800 rounded-xl border border-slate-700 p-4"><div class="flex items-center gap-3"><div class="p-2 rounded-lg bg-emerald-500/10 text-emerald-400"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></div><div><p class="text-xs text-slate-400 uppercase tracking-wider">${ssrInterpolate(_ctx.$t("users.active_users"))}</p><p class="text-2xl font-bold text-emerald-400">${ssrInterpolate(stats.value.active_users)}</p></div></div></div><div class="bg-slate-800 rounded-xl border border-slate-700 p-4"><div class="flex items-center gap-3"><div class="p-2 rounded-lg bg-red-500/10 text-red-400"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></div><div><p class="text-xs text-slate-400 uppercase tracking-wider">${ssrInterpolate(_ctx.$t("users.deleted_users"))}</p><p class="text-2xl font-bold text-red-400">${ssrInterpolate(stats.value.deleted_users)}</p></div></div></div><div class="bg-slate-800 rounded-xl border border-slate-700 p-4"><div class="flex items-center gap-3"><div class="p-2 rounded-lg bg-amber-500/10 text-amber-400"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></div><div><p class="text-xs text-slate-400 uppercase tracking-wider">${ssrInterpolate(_ctx.$t("users.banned_users"))}</p><p class="text-2xl font-bold text-amber-400">${ssrInterpolate(stats.value.banned_users)}</p></div></div></div><div class="bg-slate-800 rounded-xl border border-slate-700 p-4"><div class="flex items-center gap-3"><div class="p-2 rounded-lg bg-cyan-500/10 text-cyan-400"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></div><div><p class="text-xs text-slate-400 uppercase tracking-wider">${ssrInterpolate(_ctx.$t("users.new_today"))}</p><p class="text-2xl font-bold text-cyan-400">${ssrInterpolate(stats.value.new_users_today)}</p></div></div></div></div><div class="mb-8">`);
_push(ssrRenderComponent(_sfc_main$1, {
data: stats.value.registration_trend,
loading: loading.value
}, null, _parent));
_push(`</div>`);
if (isFilterActive.value) {
_push(`<div class="mb-4 text-xs text-slate-500">${ssrInterpolate(_ctx.$t("users.filtered_results"))}: <span class="text-slate-300 font-semibold">${ssrInterpolate(users.value.length)}</span> / ${ssrInterpolate(totalCount.value)} db </div>`);
} else {
_push(`<!---->`);
}
if (selectedUserIds.value.length > 0 && canEditUsers.value) {
_push(`<div class="mb-4 px-4 py-3 bg-indigo-900/30 border border-indigo-700/50 rounded-xl flex items-center justify-between"><p class="text-sm text-indigo-300">${ssrInterpolate(_ctx.$t("users.selected_count", { count: selectedUserIds.value.length }))}</p><div class="flex items-center gap-2"><button 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">${ssrInterpolate(_ctx.$t("users.bulk_activate"))}</button><button 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">${ssrInterpolate(_ctx.$t("users.bulk_deactivate"))}</button><button 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">${ssrInterpolate(_ctx.$t("users.clear_selection"))}</button></div></div>`);
} else {
_push(`<!---->`);
}
_push(`<div class="bg-slate-800/50 border border-slate-700 rounded-xl overflow-hidden"><div class="overflow-x-auto"><table class="w-full text-sm"><thead><tr class="border-b border-slate-700 bg-slate-800/80"><th class="text-left py-3.5 px-4 w-10"><input type="checkbox"${ssrIncludeBooleanAttr(allSelected.value) ? " checked" : ""} class="rounded border-slate-600 bg-slate-700 text-indigo-600 focus:ring-indigo-500/50"></th><th class="text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400">ID</th><th class="text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400">${ssrInterpolate(_ctx.$t("users.email"))}</th><th class="text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400">${ssrInterpolate(_ctx.$t("users.name"))}</th><th class="text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400">${ssrInterpolate(_ctx.$t("users.role"))}</th><th class="text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400">${ssrInterpolate(_ctx.$t("users.status"))}</th><th class="text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400">${ssrInterpolate(_ctx.$t("users.package"))}</th><th class="text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400">${ssrInterpolate(_ctx.$t("users.registration"))}</th><th class="text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400">${ssrInterpolate(_ctx.$t("users.language"))}</th><th class="text-left py-3.5 px-4 text-xs font-semibold uppercase tracking-wider text-slate-400">${ssrInterpolate(_ctx.$t("users.actions"))}</th></tr></thead><tbody><!--[-->`);
ssrRenderList(users.value, (user) => {
_push(`<tr class="border-b border-slate-700/50 hover:bg-slate-700/30 transition"><td class="py-3.5 px-4"><input type="checkbox"${ssrIncludeBooleanAttr(selectedUserIds.value.includes(user.id)) ? " checked" : ""} class="rounded border-slate-600 bg-slate-700 text-indigo-600 focus:ring-indigo-500/50"></td><td class="py-3.5 px-4 text-slate-400 font-mono text-xs">${ssrInterpolate(user.id)}</td><td class="py-3.5 px-4">`);
_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(`</td><td class="py-3.5 px-4"><span class="text-slate-300">${ssrInterpolate(user.person_name || "—")}</span></td><td class="py-3.5 px-4"><span class="${ssrRenderClass([roleBadgeClass(user.role), "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"])}">${ssrInterpolate(roleLabel(user.role))}</span></td><td class="py-3.5 px-4"><span class="${ssrRenderClass([statusBadgeClass(user), "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"])}"><span class="${ssrRenderClass([statusDotClass(user), "w-1.5 h-1.5 rounded-full mr-1.5"])}"></span> ${ssrInterpolate(statusLabel(user))}</span></td><td class="py-3.5 px-4"><span class="${ssrRenderClass([planBadgeClass(user.subscription_plan), "px-2.5 py-0.5 rounded-full text-xs font-medium"])}">${ssrInterpolate(user.subscription_plan || "—")}</span></td><td class="py-3.5 px-4 text-slate-300 text-xs">${ssrInterpolate(formatDate(user.created_at))}</td><td class="py-3.5 px-4"><span class="text-slate-400 text-xs uppercase">${ssrInterpolate(user.preferred_language || "—")}</span></td><td class="py-3.5 px-4"><div class="flex items-center gap-2">`);
_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("users.view"))}`);
} else {
return [
createTextVNode(toDisplayString(_ctx.$t("users.view")), 1)
];
}
}),
_: 2
}, _parent));
if (!user.is_deleted && canEditUsers.value) {
_push(`<button class="${ssrRenderClass([user.is_active ? "bg-red-500/10 text-red-300 hover:bg-red-500/20" : "bg-emerald-500/10 text-emerald-300 hover:bg-emerald-500/20", "px-3 py-1.5 text-xs font-medium rounded-lg transition"])}">${ssrInterpolate(user.is_active ? _ctx.$t("users.deactivate") : _ctx.$t("users.activate"))}</button>`);
} else {
_push(`<!---->`);
}
_push(`</div></td></tr>`);
});
_push(`<!--]--></tbody></table></div>`);
if (users.value.length === 0 && !loading.value) {
_push(`<div class="flex flex-col items-center justify-center py-16 text-center"><svg class="w-12 h-12 text-slate-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg><p class="text-slate-400 text-sm">${ssrInterpolate(_ctx.$t("users.no_results"))}</p></div>`);
} else {
_push(`<!---->`);
}
_push(`</div>`);
if (totalCount.value > limit.value) {
_push(`<div class="flex items-center justify-between mt-6"><p class="text-xs text-slate-500">${ssrInterpolate(_ctx.$t("users.showing"))} ${ssrInterpolate(skip.value + 1)}${ssrInterpolate(Math.min(skip.value + limit.value, totalCount.value))} / ${ssrInterpolate(totalCount.value)}</p><div class="flex items-center gap-2"><button${ssrIncludeBooleanAttr(skip.value === 0) ? " disabled" : ""} class="${ssrRenderClass([skip.value > 0 ? "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("users.prev"))}</button><button${ssrIncludeBooleanAttr(skip.value + limit.value >= 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("users.next"))}</button></div></div>`);
} else {
_push(`<!---->`);
}
_push(`<!--]-->`);
}
if (notification.value) {
_push(`<div class="${ssrRenderClass([notification.value.type === "success" ? "bg-emerald-600 text-white" : "bg-red-600 text-white", "fixed bottom-6 right-6 px-5 py-3 rounded-xl shadow-xl text-sm font-medium z-50"])}">${ssrInterpolate(notification.value.message)}</div>`);
} else {
_push(`<!---->`);
}
_push(`</div>`);
};
}
});
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-D8qGjmnz.js.map