import { For, Show, createResource, createSignal, onCleanup, onMount, } from 'solid-js'; import { A } from '@solidjs/router'; import AdminShell from '~/components/AdminShell'; import { Users, Building2, TrendingUp, CreditCard, Clock, BadgeCheck, BriefcaseBusiness, HeadphonesIcon, Download, Plus, X, GripVertical, } from 'lucide-solid'; import { DragDropProvider, DragDropSensors, SortableProvider, createSortable, closestCenter, type DragEventHandler, } from '@thisbeyond/solid-dnd'; // ── Types ───────────────────────────────────────────────────────────────────── type StatWidget = { id: string; label: string; icon: any; apiPath: string; totalKey: string[]; prefix?: string; deltaLabel: string; deltaValue: string; deltaUp: boolean; href: string; }; type ChartWidget = { id: string; label: string; subtitle: string; type: 'line' | 'bar'; }; // ── Stat widget definitions ─────────────────────────────────────────────────── const STAT_DEFS: StatWidget[] = [ { id: 'total-users', label: 'Total Users', icon: Users, apiPath: '/api/gateway/api/admin/users?limit=1', totalKey: ['total', 'count'], deltaLabel: 'from last month', deltaValue: '+12.5%', deltaUp: true, href: '/admin/users', }, { id: 'active-companies', label: 'Active Companies', icon: Building2, apiPath: '/api/gateway/api/admin/companies?limit=1', totalKey: ['total', 'count'], deltaLabel: 'from last month', deltaValue: '+8.2%', deltaUp: true, href: '/admin/company', }, { id: 'open-leads', label: 'Open Leads', icon: TrendingUp, apiPath: '/api/gateway/api/admin/leads?status=open&limit=1', totalKey: ['total', 'count'], deltaLabel: 'from last month', deltaValue: '-3.1%', deltaUp: false, href: '/admin/leads', }, { id: 'credits-purchased', label: 'Credits Purchased', icon: CreditCard, apiPath: '/api/gateway/api/admin/credits?limit=1', totalKey: ['total', 'count'], prefix: '₹', deltaLabel: 'from last month', deltaValue: '+18.7%', deltaUp: true, href: '/admin/credit', }, { id: 'pending-approvals', label: 'Pending Approvals', icon: Clock, apiPath: '/api/gateway/api/admin/approvals?status=pending&limit=1', totalKey: ['total', 'count'], deltaLabel: 'action required', deltaValue: 'Today', deltaUp: false, href: '/admin/approval', }, { id: 'pending-verifications', label: 'Pending Verifications', icon: BadgeCheck, apiPath: '/api/gateway/api/admin/verifications?status=pending&limit=1', totalKey: ['total', 'count'], deltaLabel: 'in queue', deltaValue: 'Today', deltaUp: false, href: '/admin/verification-status', }, { id: 'jobs-posted', label: 'Jobs Posted', icon: BriefcaseBusiness, apiPath: '/api/gateway/api/admin/jobs?limit=1', totalKey: ['total', 'count'], deltaLabel: 'from last month', deltaValue: '+5.4%', deltaUp: true, href: '/admin/jobs', }, { id: 'support-tickets', label: 'Support Tickets', icon: HeadphonesIcon, apiPath: '/api/gateway/api/admin/support?status=open&limit=1', totalKey: ['total', 'count'], deltaLabel: 'open tickets', deltaValue: 'Active', deltaUp: false, href: '/admin/support', }, ]; const CHART_DEFS: ChartWidget[] = [ { id: 'leads-trend', label: 'Leads Trend', subtitle: 'Monthly leads performance overview', type: 'line' }, { id: 'revenue-overview', label: 'Revenue Overview', subtitle: 'Monthly revenue vs expenses comparison', type: 'bar' }, { id: 'user-growth', label: 'User Growth', subtitle: 'Monthly new user registrations', type: 'line' }, ]; // Default layout const DEFAULT_STATS = ['total-users', 'active-companies', 'open-leads', 'credits-purchased']; const DEFAULT_CHARTS = ['leads-trend', 'revenue-overview']; const STORAGE_KEY = 'nxtgauge_admin_dash_v1'; function loadLayout(): { stats: string[]; charts: string[] } { if (typeof localStorage === 'undefined') return { stats: DEFAULT_STATS, charts: DEFAULT_CHARTS }; try { const raw = localStorage.getItem(STORAGE_KEY); if (raw) return JSON.parse(raw); } catch { /* */ } return { stats: DEFAULT_STATS, charts: DEFAULT_CHARTS }; } function saveLayout(stats: string[], charts: string[]) { if (typeof localStorage === 'undefined') return; localStorage.setItem(STORAGE_KEY, JSON.stringify({ stats, charts })); } // ── ApexCharts wrapper ──────────────────────────────────────────────────────── function Chart(props: { id: string; type: 'line' | 'bar'; height?: number }) { let el!: HTMLDivElement; let chartInstance: any = null; // onCleanup must be registered synchronously (outside async) to stay in reactive scope onCleanup(() => { chartInstance?.destroy(); chartInstance = null; }); onMount(async () => { const { default: ApexCharts } = await import('apexcharts'); if (!el) return; const isLine = props.type === 'line'; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; const options: Record = { chart: { type: props.type, height: props.height ?? 240, toolbar: { show: false }, fontFamily: 'Exo 2, sans-serif', animations: { enabled: true, speed: 400 }, }, grid: { borderColor: '#f1f5f9', strokeDashArray: 4, xaxis: { lines: { show: false } }, yaxis: { lines: { show: true } }, }, xaxis: { categories: months, axisBorder: { show: false }, axisTicks: { show: false }, labels: { style: { colors: '#94a3b8', fontSize: '12px' } }, }, tooltip: { theme: 'light', style: { fontFamily: 'Exo 2, sans-serif' } }, legend: { show: false }, ...(isLine ? { series: [{ name: 'Leads', data: [65, 59, 80, 81, 90, 115] }], stroke: { curve: 'smooth', width: 2.5, colors: ['#fd6216'] }, fill: { type: 'gradient', gradient: { shadeIntensity: 1, opacityFrom: 0.35, opacityTo: 0.02, colorStops: [ { offset: 0, color: '#fd6216', opacity: 0.35 }, { offset: 100, color: '#fd6216', opacity: 0 }, ], }, }, markers: { size: 0 }, colors: ['#fd6216'], yaxis: { labels: { style: { colors: '#94a3b8', fontSize: '12px' } }, min: 0, max: 130, tickAmount: 4 }, } : { series: [{ name: 'Revenue (₹)', data: [35000, 41000, 38000, 52000, 61000, 67000] }], plotOptions: { bar: { borderRadius: 4, columnWidth: '50%' } }, fill: { colors: ['#0a1d37'] }, colors: ['#0a1d37'], dataLabels: { enabled: false }, yaxis: { labels: { style: { colors: '#94a3b8', fontSize: '12px' }, formatter: (v: number) => `${(v / 1000).toFixed(0)}k`, }, }, }), }; chartInstance = new ApexCharts(el, options); await chartInstance.render(); }); return
; } // ── Stat count fetcher ──────────────────────────────────────────────────────── async function fetchCount(path: string, keys: string[]): Promise { try { const res = await fetch(path); if (!res.ok) return '—'; const data = await res.json(); for (const k of keys) { if (data[k] !== undefined) return String(Number(data[k]).toLocaleString('en-IN')); } return '—'; } catch { return '—'; } } // ── Sortable stat card ──────────────────────────────────────────────────────── function SortableStatCard(props: { def: StatWidget; onRemove: () => void; editMode: boolean; }) { const sortable = createSortable(props.def.id); const [count] = createResource(() => fetchCount(props.def.apiPath, props.def.totalKey)); const Icon = props.def.icon; return (
{/* Drag handle */}
{/* Top row: icon + delta badge */}
{props.def.deltaUp ? '↑' : props.def.deltaValue === 'Today' || props.def.deltaValue === 'Active' ? '' : '↓'} {props.def.deltaValue}
{/* Label */}

