feat(ui): premium redesign — Inter font, grouped sidebar, gradient active states

- Switch to Inter font for sharper admin feel
- Sidebar: grouped nav sections (Overview/Organization/Access & Roles/etc),
  orange gradient active fade, tighter spacing, smaller 220px width
- Header: slimmer h-14, backdrop-blur, logo+separator+title layout,
  gradient avatar, notification dot with ring-2 badge
- Dashboard: smaller rounded-xl cards, tabular-nums on KPI values,
  ArrowUpRight hover indicators, refined Control Plane grid
- app.css: sidebar/surface CSS design tokens, orange palette updates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar 2026-03-23 23:13:04 +01:00
parent a272276055
commit be179a6673
4 changed files with 234 additions and 185 deletions

View file

@ -1,13 +1,28 @@
@import url('https://fonts.googleapis.com/css2?family=Exo+2:wght@400;500;600;700;800&display=swap'); @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 "tailwindcss"; @import "tailwindcss";
@theme { @theme {
--color-brand-orange: #fd6216; --color-brand-orange: #fd6216;
--color-brand-navy: #050026; --color-brand-navy: #050026;
--color-orange-50: #fff1e8; --color-orange-50: #fff4ee;
--color-orange-100: #ffe2d2; --color-orange-100: #ffe4d0;
--color-orange-200: #ffc9ac; --color-orange-200: #ffc9a0;
--font-family-sans: 'Exo 2', sans-serif; --color-orange-500: #fd6216;
--color-orange-600: #e5560f;
/* Sidebar tokens */
--color-sidebar-bg: #ffffff;
--color-sidebar-border: #f1f5f9;
--color-sidebar-text: #64748b;
--color-sidebar-active-text: #0f172a;
/* Surface tokens */
--color-surface: #ffffff;
--color-surface-raised: #f8fafc;
--color-border-subtle: #f1f5f9;
--color-border: #e2e8f0;
--font-family-sans: 'Inter', 'Exo 2', sans-serif;
} }
* { * {
@ -17,9 +32,9 @@
} }
body { body {
font-family: 'Exo 2', sans-serif; font-family: 'Inter', 'Exo 2', sans-serif;
background: #f8fafc; background: #f8fafc;
color: #050026; color: #0f172a;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }

View file

@ -191,43 +191,48 @@ export default function AdminShell(props: { children: JSX.Element }) {
const initials = () => adminName().charAt(0).toUpperCase() || 'A'; const initials = () => adminName().charAt(0).toUpperCase() || 'A';
return ( return (
<div class="min-h-screen bg-gray-50"> <div class="min-h-screen bg-[#f8fafc]">
{/* ── Fixed Header ── */} {/* ── Fixed Header ── */}
<header class="fixed top-0 z-50 flex h-16 w-full items-center justify-between border-b border-gray-200 bg-white px-6 shadow-sm"> <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 + page title */} {/* Left: logo + separator + page title */}
<div class="flex items-center gap-8"> <div class="flex items-center gap-4">
<div class="flex h-10 items-center"> <div class="flex h-8 items-center">
<img src="/nxtgauge-logo.png" alt="NXTGAUGE" class="h-8 w-auto object-contain" /> <img src="/nxtgauge-logo.png" alt="NXTGAUGE" class="h-7 w-auto object-contain" />
</div> </div>
<h1 class="ml-28 text-base font-semibold text-gray-800">{pageTitle()}</h1> <div class="h-5 w-px bg-slate-200" />
<span class="text-sm font-medium text-slate-500">{pageTitle()}</span>
</div> </div>
{/* Right: notifications + avatar + logout */} {/* Right: notifications + avatar + logout */}
<div class="flex items-center gap-6"> <div class="flex items-center gap-1">
{/* Notification bell */} {/* Notification bell */}
<button <button
type="button" type="button"
aria-label="Notifications" aria-label="Notifications"
class="relative rounded-full p-2 text-gray-500 transition-colors hover:bg-gray-100" 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"
> >
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> <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="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="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" /> <path stroke-linecap="round" stroke-linejoin="round" d="M13.73 21a2 2 0 0 1-3.46 0" />
</svg> </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" />
</button> </button>
<div class="mx-2 h-5 w-px bg-slate-200" />
{/* Avatar + name */} {/* Avatar + name */}
<div class="flex items-center gap-2"> <div class="flex items-center gap-1.5">
<button <button
type="button" type="button"
class="flex items-center gap-3 rounded-lg p-1 pr-2 transition-colors hover:bg-gray-100" class="flex items-center gap-2.5 rounded-lg px-2 py-1.5 transition-colors hover:bg-slate-100"
> >
<div class="flex h-8 w-8 items-center justify-center overflow-hidden rounded-full border border-orange-200 bg-orange-100 text-sm font-semibold text-orange-700"> <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()} {initials()}
</div> </div>
<div class="flex flex-col items-start"> <div class="flex flex-col items-start">
<span class="mb-0.5 text-xs font-semibold leading-none text-gray-700">{adminName()}</span> <span class="text-[12px] font-semibold leading-none text-slate-700">{adminName()}</span>
<span class="text-[10px] leading-none text-gray-500">{adminRole()}</span> <span class="mt-0.5 text-[10px] leading-none text-slate-400">{adminRole()}</span>
</div> </div>
</button> </button>
@ -236,9 +241,9 @@ export default function AdminShell(props: { children: JSX.Element }) {
type="button" type="button"
onClick={onLogout} onClick={onLogout}
aria-label="Logout" aria-label="Logout"
class="rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600" 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-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> <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" /> <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> </svg>
</button> </button>
@ -248,22 +253,22 @@ export default function AdminShell(props: { children: JSX.Element }) {
{/* ── Body: sidebar + main (fixed, below header) ── */} {/* ── Body: sidebar + main (fixed, below header) ── */}
{checkedSession() ? ( {checkedSession() ? (
<div class="fixed inset-0 top-16 grid grid-cols-[auto_1fr]"> <div class="fixed inset-0 top-14 grid grid-cols-[auto_1fr]">
{/* Sidebar */} {/* Sidebar */}
<AdminSidebar /> <AdminSidebar />
{/* Main content */} {/* Main content */}
<main class="scrollbar min-w-0 overflow-y-auto bg-gray-50 p-6"> <main class="scrollbar min-w-0 overflow-y-auto bg-[#f8fafc] p-6">
{/* Sub-tabs (shown for multi-tab sections) */} {/* Sub-tabs (shown for multi-tab sections) */}
{tabs().length > 0 && ( {tabs().length > 0 && (
<div class="mb-6 flex gap-6 border-b border-gray-200"> <div class="mb-6 flex gap-1 border-b border-slate-200">
{tabs().map((tab) => ( {tabs().map((tab) => (
<A <A
href={tab.href} href={tab.href}
class={`pb-3 text-sm font-medium transition-colors ${ class={`relative px-3 pb-2.5 pt-0.5 text-[13px] font-medium transition-colors ${
isTabActive(tab) isTabActive(tab)
? 'border-b-2 border-[#fd6216] text-gray-900' ? 'text-slate-900 after:absolute after:bottom-0 after:left-0 after:right-0 after:h-0.5 after:rounded-t-full after:bg-orange-500 after:content-[""]'
: 'text-gray-500 hover:text-gray-700' : 'text-slate-500 hover:text-slate-700'
}`} }`}
> >
{tab.label} {tab.label}
@ -276,8 +281,8 @@ export default function AdminShell(props: { children: JSX.Element }) {
</div> </div>
) : ( ) : (
/* Session check loading state */ /* Session check loading state */
<div class="fixed inset-0 top-16 grid grid-cols-[auto_1fr]"> <div class="fixed inset-0 top-14 grid grid-cols-[auto_1fr]">
<div class="w-64 border-r border-slate-200 bg-[#fcfcfd]" /> <div class="w-60 border-r border-slate-100 bg-white" />
<main class="flex items-center justify-center bg-gray-50"> <main class="flex items-center justify-center bg-gray-50">
<p class="text-sm text-gray-400">Checking session</p> <p class="text-sm text-gray-400">Checking session</p>
</main> </main>

View file

@ -6,49 +6,63 @@ type LinkItem = {
label: string; label: string;
icon: string; icon: string;
aliasPrefix?: string; aliasPrefix?: string;
group?: string;
}; };
const links: LinkItem[] = [ const links: LinkItem[] = [
{ href: '/admin', label: 'Dashboard', icon: 'dashboard.svg' }, { href: '/admin', label: 'Dashboard', icon: 'dashboard.svg', group: 'Overview' },
{ href: '/admin/department', label: 'Department Management', icon: 'department.svg' },
{ href: '/admin/designation', label: 'Designation Management', icon: 'designation.svg' }, { href: '/admin/department', label: 'Department', icon: 'department.svg', group: 'Organization' },
{ href: '/admin/employees', label: 'Employee Management', icon: 'users.svg' }, { href: '/admin/designation', label: 'Designation', icon: 'designation.svg', group: 'Organization' },
{ href: '/admin/roles', label: 'Internal Role Management', icon: 'role.svg' }, { href: '/admin/employees', label: 'Employees', icon: 'users.svg', group: 'Organization' },
{ href: '/admin/runtime-roles', label: 'External Role Management', icon: 'role.svg' },
{ href: '/admin/onboarding-schemas', label: 'External Onboarding Management', icon: 'reviews.svg' }, { href: '/admin/roles', label: 'Internal Roles', icon: 'role.svg', group: 'Access & Roles' },
{ href: '/admin/internal-dashboard-management', label: 'Internal Dashboard Management', icon: 'dashboard.svg' }, { href: '/admin/runtime-roles', label: 'External Roles', icon: 'role.svg', group: 'Access & Roles' },
{ href: '/admin/external-dashboard-management', label: 'External Dashboard Management', icon: 'dashboard.svg', aliasPrefix: '/admin/role-ui-configs' }, { href: '/admin/onboarding-schemas', label: 'Onboarding Flows', icon: 'reviews.svg', group: 'Access & Roles' },
{ href: '/admin/approval', label: 'Approval Management', icon: 'approval.svg' },
{ href: '/admin/users', label: 'Users Management', icon: 'users.svg' }, { href: '/admin/internal-dashboard-management', label: 'Internal Dashboards', icon: 'dashboard.svg', group: 'Dashboards' },
{ href: '/admin/company', label: 'Company Management', icon: 'company.svg' }, { href: '/admin/external-dashboard-management', label: 'External Dashboards', icon: 'dashboard.svg', aliasPrefix: '/admin/role-ui-configs', group: 'Dashboards' },
{ href: '/admin/candidate', label: 'Candidate Management', icon: 'candidate.svg' },
{ href: '/admin/customer', label: 'Customer Management', icon: 'users.svg' }, { href: '/admin/users', label: 'Users', icon: 'users.svg', group: 'People' },
{ href: '/admin/photographer', label: 'Photographer Management', icon: 'photographer.svg' }, { href: '/admin/company', label: 'Companies', icon: 'company.svg', group: 'People' },
{ href: '/admin/makeup-artist', label: 'Makeup Artist Management', icon: 'makeup-artist.svg' }, { href: '/admin/candidate', label: 'Candidates', icon: 'candidate.svg', group: 'People' },
{ href: '/admin/tutors', label: 'Tutors Management', icon: 'tutor.svg' }, { href: '/admin/customer', label: 'Customers', icon: 'users.svg', group: 'People' },
{ href: '/admin/developers', label: 'Developers Management', icon: 'developers.svg' }, { href: '/admin/photographer', label: 'Photographers', icon: 'photographer.svg', group: 'People' },
{ href: '/admin/video-editors', label: 'Video Editor Management', icon: 'developers.svg' }, { href: '/admin/makeup-artist', label: 'Makeup Artists', icon: 'makeup-artist.svg', group: 'People' },
{ href: '/admin/fitness-trainers', label: 'Fitness Trainer Management', icon: 'tutor.svg' }, { href: '/admin/tutors', label: 'Tutors', icon: 'tutor.svg', group: 'People' },
{ href: '/admin/catering-services', label: 'Catering Services Management', icon: 'company.svg' }, { href: '/admin/developers', label: 'Developers', icon: 'developers.svg', group: 'People' },
{ href: '/admin/graphic-designers', label: 'Graphics Designer Management', icon: 'developers.svg' }, { href: '/admin/video-editors', label: 'Video Editors', icon: 'developers.svg', group: 'People' },
{ href: '/admin/social-media-managers', label: 'Social Media Manager Management', icon: 'developers.svg' }, { href: '/admin/fitness-trainers', label: 'Fitness Trainers', icon: 'tutor.svg', group: 'People' },
{ href: '/admin/jobs', label: 'Jobs Management', icon: 'jobs.svg' }, { href: '/admin/catering-services', label: 'Catering Services', icon: 'company.svg', group: 'People' },
{ href: '/admin/leads', label: 'Leads Management', icon: 'leads.svg' }, { href: '/admin/graphic-designers', label: 'Graphic Designers', icon: 'developers.svg', group: 'People' },
{ href: '/admin/pricing', label: 'Pricing Management', icon: 'pricing.svg' }, { href: '/admin/social-media-managers', label: 'Social Media Mgr.', icon: 'developers.svg', group: 'People' },
{ href: '/admin/credit', label: 'Credit Management', icon: 'credits.svg' },
{ href: '/admin/coupon', label: 'Coupon Management', icon: 'coupon.svg' }, { href: '/admin/jobs', label: 'Jobs', icon: 'jobs.svg', group: 'Content' },
{ href: '/admin/discount', label: 'Discount Management', icon: 'discount.svg' }, { href: '/admin/leads', label: 'Leads', icon: 'leads.svg', group: 'Content' },
{ href: '/admin/tax', label: 'Tax Management', icon: 'tax.svg' }, { href: '/admin/review', label: 'Reviews', icon: 'reviews.svg', group: 'Content' },
{ href: '/admin/order', label: 'Order Management', icon: 'order.svg' }, { href: '/admin/kb', label: 'Knowledge Base', icon: 'reviews.svg', group: 'Content' },
{ href: '/admin/invoice', label: 'Invoice Management', icon: 'invoice.svg' }, { href: '/admin/notifications', label: 'Notifications', icon: 'reviews.svg', group: 'Content' },
{ href: '/admin/review', label: 'Review Management', icon: 'reviews.svg' },
{ href: '/admin/support', label: 'Support Management', icon: 'support.svg' }, { href: '/admin/pricing', label: 'Pricing', icon: 'pricing.svg', group: 'Finance' },
{ href: '/admin/kb', label: 'Knowledge Base Management', icon: 'reviews.svg' }, { href: '/admin/credit', label: 'Credits', icon: 'credits.svg', group: 'Finance' },
{ href: '/admin/notifications', label: 'Notifications', icon: 'reviews.svg' }, { href: '/admin/coupon', label: 'Coupons', icon: 'coupon.svg', group: 'Finance' },
{ href: '/admin/report', label: 'Report Management', icon: 'report.svg' }, { href: '/admin/discount', label: 'Discounts', icon: 'discount.svg', group: 'Finance' },
{ href: '/admin/ledger', label: 'Ledger Management', icon: 'ledger.svg' }, { 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' },
]; ];
// 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() { export default function AdminSidebar() {
const location = useLocation(); const location = useLocation();
const [collapsed, setCollapsed] = createSignal(false); const [collapsed, setCollapsed] = createSignal(false);
@ -61,82 +75,86 @@ export default function AdminSidebar() {
return ( return (
<aside <aside
class={`flex h-full flex-col border-r border-slate-200 bg-[#fcfcfd] px-3 pb-3 pt-5 transition-all duration-300 ${ class={`flex h-full flex-col border-r border-slate-100 bg-white pb-3 pt-3 transition-all duration-300 ${
collapsed() ? 'w-20' : 'w-64' collapsed() ? 'w-[60px]' : 'w-[220px]'
}`} }`}
> >
{/* Collapse toggle */} {/* Collapse toggle */}
<div class="mb-4 flex justify-end px-2"> <div class={`mb-2 flex px-3 ${collapsed() ? 'justify-center' : 'justify-end'}`}>
<button <button
type="button" type="button"
onClick={() => setCollapsed((v) => !v)} onClick={() => setCollapsed((v) => !v)}
aria-label={collapsed() ? 'Expand sidebar' : 'Collapse sidebar'} aria-label={collapsed() ? 'Expand sidebar' : 'Collapse sidebar'}
class="rounded-lg p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-700" 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 <svg
class={`h-4 w-4 transition-transform ${collapsed() ? 'rotate-180' : ''}`} class={`h-3.5 w-3.5 transition-transform ${collapsed() ? 'rotate-180' : ''}`}
fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" 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" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
</svg> </svg>
</button> </button>
</div> </div>
{/* Nav items */} {/* Nav items grouped */}
<nav class="scrollbar min-h-0 flex-1 space-y-1.5 overflow-y-auto pr-1"> <nav class="scrollbar min-h-0 flex-1 overflow-y-auto px-2">
{links.map((item) => { {GROUP_ORDER.map((group) => {
const active = isActive(item.href, item.aliasPrefix); const groupItems = links.filter((l) => l.group === group);
return ( return (
<A <div class="mb-3">
href={item.href} {/* Group label — hidden when collapsed */}
activeClass=""
inactiveClass=""
title={collapsed() ? item.label : undefined}
class={`group relative flex items-center gap-3 rounded-xl border px-3 py-3 text-[15px] leading-5 transition-all ${
collapsed() ? 'justify-center px-2' : ''
} ${
active
? 'border-orange-200 bg-gradient-to-r from-orange-50 to-orange-100/70 text-slate-900 shadow-[inset_3px_0_0_0_#FD6216]'
: 'border-transparent text-slate-500 hover:border-slate-200 hover:bg-white hover:text-slate-800'
}`}
>
{/* Active left rail */}
<span
class={`absolute bottom-2 left-0 top-2 w-[3px] rounded-r-full bg-orange-500 transition-opacity ${
active ? 'opacity-100' : 'opacity-0'
}`}
/>
{/* Icon */}
<img
src={`/sidebar-icons/${item.icon}`}
alt=""
class="h-[18px] w-[18px] shrink-0 transition-all"
style={active
? 'filter: invert(42%) sepia(78%) saturate(1200%) hue-rotate(360deg) brightness(95%) contrast(95%); opacity:1'
: 'opacity:0.45'
}
/>
{/* Label */}
{!collapsed() && ( {!collapsed() && (
<span class={`truncate font-medium ${active ? 'text-slate-900' : 'text-slate-600 group-hover:text-slate-800'}`}> <p class="mb-1 px-2 text-[10px] font-semibold uppercase tracking-widest text-slate-400">
{item.label} {group}
</span> </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" />
)}
{/* Active badge (expanded) */} {/* Icon */}
{!collapsed() && active && ( <img
<span class="ml-auto rounded-full border border-orange-200 bg-orange-100 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-orange-700"> src={`/sidebar-icons/${item.icon}`}
Active alt=""
</span> 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'
}
/>
{/* Active dot (collapsed) */} {/* Label */}
{collapsed() && active && ( {!collapsed() && (
<span class="absolute -right-0.5 top-1/2 h-2 w-2 -translate-y-1/2 rounded-full bg-orange-500" /> <span class="truncate">{item.label}</span>
)} )}
</A>
{/* 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> </nav>

View file

@ -3,7 +3,7 @@ import AdminShell from '~/components/AdminShell';
import { import {
Users, Building2, Briefcase, Clock, Ticket, Receipt, Users, Building2, Briefcase, Clock, Ticket, Receipt,
Package, Megaphone, Shield, UserCog, FormInput, LayoutDashboard, Package, Megaphone, Shield, UserCog, FormInput, LayoutDashboard,
BadgeCheck, ChevronRight, TrendingUp, BadgeCheck, ArrowUpRight, Activity,
} from 'lucide-solid'; } from 'lucide-solid';
import type { Component } from 'solid-js'; import type { Component } from 'solid-js';
@ -12,8 +12,7 @@ type KpiCard = {
value: string; value: string;
href: string; href: string;
icon: Component<any>; icon: Component<any>;
bg: string; color: string; // tailwind color name (e.g. 'blue')
fg: string;
}; };
type ControlLink = { type ControlLink = {
@ -21,109 +20,121 @@ type ControlLink = {
href: string; href: string;
desc: string; desc: string;
icon: Component<any>; icon: Component<any>;
accent: string; iconBg: string;
iconFg: string;
}; };
const KPI: KpiCard[] = [ const KPI: KpiCard[] = [
{ label: 'Total Users', value: '—', href: '/admin/users', icon: Users, bg: 'bg-blue-50', fg: 'text-blue-600' }, { label: 'Total Users', value: '—', href: '/admin/users', icon: Users, color: 'violet' },
{ label: 'Companies', value: '—', href: '/admin/company', icon: Building2, bg: 'bg-violet-50', fg: 'text-violet-600' }, { label: 'Companies', value: '—', href: '/admin/company', icon: Building2, color: 'blue' },
{ label: 'Open Jobs', value: '—', href: '/admin/jobs', icon: Briefcase, bg: 'bg-amber-50', fg: 'text-amber-600' }, { label: 'Open Jobs', value: '—', href: '/admin/jobs', icon: Briefcase, color: 'amber' },
{ label: 'Pending Approvals', value: '—', href: '/admin/approval', icon: Clock, bg: 'bg-orange-50', fg: 'text-orange-600' }, { label: 'Pending Approvals', value: '—', href: '/admin/approval', icon: Clock, color: 'orange' },
{ label: 'Open Tickets', value: '—', href: '/admin/support', icon: Ticket, bg: 'bg-rose-50', fg: 'text-rose-600' }, { label: 'Open Tickets', value: '—', href: '/admin/support', icon: Ticket, color: 'rose' },
{ label: 'Invoices', value: '—', href: '/admin/invoice', icon: Receipt, bg: 'bg-teal-50', fg: 'text-teal-600' }, { label: 'Invoices', value: '—', href: '/admin/invoice', icon: Receipt, color: 'teal' },
{ label: 'Active Orders', value: '—', href: '/admin/order', icon: Package, bg: 'bg-sky-50', fg: 'text-sky-600' }, { label: 'Active Orders', value: '—', href: '/admin/order', icon: Package, color: 'sky' },
{ label: 'Active Leads', value: '—', href: '/admin/leads', icon: Megaphone, bg: 'bg-pink-50', fg: 'text-pink-600' }, { label: 'Active Leads', value: '—', href: '/admin/leads', icon: Megaphone, color: 'pink' },
]; ];
// 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' },
};
const CONTROLS: ControlLink[] = [ const CONTROLS: ControlLink[] = [
{ {
label: 'Internal Roles', label: 'Internal Roles',
href: '/admin/roles', href: '/admin/roles',
desc: 'Define permissions and access levels for internal staff roles.', desc: 'Permissions and access levels for internal staff.',
icon: Shield, icon: Shield,
accent: 'bg-blue-50 text-blue-600', iconBg: 'bg-blue-50',
iconFg: 'text-blue-600',
}, },
{ {
label: 'External Roles', label: 'External Roles',
href: '/admin/runtime-roles', href: '/admin/runtime-roles',
desc: 'Configure modules, credit rules, and capabilities per external role.', desc: 'Modules, credits, and capabilities per external role.',
icon: UserCog, icon: UserCog,
accent: 'bg-violet-50 text-violet-600', iconBg: 'bg-violet-50',
iconFg: 'text-violet-600',
}, },
{ {
label: 'Onboarding Flows', label: 'Onboarding Flows',
href: '/admin/onboarding-schemas', href: '/admin/onboarding-schemas',
desc: 'Build schema-driven onboarding forms for each external role.', desc: 'Schema-driven onboarding forms per external role.',
icon: FormInput, icon: FormInput,
accent: 'bg-amber-50 text-amber-600', iconBg: 'bg-amber-50',
iconFg: 'text-amber-600',
}, },
{ {
label: 'External Dashboards', label: 'External Dashboards',
href: '/admin/external-dashboard-management', href: '/admin/external-dashboard-management',
desc: 'Configure the runtimeConfig, sidebar, and widgets per external role.', desc: 'Sidebar, widgets, and runtimeConfig per external role.',
icon: LayoutDashboard, icon: LayoutDashboard,
accent: 'bg-teal-50 text-teal-600', iconBg: 'bg-teal-50',
iconFg: 'text-teal-600',
}, },
{ {
label: 'Internal Dashboards', label: 'Internal Dashboards',
href: '/admin/internal-dashboard-management', href: '/admin/internal-dashboard-management',
desc: 'Design home widgets and KPI panels for internal staff dashboards.', desc: 'Home widgets and KPI panels for internal staff.',
icon: LayoutDashboard, icon: LayoutDashboard,
accent: 'bg-orange-50 text-orange-600', iconBg: 'bg-orange-50',
iconFg: 'text-orange-600',
}, },
{ {
label: 'Approval Queue', label: 'Approval Queue',
href: '/admin/approval', href: '/admin/approval',
desc: 'Review, approve, or reject pending platform action requests.', desc: 'Review, approve, or reject pending action requests.',
icon: BadgeCheck, icon: BadgeCheck,
accent: 'bg-rose-50 text-rose-600', iconBg: 'bg-rose-50',
iconFg: 'text-rose-600',
}, },
]; ];
export default function AdminDashboard() { export default function AdminDashboard() {
return ( return (
<AdminShell> <AdminShell>
<div class="space-y-8"> <div class="mx-auto max-w-6xl space-y-7">
{/* ── Page header ── */} {/* ── Page header ── */}
<div class="flex items-center justify-between"> <div class="flex items-start justify-between">
<div> <div>
<h1 class="text-[22px] font-bold tracking-tight text-gray-900">Platform Overview</h1> <h1 class="text-xl font-bold tracking-tight text-slate-900">Platform Overview</h1>
<p class="mt-0.5 text-sm text-gray-400">Real-time snapshot of the Nxtgauge platform.</p> <p class="mt-0.5 text-sm text-slate-400">Real-time snapshot of the Nxtgauge platform.</p>
</div> </div>
<div class="flex items-center gap-2 rounded-xl border border-gray-200 bg-white px-3 py-1.5 shadow-sm"> <div class="flex items-center gap-1.5 rounded-lg border border-emerald-200 bg-emerald-50 px-2.5 py-1.5">
<TrendingUp class="h-4 w-4 text-emerald-500" /> <Activity class="h-3.5 w-3.5 text-emerald-500" />
<span class="text-xs font-medium text-gray-500">Live data</span> <span class="text-[11px] font-semibold text-emerald-600">Live</span>
</div> </div>
</div> </div>
{/* ── KPI grid ── */} {/* ── KPI grid ── */}
<div class="grid grid-cols-2 gap-4 lg:grid-cols-4"> <div class="grid grid-cols-2 gap-3 lg:grid-cols-4">
{KPI.map((kpi) => { {KPI.map((kpi) => {
const Icon = kpi.icon; const Icon = kpi.icon;
const c = colorMap[kpi.color];
return ( return (
<A <A
href={kpi.href} href={kpi.href}
class="group relative overflow-hidden rounded-2xl border border-gray-100 bg-white p-5 shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:border-gray-200 hover:shadow-md" 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"
> >
{/* Subtle background circle */} <div class="flex items-center justify-between">
<div class="absolute -right-4 -top-4 h-20 w-20 rounded-full bg-gray-50 opacity-60" /> <div class={`flex h-8 w-8 items-center justify-center rounded-lg ${c.chip}`}>
<Icon class={`h-4 w-4 ${c.icon}`} />
<div class="relative flex flex-col gap-4">
{/* Icon */}
<div class={`flex h-10 w-10 items-center justify-center rounded-xl ${kpi.bg}`}>
<Icon class={`h-5 w-5 ${kpi.fg}`} />
</div>
{/* Metric */}
<div>
<p class="text-[28px] font-bold leading-none tracking-tight text-gray-900">{kpi.value}</p>
<p class="mt-1.5 text-[13px] font-medium text-gray-400">{kpi.label}</p>
</div> </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>
</div> </div>
{/* Hover arrow */}
<ChevronRight class="absolute bottom-4 right-4 h-4 w-4 text-gray-300 opacity-0 transition-opacity group-hover:opacity-100" />
</A> </A>
); );
})} })}
@ -131,28 +142,28 @@ export default function AdminDashboard() {
{/* ── Control Plane ── */} {/* ── Control Plane ── */}
<div> <div>
<div class="mb-4 flex items-center justify-between"> <div class="mb-3 flex items-center justify-between">
<h2 class="text-[15px] font-semibold text-gray-700">Control Plane</h2> <h2 class="text-sm font-semibold text-slate-700">Control Plane</h2>
<span class="rounded-full border border-gray-200 bg-white px-2.5 py-0.5 text-[11px] font-medium text-gray-400 shadow-sm"> <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 {CONTROLS.length} modules
</span> </span>
</div> </div>
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3"> <div class="grid grid-cols-1 gap-2.5 sm:grid-cols-2 lg:grid-cols-3">
{CONTROLS.map((item) => { {CONTROLS.map((item) => {
const Icon = item.icon; const Icon = item.icon;
return ( return (
<A <A
href={item.href} href={item.href}
class="group flex items-start gap-4 rounded-2xl border border-gray-100 bg-white p-5 shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:border-orange-100 hover:shadow-md" 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"
> >
<div class={`mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-xl ${item.accent}`}> <div class={`mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${item.iconBg}`}>
<Icon class="h-4 w-4" /> <Icon class={`h-4 w-4 ${item.iconFg}`} />
</div> </div>
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<p class="text-[14px] font-semibold text-gray-800 group-hover:text-[#fd6216] transition-colors">{item.label}</p> <p class="text-[13px] font-semibold text-slate-800 transition-colors group-hover:text-[#fd6216]">{item.label}</p>
<p class="mt-0.5 text-[12px] leading-relaxed text-gray-400">{item.desc}</p> <p class="mt-0.5 text-[11px] leading-relaxed text-slate-400">{item.desc}</p>
</div> </div>
<ChevronRight class="mt-1 h-4 w-4 shrink-0 text-gray-300 transition-transform group-hover:translate-x-0.5 group-hover:text-orange-400" /> <ArrowUpRight class="mt-0.5 h-3.5 w-3.5 shrink-0 text-slate-200 transition-all group-hover:text-orange-400" />
</A> </A>
); );
})} })}