import { For, Show, createEffect, createMemo, createSignal, onCleanup, onMount, createResource, } from "solid-js"; import { useSearchParams } from "@solidjs/router"; import { BarChart3, Building2, CircleDashed, Coins, EllipsisVertical, Eye, EyeOff, GripVertical, LineChart, RotateCcw, Search, Settings2, TrendingUp, Users, } from "lucide-solid"; import { ADMIN_DASHBOARD_WIDGETS, type DashboardWidgetDefinition, type DashboardWidgetSize, } from "~/lib/admin/dashboard"; import type { RuntimeDashboardLayout } from "~/lib/runtime/types"; import { loadAdminDashboardLayout, saveAdminDashboardLayout } from "~/lib/runtime/storage"; const API = "/api"; async function fetchMetrics() { const accessToken = typeof sessionStorage !== "undefined" ? (sessionStorage.getItem("nxtgauge_admin_access_token") || "").trim() : ""; const res = await fetch(`${API}/api/admin/dashboard/metrics`, { headers: { Accept: "application/json", ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), }, credentials: "include", }); if (!res.ok) throw new Error("Failed to load metrics"); return res.json(); } type WidgetStateKind = "live" | "empty" | "pending"; type WidgetType = "summary" | "analytics"; type SortMode = "layout" | "name" | "status"; type FilterMode = "all" | WidgetType; type GridLayoutMode = "3x4" | "3x3"; type WidgetMeta = { state: WidgetStateKind; type: WidgetType; statusLabel: string; subtitle: string; emptyMessage?: string; pendingMessage?: string; }; const WIDGET_DEFINITION_MAP = Object.fromEntries( ADMIN_DASHBOARD_WIDGETS.map((definition) => [definition.widgetKey, definition]) ); const DEFAULT_LAYOUT: RuntimeDashboardLayout = { order: ADMIN_DASHBOARD_WIDGETS.slice() .sort((a, b) => a.order - b.order) .map((definition) => definition.widgetKey), visibility: Object.fromEntries( ADMIN_DASHBOARD_WIDGETS.map((definition) => [definition.widgetKey, definition.defaultVisible]) ), size: Object.fromEntries( ADMIN_DASHBOARD_WIDGETS.map((definition) => [definition.widgetKey, definition.defaultSize]) ), }; const WIDGET_META: Record = { kpi_total_users: { state: "live", type: "summary", statusLabel: "Live Data", subtitle: "Powered by USER_MANAGEMENT", }, kpi_active_companies: { state: "live", type: "summary", statusLabel: "Live Data", subtitle: "Powered by COMPANY_MANAGEMENT", }, kpi_open_leads: { state: "live", type: "summary", statusLabel: "Live Data", subtitle: "Powered by REQUIREMENTS_MANAGEMENT", }, kpi_pending_approvals: { state: "live", type: "summary", statusLabel: "Live Data", subtitle: "Powered by APPROVAL_MANAGEMENT", }, kpi_total_revenue: { state: "live", type: "summary", statusLabel: "Live Data", subtitle: "Powered by REVENUE_LEDGER", }, kpi_credits_purchased: { state: "empty", type: "summary", statusLabel: "No Data", subtitle: "Powered by CREDIT_MANAGEMENT", emptyMessage: "No credit activity available yet", }, chart_leads_trend: { state: "live", type: "analytics", statusLabel: "Live Data", subtitle: "Weekly leads performance overview • Powered by REPORTS", }, chart_revenue_overview: { state: "live", type: "analytics", statusLabel: "Live Data", subtitle: "Weekly revenue overview • Powered by REVENUE_LEDGER", }, }; function sanitizeLayout(layout: RuntimeDashboardLayout | null | undefined): RuntimeDashboardLayout { const knownKeys = new Set(ADMIN_DASHBOARD_WIDGETS.map((definition) => definition.widgetKey)); const fallbackOrder = DEFAULT_LAYOUT.order; const incomingOrder = Array.isArray(layout?.order) ? layout!.order : []; const seen = new Set(); const normalizedOrder: string[] = []; for (const key of incomingOrder) { const normalizedKey = String(key || ""); if (!knownKeys.has(normalizedKey) || seen.has(normalizedKey)) continue; seen.add(normalizedKey); normalizedOrder.push(normalizedKey); } for (const key of fallbackOrder) { if (!seen.has(key)) normalizedOrder.push(key); } const visibility: Record = {}; const size: Record = {}; for (const definition of ADMIN_DASHBOARD_WIDGETS) { const key = definition.widgetKey; visibility[key] = typeof layout?.visibility?.[key] === "boolean" ? Boolean(layout?.visibility?.[key]) : definition.defaultVisible; const rawSize = String(layout?.size?.[key] || "").toUpperCase(); size[key] = rawSize === "S" || rawSize === "M" || rawSize === "L" ? (rawSize as DashboardWidgetSize) : definition.defaultSize; } return { order: normalizedOrder, visibility, size, }; } function reorderKeys(order: string[], draggedKey: string, targetKey: string): string[] { if (draggedKey === targetKey) return order; const next = order.slice(); const fromIndex = next.indexOf(draggedKey); const toIndex = next.indexOf(targetKey); if (fromIndex === -1 || toIndex === -1) return order; next.splice(fromIndex, 1); next.splice(toIndex, 0, draggedKey); return next; } function iconForWidget(widgetKey: string) { const cls = "text-[#FA5014]"; if (widgetKey.includes("users")) return ; if (widgetKey.includes("companies")) return ; if (widgetKey.includes("leads")) return ; if (widgetKey.includes("credits")) return ; if (widgetKey.includes("revenue")) return ; if (widgetKey.includes("approvals")) return ; return ; } function badgeClass(state: WidgetStateKind): string { if (state === "live") return "border-[#FDBA8C] bg-[#FFF1EB] text-[#FA5014]"; if (state === "pending") return "border-[#D1D5DB] bg-[#F3F4F6] text-[#6B7280]"; return "border-[#E5E7EB] bg-white text-[#6B7280]"; } function widgetSpan(mode: GridLayoutMode): string { return mode === "3x3" ? "xl:col-span-4" : "xl:col-span-3"; } function EmptyPreview() { return (
--
); } function PendingPreview() { return (
); } function LivePreview(props: { value?: string; trend?: string; trendUp?: boolean }) { return (

{props.value || "0"}

{props.trendUp ? "↗" : "↘"} {props.trend || "0%"}

); } export default function AdminHomePage() { const [searchParams] = useSearchParams(); const [layout, setLayout] = createSignal(sanitizeLayout(DEFAULT_LAYOUT)); const [settingsOpen, setSettingsOpen] = createSignal(false); const [isHydrating, setIsHydrating] = createSignal(true); const [isAutoSaving, setIsAutoSaving] = createSignal(false); const [autoSaveNotice, setAutoSaveNotice] = createSignal(""); const [lastSavedSnapshot, setLastSavedSnapshot] = createSignal(""); const [draggingKey, setDraggingKey] = createSignal(null); const [openMenuId, setOpenMenuId] = createSignal(null); const [search, setSearch] = createSignal(""); const [filterMode, setFilterMode] = createSignal("all"); const [sortMode, setSortMode] = createSignal("layout"); const [gridLayout, setGridLayout] = createSignal("3x4"); const [metrics] = createResource(fetchMetrics); const getWidgetState = (key: string) => { if (key.startsWith("kpi_")) { const idMap: Record = { kpi_total_users: "users", kpi_active_companies: "companies", kpi_open_leads: "leads", kpi_pending_approvals: "approvals", kpi_total_revenue: "revenue", kpi_credits_purchased: "credits", }; if (metrics.loading) return { state: "pending", statusLabel: "Loading..." }; const m = metrics()?.kpis?.find((k: any) => k.id === idMap[key]); if (m) return { state: "live", statusLabel: "Live Data", data: m }; return { state: "empty", statusLabel: "No Data" }; } if (metrics.loading) return { state: "pending", statusLabel: "Loading..." }; const m = metrics(); if (key === "chart_leads_trend") { const data = m?.trend_series; if (data && data.length > 0) return { state: "live", statusLabel: "Live Data", data: { trend_series: data } }; return { state: "empty", statusLabel: "No Data" }; } if (key === "chart_revenue_overview") { const data = m?.rev_series; if (data && data.length > 0) return { state: "live", statusLabel: "Live Data", data: { rev_series: data } }; return { state: "empty", statusLabel: "No Data" }; } const meta = WIDGET_META[key]; return { state: meta?.state || "empty", statusLabel: meta?.statusLabel || "No Data" }; }; const orderedWidgets = createMemo(() => { const current = layout(); const rows = current.order .map((key) => WIDGET_DEFINITION_MAP[key]) .filter((definition): definition is DashboardWidgetDefinition => Boolean(definition)) .filter((definition) => current.visibility[definition.widgetKey] !== false) .filter((definition) => { const query = search().trim().toLowerCase(); if (!query) return true; const meta = WIDGET_META[definition.widgetKey]; return ( definition.title.toLowerCase().includes(query) || definition.moduleKey.toLowerCase().includes(query) || (meta?.subtitle || "").toLowerCase().includes(query) ); }) .filter((definition) => { const mode = filterMode(); if (mode === "all") return true; return (WIDGET_META[definition.widgetKey]?.type || "summary") === mode; }); const mode = sortMode(); if (mode === "layout") return rows; const next = rows.slice(); next.sort((a, b) => { if (mode === "name") return a.title.localeCompare(b.title); const rank = (key: string) => { const info = getWidgetState(key); if (info.state === "live") return 1; if (info.state === "empty") return 2; return 3; }; return rank(a.widgetKey) - rank(b.widgetKey); }); return next; }); onMount(async () => { try { const persistedLayout = await loadAdminDashboardLayout(); const normalized = sanitizeLayout(persistedLayout || DEFAULT_LAYOUT); setLayout(normalized); setLastSavedSnapshot(JSON.stringify(normalized)); } finally { setIsHydrating(false); } }); createEffect(() => { if (isHydrating()) return; const nextLayout = layout(); const nextSnapshot = JSON.stringify(nextLayout); if (nextSnapshot === lastSavedSnapshot()) return; setIsAutoSaving(true); setAutoSaveNotice(""); const timer = setTimeout(async () => { const ok = await saveAdminDashboardLayout(nextLayout); setIsAutoSaving(false); if (ok) { setLastSavedSnapshot(nextSnapshot); setAutoSaveNotice("Layout saved automatically."); } else { setAutoSaveNotice("Auto-save failed. Please try again."); } }, 450); onCleanup(() => clearTimeout(timer)); }); const setWidgetVisibility = (widgetKey: string, visible: boolean) => { setLayout((current) => ({ ...current, visibility: { ...current.visibility, [widgetKey]: visible, }, })); }; const resetLayout = () => { const normalized = sanitizeLayout(DEFAULT_LAYOUT); setLayout(normalized); setAutoSaveNotice("Layout reset to default."); }; const handleDragStart = (event: DragEvent, widgetKey: string) => { event.dataTransfer?.setData("text/plain", widgetKey); if (event.dataTransfer) event.dataTransfer.effectAllowed = "move"; setDraggingKey(widgetKey); }; const handleDrop = (event: DragEvent, targetKey: string) => { event.preventDefault(); const dragged = event.dataTransfer?.getData("text/plain") || draggingKey(); if (!dragged) return; setLayout((current) => ({ ...current, order: reorderKeys(current.order, dragged, targetKey), })); setDraggingKey(null); }; return (

You don’t have access to {String(searchParams.denied || "").replace(/_/g, " ")}.

Dashboard Overview

Manage widget layout, visibility, sizing, and dashboard presentation

{/* Settings header */}

Widget Settings

Choose visible widgets and select a grid layout.

{/* Filter controls */}
setSearch(event.currentTarget.value)} placeholder="Search widgets" class="h-10 w-full rounded-lg border border-[#E5E7EB] bg-white pl-10 pr-3 text-sm text-[#111827] outline-none focus:border-[#FA5014]" />
{/* Widget list */}
{(definition) => { const visible = () => layout().visibility[definition.widgetKey] !== false; const meta = WIDGET_META[definition.widgetKey]; const stateInfo = getWidgetState(definition.widgetKey); const state = stateInfo.state; return (
{iconForWidget(definition.widgetKey)}

{definition.title}

{definition.moduleKey}

{stateInfo.statusLabel}
); }}

{isAutoSaving() ? "Saving layout..." : autoSaveNotice()}

{(definition) => { const meta = WIDGET_META[definition.widgetKey]; const stateInfo = getWidgetState(definition.widgetKey); const state = stateInfo.state; const isOpenMenu = () => openMenuId() === definition.widgetKey; return (
handleDragStart(event, definition.widgetKey)} onDragOver={(event) => event.preventDefault()} onDrop={(event) => handleDrop(event, definition.widgetKey)} onDragEnd={() => setDraggingKey(null)} class={`relative aspect-square min-h-[235px] rounded-2xl border border-[#E5E7EB] bg-white p-5 shadow-sm md:min-h-[260px] md:p-6 ${widgetSpan(gridLayout())} ${ state === "pending" ? "opacity-95" : "" } ${draggingKey() === definition.widgetKey ? "opacity-60" : ""}`} > <>
{["Edit Widget", "Duplicate Widget", "Hide Widget", "Remove Widget"].map( (item) => ( ) )}
{iconForWidget(definition.widgetKey)}

{definition.title}

{stateInfo.statusLabel}
); }}
); }