{props.def.label}

{/* Value */}

Loading…}> {props.def.prefix ?? ''}{count()}

{/* Delta label */}

{props.def.deltaLabel}

{/* Link overlay */}
); } // ── Sortable chart card ─────────────────────────────────────────────────────── function SortableChartCard(props: { def: ChartWidget; onRemove: () => void; editMode: boolean; }) { const sortable = createSortable(props.def.id); return (

{props.def.label}

{props.def.subtitle}

); } // ── Add Widget Panel ────────────────────────────────────────────────────────── function AddWidgetPanel(props: { activeStats: string[]; activeCharts: string[]; onAddStat: (id: string) => void; onAddChart: (id: string) => void; onClose: () => void; }) { const availableStats = () => STAT_DEFS.filter((d) => !props.activeStats.includes(d.id)); const availableCharts = () => CHART_DEFS.filter((d) => !props.activeCharts.includes(d.id)); return (

Add Widgets

0}>

Stat Cards

{(def) => { const Icon = def.icon; return ( ); }}
0}>

Charts

{(def) => ( )}

All widgets are already on your dashboard.

); } // ── Dashboard page ──────────────────────────────────────────────────────────── export default function AdminDashboard() { const layout = loadLayout(); const [statIds, setStatIds] = createSignal(layout.stats); const [chartIds, setChartIds] = createSignal(layout.charts); const [editMode, setEditMode] = createSignal(false); const [showAdd, setShowAdd] = createSignal(false); // Persist whenever layout changes const persist = () => saveLayout(statIds(), chartIds()); const statDefs = () => statIds().map((id) => STAT_DEFS.find((d) => d.id === id)!).filter(Boolean); const chartDefs = () => chartIds().map((id) => CHART_DEFS.find((d) => d.id === id)!).filter(Boolean); // ── Stat drag ────────────────────────────────────────────────────────────── const onStatDragEnd: DragEventHandler = ({ draggable, droppable }) => { if (!droppable || draggable.id === droppable.id) return; const ids = [...statIds()]; const from = ids.indexOf(String(draggable.id)); const to = ids.indexOf(String(droppable.id)); if (from < 0 || to < 0) return; ids.splice(to, 0, ...ids.splice(from, 1)); setStatIds(ids); persist(); }; // ── Chart drag ───────────────────────────────────────────────────────────── const onChartDragEnd: DragEventHandler = ({ draggable, droppable }) => { if (!droppable || draggable.id === droppable.id) return; const ids = [...chartIds()]; const from = ids.indexOf(String(draggable.id)); const to = ids.indexOf(String(droppable.id)); if (from < 0 || to < 0) return; ids.splice(to, 0, ...ids.splice(from, 1)); setChartIds(ids); persist(); }; const removeStatWidget = (id: string) => { setStatIds((p) => p.filter((x) => x !== id)); persist(); }; const removeChartWidget = (id: string) => { setChartIds((p) => p.filter((x) => x !== id)); persist(); }; const addStatWidget = (id: string) => { setStatIds((p) => [...p, id]); persist(); setShowAdd(false); }; const addChartWidget = (id: string) => { setChartIds((p) => [...p, id]); persist(); setShowAdd(false); }; const handleExport = () => window.print(); return (
{/* ── Page header ── */}

Dashboard Overview

Welcome back! Here's what's happening with your platform today.

{/* ── Add widget panel ── */} setShowAdd(false)} /> {/* ── Stat cards ── */} 0}>
{(def) => ( removeStatWidget(def.id)} /> )}
{/* ── Charts ── */} 0}>
{(def) => ( removeChartWidget(def.id)} /> )}
{/* Empty state */}

Your dashboard is empty.

Click "Customise" then "Add Widget" to get started.

); }