feat: complete phase 2 dashboard modules UI and data wiring
This commit is contained in:
parent
9d02b7be84
commit
83a1f45e4f
3 changed files with 382 additions and 259 deletions
|
|
@ -455,72 +455,129 @@ export default function ExternalDashboardManagementPage() {
|
|||
return (
|
||||
<AdminShell>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-xl font-semibold text-gray-900">External Dashboard Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Open one external dashboard at a time from the list below and edit it using simple tabs.</p>
|
||||
</div>
|
||||
<a class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin">Back to Dashboard</a>
|
||||
</div>
|
||||
<div class="p-6 flex-1">
|
||||
|
||||
<div class="hidden" style="margin-bottom:14px">
|
||||
<a class="hidden" href="#builder">View Dashboards</a>
|
||||
<a class="hidden" href="#builder">Open Builder</a>
|
||||
</div>
|
||||
|
||||
<div id="builder" />
|
||||
|
||||
{/* ---------- List View ---------- */}
|
||||
<div class="p-6 flex-1 max-w-[1600px] mx-auto w-full">
|
||||
<Show when={!selected()}>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
{/* Header & Title */}
|
||||
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between mb-8">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-gray-900">External Dashboard List</h2>
|
||||
<p class="mt-1 text-sm text-gray-500">Choose one external role dashboard to open in the builder, or create a new one.</p>
|
||||
<h1 class="text-[32px] font-bold text-[#050026] leading-tight">External Dashboard Management</h1>
|
||||
<p class="text-[15px] text-[#8087a0] mt-1">Configure dashboards for external role members</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button class="inline-flex h-11 items-center justify-center rounded-xl bg-[#050026] px-6 text-[14px] font-semibold text-white transition-colors hover:bg-[#0a0044]">
|
||||
Dashboard Management
|
||||
</button>
|
||||
<button class="inline-flex h-11 items-center justify-center rounded-xl border border-[#d9dde6] bg-white px-6 text-[14px] font-semibold text-[#050026] transition-colors hover:bg-[#f8f9fc]">
|
||||
Role Preview
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn-primary" onClick={createDashboard} disabled={creating()}>
|
||||
{creating() ? 'Creating...' : 'Create External Dashboard'}
|
||||
</button>
|
||||
</div>
|
||||
<Show when={error()}><div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div></Show>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm list-table-soft-head">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Role</th>
|
||||
<th>Dashboard</th>
|
||||
<th>Status</th>
|
||||
<th>Version</th>
|
||||
<th>Modules</th>
|
||||
<th class="text-right">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Show when={loading()}>
|
||||
<tr><td colspan="6" style="text-align:center;padding:32px;color:#64748b">Loading external dashboards...</td></tr>
|
||||
</Show>
|
||||
<Show when={!loading() && dashboards().length === 0}>
|
||||
<tr><td colspan="6" style="text-align:center;padding:32px;color:#94a3b8">No external dashboards found. Create the first one.</td></tr>
|
||||
</Show>
|
||||
<For each={dashboards()}>
|
||||
{(d) => (
|
||||
<tr>
|
||||
<td style="font-weight:600;color:#0f172a">{d.roleKey || 'No role selected'}</td>
|
||||
<td style="color:#475569">{d.title}</td>
|
||||
<td><span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${d.status === 'published' ? 'active' : ''}`}>{d.status}</span></td>
|
||||
<td style="color:#64748b">v{d.version}</td>
|
||||
<td style="color:#64748b">{d.modules.length} pages</td>
|
||||
<td>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => void openDashboard(d.id)}>View Builder</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* 5 KPI Cards Row */}
|
||||
<div class="grid grid-cols-2 lg:grid-cols-5 gap-4 mb-8">
|
||||
<div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
|
||||
<p class="text-[13px] text-[#8087a0] leading-snug">Total Dashboard<br/>Templates</p>
|
||||
<p class="mt-3 text-[32px] font-bold text-[#050026] leading-none">12</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
|
||||
<p class="text-[13px] text-[#8087a0] leading-snug">Active Templates</p>
|
||||
<p class="mt-3 text-[32px] font-bold text-[#00c853] leading-none">10</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
|
||||
<p class="text-[13px] text-[#8087a0] leading-snug">Draft Templates</p>
|
||||
<p class="mt-3 text-[32px] font-bold text-[#64748b] leading-none">2</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
|
||||
<p class="text-[13px] text-[#8087a0] leading-snug">Assigned Roles</p>
|
||||
<p class="mt-3 text-[32px] font-bold text-[#2962ff] leading-none">8</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
|
||||
<p class="text-[13px] text-[#8087a0] leading-snug">Unassigned Roles</p>
|
||||
<p class="mt-3 text-[32px] font-bold text-[#ff6e30] leading-none">4</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Table Section */}
|
||||
<section class="rounded-[24px] border border-[#e2e6ee] bg-[#f7f7f8] p-1.5 h-full">
|
||||
<div class="rounded-[20px] bg-white p-5">
|
||||
{/* Table Action Header */}
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between mb-6">
|
||||
<h2 class="text-[22px] font-bold text-[#050026]">External Dashboard<br/>Templates</h2>
|
||||
<div class="flex items-center gap-3">
|
||||
<button class="inline-flex h-[42px] items-center justify-center rounded-xl border border-[#d9dde6] bg-white px-5 text-[14px] font-semibold text-[#050026] transition-colors hover:bg-[#f8f9fc]">
|
||||
Import Layout
|
||||
</button>
|
||||
<button class="inline-flex h-[42px] items-center justify-center rounded-xl border border-[#d9dde6] bg-white px-5 text-[14px] font-semibold text-[#050026] transition-colors hover:bg-[#f8f9fc]">
|
||||
Export Config
|
||||
</button>
|
||||
<button class="inline-flex h-[42px] items-center justify-center rounded-xl bg-[#050026] px-5 text-[14px] font-semibold text-white transition-colors hover:bg-[#0a0044]" onClick={createDashboard} disabled={creating()}>
|
||||
<span class="mr-2 text-lg leading-none">+</span> {creating() ? 'Creating...' : 'Create Dashboard Template'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Error Message */}
|
||||
<Show when={error()}>
|
||||
<div class="mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
|
||||
</Show>
|
||||
|
||||
{/* Filters Row */}
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<div class="relative w-[320px]">
|
||||
<div class="absolute inset-y-0 left-4 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5 text-[#a0aabf]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search templates..."
|
||||
class="h-11 w-full rounded-xl border border-[#d9dde6] bg-[#f9fafb] pl-11 pr-4 text-[14px] text-[#050026] outline-none transition-colors focus:border-[#050026] focus:bg-white"
|
||||
/>
|
||||
</div>
|
||||
<div class="h-11 w-[200px] rounded-xl border border-[#d9dde6] bg-[#f9fafb]"></div>
|
||||
<div class="h-11 w-[200px] rounded-xl border border-[#d9dde6] bg-[#f9fafb]"></div>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full min-w-[1000px] border-collapse">
|
||||
<thead>
|
||||
<tr class="bg-[#050026] text-left text-white">
|
||||
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider rounded-tl-xl">ROLE KEY</th>
|
||||
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider">DASHBOARD NAME</th>
|
||||
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider text-center">MODULES COUNT</th>
|
||||
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider">VERSION</th>
|
||||
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider text-center rounded-tr-xl">STATUS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Show when={loading()}>
|
||||
<tr><td colspan="5" class="text-center py-12 text-[#8087a0] text-sm">Loading external dashboards...</td></tr>
|
||||
</Show>
|
||||
<Show when={!loading() && dashboards().length === 0}>
|
||||
<tr><td colspan="5" class="text-center py-12 text-[#8087a0] text-sm">No external dashboards found. Create the first one.</td></tr>
|
||||
</Show>
|
||||
<For each={dashboards()}>
|
||||
{(d) => (
|
||||
<tr class="border-b border-[#e2e6ee] bg-white transition-colors hover:bg-[#f8f9fc] cursor-pointer" onClick={() => void openDashboard(d.id)}>
|
||||
<td class="px-6 py-4 text-[14px] font-bold text-[#050026]">{d.roleKey || 'No role selected'}</td>
|
||||
<td class="px-6 py-4 text-[14px] text-[#475569]">{d.title}</td>
|
||||
<td class="px-6 py-4 text-[14px] text-[#475569] text-center">{d.modules.length} pages</td>
|
||||
<td class="px-6 py-4 text-[14px] text-[#64748b]">v{d.version}</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<span class={`inline-flex items-center justify-center rounded-lg px-3 py-1 text-[12px] font-bold ${
|
||||
d.status === 'published' ? 'bg-[#e6f9ed] text-[#00c853]' : 'bg-[#f1f5f9] text-[#64748b]'
|
||||
}`}>
|
||||
{d.status === 'published' ? 'Active' : 'Draft'}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
|
|
|
|||
|
|
@ -1,26 +1,24 @@
|
|||
import { For } from 'solid-js';
|
||||
import { For, Show, createResource } from 'solid-js';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
const kpis = [
|
||||
{ title: 'Total Users', value: '12,458', delta: '+12.5%', note: '+1,245 from last month', tone: 'up' as const, icon: 'US' },
|
||||
{ title: 'Active Companies', value: '1,234', delta: '+8.2%', note: '+94 from last month', tone: 'up' as const, icon: 'CP' },
|
||||
{ title: 'Open Leads', value: '847', delta: '-3.1%', note: '-27 from last month', tone: 'down' as const, icon: 'LD' },
|
||||
{ title: 'Credits Purchased', value: '$45,890', delta: '+18.7%', note: '+$7,234 from last month', tone: 'up' as const, icon: 'CR' },
|
||||
];
|
||||
const API = '/api/gateway';
|
||||
|
||||
async function fetchMetrics() {
|
||||
const res = await fetch(`${API}/api/admin/dashboard/metrics`);
|
||||
if (!res.ok) throw new Error('Failed to fetch dashboard metrics');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
const trendSeries = [62, 70, 81, 75, 88, 102];
|
||||
const revSeries = [42000, 48000, 55000, 51000, 62000, 69000];
|
||||
const maxAmount = 80000;
|
||||
|
||||
const leadRows = [
|
||||
{ title: 'Corporate Event Photographer', customer: 'Bright Media', category: 'Photography', budget: '$3,500', status: 'New', priority: 'High' },
|
||||
{ title: 'Wedding Makeup Artist', customer: 'Aster Weddings', category: 'Makeup Artist', budget: '$1,800', status: 'In Review', priority: 'Medium' },
|
||||
{ title: 'SAT Batch Tutor', customer: 'EduPath', category: 'Tutors', budget: '$2,300', status: 'Assigned', priority: 'Low' },
|
||||
{ title: 'Personal Fitness Trainer', customer: 'Core Fitness', category: 'Fitness', budget: '$2,900', status: 'Escalated', priority: 'High' },
|
||||
{ title: 'Corporate Video Editor', customer: 'Pixel Forge', category: 'Video Editor', budget: '$4,200', status: 'New', priority: 'Critical' },
|
||||
];
|
||||
|
||||
export default function AdminDashboard() {
|
||||
const [data] = createResource(fetchMetrics);
|
||||
|
||||
const kpis = () => data()?.kpis || [];
|
||||
const trendSeries = () => data()?.trend_series?.map((d: any) => d.Freelancers) || [0, 0, 0, 0, 0, 0];
|
||||
const revSeries = () => data()?.rev_series?.map((d: any) => d.Revenue) || [0, 0, 0, 0, 0, 0];
|
||||
const leadRows = () => data()?.lead_rows || [];
|
||||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="space-y-6">
|
||||
|
|
@ -30,139 +28,147 @@ export default function AdminDashboard() {
|
|||
<h1 class="text-[40px] font-semibold leading-[1.1] text-[#050026]">Dashboard Overview</h1>
|
||||
<p class="mt-1 text-[15px] text-[#7b8099]">Welcome back! Here's what's happening with your platform today.</p>
|
||||
</div>
|
||||
<button class="inline-flex h-11 items-center rounded-2xl bg-[#050026] px-5 text-sm font-semibold text-white">
|
||||
<button class="inline-flex h-11 items-center rounded-2xl bg-[#050026] px-5 text-sm font-semibold text-white transition-colors hover:bg-[#0a0044]">
|
||||
Export Report
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="grid gap-4 xl:grid-cols-4">
|
||||
<For each={kpis}>
|
||||
{(item) => (
|
||||
<article class="rounded-3xl border border-[#d9dde6] bg-[#f7f7f8] p-5 shadow-[0_0_0_1px_rgba(0,0,0,0.01)]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-[#fff1ea] text-xs font-bold text-[#fd6116]">{item.icon}</div>
|
||||
<span
|
||||
class={`inline-flex items-center rounded-xl px-2.5 py-1 text-xs font-semibold ${
|
||||
item.tone === 'up' ? 'bg-[#ffe8dc] text-[#fd6116]' : 'bg-[#eceff6] text-[#383e5c]'
|
||||
}`}
|
||||
>
|
||||
{item.delta}
|
||||
</span>
|
||||
</div>
|
||||
<p class="mt-5 text-[15px] text-[#747a93]">{item.title}</p>
|
||||
<p class="mt-1 text-[44px] font-semibold leading-none text-[#050026]">{item.value}</p>
|
||||
<p class="mt-1 text-[14px] text-[#8a90a8]">{item.note}</p>
|
||||
</article>
|
||||
)}
|
||||
</For>
|
||||
</section>
|
||||
<Show when={data.loading}>
|
||||
<div class="flex items-center justify-center p-12 text-[#8087a0]">Loading metrics...</div>
|
||||
</Show>
|
||||
|
||||
<section class="grid gap-4 xl:grid-cols-2">
|
||||
<article class="rounded-3xl border border-[#d9dde6] bg-[#f7f7f8] p-5">
|
||||
<h2 class="text-[34px] font-semibold leading-[1.1] text-[#050026]">Leads Trend</h2>
|
||||
<p class="mt-1 text-[14px] text-[#8087a0]">Monthly leads performance overview</p>
|
||||
<div class="mt-5 rounded-2xl border border-[#e2e6ee] bg-[#f5f5f6] p-4">
|
||||
<div class="relative h-52">
|
||||
<div class="absolute inset-0">
|
||||
<For each={[0, 1, 2, 3]}>{() => <div class="h-1/4 border-b border-dashed border-[#d9dde6]" />}</For>
|
||||
<Show when={!data.loading && data()}>
|
||||
<section class="grid gap-4 xl:grid-cols-4">
|
||||
<For each={kpis()}>
|
||||
{(item: any) => (
|
||||
<article class="rounded-3xl border border-[#d9dde6] bg-[#f7f7f8] p-5 shadow-[0_0_0_1px_rgba(0,0,0,0.01)]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-[#fff1ea] text-xs font-bold text-[#fd6116]">
|
||||
{item.id === 'users' ? 'US' : item.id === 'companies' ? 'CP' : item.id === 'leads' ? 'LD' : 'CR'}
|
||||
</div>
|
||||
<span
|
||||
class={`inline-flex items-center rounded-xl px-2.5 py-1 text-xs font-semibold ${
|
||||
item.trendUp ? 'bg-[#ffe8dc] text-[#fd6116]' : 'bg-[#eceff6] text-[#383e5c]'
|
||||
}`}
|
||||
>
|
||||
{item.trend}
|
||||
</span>
|
||||
</div>
|
||||
<p class="mt-5 text-[15px] text-[#747a93]">{item.title}</p>
|
||||
<p class="mt-1 text-[44px] font-semibold leading-none text-[#050026]">{item.value}</p>
|
||||
<p class="mt-1 text-[14px] text-[#8a90a8]">{item.trendUp ? 'Increased from last month' : 'Decreased from last month'}</p>
|
||||
</article>
|
||||
)}
|
||||
</For>
|
||||
</section>
|
||||
|
||||
<section class="grid gap-4 xl:grid-cols-2">
|
||||
<article class="rounded-3xl border border-[#d9dde6] bg-[#f7f7f8] p-5">
|
||||
<h2 class="text-[34px] font-semibold leading-[1.1] text-[#050026]">Leads Trend</h2>
|
||||
<p class="mt-1 text-[14px] text-[#8087a0]">Monthly leads performance overview</p>
|
||||
<div class="mt-5 rounded-2xl border border-[#e2e6ee] bg-[#f5f5f6] p-4">
|
||||
<div class="relative h-52">
|
||||
<div class="absolute inset-0">
|
||||
<For each={[0, 1, 2, 3]}>{() => <div class="h-1/4 border-b border-dashed border-[#d9dde6]" />}</For>
|
||||
</div>
|
||||
<svg viewBox="0 0 100 40" class="relative h-full w-full overflow-visible" preserveAspectRatio="none" aria-hidden="true">
|
||||
<defs>
|
||||
<linearGradient id="trendFill" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#fd6116" stop-opacity="0.28" />
|
||||
<stop offset="100%" stop-color="#fd6116" stop-opacity="0.02" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="#050026"
|
||||
stroke-width="1.1"
|
||||
points={trendSeries().map((v: number, i: number) => `${i * 20},${40 - v / 3}`).join(' ')}
|
||||
/>
|
||||
<polygon
|
||||
fill="url(#trendFill)"
|
||||
points={`0,40 ${trendSeries().map((v: number, i: number) => `${i * 20},${40 - v / 3}`).join(' ')} 100,40`}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mt-1 grid grid-cols-6 text-center text-xs font-semibold text-[#3f4562]">
|
||||
<For each={['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']}>{(day) => <span>{day}</span>}</For>
|
||||
</div>
|
||||
<svg viewBox="0 0 100 40" class="relative h-full w-full overflow-visible" preserveAspectRatio="none" aria-hidden="true">
|
||||
<defs>
|
||||
<linearGradient id="trendFill" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#fd6116" stop-opacity="0.28" />
|
||||
<stop offset="100%" stop-color="#fd6116" stop-opacity="0.02" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="#050026"
|
||||
stroke-width="1.1"
|
||||
points={trendSeries.map((v, i) => `${i * 20},${40 - v / 3}`).join(' ')}
|
||||
/>
|
||||
<polygon
|
||||
fill="url(#trendFill)"
|
||||
points={`0,40 ${trendSeries.map((v, i) => `${i * 20},${40 - v / 3}`).join(' ')} 100,40`}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mt-1 grid grid-cols-6 text-center text-xs font-semibold text-[#3f4562]">
|
||||
<For each={['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']}>{(month) => <span>{month}</span>}</For>
|
||||
</article>
|
||||
|
||||
<article class="rounded-3xl border border-[#d9dde6] bg-[#f7f7f8] p-5">
|
||||
<h2 class="text-[34px] font-semibold leading-[1.1] text-[#050026]">Revenue Overview</h2>
|
||||
<p class="mt-1 text-[14px] text-[#8087a0]">Monthly revenue vs expenses comparison</p>
|
||||
<div class="mt-5 rounded-2xl border border-[#e2e6ee] bg-[#f5f5f6] p-4">
|
||||
<div class="relative h-52">
|
||||
<div class="absolute inset-0">
|
||||
<For each={[0, 1, 2, 3]}>{() => <div class="h-1/4 border-b border-dashed border-[#d9dde6]" />}</For>
|
||||
</div>
|
||||
<div class="relative flex h-full items-end gap-4 px-2">
|
||||
<For each={revSeries()}>
|
||||
{(value: number) => (
|
||||
<div class="flex h-full flex-1 items-end justify-center">
|
||||
<div class="w-2.5 rounded-t bg-[#050026]" style={{ height: `${(value / maxAmount) * 100}%` }} />
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1 grid grid-cols-6 text-center text-xs font-semibold text-[#3f4562]">
|
||||
<For each={['Wk 1', 'Wk 2', 'Wk 3', 'Wk 4']}>{(week) => <span>{week}</span>}</For>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="rounded-3xl border border-[#d9dde6] bg-[#f7f7f8] p-1">
|
||||
<div class="flex flex-col gap-3 px-5 py-4 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h2 class="text-[32px] font-semibold leading-[1.1] text-[#050026]">Recent Leads</h2>
|
||||
<p class="mt-1 text-[14px] text-[#8087a0]">Latest customer inquiries and opportunities</p>
|
||||
</div>
|
||||
<button class="inline-flex h-10 items-center rounded-2xl border border-[#d9dde6] bg-white px-4 text-sm font-semibold text-[#050026] hover:bg-[#f8f9fc]">
|
||||
View All Leads
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="rounded-3xl border border-[#d9dde6] bg-[#f7f7f8] p-5">
|
||||
<h2 class="text-[34px] font-semibold leading-[1.1] text-[#050026]">Revenue Overview</h2>
|
||||
<p class="mt-1 text-[14px] text-[#8087a0]">Monthly revenue vs expenses comparison</p>
|
||||
<div class="mt-5 rounded-2xl border border-[#e2e6ee] bg-[#f5f5f6] p-4">
|
||||
<div class="relative h-52">
|
||||
<div class="absolute inset-0">
|
||||
<For each={[0, 1, 2, 3]}>{() => <div class="h-1/4 border-b border-dashed border-[#d9dde6]" />}</For>
|
||||
</div>
|
||||
<div class="relative flex h-full items-end gap-4 px-2">
|
||||
<For each={revSeries}>
|
||||
{(value) => (
|
||||
<div class="flex h-full flex-1 items-end justify-center">
|
||||
<div class="w-2.5 rounded-t bg-[#050026]" style={{ height: `${(value / maxAmount) * 100}%` }} />
|
||||
</div>
|
||||
<div class="overflow-x-auto px-1 pb-1">
|
||||
<table class="w-full min-w-[860px] border-collapse overflow-hidden rounded-2xl">
|
||||
<thead>
|
||||
<tr class="bg-[#eef1f7] text-left">
|
||||
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Lead Title</th>
|
||||
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Customer</th>
|
||||
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Category</th>
|
||||
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Budget</th>
|
||||
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Status</th>
|
||||
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Date</th>
|
||||
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={leadRows()}>
|
||||
{(row: any) => (
|
||||
<tr class="border-t border-[#e1e6f0] bg-white transition-colors hover:bg-[#f8f9fc]">
|
||||
<td class="px-5 py-3 text-sm font-medium text-[#050026]">{row.service}</td>
|
||||
<td class="px-5 py-3 text-sm text-[#3c4260]">{row.client}</td>
|
||||
<td class="px-5 py-3 text-sm text-[#3c4260]">{row.service}</td>
|
||||
<td class="px-5 py-3 text-sm font-semibold text-[#050026]">{row.value}</td>
|
||||
<td class="px-5 py-3 text-sm text-[#3c4260]">{row.status}</td>
|
||||
<td class="px-5 py-3 text-sm text-[#3c4260]">{row.date}</td>
|
||||
<td class="px-5 py-3 text-sm">
|
||||
<button class="rounded-lg border border-[#d9dde6] bg-white px-3 py-1.5 font-medium text-[#050026] hover:bg-[#f8f9fc]">
|
||||
Open
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1 grid grid-cols-6 text-center text-xs font-semibold text-[#3f4562]">
|
||||
<For each={['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']}>{(month) => <span>{month}</span>}</For>
|
||||
</div>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="rounded-3xl border border-[#d9dde6] bg-[#f7f7f8] p-1">
|
||||
<div class="flex flex-col gap-3 px-5 py-4 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h2 class="text-[32px] font-semibold leading-[1.1] text-[#050026]">Recent Leads</h2>
|
||||
<p class="mt-1 text-[14px] text-[#8087a0]">Latest customer inquiries and opportunities</p>
|
||||
</div>
|
||||
<button class="inline-flex h-10 items-center rounded-2xl border border-[#d9dde6] bg-white px-4 text-sm font-semibold text-[#050026]">
|
||||
View All Leads
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto px-1 pb-1">
|
||||
<table class="w-full min-w-[860px] border-collapse overflow-hidden rounded-2xl">
|
||||
<thead>
|
||||
<tr class="bg-[#eef1f7] text-left">
|
||||
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Lead Title</th>
|
||||
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Customer</th>
|
||||
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Category</th>
|
||||
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Budget</th>
|
||||
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Status</th>
|
||||
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Priority</th>
|
||||
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={leadRows}>
|
||||
{(row) => (
|
||||
<tr class="border-t border-[#e1e6f0] bg-white">
|
||||
<td class="px-5 py-3 text-sm font-medium text-[#050026]">{row.title}</td>
|
||||
<td class="px-5 py-3 text-sm text-[#3c4260]">{row.customer}</td>
|
||||
<td class="px-5 py-3 text-sm text-[#3c4260]">{row.category}</td>
|
||||
<td class="px-5 py-3 text-sm font-semibold text-[#050026]">{row.budget}</td>
|
||||
<td class="px-5 py-3 text-sm text-[#3c4260]">{row.status}</td>
|
||||
<td class="px-5 py-3 text-sm text-[#3c4260]">{row.priority}</td>
|
||||
<td class="px-5 py-3 text-sm">
|
||||
<button class="rounded-lg border border-[#d9dde6] bg-white px-3 py-1.5 font-medium text-[#050026] hover:bg-[#f8f9fc]">
|
||||
Open
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -414,71 +414,131 @@ export default function InternalDashboardManagementPage() {
|
|||
return (
|
||||
<AdminShell>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Internal Dashboard Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Open one internal dashboard at a time from the list below and edit it using simple tabs.</p>
|
||||
</div>
|
||||
<a class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin">Back to Dashboard</a>
|
||||
</div>
|
||||
<div class="p-6 flex-1">
|
||||
|
||||
<div class="hidden" style="margin-bottom:14px">
|
||||
<a class="hidden" href="#builder">View Dashboards</a>
|
||||
<a class="hidden" href="#builder">Open Builder</a>
|
||||
</div>
|
||||
|
||||
<div id="builder" />
|
||||
|
||||
<div class="p-6 flex-1 max-w-[1600px] mx-auto w-full">
|
||||
<Show when={!selected()}>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
{/* Header & Title */}
|
||||
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between mb-8">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-gray-900">Internal Dashboard List</h2>
|
||||
<p class="mt-1 text-sm text-gray-500">Choose one internal dashboard to open in the builder, or create a new dashboard for an internal role.</p>
|
||||
<h1 class="text-[32px] font-bold text-[#050026] leading-tight">Internal Dashboard Management</h1>
|
||||
<p class="text-[15px] text-[#8087a0] mt-1">Configure dashboards for internal staff members</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button class="inline-flex h-11 items-center justify-center rounded-xl bg-[#050026] px-6 text-[14px] font-semibold text-white transition-colors hover:bg-[#0a0044]">
|
||||
Dashboard Management
|
||||
</button>
|
||||
<button class="inline-flex h-11 items-center justify-center rounded-xl border border-[#d9dde6] bg-white px-6 text-[14px] font-semibold text-[#050026] transition-colors hover:bg-[#f8f9fc]">
|
||||
Role Preview
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn-primary" onClick={createDashboard} disabled={creating()}>
|
||||
{creating() ? 'Creating...' : 'Create Internal Dashboard'}
|
||||
</button>
|
||||
</div>
|
||||
<Show when={error()}><div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div></Show>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm list-table-soft-head">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Role</th>
|
||||
<th>Role ID</th>
|
||||
<th>Dashboard</th>
|
||||
<th>Status</th>
|
||||
<th>Version</th>
|
||||
<th class="text-right">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Show when={loading()}>
|
||||
<tr><td colspan="6" style="text-align:center;padding:32px;color:#64748b">Loading internal dashboards...</td></tr>
|
||||
</Show>
|
||||
<Show when={!loading() && dashboards().length === 0}>
|
||||
<tr><td colspan="6" style="text-align:center;padding:32px;color:#94a3b8">No internal dashboards found. Create the first one.</td></tr>
|
||||
</Show>
|
||||
<For each={dashboards()}>
|
||||
{(d) => (
|
||||
<tr>
|
||||
<td style="color:#475569">{d.roleName || 'Not linked'}</td>
|
||||
<td style="color:#475569">{d.roleId || 'Not linked'}</td>
|
||||
<td style="font-weight:600;color:#0f172a">{d.title}</td>
|
||||
<td><span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${d.status === 'published' ? 'active' : ''}`}>{d.status}</span></td>
|
||||
<td style="color:#64748b">v{d.version}</td>
|
||||
<td>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => void openDashboard(d.id)}>View Builder</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* 5 KPI Cards Row */}
|
||||
<div class="grid grid-cols-2 lg:grid-cols-5 gap-4 mb-8">
|
||||
<div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
|
||||
<p class="text-[13px] text-[#8087a0] leading-snug">Total Dashboard<br/>Templates</p>
|
||||
<p class="mt-3 text-[32px] font-bold text-[#050026] leading-none">18</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
|
||||
<p class="text-[13px] text-[#8087a0] leading-snug">Active Templates</p>
|
||||
<p class="mt-3 text-[32px] font-bold text-[#00c853] leading-none">14</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
|
||||
<p class="text-[13px] text-[#8087a0] leading-snug">Draft Templates</p>
|
||||
<p class="mt-3 text-[32px] font-bold text-[#64748b] leading-none">2</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
|
||||
<p class="text-[13px] text-[#8087a0] leading-snug">Assigned Roles</p>
|
||||
<p class="mt-3 text-[32px] font-bold text-[#2962ff] leading-none">9</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
|
||||
<p class="text-[13px] text-[#8087a0] leading-snug">Unassigned Roles</p>
|
||||
<p class="mt-3 text-[32px] font-bold text-[#ff6e30] leading-none">2</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Table Section */}
|
||||
<section class="rounded-[24px] border border-[#e2e6ee] bg-[#f7f7f8] p-1.5 h-full">
|
||||
<div class="rounded-[20px] bg-white p-5">
|
||||
{/* Table Action Header */}
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between mb-6">
|
||||
<h2 class="text-[22px] font-bold text-[#050026]">Internal Dashboard<br/>Templates</h2>
|
||||
<div class="flex items-center gap-3">
|
||||
<button class="inline-flex h-[42px] items-center justify-center rounded-xl border border-[#d9dde6] bg-white px-5 text-[14px] font-semibold text-[#050026] transition-colors hover:bg-[#f8f9fc]">
|
||||
Import Layout
|
||||
</button>
|
||||
<button class="inline-flex h-[42px] items-center justify-center rounded-xl border border-[#d9dde6] bg-white px-5 text-[14px] font-semibold text-[#050026] transition-colors hover:bg-[#f8f9fc]">
|
||||
Export Config
|
||||
</button>
|
||||
<button class="inline-flex h-[42px] items-center justify-center rounded-xl bg-[#050026] px-5 text-[14px] font-semibold text-white transition-colors hover:bg-[#0a0044]" onClick={createDashboard} disabled={creating()}>
|
||||
<span class="mr-2 text-lg leading-none">+</span> {creating() ? 'Creating...' : 'Create Dashboard Template'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Error Message */}
|
||||
<Show when={error()}>
|
||||
<div class="mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
|
||||
</Show>
|
||||
|
||||
{/* Filters Row */}
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<div class="relative w-[320px]">
|
||||
<div class="absolute inset-y-0 left-4 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5 text-[#a0aabf]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search templates..."
|
||||
class="h-11 w-full rounded-xl border border-[#d9dde6] bg-[#f9fafb] pl-11 pr-4 text-[14px] text-[#050026] outline-none transition-colors focus:border-[#050026] focus:bg-white"
|
||||
/>
|
||||
</div>
|
||||
<div class="h-11 w-[200px] rounded-xl border border-[#d9dde6] bg-[#f9fafb]"></div>
|
||||
<div class="h-11 w-[200px] rounded-xl border border-[#d9dde6] bg-[#f9fafb]"></div>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full min-w-[1000px] border-collapse">
|
||||
<thead>
|
||||
<tr class="bg-[#050026] text-left text-white">
|
||||
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider rounded-tl-xl">DASHBOARD NAME</th>
|
||||
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider">ASSIGNED DEPARTMENT</th>
|
||||
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider">ASSIGNED DESIGNATION</th>
|
||||
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider">ASSIGNED INTERNAL ROLE</th>
|
||||
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider text-center">WIDGETS COUNT</th>
|
||||
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider rounded-tr-xl text-center">STATUS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Show when={loading()}>
|
||||
<tr><td colspan="6" class="text-center py-12 text-[#8087a0] text-sm">Loading templates...</td></tr>
|
||||
</Show>
|
||||
<Show when={!loading() && dashboards().length === 0}>
|
||||
<tr><td colspan="6" class="text-center py-12 text-[#8087a0] text-sm">No dashboard templates found.</td></tr>
|
||||
</Show>
|
||||
<For each={dashboards()}>
|
||||
{(d) => (
|
||||
<tr class="border-b border-[#e2e6ee] bg-white transition-colors hover:bg-[#f8f9fc] cursor-pointer" onClick={() => void openDashboard(d.id)}>
|
||||
<td class="px-6 py-4 text-[14px] font-bold text-[#050026]">{d.title}</td>
|
||||
<td class="px-6 py-4 text-[14px] text-[#475569]">Administration</td>
|
||||
<td class="px-6 py-4 text-[14px] text-[#475569]">Super Admin</td>
|
||||
<td class="px-6 py-4 text-[14px] text-[#475569]">{d.roleName || 'Unassigned'}</td>
|
||||
<td class="px-6 py-4 text-[14px] text-[#475569] text-center">{d.sections.length * 3 || 12}</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<span class={`inline-flex items-center justify-center rounded-lg px-3 py-1 text-[12px] font-bold ${
|
||||
d.status === 'published' ? 'bg-[#e6f9ed] text-[#00c853]' : 'bg-[#f1f5f9] text-[#64748b]'
|
||||
}`}>
|
||||
{d.status === 'published' ? 'Active' : 'Draft'}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue