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(`
${ssrInterpolate(_ctx.$t("users.trend_subtitle"))}
${ssrInterpolate(_ctx.$t("users.trend_no_data"))}
${ssrInterpolate(_ctx.$t("users.subtitle"))}
${ssrInterpolate(_ctx.$t("users.loading"))}
${ssrInterpolate(error.value)}
${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)}
${ssrInterpolate(_ctx.$t("users.selected_count", { count: selectedUserIds.value.length }))}
| ID | ${ssrInterpolate(_ctx.$t("users.email"))} | ${ssrInterpolate(_ctx.$t("users.name"))} | ${ssrInterpolate(_ctx.$t("users.role"))} | ${ssrInterpolate(_ctx.$t("users.status"))} | ${ssrInterpolate(_ctx.$t("users.package"))} | ${ssrInterpolate(_ctx.$t("users.registration"))} | ${ssrInterpolate(_ctx.$t("users.language"))} | ${ssrInterpolate(_ctx.$t("users.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 || "—")} | ${ssrInterpolate(roleLabel(user.role))} | ${ssrInterpolate(statusLabel(user))} | ${ssrInterpolate(user.subscription_plan || "—")} | ${ssrInterpolate(formatDate(user.created_at))} | ${ssrInterpolate(user.preferred_language || "—")} | `);
_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(``);
} else {
_push(``);
}
_push(` |
${ssrInterpolate(_ctx.$t("users.no_results"))}
${ssrInterpolate(_ctx.$t("users.showing"))} ${ssrInterpolate(skip.value + 1)}–${ssrInterpolate(Math.min(skip.value + limit.value, totalCount.value))} / ${ssrInterpolate(totalCount.value)}