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

268 lines
11 KiB
TypeScript
Raw Normal View History

import { For, onCleanup, onMount } from 'solid-js';
import { A } from '@solidjs/router';
import { Users, Building2, TrendingUp, CreditCard, Download, Eye, Pencil, Trash2 } from 'lucide-solid';
import AdminShell from '~/components/AdminShell';
type StatDef = {
id: string;
label: string;
icon: any;
value: string;
delta: string;
deltaPositive?: boolean;
subtext: string;
};
const STAT_DEFS: StatDef[] = [
{
id: 'total-users',
label: 'Total Users',
icon: Users,
value: '12,458',
delta: '+12.5%',
deltaPositive: true,
subtext: '+1,245 from last month',
},
{
id: 'active-companies',
label: 'Active Companies',
icon: Building2,
value: '1,234',
delta: '+8.2%',
deltaPositive: true,
subtext: '+94 from last month',
},
{
id: 'open-leads',
label: 'Open Leads',
icon: TrendingUp,
value: '847',
delta: '-3.1%',
deltaPositive: false,
subtext: '-27 from last month',
},
{
id: 'credits-purchased',
label: 'Credits Purchased',
icon: CreditCard,
value: '$45,890',
delta: '+18.7%',
deltaPositive: true,
subtext: '+$7,234 from last month',
},
];
type RecentLead = {
title: string;
customer: string;
category: string;
budget: string;
status: 'Active' | 'Pending' | 'Negotiating';
};
const RECENT_LEADS: RecentLead[] = [
{ title: 'Website Redesign Project', customer: 'TechCorp Inc.', category: 'Developers', budget: '$15,000', status: 'Active' },
{ title: 'Corporate Event Photography', customer: 'EventMasters LLC', category: 'Photographer', budget: '$3,500', status: 'Pending' },
{ title: 'Marketing Campaign Design', customer: 'BrandHub Co.', category: 'Graphics Designer', budget: '$8,200', status: 'Active' },
{ title: 'Social Media Management', customer: 'GrowthStart', category: 'Social Media Manager', budget: '$5,000', status: 'Negotiating' },
];
function classForStatus(status: RecentLead['status']) {
if (status === 'Active') return 'bg-[rgba(250,80,20,0.1)] border-[rgba(250,80,20,0.2)] text-[#fa5014]';
if (status === 'Pending') return 'bg-[rgba(0,0,50,0.1)] border-[rgba(0,0,50,0.2)] text-[#000032]';
return 'bg-[#f9fafb] border-[#e5e7eb] text-[rgba(0,0,50,0.6)]';
}
function MiniChart(props: { type: 'line' | 'bar' }) {
let el!: HTMLDivElement;
let chart: any = null;
onCleanup(() => { chart?.destroy(); chart = null; });
onMount(async () => {
const { default: ApexCharts } = await import('apexcharts');
if (!el) return;
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'];
const isLine = props.type === 'line';
chart = new ApexCharts(el, {
chart: {
type: props.type,
height: 240,
toolbar: { show: false },
fontFamily: 'Exo 2, sans-serif',
},
grid: {
borderColor: '#e5e7eb',
strokeDashArray: 4,
xaxis: { lines: { show: false } },
},
xaxis: {
categories: months,
labels: { style: { colors: '#000032', fontSize: '11px', fontWeight: 700 } },
},
yaxis: {
labels: { style: { colors: '#000032', fontSize: '11px', fontWeight: 700 } },
},
tooltip: { theme: 'light' },
legend: { show: false },
dataLabels: { enabled: false },
...(isLine
? {
series: [{ name: 'Total Leads', data: [65, 75, 90, 80, 95, 110] }],
stroke: { width: 3, curve: 'smooth', colors: ['#000032'] },
fill: {
type: 'gradient',
gradient: {
shadeIntensity: 1,
opacityFrom: 0.2,
opacityTo: 0.01,
colorStops: [{ offset: 0, color: '#000032', opacity: 0.16 }, { offset: 100, color: '#000032', opacity: 0.01 }],
},
},
markers: { size: 0 },
colors: ['#000032'],
yaxis: { min: 0, max: 120, tickAmount: 4, labels: { style: { colors: '#000032', fontSize: '11px', fontWeight: 700 } } },
}
: {
series: [{ name: 'Revenue', data: [42000, 48000, 55000, 51000, 62000, 69000] }],
plotOptions: { bar: { columnWidth: '38%', borderRadius: 4 } },
colors: ['#000032'],
yaxis: { min: 0, max: 80000, tickAmount: 4, labels: { formatter: (v: number) => `${v}` } },
}),
});
await chart.render();
});
return <div ref={el!} />;
}
function StatCard(props: { def: StatDef }) {
const Icon = props.def.icon;
return (
<div class="rounded-2xl border-2 border-[#e5e7eb] bg-white px-[22px] pb-[22px] pt-[22px]">
<div class="mb-3 flex items-start justify-between">
<div class="flex h-10 w-10 items-center justify-center rounded-xl bg-[rgba(250,80,20,0.06)]">
<Icon size={22} class="text-[#fa5014]" />
</div>
<div class={`flex h-6 items-center gap-1 rounded-[10px] px-[10px] text-[12px] font-bold ${props.def.deltaPositive ? 'bg-[rgba(250,80,20,0.1)] text-[#fa5014]' : 'bg-[rgba(0,0,50,0.1)] text-[#000032]'}`}>
<span>{props.def.deltaPositive ? '↗' : '↘'}</span>
<span>{props.def.delta}</span>
</div>
</div>
<p class="text-[12px] font-medium leading-4 text-[rgba(0,0,50,0.6)]">{props.def.label}</p>
<p class="mt-1 text-[24px] font-bold leading-[32px] text-[#000032]">
{props.def.value}
</p>
<p class="text-[12px] leading-4 text-[rgba(0,0,50,0.5)]">{props.def.subtext}</p>
</div>
);
}
export default function AdminDashboard() {
const handleExport = () => window.print();
return (
<AdminShell>
<div class="space-y-6">
<div class="flex items-center justify-between">
<div>
<h1 class="text-[24px] font-bold leading-8 text-[#000032]">Dashboard Overview</h1>
<p class="mt-1 text-[12px] leading-4 text-[rgba(0,0,50,0.6)]">
Welcome back! Here's what's happening with your platform today.
</p>
</div>
<button
type="button"
onClick={handleExport}
class="inline-flex h-10 items-center gap-2 rounded-2xl bg-[#000032] px-5 text-[14px] font-medium text-white"
>
<Download size={16} />
Export Report
</button>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<For each={STAT_DEFS}>{(def) => <StatCard def={def} />}</For>
</div>
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
<section class="rounded-2xl border-2 border-[#e5e7eb] bg-white px-[22px] pb-[22px] pt-[22px] shadow-[0px_1px_3px_0px_rgba(0,0,0,0.1),0px_1px_2px_0px_rgba(0,0,0,0.1)]">
<h2 class="text-[18px] font-bold leading-7 text-[#000032]">Leads Trend</h2>
<p class="text-[12px] leading-4 text-[rgba(0,0,50,0.6)]">Monthly leads performance overview</p>
<div class="mt-4">
<MiniChart type="line" />
</div>
</section>
<section class="rounded-2xl border-2 border-[#e5e7eb] bg-white px-[22px] pb-[22px] pt-[22px] shadow-[0px_1px_3px_0px_rgba(0,0,0,0.1),0px_1px_2px_0px_rgba(0,0,0,0.1)]">
<h2 class="text-[18px] font-bold leading-7 text-[#000032]">Revenue Overview</h2>
<p class="text-[12px] leading-4 text-[rgba(0,0,50,0.6)]">Monthly revenue vs expenses comparison</p>
<div class="mt-4">
<MiniChart type="bar" />
</div>
</section>
</div>
<section class="overflow-hidden rounded-2xl border-2 border-[#e5e7eb] bg-white shadow-[0px_1px_3px_0px_rgba(0,0,0,0.1),0px_1px_2px_-1px_rgba(0,0,0,0.1)]">
<div class="flex h-20 items-center justify-between border-b-2 border-[#e5e7eb] bg-gradient-to-r from-white to-[#f9fafb] px-6">
<div>
<h2 class="text-[18px] font-bold leading-7 text-[#000032]">Recent Leads</h2>
<p class="text-[12px] leading-4 text-[rgba(0,0,50,0.6)]">Latest customer inquiries and opportunities</p>
</div>
<A href="/admin/leads" class="inline-flex h-9 items-center rounded-2xl bg-[#000032] px-5 text-[12px] font-semibold text-white">
View All Leads
</A>
</div>
<div class="overflow-x-auto">
<table class="w-full min-w-[760px] border-collapse">
<thead>
<tr class="bg-[#f9fafb]">
<th class="px-6 py-3 text-left text-[12px] font-bold uppercase tracking-[0.6px] text-[rgba(0,0,50,0.6)]">Lead Title</th>
<th class="px-6 py-3 text-left text-[12px] font-bold uppercase tracking-[0.6px] text-[rgba(0,0,50,0.6)]">Customer</th>
<th class="px-6 py-3 text-left text-[12px] font-bold uppercase tracking-[0.6px] text-[rgba(0,0,50,0.6)]">Category</th>
<th class="px-6 py-3 text-left text-[12px] font-bold uppercase tracking-[0.6px] text-[rgba(0,0,50,0.6)]">Budget</th>
<th class="px-6 py-3 text-left text-[12px] font-bold uppercase tracking-[0.6px] text-[rgba(0,0,50,0.6)]">Status</th>
<th class="px-6 py-3 text-left text-[12px] font-bold uppercase tracking-[0.6px] text-[rgba(0,0,50,0.6)]">Actions</th>
</tr>
</thead>
<tbody>
<For each={RECENT_LEADS}>
{(lead) => (
<tr class="border-t border-[#e5e7eb]">
<td class="px-6 py-4 text-[14px] font-semibold leading-5 text-[#000032]">{lead.title}</td>
<td class="px-6 py-4 text-[14px] text-[rgba(0,0,50,0.8)]">{lead.customer}</td>
<td class="px-6 py-4 text-[14px] text-[rgba(0,0,50,0.6)]">{lead.category}</td>
<td class="px-6 py-4 text-[14px] font-bold text-[#000032]">{lead.budget}</td>
<td class="px-6 py-4">
<span class={`inline-flex h-[30px] items-center rounded-[10px] border px-3 text-[12px] font-bold ${classForStatus(lead.status)}`}>{lead.status}</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-1.5">
<button type="button" class="inline-flex h-8 w-8 items-center justify-center rounded-[10px] text-[#71759a] hover:bg-[#f9fafb]" aria-label="View">
<Eye size={16} />
</button>
<button type="button" class="inline-flex h-8 w-8 items-center justify-center rounded-[10px] text-[#71759a] hover:bg-[#f9fafb]" aria-label="Edit">
<Pencil size={16} />
</button>
<button type="button" class="inline-flex h-8 w-8 items-center justify-center rounded-[10px] text-[#71759a] hover:bg-[#f9fafb]" aria-label="Delete">
<Trash2 size={16} />
</button>
</div>
</td>
</tr>
)}
</For>
</tbody>
</table>
</div>
</section>
</div>
</AdminShell>
);
}