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:
parent
be179a6673
commit
df560a91ea
4 changed files with 362 additions and 400 deletions
150
src/app.css
150
src/app.css
|
|
@ -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";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-brand-orange: #fd6216;
|
--color-primary: #fd6216;
|
||||||
--color-brand-navy: #050026;
|
--color-navy: #0a1d37;
|
||||||
--color-orange-50: #fff4ee;
|
|
||||||
--color-orange-100: #ffe4d0;
|
|
||||||
--color-orange-200: #ffc9a0;
|
|
||||||
--color-orange-500: #fd6216;
|
|
||||||
--color-orange-600: #e5560f;
|
|
||||||
|
|
||||||
/* Sidebar tokens */
|
/* Brand surfaces */
|
||||||
--color-sidebar-bg: #ffffff;
|
--color-bg: #f9f9fd;
|
||||||
--color-sidebar-border: #f1f5f9;
|
--color-surface: #ffffff;
|
||||||
--color-sidebar-text: #64748b;
|
--color-surface-low: #f3f3f7;
|
||||||
--color-sidebar-active-text: #0f172a;
|
--color-surface-container: #edeef1;
|
||||||
|
|
||||||
/* Surface tokens */
|
/* Tailwind orange overrides */
|
||||||
--color-surface: #ffffff;
|
--color-orange-50: #fff4ee;
|
||||||
--color-surface-raised: #f8fafc;
|
--color-orange-100: #ffe4d0;
|
||||||
--color-border-subtle: #f1f5f9;
|
--color-orange-200: #ffc9a0;
|
||||||
--color-border: #e2e8f0;
|
--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 {
|
body {
|
||||||
font-family: 'Inter', 'Exo 2', sans-serif;
|
font-family: 'Exo 2', sans-serif;
|
||||||
background: #f8fafc;
|
background: #f9f9fd;
|
||||||
color: #0f172a;
|
color: #0a1d37;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== Scrollbar ===== */
|
/* ===== Scrollbar ===== */
|
||||||
.scrollbar {
|
.scrollbar { scrollbar-width: thin; scrollbar-color: #c7c7c7 transparent; }
|
||||||
scrollbar-width: thin;
|
.scrollbar::-webkit-scrollbar { height: 6px; width: 6px; }
|
||||||
scrollbar-color: #c7c7c7 transparent;
|
|
||||||
}
|
|
||||||
.scrollbar::-webkit-scrollbar { height: 8px; width: 8px; }
|
|
||||||
.scrollbar::-webkit-scrollbar-track { background: transparent; }
|
.scrollbar::-webkit-scrollbar-track { background: transparent; }
|
||||||
.scrollbar::-webkit-scrollbar-thumb { background-color: #c7c7c7; border-radius: 9999px; }
|
.scrollbar::-webkit-scrollbar-thumb { background-color: #c7c7c7; border-radius: 9999px; }
|
||||||
.scrollbar::-webkit-scrollbar-thumb:hover { background-color: #9ca3af; }
|
.scrollbar::-webkit-scrollbar-thumb:hover { background-color: #9ca3af; }
|
||||||
|
|
||||||
/* ===== Auth / Login page ===== */
|
/* ===== Auth / Login page ===== */
|
||||||
.auth-page {
|
.auth-page {
|
||||||
position: relative;
|
position: relative; min-height: 100vh;
|
||||||
min-height: 100vh;
|
display: grid; place-items: center; padding: 20px; overflow: hidden;
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
padding: 20px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-bg {
|
.auth-bg {
|
||||||
position: absolute;
|
position: absolute; inset: 0;
|
||||||
inset: 0;
|
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at 20% 20%, rgba(253, 98, 22, 0.24), transparent 42%),
|
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%),
|
radial-gradient(circle at 80% 10%, rgba(99, 102, 241, 0.16), transparent 34%),
|
||||||
linear-gradient(180deg, #100b2f 0%, #0c0828 52%, #07051d 100%);
|
linear-gradient(180deg, #100b2f 0%, #0c0828 52%, #07051d 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-layout {
|
.auth-layout {
|
||||||
position: relative;
|
position: relative; z-index: 1; width: min(1120px, 100%);
|
||||||
z-index: 1;
|
display: grid; grid-template-columns: 1.08fr 0.92fr; gap: 24px; align-items: stretch;
|
||||||
width: min(1120px, 100%);
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1.08fr 0.92fr;
|
|
||||||
gap: 24px;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-visual {
|
.auth-visual {
|
||||||
border-radius: 24px;
|
border-radius: 24px; border: 1px solid rgba(255,255,255,0.18);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
background: rgba(255,255,255,0.08); padding: 28px;
|
||||||
background: rgba(255, 255, 255, 0.08);
|
box-shadow: 0 24px 72px -34px rgba(0,0,0,0.72);
|
||||||
padding: 28px;
|
|
||||||
box-shadow: 0 24px 72px -34px rgba(0, 0, 0, 0.72);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-visual-kicker {
|
.auth-visual-kicker {
|
||||||
margin: 0;
|
margin: 0; display: inline-flex; border-radius: 999px;
|
||||||
display: inline-flex;
|
border: 1px solid rgba(255,255,255,0.2); background: rgba(255,255,255,0.1);
|
||||||
border-radius: 999px;
|
color: #ffd7c2; font-size: 11px; font-weight: 700;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
letter-spacing: 0.12em; text-transform: uppercase; padding: 6px 10px;
|
||||||
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 h1 {
|
.auth-visual p { margin: 12px 0 0; color: rgba(255,255,255,0.82); }
|
||||||
margin: 16px 0 0;
|
.auth-visual img { margin-top: 18px; width: 100%; height: 280px; border-radius: 16px; object-fit: cover; }
|
||||||
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 {
|
.auth-card {
|
||||||
position: relative;
|
position: relative; z-index: 1; border-radius: 24px;
|
||||||
z-index: 1;
|
border: 1px solid rgba(255,255,255,0.22); background: rgba(255,255,255,0.96);
|
||||||
border-radius: 24px;
|
box-shadow: 0 24px 72px -34px rgba(0,0,0,0.72); padding: 30px; color: #0f172a;
|
||||||
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-form-card { display: flex; flex-direction: column; }
|
.auth-logo { height: 52px; width: auto; object-fit: contain; margin: 0 auto; }
|
||||||
.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-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-copy { margin: 12px 0 0; text-align: center; color: #475569; }
|
.auth-form-grid { margin-top: 24px; }
|
||||||
.auth-form-grid { margin-top: 24px; }
|
.auth-switch { display: flex; justify-content: flex-end; margin-top: -2px; }
|
||||||
|
|
||||||
.auth-switch { display: flex; justify-content: flex-end; margin-top: -2px; }
|
|
||||||
.auth-switch.split { justify-content: space-between; }
|
.auth-switch.split { justify-content: space-between; }
|
||||||
|
|
||||||
.auth-link-btn {
|
.auth-link-btn {
|
||||||
border: 0; padding: 0; background: transparent;
|
border: 0; padding: 0; background: transparent;
|
||||||
color: #fd6216; font-size: 13px; font-weight: 600; cursor: pointer;
|
color: #fd6216; font-size: 13px; font-weight: 600; cursor: pointer;
|
||||||
|
|
|
||||||
|
|
@ -191,74 +191,78 @@ 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-[#f8fafc]">
|
<div class="min-h-screen bg-[#f9f9fd]">
|
||||||
{/* ── Fixed Header ── */}
|
{/* ── 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">
|
<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 + separator + page title */}
|
{/* Left: logo */}
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex w-64 shrink-0 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 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 */}
|
{/* Center: search */}
|
||||||
<div class="flex items-center gap-1">
|
<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 */}
|
{/* Notification bell */}
|
||||||
<button
|
<button type="button" aria-label="Notifications"
|
||||||
type="button"
|
class="relative flex h-9 w-9 items-center justify-center rounded-full text-[#0a1d37]/60 transition-colors hover:bg-slate-50"
|
||||||
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"
|
|
||||||
>
|
>
|
||||||
<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="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-2 top-2 h-2 w-2 rounded-full border-2 border-white bg-[#fd6216]" />
|
||||||
<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" />
|
{/* 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="h-8 w-px bg-slate-200" />
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* Logout */}
|
{/* Name + avatar */}
|
||||||
<button
|
<div class="flex items-center gap-2.5">
|
||||||
type="button"
|
<span class="text-sm font-bold text-[#0a1d37]">{adminName()}</span>
|
||||||
onClick={onLogout}
|
<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">
|
||||||
aria-label="Logout"
|
{initials()}
|
||||||
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"
|
</div>
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</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>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* ── Body: sidebar + main (fixed, below header) ── */}
|
{/* ── Body: sidebar + main (fixed, below header) ── */}
|
||||||
{checkedSession() ? (
|
{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 */}
|
{/* Sidebar */}
|
||||||
<AdminSidebar />
|
<AdminSidebar />
|
||||||
|
|
||||||
{/* Main content */}
|
{/* 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) */}
|
{/* Sub-tabs (shown for multi-tab sections) */}
|
||||||
{tabs().length > 0 && (
|
{tabs().length > 0 && (
|
||||||
<div class="mb-6 flex gap-1 border-b border-slate-200">
|
<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>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
/* Session check loading state */
|
/* Session check loading state */
|
||||||
<div class="fixed inset-0 top-14 grid grid-cols-[auto_1fr]">
|
<div class="fixed inset-0 top-16 grid grid-cols-[auto_1fr]">
|
||||||
<div class="w-60 border-r border-slate-100 bg-white" />
|
<div class="w-64 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>
|
||||||
|
|
|
||||||
|
|
@ -1,71 +1,59 @@
|
||||||
import { A, useLocation } from '@solidjs/router';
|
import { A, useLocation } from '@solidjs/router';
|
||||||
import { createSignal } from 'solid-js';
|
|
||||||
|
|
||||||
type LinkItem = {
|
type LinkItem = {
|
||||||
href: string;
|
href: string;
|
||||||
label: string;
|
label: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
aliasPrefix?: string;
|
aliasPrefix?: string;
|
||||||
group?: string;
|
group: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const links: LinkItem[] = [
|
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/roles', label: 'Internal Role Management', icon: 'role.svg', group: 'Management' },
|
||||||
{ href: '/admin/designation', label: 'Designation', icon: 'designation.svg', group: 'Organization' },
|
{ href: '/admin/runtime-roles', label: 'External Role Management', icon: 'role.svg', group: 'Management' },
|
||||||
{ href: '/admin/employees', label: 'Employees', icon: 'users.svg', group: 'Organization' },
|
{ 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/pricing', label: 'Pricing Management', icon: 'pricing.svg', group: 'Finance' },
|
||||||
{ href: '/admin/runtime-roles', label: 'External Roles', icon: 'role.svg', group: 'Access & Roles' },
|
{ href: '/admin/credit', label: 'Credit Management', icon: 'credits.svg', group: 'Finance' },
|
||||||
{ href: '/admin/onboarding-schemas', label: 'Onboarding Flows', icon: 'reviews.svg', group: 'Access & Roles' },
|
{ 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/review', label: 'Review Management', icon: 'reviews.svg', group: 'Platform' },
|
||||||
{ href: '/admin/external-dashboard-management', label: 'External Dashboards', icon: 'dashboard.svg', aliasPrefix: '/admin/role-ui-configs', group: 'Dashboards' },
|
{ 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/users', label: 'Users', icon: 'users.svg', group: 'People' },
|
{ href: '/admin/notifications', label: 'Notifications', icon: 'reviews.svg', group: 'Platform' },
|
||||||
{ href: '/admin/company', label: 'Companies', icon: 'company.svg', group: 'People' },
|
{ href: '/admin/report', label: 'Report Management', icon: 'report.svg', group: 'Platform' },
|
||||||
{ 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' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// 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 isActive = (href: string, aliasPrefix?: string) => {
|
const isActive = (href: string, aliasPrefix?: string) => {
|
||||||
if (href === '/admin') return location.pathname === '/admin';
|
if (href === '/admin') return location.pathname === '/admin';
|
||||||
|
|
@ -73,91 +61,73 @@ export default function AdminSidebar() {
|
||||||
return location.pathname === href || location.pathname.startsWith(`${href}/`);
|
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 (
|
return (
|
||||||
<aside
|
<aside class="flex h-full w-64 flex-col border-r border-slate-100 bg-white py-5">
|
||||||
class={`flex h-full flex-col border-r border-slate-100 bg-white pb-3 pt-3 transition-all duration-300 ${
|
{/* Top (Dashboard) */}
|
||||||
collapsed() ? 'w-[60px]' : 'w-[220px]'
|
<nav class="px-3">
|
||||||
}`}
|
{topLinks.map(NavItem)}
|
||||||
>
|
</nav>
|
||||||
{/* Collapse toggle */}
|
|
||||||
<div class={`mb-2 flex px-3 ${collapsed() ? 'justify-center' : 'justify-end'}`}>
|
{/* Scrollable nav groups */}
|
||||||
<button
|
<div class="scrollbar mt-1 min-h-0 flex-1 overflow-y-auto px-3">
|
||||||
type="button"
|
{GroupLabel('Management')}
|
||||||
onClick={() => setCollapsed((v) => !v)}
|
{mgmtLinks.map(NavItem)}
|
||||||
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"
|
{GroupLabel('Finance')}
|
||||||
>
|
{finLinks.map(NavItem)}
|
||||||
<svg
|
|
||||||
class={`h-3.5 w-3.5 transition-transform ${collapsed() ? 'rotate-180' : ''}`}
|
{GroupLabel('Platform')}
|
||||||
fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"
|
{platLinks.map(NavItem)}
|
||||||
>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Nav items grouped */}
|
{/* User card at bottom */}
|
||||||
<nav class="scrollbar min-h-0 flex-1 overflow-y-auto px-2">
|
<div class="mt-2 border-t border-slate-100 px-4 pt-4">
|
||||||
{GROUP_ORDER.map((group) => {
|
<div class="flex items-center gap-3 rounded-xl bg-slate-50 p-3">
|
||||||
const groupItems = links.filter((l) => l.group === group);
|
<div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[#fd6216] text-sm font-black text-white">
|
||||||
return (
|
A
|
||||||
<div class="mb-3">
|
</div>
|
||||||
{/* Group label — hidden when collapsed */}
|
<div class="min-w-0 flex-1 overflow-hidden">
|
||||||
{!collapsed() && (
|
<p class="truncate text-xs font-bold text-[#0a1d37]">Admin User</p>
|
||||||
<p class="mb-1 px-2 text-[10px] font-semibold uppercase tracking-widest text-slate-400">
|
<p class="truncate text-[10px] text-slate-500">master_admin@nxtgauge.io</p>
|
||||||
{group}
|
</div>
|
||||||
</p>
|
</div>
|
||||||
)}
|
</div>
|
||||||
<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>
|
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,167 +3,215 @@ 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, ArrowUpRight, Activity,
|
BadgeCheck, ArrowRight,
|
||||||
} from 'lucide-solid';
|
} from 'lucide-solid';
|
||||||
import type { Component } from 'solid-js';
|
import type { Component } from 'solid-js';
|
||||||
|
|
||||||
type KpiCard = {
|
type KpiCard = {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
badge?: string;
|
||||||
|
badgeColor?: string;
|
||||||
href: string;
|
href: string;
|
||||||
icon: Component<any>;
|
icon: Component<any>;
|
||||||
color: string; // tailwind color name (e.g. 'blue')
|
accentColor: string; // full tailwind class for the left bar bg
|
||||||
};
|
chipBg: string;
|
||||||
|
chipFg: string;
|
||||||
type ControlLink = {
|
|
||||||
label: string;
|
|
||||||
href: string;
|
|
||||||
desc: string;
|
|
||||||
icon: Component<any>;
|
|
||||||
iconBg: string;
|
|
||||||
iconFg: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const KPI: KpiCard[] = [
|
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: 'Total Users', value: '—', badge: '+0%', badgeColor: 'text-emerald-600 bg-emerald-50',
|
||||||
{ label: 'Open Jobs', value: '—', href: '/admin/jobs', icon: Briefcase, color: 'amber' },
|
href: '/admin/users', icon: Users,
|
||||||
{ label: 'Pending Approvals', value: '—', href: '/admin/approval', icon: Clock, color: 'orange' },
|
accentColor: 'bg-[#fd6216]', chipBg: 'bg-[#fd6216]/5', chipFg: 'text-[#fd6216]',
|
||||||
{ 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: 'Total Revenue', value: '—', badge: 'Weekly', badgeColor: 'text-[#0a1d37] bg-slate-100',
|
||||||
{ label: 'Active Leads', value: '—', href: '/admin/leads', icon: Megaphone, color: 'pink' },
|
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
|
// Activity chart data (placeholder heights)
|
||||||
const colorMap: Record<string, { chip: string; icon: string; value: string }> = {
|
const ACTIVITY = [
|
||||||
violet: { chip: 'bg-violet-100', icon: 'text-violet-600', value: 'text-violet-700' },
|
{ day: 'MON', h: 40 }, { day: 'TUE', h: 65 }, { day: 'WED', h: 95 },
|
||||||
blue: { chip: 'bg-blue-100', icon: 'text-blue-600', value: 'text-blue-700' },
|
{ day: 'THU', h: 70 }, { day: 'FRI', h: 55 }, { day: 'SAT', h: 30 }, { day: 'SUN', h: 45 },
|
||||||
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 QUICK_ACTIONS = [
|
||||||
{
|
{ label: 'Create New Role', href: '/admin/roles/create', icon: Shield },
|
||||||
label: 'Internal Roles',
|
{ label: 'Configure Dashboard', href: '/admin/external-dashboard-management', icon: LayoutDashboard },
|
||||||
href: '/admin/roles',
|
{ label: 'Add Employee', href: '/admin/employees', icon: Users },
|
||||||
desc: 'Permissions and access levels for internal staff.',
|
];
|
||||||
icon: Shield,
|
|
||||||
iconBg: 'bg-blue-50',
|
const PIPELINE = [
|
||||||
iconFg: 'text-blue-600',
|
{ 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' },
|
||||||
label: 'External Roles',
|
];
|
||||||
href: '/admin/runtime-roles',
|
|
||||||
desc: 'Modules, credits, and capabilities per external role.',
|
const CONTROL: Array<{ label: string; href: string; desc: string; icon: Component<any>; iconBg: string; iconFg: string }> = [
|
||||||
icon: UserCog,
|
{ label: 'Internal Roles', href: '/admin/roles', desc: 'Permissions & access levels for internal staff.', icon: Shield, iconBg: 'bg-blue-50', iconFg: 'text-blue-600' },
|
||||||
iconBg: 'bg-violet-50',
|
{ label: 'External Roles', href: '/admin/runtime-roles', desc: 'Modules, credits & capabilities per external role.', icon: UserCog, iconBg: 'bg-violet-50', iconFg: 'text-violet-600' },
|
||||||
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: 'Onboarding Flows',
|
{ label: 'Approval Queue', href: '/admin/approval', desc: 'Review, approve or reject pending action requests.', icon: BadgeCheck, iconBg: 'bg-rose-50', iconFg: 'text-rose-600' },
|
||||||
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',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function AdminDashboard() {
|
export default function AdminDashboard() {
|
||||||
return (
|
return (
|
||||||
<AdminShell>
|
<AdminShell>
|
||||||
<div class="mx-auto max-w-6xl space-y-7">
|
<div class="space-y-8">
|
||||||
|
|
||||||
{/* ── Page header ── */}
|
{/* ── KPI Cards ── */}
|
||||||
<div class="flex items-start justify-between">
|
<div class="grid grid-cols-2 gap-6 lg:grid-cols-4">
|
||||||
<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.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 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">
|
{/* Left accent bar */}
|
||||||
<div class={`flex h-8 w-8 items-center justify-center rounded-lg ${c.chip}`}>
|
<div class={`absolute left-0 top-0 h-full w-1.5 ${kpi.accentColor}`} />
|
||||||
<Icon class={`h-4 w-4 ${c.icon}`} />
|
|
||||||
|
<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>
|
</div>
|
||||||
<ArrowUpRight class="h-3.5 w-3.5 text-slate-200 transition-colors group-hover:text-slate-400" />
|
{kpi.badge && (
|
||||||
</div>
|
<span class={`rounded-full px-2 py-1 text-[11px] font-bold ${kpi.badgeColor}`}>
|
||||||
<div>
|
{kpi.badge}
|
||||||
<p class="text-2xl font-bold tabular-nums tracking-tight text-slate-900">{kpi.value}</p>
|
</span>
|
||||||
<p class="mt-0.5 text-xs font-medium text-slate-400">{kpi.label}</p>
|
)}
|
||||||
</div>
|
</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>
|
</A>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</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 ── */}
|
{/* ── Control Plane ── */}
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-3 flex items-center justify-between">
|
<div class="mb-5 flex items-center justify-between">
|
||||||
<h2 class="text-sm font-semibold text-slate-700">Control Plane</h2>
|
<h2 class="text-xl font-black tracking-tight text-[#0a1d37]">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">
|
<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">
|
||||||
{CONTROLS.length} modules
|
{CONTROL.length} modules
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 gap-2.5 sm:grid-cols-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{CONTROLS.map((item) => {
|
{CONTROL.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-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}`} />
|
<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-[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>
|
<p class="mt-0.5 text-[11px] leading-relaxed text-slate-400">{item.desc}</p>
|
||||||
</div>
|
</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>
|
</A>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue