nxtgauge-admin-solid/src/routes/admin/index.tsx

192 lines
8.7 KiB
TypeScript
Raw Normal View History

import { For, Show, createResource } from 'solid-js';
import { Download, Users, Building2, TrendingUp, CreditCard, ArrowUpRight, ArrowDownRight } from 'lucide-solid';
import AdminShell from '~/components/AdminShell';
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 maxAmount = 80000;
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">
{/* Header Section without background */}
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between px-1">
<div>
<h1 class="text-[32px] font-bold leading-tight text-[#0A1128]">Dashboard Overview</h1>
<p class="mt-1 text-[15px] text-[#64748B]">Welcome back! Here's what's happening with your platform today.</p>
</div>
<button class="inline-flex h-12 items-center justify-center gap-2 rounded-full bg-[#0A1128] px-6 text-[14px] font-semibold text-white transition-colors hover:bg-[#1E293B]">
<Download size={18} strokeWidth={2.5} />
Export Report
</button>
</div>
<Show when={data.loading}>
<div class="flex items-center justify-center p-12 text-[#64748B]">Loading metrics...</div>
</Show>
<Show when={!data.loading && data()}>
{/* KPI Cards */}
<section class="grid gap-6 xl:grid-cols-4">
<For each={kpis()}>
{(item: any) => {
const isUsers = item.id === 'users';
const isCompanies = item.id === 'companies';
const isLeads = item.id === 'leads';
const Icon = isUsers ? Users : isCompanies ? Building2 : isLeads ? TrendingUp : CreditCard;
return (
<article class="flex flex-col rounded-2xl border border-[#E2E8F0] bg-white p-6 shadow-sm">
<div class="flex items-start justify-between">
<Icon size={32} class="text-[#FA5A1F]" strokeWidth={2} />
<div
class={`flex items-center gap-1 rounded-full px-2.5 py-1 text-[13px] font-bold ${
item.trendUp ? 'bg-[#FFF5F0] text-[#FA5A1F]' : 'bg-[#F1F5F9] text-[#0A1128]'
}`}
>
<Show when={item.trendUp} fallback={<ArrowDownRight size={14} strokeWidth={2.5} />}>
<ArrowUpRight size={14} strokeWidth={2.5} />
</Show>
{item.trend}
</div>
</div>
<div class="mt-4">
<p class="text-[14px] text-[#64748B]">{item.title}</p>
<p class="mt-2 text-[32px] font-bold tracking-tight text-[#0A1128]">{item.value}</p>
</div>
<p class="mt-3 text-[13px] text-[#94A3B8]">
<span class={`font-medium ${item.trendUp ? 'text-[#FA5A1F]' : 'text-[#64748B]'}`}>
{item.trendUp ? '+1,245' : '-27'}
</span>{' '}
from last month
</p>
</article>
);
}}
</For>
</section>
{/* Charts Row */}
<section class="grid gap-6 xl:grid-cols-2">
{/* Leads Trend Card */}
<article class="rounded-2xl border border-[#E2E8F0] bg-white p-6 shadow-sm">
<h2 class="text-[20px] font-bold text-[#0A1128]">Leads Trend</h2>
<p class="mt-1 text-[14px] text-[#64748B]">Monthly leads performance overview</p>
<div class="mt-8">
<div class="relative h-[250px]">
{/* Y-Axis labels and grid */}
<div class="absolute inset-0 flex flex-col justify-between pt-1 pb-6">
<For each={[120, 90, 60, 30, 0]}>
{(val) => (
<div class="flex items-center gap-4">
<span class="w-8 text-right text-[12px] font-bold text-[#0A1128]">{val}</span>
<div class="h-px flex-1 border-b border-dashed border-[#E2E8F0]" />
</div>
)}
</For>
</div>
{/* Line Chart Component */}
<div class="absolute inset-0 ml-12 mb-6">
<svg viewBox="0 0 100 40" class="h-full w-full overflow-visible" preserveAspectRatio="none">
<defs>
<linearGradient id="trendFill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0A1128" stop-opacity="0.3" />
<stop offset="100%" stop-color="#0A1128" stop-opacity="0.0" />
</linearGradient>
</defs>
<polygon
fill="url(#trendFill)"
points={`0,40 ${trendSeries().map((v: number, i: number) => `${i * (100 / 6)},${40 - v / 3}`).join(' ')} 100,40`}
/>
<polyline
fill="none"
stroke="#0A1128"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
points={trendSeries().map((v: number, i: number) => `${i * (100 / 6)},${40 - v / 3}`).join(' ')}
/>
</svg>
</div>
{/* X-Axis labels */}
<div class="absolute bottom-0 left-12 right-0 flex justify-between px-2 text-[12px] font-bold text-[#0A1128]">
<For each={['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']}>
{(month) => <span>{month}</span>}
</For>
</div>
</div>
</div>
</article>
{/* Revenue Overview Card */}
<article class="rounded-2xl border border-[#E2E8F0] bg-white p-6 shadow-sm">
<h2 class="text-[20px] font-bold text-[#0A1128]">Revenue Overview</h2>
<p class="mt-1 text-[14px] text-[#64748B]">Monthly revenue vs expenses comparison</p>
<div class="mt-8">
<div class="relative h-[250px]">
{/* Y-Axis labels and grid */}
<div class="absolute inset-0 flex flex-col justify-between pt-1 pb-6">
<For each={[80000, 60000, 40000, 20000, 0]}>
{(val) => (
<div class="flex items-center gap-4">
<span class="w-12 text-right text-[12px] font-bold text-[#0A1128]">{val}</span>
<div class="h-px flex-1 border-b border-dashed border-[#E2E8F0]" />
</div>
)}
</For>
</div>
{/* Bar Chart Component */}
<div class="absolute inset-0 ml-16 mb-6 mt-1 flex items-end justify-between px-4">
<For each={revSeries()}>
{(value: number) => (
<div class="group relative flex h-full w-full justify-center">
<div
class="absolute bottom-0 w-4 rounded-t-full bg-[#0A1128] transition-all hover:bg-[#FA5A1F]"
style={{ height: `${(value / maxAmount) * 100}%` }}
/>
</div>
)}
</For>
</div>
{/* X-Axis labels */}
<div class="absolute bottom-0 left-16 right-0 flex justify-between px-4 text-[12px] font-bold text-[#0A1128]">
<For each={['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']}>
{(month) => <span class="w-4 text-center">{month}</span>}
</For>
</div>
</div>
</div>
</article>
</section>
</Show>
</div>
</AdminShell>
);
}