feat(ui): apply stitch design system — left-accent KPI cards, Intelligence Hub, glassmorphism header

- app.css: Exo 2 font, #0a1d37 navy + #fd6216 orange tokens, #f9f9fd background
- AdminShell: backdrop-blur-xl glassmorphism header, centered search bar, settings icon,
  name + bordered avatar, ambient shadow-[0_4px_24px_rgba(10,29,55,0.04)]
- AdminSidebar: white bg, left orange 4px pill accent on active (before: pseudo),
  bg-slate-50 active state, MANAGEMENT/FINANCE/PLATFORM group labels, user card at bottom
- Dashboard: KPI cards with absolute left-0 accent bar + ambient shadows, big font-black numbers,
  System Activity bar chart, Intelligence Hub dark navy panel with quick-action buttons,
  Pipeline Status cards with progress bars, Control Plane 3-col grid

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar 2026-03-23 23:34:33 +01:00
parent be179a6673
commit df560a91ea
4 changed files with 362 additions and 400 deletions

View file

@ -1,145 +1,85 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Exo+2:wght@400;500;600;700;800&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Exo+2:ital,wght@0,400;0,500;0,600;0,700;0,800;0,900;1,400&display=swap');
@import "tailwindcss";
@theme {
--color-brand-orange: #fd6216;
--color-brand-navy: #050026;
--color-orange-50: #fff4ee;
--color-orange-100: #ffe4d0;
--color-orange-200: #ffc9a0;
--color-orange-500: #fd6216;
--color-orange-600: #e5560f;
--color-primary: #fd6216;
--color-navy: #0a1d37;
/* Sidebar tokens */
--color-sidebar-bg: #ffffff;
--color-sidebar-border: #f1f5f9;
--color-sidebar-text: #64748b;
--color-sidebar-active-text: #0f172a;
/* Brand surfaces */
--color-bg: #f9f9fd;
--color-surface: #ffffff;
--color-surface-low: #f3f3f7;
--color-surface-container: #edeef1;
/* Surface tokens */
--color-surface: #ffffff;
--color-surface-raised: #f8fafc;
--color-border-subtle: #f1f5f9;
--color-border: #e2e8f0;
/* Tailwind orange overrides */
--color-orange-50: #fff4ee;
--color-orange-100: #ffe4d0;
--color-orange-200: #ffc9a0;
--color-orange-500: #fd6216;
--color-orange-600: #e5560f;
--font-family-sans: 'Inter', 'Exo 2', sans-serif;
--font-family-sans: 'Exo 2', sans-serif;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Inter', 'Exo 2', sans-serif;
background: #f8fafc;
color: #0f172a;
font-family: 'Exo 2', sans-serif;
background: #f9f9fd;
color: #0a1d37;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* ===== Scrollbar ===== */
.scrollbar {
scrollbar-width: thin;
scrollbar-color: #c7c7c7 transparent;
}
.scrollbar::-webkit-scrollbar { height: 8px; width: 8px; }
.scrollbar { scrollbar-width: thin; scrollbar-color: #c7c7c7 transparent; }
.scrollbar::-webkit-scrollbar { height: 6px; width: 6px; }
.scrollbar::-webkit-scrollbar-track { background: transparent; }
.scrollbar::-webkit-scrollbar-thumb { background-color: #c7c7c7; border-radius: 9999px; }
.scrollbar::-webkit-scrollbar-thumb:hover { background-color: #9ca3af; }
/* ===== Auth / Login page ===== */
.auth-page {
position: relative;
min-height: 100vh;
display: grid;
place-items: center;
padding: 20px;
overflow: hidden;
position: relative; min-height: 100vh;
display: grid; place-items: center; padding: 20px; overflow: hidden;
}
.auth-bg {
position: absolute;
inset: 0;
position: absolute; inset: 0;
background:
radial-gradient(circle at 20% 20%, rgba(253, 98, 22, 0.24), transparent 42%),
radial-gradient(circle at 80% 10%, rgba(99, 102, 241, 0.16), transparent 34%),
linear-gradient(180deg, #100b2f 0%, #0c0828 52%, #07051d 100%);
}
.auth-layout {
position: relative;
z-index: 1;
width: min(1120px, 100%);
display: grid;
grid-template-columns: 1.08fr 0.92fr;
gap: 24px;
align-items: stretch;
position: relative; z-index: 1; width: min(1120px, 100%);
display: grid; grid-template-columns: 1.08fr 0.92fr; gap: 24px; align-items: stretch;
}
.auth-visual {
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.18);
background: rgba(255, 255, 255, 0.08);
padding: 28px;
box-shadow: 0 24px 72px -34px rgba(0, 0, 0, 0.72);
border-radius: 24px; border: 1px solid rgba(255,255,255,0.18);
background: rgba(255,255,255,0.08); padding: 28px;
box-shadow: 0 24px 72px -34px rgba(0,0,0,0.72);
}
.auth-visual-kicker {
margin: 0;
display: inline-flex;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.1);
color: #ffd7c2;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
padding: 6px 10px;
margin: 0; display: inline-flex; border-radius: 999px;
border: 1px solid rgba(255,255,255,0.2); background: rgba(255,255,255,0.1);
color: #ffd7c2; font-size: 11px; font-weight: 700;
letter-spacing: 0.12em; text-transform: uppercase; padding: 6px 10px;
}
.auth-visual h1 {
margin: 16px 0 0;
font-size: clamp(30px, 3.4vw, 42px);
line-height: 1.1;
color: #fff;
}
.auth-visual p {
margin: 12px 0 0;
color: rgba(255, 255, 255, 0.82);
}
.auth-visual img {
margin-top: 18px;
width: 100%;
height: 280px;
border-radius: 16px;
object-fit: cover;
}
.auth-visual h1 { margin: 16px 0 0; font-size: clamp(30px,3.4vw,42px); line-height: 1.1; color: #fff; }
.auth-visual p { margin: 12px 0 0; color: rgba(255,255,255,0.82); }
.auth-visual img { margin-top: 18px; width: 100%; height: 280px; border-radius: 16px; object-fit: cover; }
.auth-card {
position: relative;
z-index: 1;
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.22);
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 24px 72px -34px rgba(0, 0, 0, 0.72);
padding: 30px;
color: #0f172a;
position: relative; z-index: 1; border-radius: 24px;
border: 1px solid rgba(255,255,255,0.22); background: rgba(255,255,255,0.96);
box-shadow: 0 24px 72px -34px rgba(0,0,0,0.72); padding: 30px; color: #0f172a;
}
.auth-form-card { display: flex; flex-direction: column; }
.auth-logo { height: 52px; width: auto; object-fit: contain; margin: 0 auto; }
.auth-title { margin: 18px 0 0; text-align: center; font-size: 32px; font-weight: 800; color: #0f172a; }
.auth-copy { margin: 12px 0 0; text-align: center; color: #475569; }
.auth-form-grid { margin-top: 24px; }
.auth-switch { display: flex; justify-content: flex-end; margin-top: -2px; }
.auth-form-card { display: flex; flex-direction: column; }
.auth-logo { height: 52px; width: auto; object-fit: contain; margin: 0 auto; }
.auth-title { margin: 18px 0 0; text-align: center; font-size: 32px; font-weight: 800; color: #0f172a; }
.auth-copy { margin: 12px 0 0; text-align: center; color: #475569; }
.auth-form-grid { margin-top: 24px; }
.auth-switch { display: flex; justify-content: flex-end; margin-top: -2px; }
.auth-switch.split { justify-content: space-between; }
.auth-link-btn {
border: 0; padding: 0; background: transparent;
color: #fd6216; font-size: 13px; font-weight: 600; cursor: pointer;

View file

@ -191,74 +191,78 @@ export default function AdminShell(props: { children: JSX.Element }) {
const initials = () => adminName().charAt(0).toUpperCase() || 'A';
return (
<div class="min-h-screen bg-[#f8fafc]">
<div class="min-h-screen bg-[#f9f9fd]">
{/* ── Fixed Header ── */}
<header class="fixed top-0 z-50 flex h-14 w-full items-center justify-between border-b border-slate-200/80 bg-white/95 px-5 backdrop-blur-md">
{/* Left: logo + separator + page title */}
<div class="flex items-center gap-4">
<div class="flex h-8 items-center">
<img src="/nxtgauge-logo.png" alt="NXTGAUGE" class="h-7 w-auto object-contain" />
</div>
<div class="h-5 w-px bg-slate-200" />
<span class="text-sm font-medium text-slate-500">{pageTitle()}</span>
<header class="fixed top-0 z-50 flex h-16 w-full items-center justify-between bg-white/80 px-6 shadow-[0_4px_24px_rgba(10,29,55,0.04)] backdrop-blur-xl">
{/* Left: logo */}
<div class="flex w-64 shrink-0 items-center">
<img src="/nxtgauge-logo.png" alt="NXTGAUGE" class="h-8 w-auto object-contain" />
</div>
{/* Right: notifications + avatar + logout */}
<div class="flex items-center gap-1">
{/* Center: search */}
<div class="relative mx-8 w-full max-w-xl">
<svg class="absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<circle cx="11" cy="11" r="8"/><path stroke-linecap="round" d="M21 21l-4.35-4.35"/>
</svg>
<input
type="text"
placeholder="Search roles, users, or reports…"
class="w-full rounded-full border-0 bg-slate-50 py-2 pl-10 pr-4 text-sm text-[#0a1d37] placeholder-slate-400 outline-none ring-0 transition-all focus:bg-white focus:ring-2 focus:ring-[#fd6216]/20"
/>
</div>
{/* Right: actions + avatar */}
<div class="flex items-center gap-3">
{/* Notification bell */}
<button
type="button"
aria-label="Notifications"
class="relative flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition-all hover:bg-slate-100 hover:text-slate-600"
<button type="button" aria-label="Notifications"
class="relative flex h-9 w-9 items-center justify-center rounded-full text-[#0a1d37]/60 transition-colors hover:bg-slate-50"
>
<svg class="h-[17px] w-[17px]" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
<path stroke-linecap="round" stroke-linejoin="round" d="M13.73 21a2 2 0 0 1-3.46 0" />
</svg>
{/* Badge */}
<span class="absolute right-1.5 top-1.5 flex h-[7px] w-[7px] items-center justify-center rounded-full bg-orange-500 ring-2 ring-white" />
<span class="absolute right-2 top-2 h-2 w-2 rounded-full border-2 border-white bg-[#fd6216]" />
</button>
<div class="mx-2 h-5 w-px bg-slate-200" />
{/* Settings */}
<button type="button" aria-label="Settings"
class="flex h-9 w-9 items-center justify-center rounded-full text-[#0a1d37]/60 transition-colors hover:bg-slate-50"
>
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="3"/>
<path stroke-linecap="round" d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>
</button>
{/* Avatar + name */}
<div class="flex items-center gap-1.5">
<button
type="button"
class="flex items-center gap-2.5 rounded-lg px-2 py-1.5 transition-colors hover:bg-slate-100"
>
<div class="flex h-7 w-7 items-center justify-center rounded-full bg-gradient-to-br from-orange-400 to-orange-600 text-[11px] font-bold text-white shadow-sm">
{initials()}
</div>
<div class="flex flex-col items-start">
<span class="text-[12px] font-semibold leading-none text-slate-700">{adminName()}</span>
<span class="mt-0.5 text-[10px] leading-none text-slate-400">{adminRole()}</span>
</div>
</button>
<div class="h-8 w-px bg-slate-200" />
{/* Logout */}
<button
type="button"
onClick={onLogout}
aria-label="Logout"
class="flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 transition-all hover:bg-red-50 hover:text-red-500"
>
<svg class="h-[17px] w-[17px]" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
</button>
{/* Name + avatar */}
<div class="flex items-center gap-2.5">
<span class="text-sm font-bold text-[#0a1d37]">{adminName()}</span>
<div class="flex h-8 w-8 items-center justify-center rounded-full border-2 border-[#fd6216] bg-[#fd6216] text-xs font-black text-white">
{initials()}
</div>
</div>
{/* Logout */}
<button type="button" onClick={onLogout} aria-label="Logout"
class="flex h-9 w-9 items-center justify-center rounded-full text-[#0a1d37]/60 transition-colors hover:bg-red-50 hover:text-red-500"
>
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
</button>
</div>
</header>
{/* ── Body: sidebar + main (fixed, below header) ── */}
{checkedSession() ? (
<div class="fixed inset-0 top-14 grid grid-cols-[auto_1fr]">
<div class="fixed inset-0 top-16 grid grid-cols-[auto_1fr]">
{/* Sidebar */}
<AdminSidebar />
{/* Main content */}
<main class="scrollbar min-w-0 overflow-y-auto bg-[#f8fafc] p-6">
<main class="scrollbar min-w-0 overflow-y-auto bg-[#f9f9fd] p-8">
{/* Sub-tabs (shown for multi-tab sections) */}
{tabs().length > 0 && (
<div class="mb-6 flex gap-1 border-b border-slate-200">
@ -281,8 +285,8 @@ export default function AdminShell(props: { children: JSX.Element }) {
</div>
) : (
/* Session check loading state */
<div class="fixed inset-0 top-14 grid grid-cols-[auto_1fr]">
<div class="w-60 border-r border-slate-100 bg-white" />
<div class="fixed inset-0 top-16 grid grid-cols-[auto_1fr]">
<div class="w-64 border-r border-slate-100 bg-white" />
<main class="flex items-center justify-center bg-gray-50">
<p class="text-sm text-gray-400">Checking session</p>
</main>

View file

@ -1,71 +1,59 @@
import { A, useLocation } from '@solidjs/router';
import { createSignal } from 'solid-js';
type LinkItem = {
href: string;
label: string;
icon: string;
aliasPrefix?: string;
group?: string;
group: string;
};
const links: LinkItem[] = [
{ href: '/admin', label: 'Dashboard', icon: 'dashboard.svg', group: 'Overview' },
{ href: '/admin', label: 'Dashboard', icon: 'dashboard.svg', group: '__top__' },
{ href: '/admin/department', label: 'Department', icon: 'department.svg', group: 'Organization' },
{ href: '/admin/designation', label: 'Designation', icon: 'designation.svg', group: 'Organization' },
{ href: '/admin/employees', label: 'Employees', icon: 'users.svg', group: 'Organization' },
{ href: '/admin/roles', label: 'Internal Role Management', icon: 'role.svg', group: 'Management' },
{ href: '/admin/runtime-roles', label: 'External Role Management', icon: 'role.svg', group: 'Management' },
{ href: '/admin/onboarding-schemas', label: 'External Onboarding', icon: 'reviews.svg', group: 'Management' },
{ href: '/admin/internal-dashboard-management',label: 'Internal Dashboard Mgmt.', icon: 'dashboard.svg', group: 'Management' },
{ href: '/admin/external-dashboard-management',label: 'External Dashboard Mgmt.', icon: 'dashboard.svg', aliasPrefix: '/admin/role-ui-configs', group: 'Management' },
{ href: '/admin/approval', label: 'Approval Management', icon: 'approval.svg', group: 'Management' },
{ href: '/admin/department', label: 'Department Management', icon: 'department.svg', group: 'Management' },
{ href: '/admin/designation', label: 'Designation Management', icon: 'designation.svg',group: 'Management' },
{ href: '/admin/employees', label: 'Employee Management', icon: 'users.svg', group: 'Management' },
{ href: '/admin/users', label: 'Users Management', icon: 'users.svg', group: 'Management' },
{ href: '/admin/company', label: 'Company Management', icon: 'company.svg', group: 'Management' },
{ href: '/admin/candidate', label: 'Candidate Management', icon: 'candidate.svg', group: 'Management' },
{ href: '/admin/customer', label: 'Customer Management', icon: 'users.svg', group: 'Management' },
{ href: '/admin/photographer', label: 'Photographer Management', icon: 'photographer.svg',group: 'Management' },
{ href: '/admin/makeup-artist', label: 'Makeup Artist Management', icon: 'makeup-artist.svg',group: 'Management'},
{ href: '/admin/tutors', label: 'Tutors Management', icon: 'tutor.svg', group: 'Management' },
{ href: '/admin/developers', label: 'Developers Management', icon: 'developers.svg', group: 'Management' },
{ href: '/admin/video-editors', label: 'Video Editor Management', icon: 'developers.svg', group: 'Management' },
{ href: '/admin/fitness-trainers', label: 'Fitness Trainer Management', icon: 'tutor.svg', group: 'Management' },
{ href: '/admin/catering-services', label: 'Catering Services Mgmt.', icon: 'company.svg', group: 'Management' },
{ href: '/admin/graphic-designers', label: 'Graphics Designer Mgmt.', icon: 'developers.svg', group: 'Management' },
{ href: '/admin/social-media-managers', label: 'Social Media Mgr. Mgmt.', icon: 'developers.svg', group: 'Management' },
{ href: '/admin/jobs', label: 'Jobs Management', icon: 'jobs.svg', group: 'Management' },
{ href: '/admin/leads', label: 'Leads Management', icon: 'leads.svg', group: 'Management' },
{ href: '/admin/roles', label: 'Internal Roles', icon: 'role.svg', group: 'Access & Roles' },
{ href: '/admin/runtime-roles', label: 'External Roles', icon: 'role.svg', group: 'Access & Roles' },
{ href: '/admin/onboarding-schemas', label: 'Onboarding Flows', icon: 'reviews.svg', group: 'Access & Roles' },
{ href: '/admin/pricing', label: 'Pricing Management', icon: 'pricing.svg', group: 'Finance' },
{ href: '/admin/credit', label: 'Credit Management', icon: 'credits.svg', group: 'Finance' },
{ href: '/admin/coupon', label: 'Coupon Management', icon: 'coupon.svg', group: 'Finance' },
{ href: '/admin/discount', label: 'Discount Management', icon: 'discount.svg', group: 'Finance' },
{ href: '/admin/tax', label: 'Tax Management', icon: 'tax.svg', group: 'Finance' },
{ href: '/admin/order', label: 'Order Management', icon: 'order.svg', group: 'Finance' },
{ href: '/admin/invoice', label: 'Invoice Management', icon: 'invoice.svg', group: 'Finance' },
{ href: '/admin/ledger', label: 'Ledger Management', icon: 'ledger.svg', group: 'Finance' },
{ href: '/admin/internal-dashboard-management', label: 'Internal Dashboards', icon: 'dashboard.svg', group: 'Dashboards' },
{ href: '/admin/external-dashboard-management', label: 'External Dashboards', icon: 'dashboard.svg', aliasPrefix: '/admin/role-ui-configs', group: 'Dashboards' },
{ href: '/admin/users', label: 'Users', icon: 'users.svg', group: 'People' },
{ href: '/admin/company', label: 'Companies', icon: 'company.svg', group: 'People' },
{ href: '/admin/candidate', label: 'Candidates', icon: 'candidate.svg', group: 'People' },
{ href: '/admin/customer', label: 'Customers', icon: 'users.svg', group: 'People' },
{ href: '/admin/photographer', label: 'Photographers', icon: 'photographer.svg', group: 'People' },
{ href: '/admin/makeup-artist', label: 'Makeup Artists', icon: 'makeup-artist.svg', group: 'People' },
{ href: '/admin/tutors', label: 'Tutors', icon: 'tutor.svg', group: 'People' },
{ href: '/admin/developers', label: 'Developers', icon: 'developers.svg', group: 'People' },
{ href: '/admin/video-editors', label: 'Video Editors', icon: 'developers.svg', group: 'People' },
{ href: '/admin/fitness-trainers', label: 'Fitness Trainers', icon: 'tutor.svg', group: 'People' },
{ href: '/admin/catering-services', label: 'Catering Services', icon: 'company.svg', group: 'People' },
{ href: '/admin/graphic-designers', label: 'Graphic Designers', icon: 'developers.svg', group: 'People' },
{ href: '/admin/social-media-managers', label: 'Social Media Mgr.', icon: 'developers.svg', group: 'People' },
{ href: '/admin/jobs', label: 'Jobs', icon: 'jobs.svg', group: 'Content' },
{ href: '/admin/leads', label: 'Leads', icon: 'leads.svg', group: 'Content' },
{ href: '/admin/review', label: 'Reviews', icon: 'reviews.svg', group: 'Content' },
{ href: '/admin/kb', label: 'Knowledge Base', icon: 'reviews.svg', group: 'Content' },
{ href: '/admin/notifications', label: 'Notifications', icon: 'reviews.svg', group: 'Content' },
{ href: '/admin/pricing', label: 'Pricing', icon: 'pricing.svg', group: 'Finance' },
{ href: '/admin/credit', label: 'Credits', icon: 'credits.svg', group: 'Finance' },
{ href: '/admin/coupon', label: 'Coupons', icon: 'coupon.svg', group: 'Finance' },
{ href: '/admin/discount', label: 'Discounts', icon: 'discount.svg', group: 'Finance' },
{ href: '/admin/tax', label: 'Tax', icon: 'tax.svg', group: 'Finance' },
{ href: '/admin/order', label: 'Orders', icon: 'order.svg', group: 'Finance' },
{ href: '/admin/invoice', label: 'Invoices', icon: 'invoice.svg', group: 'Finance' },
{ href: '/admin/ledger', label: 'Ledger', icon: 'ledger.svg', group: 'Finance' },
{ href: '/admin/approval', label: 'Approvals', icon: 'approval.svg', group: 'Operations' },
{ href: '/admin/support', label: 'Support', icon: 'support.svg', group: 'Operations' },
{ href: '/admin/report', label: 'Reports', icon: 'report.svg', group: 'Operations' },
{ href: '/admin/review', label: 'Review Management', icon: 'reviews.svg', group: 'Platform' },
{ href: '/admin/support', label: 'Support Management', icon: 'support.svg', group: 'Platform' },
{ href: '/admin/kb', label: 'Knowledge Base Mgmt.', icon: 'reviews.svg', group: 'Platform' },
{ href: '/admin/notifications', label: 'Notifications', icon: 'reviews.svg', group: 'Platform' },
{ href: '/admin/report', label: 'Report Management', icon: 'report.svg', group: 'Platform' },
];
// Build ordered group list preserving first appearance
const GROUP_ORDER: string[] = [];
for (const item of links) {
if (item.group && !GROUP_ORDER.includes(item.group)) GROUP_ORDER.push(item.group);
}
export default function AdminSidebar() {
const location = useLocation();
const [collapsed, setCollapsed] = createSignal(false);
const isActive = (href: string, aliasPrefix?: string) => {
if (href === '/admin') return location.pathname === '/admin';
@ -73,91 +61,73 @@ export default function AdminSidebar() {
return location.pathname === href || location.pathname.startsWith(`${href}/`);
};
const topLinks = links.filter(l => l.group === '__top__');
const mgmtLinks = links.filter(l => l.group === 'Management');
const finLinks = links.filter(l => l.group === 'Finance');
const platLinks = links.filter(l => l.group === 'Platform');
const NavItem = (item: LinkItem) => {
const active = isActive(item.href, item.aliasPrefix);
return (
<A
href={item.href}
activeClass=""
inactiveClass=""
title={item.label}
class={`relative flex items-center gap-3 rounded-xl py-2.5 pl-4 pr-3 text-sm font-semibold tracking-wide transition-all duration-150 ${
active
? 'bg-slate-50 text-[#0a1d37] before:absolute before:left-0 before:top-1/2 before:h-6 before:-translate-y-1/2 before:w-1 before:rounded-r-full before:bg-[#fd6216] before:content-[\'\']'
: 'text-[#0a1d37]/60 hover:bg-slate-50 hover:text-[#0a1d37]'
}`}
>
<img
src={`/sidebar-icons/${item.icon}`}
alt=""
class="h-[18px] w-[18px] shrink-0"
style={active ? 'opacity:0.9' : 'opacity:0.4'}
/>
<span class="truncate">{item.label}</span>
</A>
);
};
const GroupLabel = (label: string) => (
<div class="px-4 pb-2 pt-4">
<p class="text-[10px] font-bold uppercase tracking-widest text-slate-400">{label}</p>
</div>
);
return (
<aside
class={`flex h-full flex-col border-r border-slate-100 bg-white pb-3 pt-3 transition-all duration-300 ${
collapsed() ? 'w-[60px]' : 'w-[220px]'
}`}
>
{/* Collapse toggle */}
<div class={`mb-2 flex px-3 ${collapsed() ? 'justify-center' : 'justify-end'}`}>
<button
type="button"
onClick={() => setCollapsed((v) => !v)}
aria-label={collapsed() ? 'Expand sidebar' : 'Collapse sidebar'}
class="flex h-7 w-7 items-center justify-center rounded-md text-slate-400 transition hover:bg-slate-100 hover:text-slate-600"
>
<svg
class={`h-3.5 w-3.5 transition-transform ${collapsed() ? 'rotate-180' : ''}`}
fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
</svg>
</button>
<aside class="flex h-full w-64 flex-col border-r border-slate-100 bg-white py-5">
{/* Top (Dashboard) */}
<nav class="px-3">
{topLinks.map(NavItem)}
</nav>
{/* Scrollable nav groups */}
<div class="scrollbar mt-1 min-h-0 flex-1 overflow-y-auto px-3">
{GroupLabel('Management')}
{mgmtLinks.map(NavItem)}
{GroupLabel('Finance')}
{finLinks.map(NavItem)}
{GroupLabel('Platform')}
{platLinks.map(NavItem)}
</div>
{/* Nav items grouped */}
<nav class="scrollbar min-h-0 flex-1 overflow-y-auto px-2">
{GROUP_ORDER.map((group) => {
const groupItems = links.filter((l) => l.group === group);
return (
<div class="mb-3">
{/* Group label — hidden when collapsed */}
{!collapsed() && (
<p class="mb-1 px-2 text-[10px] font-semibold uppercase tracking-widest text-slate-400">
{group}
</p>
)}
<div class="space-y-0.5">
{groupItems.map((item) => {
const active = isActive(item.href, item.aliasPrefix);
return (
<A
href={item.href}
activeClass=""
inactiveClass=""
title={collapsed() ? item.label : undefined}
class={`group relative flex items-center gap-2.5 rounded-lg px-2 py-2 text-[13px] font-medium leading-none transition-all ${
collapsed() ? 'justify-center' : ''
} ${
active
? 'bg-gradient-to-r from-orange-500/[0.12] to-orange-500/[0.04] text-orange-600'
: 'text-slate-500 hover:bg-slate-50 hover:text-slate-800'
}`}
>
{/* Active left accent */}
{active && !collapsed() && (
<span class="absolute bottom-1.5 left-0 top-1.5 w-[2.5px] rounded-r-full bg-orange-500" />
)}
{/* Icon */}
<img
src={`/sidebar-icons/${item.icon}`}
alt=""
class="h-[16px] w-[16px] shrink-0"
style={active
? 'filter: invert(42%) sepia(78%) saturate(1200%) hue-rotate(360deg) brightness(95%) contrast(95%); opacity:1'
: 'opacity:0.4'
}
/>
{/* Label */}
{!collapsed() && (
<span class="truncate">{item.label}</span>
)}
{/* Active dot (collapsed) */}
{collapsed() && active && (
<span class="absolute -right-0.5 top-1.5 h-1.5 w-1.5 rounded-full bg-orange-500" />
)}
</A>
);
})}
</div>
</div>
);
})}
</nav>
{/* User card at bottom */}
<div class="mt-2 border-t border-slate-100 px-4 pt-4">
<div class="flex items-center gap-3 rounded-xl bg-slate-50 p-3">
<div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[#fd6216] text-sm font-black text-white">
A
</div>
<div class="min-w-0 flex-1 overflow-hidden">
<p class="truncate text-xs font-bold text-[#0a1d37]">Admin User</p>
<p class="truncate text-[10px] text-slate-500">master_admin@nxtgauge.io</p>
</div>
</div>
</div>
</aside>
);
}

View file

@ -3,167 +3,215 @@ import AdminShell from '~/components/AdminShell';
import {
Users, Building2, Briefcase, Clock, Ticket, Receipt,
Package, Megaphone, Shield, UserCog, FormInput, LayoutDashboard,
BadgeCheck, ArrowUpRight, Activity,
BadgeCheck, ArrowRight,
} from 'lucide-solid';
import type { Component } from 'solid-js';
type KpiCard = {
label: string;
value: string;
badge?: string;
badgeColor?: string;
href: string;
icon: Component<any>;
color: string; // tailwind color name (e.g. 'blue')
};
type ControlLink = {
label: string;
href: string;
desc: string;
icon: Component<any>;
iconBg: string;
iconFg: string;
accentColor: string; // full tailwind class for the left bar bg
chipBg: string;
chipFg: string;
};
const KPI: KpiCard[] = [
{ label: 'Total Users', value: '—', href: '/admin/users', icon: Users, color: 'violet' },
{ label: 'Companies', value: '—', href: '/admin/company', icon: Building2, color: 'blue' },
{ label: 'Open Jobs', value: '—', href: '/admin/jobs', icon: Briefcase, color: 'amber' },
{ label: 'Pending Approvals', value: '—', href: '/admin/approval', icon: Clock, color: 'orange' },
{ label: 'Open Tickets', value: '—', href: '/admin/support', icon: Ticket, color: 'rose' },
{ label: 'Invoices', value: '—', href: '/admin/invoice', icon: Receipt, color: 'teal' },
{ label: 'Active Orders', value: '—', href: '/admin/order', icon: Package, color: 'sky' },
{ label: 'Active Leads', value: '—', href: '/admin/leads', icon: Megaphone, color: 'pink' },
{
label: 'Total Users', value: '—', badge: '+0%', badgeColor: 'text-emerald-600 bg-emerald-50',
href: '/admin/users', icon: Users,
accentColor: 'bg-[#fd6216]', chipBg: 'bg-[#fd6216]/5', chipFg: 'text-[#fd6216]',
},
{
label: 'Total Revenue', value: '—', badge: 'Weekly', badgeColor: 'text-[#0a1d37] bg-slate-100',
href: '/admin/invoice', icon: Receipt,
accentColor: 'bg-[#0a1d37]', chipBg: 'bg-[#0a1d37]/5', chipFg: 'text-[#0a1d37]',
},
{
label: 'Active Roles', value: '—', badge: 'Live', badgeColor: 'text-amber-600 bg-amber-50',
href: '/admin/runtime-roles', icon: Briefcase,
accentColor: 'bg-[#fd6216]', chipBg: 'bg-amber-50', chipFg: 'text-amber-600',
},
{
label: 'Pending Approvals', value: '—', badge: 'Action Required', badgeColor: 'text-white bg-red-500',
href: '/admin/approval', icon: Clock,
accentColor: 'bg-[#fd6216]', chipBg: 'bg-red-50', chipFg: 'text-red-600',
},
];
// Tailwind doesn't tree-shake dynamic class names, so we map them explicitly
const colorMap: Record<string, { chip: string; icon: string; value: string }> = {
violet: { chip: 'bg-violet-100', icon: 'text-violet-600', value: 'text-violet-700' },
blue: { chip: 'bg-blue-100', icon: 'text-blue-600', value: 'text-blue-700' },
amber: { chip: 'bg-amber-100', icon: 'text-amber-600', value: 'text-amber-700' },
orange: { chip: 'bg-orange-100', icon: 'text-orange-600', value: 'text-orange-700' },
rose: { chip: 'bg-rose-100', icon: 'text-rose-600', value: 'text-rose-700' },
teal: { chip: 'bg-teal-100', icon: 'text-teal-600', value: 'text-teal-700' },
sky: { chip: 'bg-sky-100', icon: 'text-sky-600', value: 'text-sky-700' },
pink: { chip: 'bg-pink-100', icon: 'text-pink-600', value: 'text-pink-700' },
};
// Activity chart data (placeholder heights)
const ACTIVITY = [
{ day: 'MON', h: 40 }, { day: 'TUE', h: 65 }, { day: 'WED', h: 95 },
{ day: 'THU', h: 70 }, { day: 'FRI', h: 55 }, { day: 'SAT', h: 30 }, { day: 'SUN', h: 45 },
];
const CONTROLS: ControlLink[] = [
{
label: 'Internal Roles',
href: '/admin/roles',
desc: 'Permissions and access levels for internal staff.',
icon: Shield,
iconBg: 'bg-blue-50',
iconFg: 'text-blue-600',
},
{
label: 'External Roles',
href: '/admin/runtime-roles',
desc: 'Modules, credits, and capabilities per external role.',
icon: UserCog,
iconBg: 'bg-violet-50',
iconFg: 'text-violet-600',
},
{
label: 'Onboarding Flows',
href: '/admin/onboarding-schemas',
desc: 'Schema-driven onboarding forms per external role.',
icon: FormInput,
iconBg: 'bg-amber-50',
iconFg: 'text-amber-600',
},
{
label: 'External Dashboards',
href: '/admin/external-dashboard-management',
desc: 'Sidebar, widgets, and runtimeConfig per external role.',
icon: LayoutDashboard,
iconBg: 'bg-teal-50',
iconFg: 'text-teal-600',
},
{
label: 'Internal Dashboards',
href: '/admin/internal-dashboard-management',
desc: 'Home widgets and KPI panels for internal staff.',
icon: LayoutDashboard,
iconBg: 'bg-orange-50',
iconFg: 'text-orange-600',
},
{
label: 'Approval Queue',
href: '/admin/approval',
desc: 'Review, approve, or reject pending action requests.',
icon: BadgeCheck,
iconBg: 'bg-rose-50',
iconFg: 'text-rose-600',
},
const QUICK_ACTIONS = [
{ label: 'Create New Role', href: '/admin/roles/create', icon: Shield },
{ label: 'Configure Dashboard', href: '/admin/external-dashboard-management', icon: LayoutDashboard },
{ label: 'Add Employee', href: '/admin/employees', icon: Users },
];
const PIPELINE = [
{ name: 'Candidate Roles', type: 'External Role', status: 'Active', statusBg: 'bg-emerald-50 text-emerald-600', progress: 85, color: 'bg-[#fd6216]' },
{ name: 'Onboarding Flows', type: 'Schema Builder', status: 'Pending', statusBg: 'bg-amber-50 text-amber-600', progress: 42, color: 'bg-amber-400' },
{ name: 'Dashboard Config', type: 'UI Config', status: 'Draft', statusBg: 'bg-slate-100 text-slate-500', progress: 12, color: 'bg-slate-400' },
];
const CONTROL: Array<{ label: string; href: string; desc: string; icon: Component<any>; iconBg: string; iconFg: string }> = [
{ label: 'Internal Roles', href: '/admin/roles', desc: 'Permissions & access levels for internal staff.', icon: Shield, iconBg: 'bg-blue-50', iconFg: 'text-blue-600' },
{ label: 'External Roles', href: '/admin/runtime-roles', desc: 'Modules, credits & capabilities per external role.', icon: UserCog, iconBg: 'bg-violet-50', iconFg: 'text-violet-600' },
{ label: 'Onboarding Flows', href: '/admin/onboarding-schemas', desc: 'Schema-driven onboarding forms per external role.', icon: FormInput, iconBg: 'bg-amber-50', iconFg: 'text-amber-600' },
{ label: 'External Dashboards', href: '/admin/external-dashboard-management', desc: 'Sidebar, widgets & runtimeConfig per role.', icon: LayoutDashboard, iconBg: 'bg-teal-50', iconFg: 'text-teal-600' },
{ label: 'Internal Dashboards', href: '/admin/internal-dashboard-management', desc: 'Home widgets & KPI panels for internal staff.', icon: LayoutDashboard, iconBg: 'bg-orange-50', iconFg: 'text-orange-600' },
{ label: 'Approval Queue', href: '/admin/approval', desc: 'Review, approve or reject pending action requests.', icon: BadgeCheck, iconBg: 'bg-rose-50', iconFg: 'text-rose-600' },
];
export default function AdminDashboard() {
return (
<AdminShell>
<div class="mx-auto max-w-6xl space-y-7">
<div class="space-y-8">
{/* ── Page header ── */}
<div class="flex items-start justify-between">
<div>
<h1 class="text-xl font-bold tracking-tight text-slate-900">Platform Overview</h1>
<p class="mt-0.5 text-sm text-slate-400">Real-time snapshot of the Nxtgauge platform.</p>
</div>
<div class="flex items-center gap-1.5 rounded-lg border border-emerald-200 bg-emerald-50 px-2.5 py-1.5">
<Activity class="h-3.5 w-3.5 text-emerald-500" />
<span class="text-[11px] font-semibold text-emerald-600">Live</span>
</div>
</div>
{/* ── KPI grid ── */}
<div class="grid grid-cols-2 gap-3 lg:grid-cols-4">
{/* ── KPI Cards ── */}
<div class="grid grid-cols-2 gap-6 lg:grid-cols-4">
{KPI.map((kpi) => {
const Icon = kpi.icon;
const c = colorMap[kpi.color];
return (
<A
href={kpi.href}
class="group flex flex-col gap-3 rounded-xl border border-slate-100 bg-white p-4 shadow-sm transition-all duration-200 hover:-translate-y-px hover:border-slate-200 hover:shadow-md"
class="relative overflow-hidden rounded-2xl bg-white p-6 shadow-[0_8px_32px_rgba(10,29,55,0.04)] transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_12px_40px_rgba(10,29,55,0.08)]"
>
<div class="flex items-center justify-between">
<div class={`flex h-8 w-8 items-center justify-center rounded-lg ${c.chip}`}>
<Icon class={`h-4 w-4 ${c.icon}`} />
{/* Left accent bar */}
<div class={`absolute left-0 top-0 h-full w-1.5 ${kpi.accentColor}`} />
<div class="flex items-start justify-between">
<div class={`flex h-12 w-12 items-center justify-center rounded-xl ${kpi.chipBg}`}>
<Icon class={`h-6 w-6 ${kpi.chipFg}`} />
</div>
<ArrowUpRight class="h-3.5 w-3.5 text-slate-200 transition-colors group-hover:text-slate-400" />
</div>
<div>
<p class="text-2xl font-bold tabular-nums tracking-tight text-slate-900">{kpi.value}</p>
<p class="mt-0.5 text-xs font-medium text-slate-400">{kpi.label}</p>
{kpi.badge && (
<span class={`rounded-full px-2 py-1 text-[11px] font-bold ${kpi.badgeColor}`}>
{kpi.badge}
</span>
)}
</div>
<h3 class="mt-4 text-[10px] font-bold uppercase tracking-widest text-slate-400">
{kpi.label}
</h3>
<p class="mt-1 text-3xl font-black tracking-tight text-[#0a1d37]">
{kpi.value}
</p>
</A>
);
})}
</div>
{/* ── Activity Chart + Intelligence Hub ── */}
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
{/* Activity Chart */}
<div class="lg:col-span-2 rounded-3xl bg-white p-8 shadow-[0_8px_32px_rgba(10,29,55,0.04)]">
<div class="mb-8 flex items-end justify-between">
<div>
<h2 class="text-xl font-black tracking-tight text-[#0a1d37]">System Activity</h2>
<p class="mt-0.5 text-sm text-slate-500">Platform traffic & Tracecoin velocity</p>
</div>
<select class="rounded-lg border-0 bg-slate-50 py-2 pl-3 pr-8 text-xs font-bold text-[#0a1d37] focus:ring-0">
<option>Last 7 Days</option>
<option>Last 30 Days</option>
</select>
</div>
<div class="flex h-52 items-end justify-between gap-3">
{ACTIVITY.map((d) => (
<div class="group flex flex-1 flex-col items-center gap-2">
<div
class={`w-full rounded-t-xl transition-all duration-200 ${d.day === 'WED' ? 'bg-[#fd6216] shadow-lg shadow-[#fd6216]/20' : 'bg-slate-100 group-hover:bg-[#0a1d37]'}`}
style={`height: ${d.h}%`}
/>
<span class={`text-[10px] font-bold ${d.day === 'WED' ? 'text-[#0a1d37]' : 'text-slate-400'}`}>
{d.day}
</span>
</div>
))}
</div>
</div>
{/* Intelligence Hub + System Health */}
<div class="space-y-4">
{/* Intelligence Hub */}
<div class="relative overflow-hidden rounded-3xl bg-[#0a1d37] p-6 text-white">
<div class="absolute -right-4 -top-4 h-24 w-24 rounded-full bg-white/5 blur-2xl" />
<h2 class="mb-4 text-lg font-black">Intelligence Hub</h2>
<div class="space-y-2.5">
{QUICK_ACTIONS.map((a) => {
const Icon = a.icon;
return (
<A
href={a.href}
class="group flex w-full items-center justify-between rounded-2xl bg-white/10 p-3.5 transition-all hover:bg-white/15"
>
<span class="text-sm font-semibold">{a.label}</span>
<Icon class="h-4 w-4 text-[#fd6216] transition-transform group-hover:translate-x-0.5" />
</A>
);
})}
</div>
</div>
{/* Pipeline Status */}
<div class="rounded-3xl bg-white p-6 shadow-[0_8px_32px_rgba(10,29,55,0.04)]">
<h2 class="mb-4 text-[11px] font-black uppercase tracking-widest text-[#0a1d37]">
Pipeline Status
</h2>
<div class="space-y-4">
{PIPELINE.map((p) => (
<div class="space-y-1.5">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-bold text-[#0a1d37]">{p.name}</p>
<p class="text-[10px] text-slate-400">{p.type}</p>
</div>
<span class={`rounded-full px-2 py-0.5 text-[10px] font-black uppercase ${p.statusBg}`}>
{p.status}
</span>
</div>
<div class="h-1.5 w-full overflow-hidden rounded-full bg-slate-100">
<div class={`h-full rounded-full ${p.color}`} style={`width: ${p.progress}%`} />
</div>
<p class="text-right text-[10px] font-bold text-slate-400">{p.progress}%</p>
</div>
))}
</div>
</div>
</div>
</div>
{/* ── Control Plane ── */}
<div>
<div class="mb-3 flex items-center justify-between">
<h2 class="text-sm font-semibold text-slate-700">Control Plane</h2>
<span class="rounded-full border border-slate-200 bg-white px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-slate-400">
{CONTROLS.length} modules
<div class="mb-5 flex items-center justify-between">
<h2 class="text-xl font-black tracking-tight text-[#0a1d37]">Control Plane</h2>
<span class="rounded-full border border-slate-200 bg-white px-3 py-1 text-[11px] font-bold uppercase tracking-widest text-slate-400 shadow-sm">
{CONTROL.length} modules
</span>
</div>
<div class="grid grid-cols-1 gap-2.5 sm:grid-cols-2 lg:grid-cols-3">
{CONTROLS.map((item) => {
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{CONTROL.map((item) => {
const Icon = item.icon;
return (
<A
href={item.href}
class="group flex items-start gap-3.5 rounded-xl border border-slate-100 bg-white p-4 shadow-sm transition-all duration-200 hover:-translate-y-px hover:border-orange-100 hover:shadow-md"
class="group flex items-start gap-4 rounded-2xl border border-slate-100 bg-white p-5 shadow-[0_8px_32px_rgba(10,29,55,0.04)] transition-all duration-200 hover:-translate-y-0.5 hover:border-[#fd6216]/20 hover:shadow-[0_12px_40px_rgba(10,29,55,0.08)]"
>
<div class={`mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${item.iconBg}`}>
<div class={`mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-xl ${item.iconBg}`}>
<Icon class={`h-4 w-4 ${item.iconFg}`} />
</div>
<div class="min-w-0 flex-1">
<p class="text-[13px] font-semibold text-slate-800 transition-colors group-hover:text-[#fd6216]">{item.label}</p>
<p class="text-sm font-bold text-[#0a1d37] transition-colors group-hover:text-[#fd6216]">{item.label}</p>
<p class="mt-0.5 text-[11px] leading-relaxed text-slate-400">{item.desc}</p>
</div>
<ArrowUpRight class="mt-0.5 h-3.5 w-3.5 shrink-0 text-slate-200 transition-all group-hover:text-orange-400" />
<ArrowRight class="mt-0.5 h-4 w-4 shrink-0 text-slate-200 transition-all group-hover:translate-x-0.5 group-hover:text-[#fd6216]" />
</A>
);
})}