From 13b428913fb2ef30ce98a14048333e9a3c50cd22 Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Date: Wed, 25 Mar 2026 19:21:54 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20phase=201+2=20=E2=80=94=20shell=20redes?= =?UTF-8?q?ign=20+=20drag-and-drop=20dashboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shell (AdminShell + AdminSidebar): - Logo moved to header-left section, width syncs with sidebar collapse state - Global search bar with debounced multi-module API calls and grouped dropdown - Bell notification icon with badge, gear → /admin/settings, user dropdown with logout - Sidebar: 7 grouped nav sections with dividers, orange left-border active state, removed "Active" badge pill, user info (avatar + name + role) pinned to bottom - Fixed all sidebar labels to match Figma (Employee Management, External Onboarding Management, Users Management, Verification Management) - Added missing sidebar items: Verification, Fitness Trainers, Graphic Designers, Social Media, Video Editors, Catering Services, Applications, Responses Dashboard (admin/index): - Rebuilt to match Figma: "Dashboard Overview" title, Export Report button - 4 stat cards (Total Users, Active Companies, Open Leads, Credits Purchased) with real API fetch, orange icons, delta badges, graceful — fallback - ApexCharts: Leads Trend (orange gradient line) + Revenue Overview (navy bars) - Drag-and-drop widget system via @thisbeyond/solid-dnd — sortable stat cards and chart cards with handles and remove buttons in Customise mode - Add Widget panel shows all available widgets not on dashboard - 8 stat widgets + 3 chart widgets available; layout persists in localStorage Co-Authored-By: Claude Sonnet 4.6 --- package-lock.json | 21 + package.json | 2 + src/components/AdminShell.tsx | 568 +++++++++++++++++++------ src/components/AdminSidebar.tsx | 244 +++++++---- src/routes/admin/index.tsx | 731 ++++++++++++++++++++++++-------- 5 files changed, 1172 insertions(+), 394 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4fb7976..4bdd739 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ "@solidjs/router": "^0.15.0", "@solidjs/start": "^1.3.2", "@tailwindcss/vite": "^4.2.2", + "@thisbeyond/solid-dnd": "^0.7.5", + "apexcharts": "^5.10.4", "lucide-solid": "^1.0.1", "solid-js": "^1.9.5", "tailwindcss": "^4.2.2", @@ -2650,6 +2652,19 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@thisbeyond/solid-dnd": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@thisbeyond/solid-dnd/-/solid-dnd-0.7.5.tgz", + "integrity": "sha512-DfI5ff+yYGpK9M21LhYwIPlbP2msKxN2ARwuu6GF8tT1GgNVDTI8VCQvH4TJFoVApP9d44izmAcTh/iTCH2UUw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0", + "pnpm": ">=8.6.0" + }, + "peerDependencies": { + "solid-js": "^1.5" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -3046,6 +3061,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/apexcharts": { + "version": "5.10.4", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-5.10.4.tgz", + "integrity": "sha512-gt0VUqZ2+mr25ScbUcKZgJr96jKYm4vjOcxEWCEh/E5F4dWqhyo3dBhPRvNNnkKiWxkMd2cBwj3ZYH3rK39fkA==", + "license": "SEE LICENSE IN LICENSE" + }, "node_modules/archiver": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", diff --git a/package.json b/package.json index c61e686..f2832bc 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "@solidjs/router": "^0.15.0", "@solidjs/start": "^1.3.2", "@tailwindcss/vite": "^4.2.2", + "@thisbeyond/solid-dnd": "^0.7.5", + "apexcharts": "^5.10.4", "lucide-solid": "^1.0.1", "solid-js": "^1.9.5", "tailwindcss": "^4.2.2", diff --git a/src/components/AdminShell.tsx b/src/components/AdminShell.tsx index 6ca748b..64371fb 100644 --- a/src/components/AdminShell.tsx +++ b/src/components/AdminShell.tsx @@ -1,12 +1,22 @@ import { A, useLocation, useNavigate, useSearchParams } from '@solidjs/router'; -import { For, createEffect, createMemo, createSignal, onCleanup, onMount, type JSX } from 'solid-js'; +import { + For, Show, createEffect, createMemo, createSignal, + onCleanup, onMount, type JSX, +} from 'solid-js'; import AdminSidebar from './AdminSidebar'; import { isExternalIdentity } from '~/lib/admin-auth'; import { clearAdminSession, hasAdminSession, setAdminSession } from '~/lib/admin-session'; -import { Bell } from 'lucide-solid'; +import { Bell, Search, Settings, LogOut, User, ChevronDown } from 'lucide-solid'; + +// ── Types ──────────────────────────────────────────────────────────────────── type Tab = { href: string; label: string; exact?: boolean }; +type SearchResult = { id: string; title: string; subtitle: string; href: string }; +type SearchGroup = { label: string; viewAllHref: string; results: SearchResult[] }; + +// ── Tab sets ───────────────────────────────────────────────────────────────── + const TAB_SETS: Array<{ prefixes: string[]; tabs: Tab[] }> = [ { prefixes: ['/admin/roles'], @@ -26,22 +36,373 @@ const TAB_SETS: Array<{ prefixes: string[]; tabs: Tab[] }> = [ }, ]; +// ── Global search ───────────────────────────────────────────────────────────── + +const SEARCH_MODULES = [ + { + label: 'Users', + viewAllHref: '/admin/users', + api: '/api/gateway/api/admin/users', + listKeys: ['users', 'items'], + titleKeys: ['full_name', 'name'], + subtitleKeys: ['email', 'phone'], + detailBase: '/admin/users', + }, + { + label: 'Companies', + viewAllHref: '/admin/company', + api: '/api/gateway/api/admin/companies', + listKeys: ['companies', 'items'], + titleKeys: ['name', 'companyName'], + subtitleKeys: ['email', 'phone'], + detailBase: '/admin/company', + }, + { + label: 'Employees', + viewAllHref: '/admin/employees', + api: '/api/gateway/api/admin/employees', + listKeys: ['employees', 'items'], + titleKeys: ['full_name', 'name'], + subtitleKeys: ['email', 'department_name'], + detailBase: '/admin/employees', + }, + { + label: 'Jobs', + viewAllHref: '/admin/jobs', + api: '/api/gateway/api/admin/jobs', + listKeys: ['jobs', 'items'], + titleKeys: ['title', 'name'], + subtitleKeys: ['status', 'company_name'], + detailBase: '/admin/jobs', + }, + { + label: 'Leads', + viewAllHref: '/admin/leads', + api: '/api/gateway/api/admin/leads', + listKeys: ['leads', 'items'], + titleKeys: ['name', 'full_name'], + subtitleKeys: ['email', 'status'], + detailBase: '/admin/leads', + }, + { + label: 'Candidates', + viewAllHref: '/admin/candidate', + api: '/api/gateway/api/admin/candidates', + listKeys: ['candidates', 'items'], + titleKeys: ['full_name', 'name'], + subtitleKeys: ['email', 'phone'], + detailBase: '/admin/candidate', + }, +]; + +function pickStr(obj: Record, keys: string[]): string { + for (const k of keys) if (obj[k]) return String(obj[k]); + return '—'; +} + +function extractList(data: any, keys: string[]): any[] { + if (Array.isArray(data)) return data; + for (const k of keys) if (Array.isArray(data[k])) return data[k]; + return []; +} + +function GlobalSearch() { + const [query, setQuery] = createSignal(''); + const [open, setOpen] = createSignal(false); + const [groups, setGroups] = createSignal([]); + const [searching, setSearching] = createSignal(false); + let wrapRef!: HTMLDivElement; + let timer: ReturnType; + + const doSearch = async (q: string) => { + const trimmed = q.trim(); + if (trimmed.length < 2) { setGroups([]); setOpen(false); return; } + setSearching(true); + const settled = await Promise.allSettled( + SEARCH_MODULES.map(async (mod) => { + const res = await fetch( + `${mod.api}?search=${encodeURIComponent(trimmed)}&limit=4`, + ).catch(() => null); + if (!res?.ok) return null; + const data = await res.json().catch(() => null); + if (!data) return null; + const items = extractList(data, mod.listKeys).slice(0, 4); + if (!items.length) return null; + return { + label: mod.label, + viewAllHref: mod.viewAllHref, + results: items.map((item: any) => ({ + id: item.id, + title: pickStr(item, mod.titleKeys), + subtitle: pickStr(item, mod.subtitleKeys), + href: `${mod.detailBase}/${item.id}`, + })), + } satisfies SearchGroup; + }), + ); + setGroups(settled.flatMap((r) => (r.status === 'fulfilled' && r.value ? [r.value] : []))); + setOpen(true); + setSearching(false); + }; + + const handleInput = (val: string) => { + setQuery(val); + clearTimeout(timer); + if (val.trim().length < 2) { setGroups([]); setOpen(false); return; } + timer = setTimeout(() => doSearch(val), 400); + }; + + const close = () => { setOpen(false); setQuery(''); setGroups([]); }; + + const onOutside = (e: MouseEvent) => { + if (!wrapRef.contains(e.target as Node)) setOpen(false); + }; + onMount(() => document.addEventListener('mousedown', onOutside)); + onCleanup(() => document.removeEventListener('mousedown', onOutside)); + + return ( +
+ {/* Search icon */} + + + handleInput(e.currentTarget.value)} + onFocus={() => groups().length > 0 && setOpen(true)} + onKeyDown={(e) => e.key === 'Escape' && close()} + class="h-10 w-full rounded-xl border border-gray-200 bg-gray-50 pl-10 pr-4 text-sm text-gray-700 placeholder:text-gray-400 transition-colors focus:border-gray-300 focus:bg-white focus:outline-none" + /> + + {/* Results dropdown */} + 0}> +
+ + {(group) => ( + + )} + +
+
+ + {/* No results */} + = 2 && groups().length === 0}> +
+

+ No results for "{query()}" +

+
+
+
+ ); +} + +// ── Notification bell ───────────────────────────────────────────────────────── + +function NotificationBell(props: { count: number }) { + const [open, setOpen] = createSignal(false); + let ref!: HTMLDivElement; + + const onOutside = (e: MouseEvent) => { + if (!ref.contains(e.target as Node)) setOpen(false); + }; + onMount(() => document.addEventListener('mousedown', onOutside)); + onCleanup(() => document.removeEventListener('mousedown', onOutside)); + + return ( +
+ + + +
+
+ Notifications + 0}> + + {props.count} new + + +
+
+ +

Real-time notifications

+

Connecting to live feed…

+
+
+
+
+ ); +} + +// ── User menu ───────────────────────────────────────────────────────────────── + +function UserMenu(props: { name: string; initials: string; onLogout: () => void }) { + const [open, setOpen] = createSignal(false); + let ref!: HTMLDivElement; + + const onOutside = (e: MouseEvent) => { + if (!ref.contains(e.target as Node)) setOpen(false); + }; + onMount(() => document.addEventListener('mousedown', onOutside)); + onCleanup(() => document.removeEventListener('mousedown', onOutside)); + + return ( +
+ + + + + ); +} + +// ── Tabs ────────────────────────────────────────────────────────────────────── + +function ShowTabs(props: { + tabs: Tab[]; + isTabActive: (tab: Tab) => boolean; + setTabsTrackEl: (el: HTMLDivElement) => void; + setTabRefs: (fn: (prev: Record) => Record) => void; + tabIndicator: () => { left: number; width: number; ready: boolean }; +}) { + if (props.tabs.length === 0) return null; + return ( +
+ + {(tab) => ( + props.setTabRefs((prev) => ({ ...prev, [tab.href]: el }))} + aria-current={props.isTabActive(tab) ? 'page' : undefined} + class={`px-4 pb-3 pt-3 text-[14px] font-semibold transition-colors ${ + props.isTabActive(tab) + ? 'text-[#fd6216]' + : 'text-slate-500 hover:text-slate-800' + }`} + > + {tab.label} + + )} + +
+
+ ); +} + +// ── Main shell ──────────────────────────────────────────────────────────────── + export default function AdminShell(props: { children: JSX.Element }) { - const location = useLocation(); - const navigate = useNavigate(); + const location = useLocation(); + const navigate = useNavigate(); const [searchParams] = useSearchParams(); + const [checkedSession, setCheckedSession] = createSignal(false); - const [adminName, setAdminName] = createSignal('Admin'); - const [sidebarOpen, setSidebarOpen] = createSignal(false); + const [adminName, setAdminName] = createSignal('Admin User'); + const [sidebarOpen, setSidebarOpen] = createSignal(false); const [sidebarCollapsed, setSidebarCollapsed] = createSignal(false); + const [notifCount] = createSignal(0); // wired in Phase 2 (SSE) + const [tabsTrackEl, setTabsTrackEl] = createSignal(); - const [tabRefs, setTabRefs] = createSignal>({}); + const [tabRefs, setTabRefs] = createSignal>({}); const [tabIndicator, setTabIndicator] = createSignal({ left: 0, width: 0, ready: false }); + // ── Tab logic ────────────────────────────────────────────────────────────── const tabs = createMemo(() => { const path = location.pathname; for (const set of TAB_SETS) { - if (set.prefixes.some((p) => path === p || path.startsWith(`${p}/`))) return set.tabs; + if (set.prefixes.some((p) => path === p || path.startsWith(`${p}/`))) + return set.tabs; } return []; }); @@ -54,31 +415,24 @@ export default function AdminShell(props: { children: JSX.Element }) { const refreshTabIndicator = () => { const activeTab = tabs().find((tab) => isTabActive(tab)); const track = tabsTrackEl(); - if (!activeTab || !track) { - setTabIndicator((prev) => ({ ...prev, ready: false })); - return; - } + if (!activeTab || !track) { setTabIndicator((p) => ({ ...p, ready: false })); return; } const el = tabRefs()[activeTab.href]; if (!el) return; setTabIndicator({ left: el.offsetLeft, width: el.offsetWidth, ready: true }); }; createEffect(() => { - tabs(); - location.pathname; + tabs(); location.pathname; requestAnimationFrame(refreshTabIndicator); }); onMount(() => { - const onResize = () => refreshTabIndicator(); - window.addEventListener('resize', onResize); - onCleanup(() => window.removeEventListener('resize', onResize)); + window.addEventListener('resize', refreshTabIndicator); + onCleanup(() => window.removeEventListener('resize', refreshTabIndicator)); - const isLocalDev = - typeof window !== 'undefined' && + const isLocalDev = typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'); - const isPreview = - searchParams._preview === '1' || + const isPreview = searchParams._preview === '1' || (typeof sessionStorage !== 'undefined' && sessionStorage.getItem('nxtgauge_admin_preview') === '1'); @@ -92,15 +446,13 @@ export default function AdminShell(props: { children: JSX.Element }) { const verify = async () => { if (!hasAdminSession()) { - const from = encodeURIComponent(location.pathname + location.search); - navigate(`/login?from=${from}`, { replace: true }); + navigate(`/login?from=${encodeURIComponent(location.pathname + location.search)}`, { replace: true }); return; } try { - const accessToken = - typeof sessionStorage !== 'undefined' - ? sessionStorage.getItem('nxtgauge_admin_access_token') || '' - : ''; + const accessToken = typeof sessionStorage !== 'undefined' + ? sessionStorage.getItem('nxtgauge_admin_access_token') || '' + : ''; const response = await fetch('/api/gateway/users/auth/me', { method: 'GET', headers: { @@ -116,11 +468,9 @@ export default function AdminShell(props: { children: JSX.Element }) { setCheckedSession(true); } catch { clearAdminSession(); - const from = encodeURIComponent(location.pathname + location.search); - navigate(`/login?from=${from}`, { replace: true }); + navigate(`/login?from=${encodeURIComponent(location.pathname + location.search)}`, { replace: true }); } }; - void verify(); }); @@ -141,98 +491,110 @@ export default function AdminShell(props: { children: JSX.Element }) { const adminInitials = createMemo(() => { const parts = adminName().split(' ').map((s) => s.trim()).filter(Boolean); if (parts.length === 0) return 'U'; - if (parts.length === 1) return parts[0].slice(0, 1).toUpperCase(); - return `${parts[0][0] || ''}`.toUpperCase(); + if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase(); + return `${parts[0][0]}${parts[1][0]}`.toUpperCase(); }); - const sidebarWidth = () => (sidebarCollapsed() ? 'w-20' : 'w-64'); + // Sidebar width — synced between header logo section and sidebar + const sidebarW = () => (sidebarCollapsed() ? 'w-[72px]' : 'w-64'); return (
+ {/* ── Header ── */} -
- {/* Left: logo + role title */} -
- - NXTGAUGE +
+ + {/* Logo section — same width as sidebar */} +
+ + Nxtgauge -

Super Admin

+
+ + {/* Search bar */} +
+
{/* Mobile menu button */} - {/* Right: bell + avatar + logout */} -
{/* ── Body ── */} - {checkedSession() ? ( + +
+
+

Checking session…

+
+
+ } + >
{/* Mobile overlay */}
setSidebarOpen(false)} /> {/* Sidebar */}
setSidebarCollapsed((v) => !v)} onNavigate={() => setSidebarOpen(false)} - onLogout={onLogout} + adminName={adminName()} + adminInitials={adminInitials()} />
- {/* Main */} + {/* Main content */}
- ) : ( -
- - )} -
- ); -} - -function ShowTabs(props: { - tabs: Tab[]; - isTabActive: (tab: Tab) => boolean; - setTabsTrackEl: (el: HTMLDivElement) => void; - setTabRefs: (fn: (prev: Record) => Record) => void; - tabIndicator: () => { left: number; width: number; ready: boolean }; -}) { - if (props.tabs.length === 0) return null; - - return ( -
- - {(tab) => ( - props.setTabRefs((prev) => ({ ...prev, [tab.href]: el }))} - aria-current={props.isTabActive(tab) ? 'page' : undefined} - class={`px-4 pb-3 pt-3 text-[14px] font-semibold transition-colors ${ - props.isTabActive(tab) - ? 'text-[#fd6216]' - : 'text-slate-500 hover:text-slate-800' - }`} - > - {tab.label} - - )} - -
+
); } diff --git a/src/components/AdminSidebar.tsx b/src/components/AdminSidebar.tsx index 7de8bf5..92e764a 100644 --- a/src/components/AdminSidebar.tsx +++ b/src/components/AdminSidebar.tsx @@ -1,79 +1,120 @@ import { A, useLocation } from '@solidjs/router'; -import { For, Show, createSignal } from 'solid-js'; +import { For, Show } from 'solid-js'; import { LayoutGrid, Building2, Briefcase, Users, ShieldCheck, FileText, LayoutDashboard, ClipboardList, UserRoundSearch, UserCircle, Camera, Palette, BookOpen, Code2, BriefcaseBusiness, HandHelping, WalletCards, CreditCard, Tag, Percent, Receipt, ShoppingCart, FileCheck, Star, HeadphonesIcon, BarChart3, BookMarked, Bell, - ChevronLeft, + ChevronLeft, BadgeCheck, Activity, Film, Utensils, PenTool, + MessageSquare, Megaphone, } from 'lucide-solid'; -type Item = { +type NavItem = { href: string; label: string; icon: any; aliasPrefix?: string; }; -const items: Item[] = [ - { href: '/admin', label: 'Dashboard', icon: LayoutGrid }, - { href: '/admin/department', label: 'Department Management', icon: Building2 }, - { href: '/admin/designation', label: 'Designation Management', icon: Briefcase }, - { href: '/admin/employees', label: 'Internal User Management', icon: Users }, - { href: '/admin/roles', label: 'Internal Role Management', icon: ShieldCheck }, - { href: '/admin/runtime-roles', label: 'External Role Management', icon: ShieldCheck }, - { href: '/admin/onboarding-management', label: 'Onboarding Management', icon: FileText, aliasPrefix: '/admin/onboarding-schemas' }, - { href: '/admin/internal-dashboard-management',label: 'Internal Dashboard Management', icon: LayoutDashboard }, - { href: '/admin/external-dashboard-management',label: 'External Dashboard Management', icon: LayoutDashboard, aliasPrefix: '/admin/role-ui-configs' }, - { href: '/admin/approval', label: 'Approval Management', icon: ClipboardList }, - { href: '/admin/users', label: 'External User Management', icon: UserRoundSearch }, - { href: '/admin/customer', label: 'Customer Management', icon: UserCircle }, - { href: '/admin/company', label: 'Company Management', icon: Building2 }, - { href: '/admin/candidate', label: 'Candidate Management', icon: UserCircle }, - { href: '/admin/photographer', label: 'Photographer Management', icon: Camera }, - { href: '/admin/makeup-artist', label: 'Makeup Artist Management', icon: Palette }, - { href: '/admin/tutors', label: 'Tutor Management', icon: BookOpen }, - { href: '/admin/developers', label: 'Developer Management', icon: Code2 }, - { href: '/admin/jobs', label: 'Jobs Management', icon: BriefcaseBusiness }, - { href: '/admin/leads', label: 'Leads Management', icon: HandHelping }, - { href: '/admin/pricing', label: 'Pricing Management', icon: WalletCards }, - { href: '/admin/credit', label: 'Credit Management', icon: CreditCard }, - { href: '/admin/coupon', label: 'Coupon Management', icon: Tag }, - { href: '/admin/discount', label: 'Discount Management', icon: Percent }, - { href: '/admin/tax', label: 'Tax Management', icon: Receipt }, - { href: '/admin/order', label: 'Order Management', icon: ShoppingCart }, - { href: '/admin/invoice', label: 'Invoice Management', icon: FileCheck }, - { href: '/admin/review', label: 'Review Management', icon: Star }, - { href: '/admin/kb', label: 'Knowledge Base Management', icon: BookMarked }, - { href: '/admin/notifications', label: 'Notifications', icon: Bell }, - { href: '/admin/support', label: 'Support Management', icon: HeadphonesIcon }, - { href: '/admin/report', label: 'Report Management', icon: BarChart3 }, - { href: '/admin/ledger', label: 'Ledger Management', icon: Receipt }, +// Groups match Figma sidebar order with dividers between sections +const GROUPS: NavItem[][] = [ + // ── Internal management ────────────────────────────────────────────────── + [ + { href: '/admin', label: 'Dashboard', icon: LayoutGrid }, + { href: '/admin/department', label: 'Department Management', icon: Building2 }, + { href: '/admin/designation', label: 'Designation Management', icon: Briefcase }, + { href: '/admin/internal-role-management', label: 'Internal Role Management', icon: ShieldCheck }, + { href: '/admin/employees', label: 'Employee Management', icon: Users }, + ], + // ── External configuration ─────────────────────────────────────────────── + [ + { href: '/admin/runtime-roles', label: 'External Role Management', icon: ShieldCheck, aliasPrefix: '/admin/external-role-management' }, + { href: '/admin/onboarding-management', label: 'External Onboarding Management', icon: FileText, aliasPrefix: '/admin/onboarding-schemas' }, + { href: '/admin/internal-dashboard-management', label: 'Internal Dashboard Management', icon: LayoutDashboard }, + { href: '/admin/external-dashboard-management', label: 'External Dashboard Management', icon: LayoutDashboard, aliasPrefix: '/admin/role-ui-configs' }, + ], + // ── Compliance ─────────────────────────────────────────────────────────── + [ + { href: '/admin/verification-status', label: 'Verification Management', icon: BadgeCheck }, + { href: '/admin/approval', label: 'Approval Management', icon: ClipboardList }, + ], + // ── External users ─────────────────────────────────────────────────────── + [ + { href: '/admin/users', label: 'Users Management', icon: UserRoundSearch }, + { href: '/admin/company', label: 'Company Management', icon: Building2 }, + { href: '/admin/candidate', label: 'Candidate Management', icon: UserCircle }, + { href: '/admin/customer', label: 'Customer Management', icon: UserCircle }, + { href: '/admin/photographer', label: 'Photographer Management', icon: Camera }, + { href: '/admin/makeup-artist', label: 'Makeup Artist Management', icon: Palette }, + { href: '/admin/tutors', label: 'Tutor Management', icon: BookOpen }, + { href: '/admin/developers', label: 'Developer Management', icon: Code2 }, + { href: '/admin/fitness-trainers', label: 'Fitness Trainer Management', icon: Activity }, + { href: '/admin/graphic-designers', label: 'Graphic Designer Management', icon: PenTool }, + { href: '/admin/social-media-managers', label: 'Social Media Management', icon: Megaphone }, + { href: '/admin/video-editors', label: 'Video Editor Management', icon: Film }, + { href: '/admin/catering-services', label: 'Catering Services Management', icon: Utensils }, + ], + // ── Business operations ────────────────────────────────────────────────── + [ + { href: '/admin/jobs', label: 'Jobs Management', icon: BriefcaseBusiness }, + { href: '/admin/leads', label: 'Leads Management', icon: HandHelping }, + { href: '/admin/applications', label: 'Applications Management', icon: ClipboardList }, + { href: '/admin/responses', label: 'Responses Management', icon: MessageSquare }, + { href: '/admin/review', label: 'Review Management', icon: Star }, + ], + // ── Finance ────────────────────────────────────────────────────────────── + [ + { href: '/admin/pricing', label: 'Pricing Management', icon: WalletCards }, + { href: '/admin/credit', label: 'Credit Management', icon: CreditCard }, + { href: '/admin/coupon', label: 'Coupon Management', icon: Tag }, + { href: '/admin/discount', label: 'Discount Management', icon: Percent }, + { href: '/admin/tax', label: 'Tax Management', icon: Receipt }, + { href: '/admin/order', label: 'Order Management', icon: ShoppingCart }, + { href: '/admin/invoice', label: 'Invoice Management', icon: FileCheck }, + { href: '/admin/ledger', label: 'Ledger Management', icon: Receipt }, + ], + // ── Platform operations ────────────────────────────────────────────────── + [ + { href: '/admin/kb', label: 'Knowledge Base Management', icon: BookMarked }, + { href: '/admin/notifications', label: 'Notifications', icon: Bell }, + { href: '/admin/support', label: 'Support Management', icon: HeadphonesIcon }, + { href: '/admin/report', label: 'Report Management', icon: BarChart3 }, + ], ]; export default function AdminSidebar(props: { collapsed: boolean; onToggle: () => void; onNavigate?: () => void; - onLogout?: () => void; + adminName: string; + adminInitials: string; }) { const location = useLocation(); - const active = (item: Item) => { + const isActive = (item: NavItem) => { if (item.href === '/admin') return location.pathname === '/admin'; if (item.aliasPrefix && location.pathname.startsWith(item.aliasPrefix)) return true; return location.pathname === item.href || location.pathname.startsWith(`${item.href}/`); }; return ( -