Tech4biz-channel/Channel-Frontend/src/features/admin-analytics/components/AnalyticsDashboard.tsx
2026-07-09 09:31:15 +05:30

314 lines
12 KiB
TypeScript

import React, { useState, useEffect } from "react";
import type { User, Asset } from "../../../types";
import { apiClient } from "../../../lib/api-client";
import {
Users,
FileStack,
DownloadCloud,
AlertTriangle,
Cpu,
Terminal,
Brain,
Server,
ShieldCheck,
} from "lucide-react";
export const AnalyticsDashboard: React.FC = () => {
const [clients, setClients] = useState<User[]>([]);
const [assets, setAssets] = useState<Asset[]>([]);
const [loading, setLoading] = useState(true);
const fetchStats = async () => {
setLoading(true);
try {
const clientRes = await apiClient.get<User[]>("/admin/clients");
const assetRes = await apiClient.get<Asset[]>("/assets");
setClients(clientRes.data);
setAssets(assetRes.data);
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchStats();
}, []);
// Compute analytics data
const totalClients = clients.length;
const totalAssets = assets.length;
const totalDownloads = assets.reduce((sum, a) => sum + a.downloadsCount, 0);
const pendingApprovals = clients.filter(
(c) => c.onboardingStatus === "PENDING_APPROVAL",
).length;
// Category counts
const categoryCounts = assets.reduce(
(acc, a) => {
acc[a.categoryId] = (acc[a.categoryId] || 0) + 1;
return acc;
},
{ silicon: 0, software: 0, ai: 0, cloud: 0 } as Record<string, number>,
);
const categoryMax = Math.max(...Object.values(categoryCounts), 1);
// Onboarding Status distributions
const onboardingCounts = clients.reduce(
(acc, c) => {
acc[c.onboardingStatus] = (acc[c.onboardingStatus] || 0) + 1;
return acc;
},
{
NOT_STARTED: 0,
FORM_COMPLETED: 0,
NDA_SIGNED: 0,
PENDING_APPROVAL: 0,
APPROVED: 0,
REJECTED: 0,
} as Record<string, number>,
);
const onboardingMax = Math.max(...Object.values(onboardingCounts), 1);
return (
<div className="space-y-6">
<div>
<h2 className="text-xl font-bold text-ink-800">
Operational Analytics
</h2>
<p className="text-sm text-ink-600">
Overview of client onboarding metrics, asset repository distribution,
and core library downloads.
</p>
</div>
{loading ? (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{[1, 2, 3, 4].map((n) => (
<div
key={n}
className="h-28 animate-pulse rounded-xl bg-white border border-ink-100"
/>
))}
</div>
) : (
/* Metric Cards */
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-4">
<div className="rounded-xl border border-ink-100 bg-white p-5 shadow-sm flex items-center gap-4 hover:shadow-premium transition-all duration-300">
<div className="rounded-lg bg-primary-50 p-3 text-primary-700">
<Users className="h-6 w-6" />
</div>
<div>
<p className="text-[10px] font-bold uppercase tracking-wider text-ink-600">
Total Clients
</p>
<p className="text-2xl font-bold text-ink-800">{totalClients}</p>
</div>
</div>
<div className="rounded-xl border border-ink-100 bg-white p-5 shadow-sm flex items-center gap-4 hover:shadow-premium transition-all duration-300">
<div className="rounded-lg bg-primary-50 p-3 text-primary-700">
<FileStack className="h-6 w-6" />
</div>
<div>
<p className="text-[10px] font-bold uppercase tracking-wider text-ink-600">
Active Assets
</p>
<p className="text-2xl font-bold text-ink-800">{totalAssets}</p>
</div>
</div>
<div className="rounded-xl border border-ink-100 bg-white p-5 shadow-sm flex items-center gap-4 hover:shadow-premium transition-all duration-300">
<div className="rounded-lg bg-primary-50 p-3 text-primary-700">
<DownloadCloud className="h-6 w-6" />
</div>
<div>
<p className="text-[10px] font-bold uppercase tracking-wider text-ink-600">
Total Downloads
</p>
<p className="text-2xl font-bold text-ink-800">
{totalDownloads}
</p>
</div>
</div>
<div className="rounded-xl border border-ink-100 bg-white p-5 shadow-sm flex items-center gap-4 hover:shadow-premium transition-all duration-300">
<div
className={`rounded-lg p-3 ${pendingApprovals > 0 ? "bg-warning/10 text-warning" : "bg-success/10 text-success"}`}
>
<AlertTriangle className="h-6 w-6" />
</div>
<div>
<p className="text-[10px] font-bold uppercase tracking-wider text-ink-600">
Pending Review
</p>
<p className="text-2xl font-bold text-ink-800">
{pendingApprovals}
</p>
</div>
</div>
</div>
)}
{/* Charts Grid */}
<div className="grid gap-6 md:grid-cols-2">
{/* Category Breakdown */}
<div className="rounded-xl border border-ink-100 bg-white p-5 shadow-sm space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-sm font-bold text-ink-800">
Assets by Domain Category
</h3>
<span className="text-[10px] uppercase font-bold tracking-wider text-ink-600">
Count
</span>
</div>
{loading ? (
<div className="space-y-4 py-6">
{[1, 2, 3].map((n) => (
<div key={n} className="h-8 animate-pulse rounded bg-ink-50" />
))}
</div>
) : (
<div className="space-y-4 py-2">
{[
{ key: "silicon", name: "Silicon Core IP", icon: Cpu },
{
key: "software",
name: "Software & Libraries",
icon: Terminal,
},
{ key: "ai", name: "AI & Agent Workflows", icon: Brain },
{ key: "cloud", name: "Cloud Configs & IaC", icon: Server },
].map((item) => {
const count = categoryCounts[item.key] || 0;
const percentage = (count / categoryMax) * 100;
const Icon = item.icon;
return (
<div key={item.key} className="space-y-1">
<div className="flex items-center justify-between text-xs">
<span className="flex items-center gap-1.5 font-semibold text-ink-700">
<Icon className="h-4 w-4 text-primary-700" />
{item.name}
</span>
<span className="font-mono font-bold text-ink-800">
{count}
</span>
</div>
<div className="h-3.5 w-full rounded-full bg-ink-50 overflow-hidden border border-ink-100/50">
<div
className="h-full rounded-full bg-gradient-to-r from-primary-200 to-primary-400 transition-all duration-500"
style={{ width: `${percentage}%` }}
/>
</div>
</div>
);
})}
</div>
)}
</div>
{/* Client Onboarding Funnel */}
<div className="rounded-xl border border-ink-100 bg-white p-5 shadow-sm space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-sm font-bold text-ink-800">
Client Onboarding Funnel
</h3>
<span className="text-[10px] uppercase font-bold tracking-wider text-ink-600">
Accounts
</span>
</div>
{loading ? (
<div className="space-y-4 py-6">
{[1, 2, 3].map((n) => (
<div key={n} className="h-8 animate-pulse rounded bg-ink-50" />
))}
</div>
) : (
<div className="space-y-4 py-2">
{[
{ key: "NOT_STARTED", name: "Registration Initiated" },
{ key: "FORM_COMPLETED", name: "Profile Form Saved" },
{ key: "NDA_SIGNED", name: "NDA Document Signed" },
{ key: "PENDING_APPROVAL", name: "Awaiting Admin Approval" },
{ key: "APPROVED", name: "Approved & Active Portal" },
].map((item) => {
const count = onboardingCounts[item.key] || 0;
const percentage = (count / onboardingMax) * 100;
return (
<div key={item.key} className="space-y-1">
<div className="flex items-center justify-between text-xs">
<span className="font-semibold text-ink-700">
{item.name}
</span>
<span className="font-mono font-bold text-ink-800">
{count}
</span>
</div>
<div className="h-3.5 w-full rounded-full bg-ink-50 overflow-hidden border border-ink-100/50">
<div
className={`h-full rounded-full transition-all duration-500 ${
item.key === "APPROVED"
? "bg-success/70"
: item.key === "PENDING_APPROVAL"
? "bg-warning/70"
: "bg-primary-300"
}`}
style={{ width: `${percentage}%` }}
/>
</div>
</div>
);
})}
</div>
)}
</div>
</div>
{/* Bottom list showing Top Downloaded Assets */}
<div className="rounded-xl border border-ink-100 bg-white p-5 shadow-sm space-y-4">
<h3 className="text-sm font-bold text-ink-800">
Most Downloaded Catalog Resources
</h3>
<div className="divide-y divide-ink-50">
{assets
.slice()
.sort((a, b) => b.downloadsCount - a.downloadsCount)
.slice(0, 3)
.map((asset, index) => (
<div
key={asset.id}
className="flex items-center justify-between py-2.5 first:pt-0 last:pb-0"
>
<div className="flex items-center gap-3">
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-primary-50 text-xs font-bold text-primary-800 border border-primary-100">
{index + 1}
</span>
<div>
<p className="text-xs font-bold text-ink-800">
{asset.title}
</p>
<p className="text-[10px] text-ink-600 font-semibold">
{asset.subcategory}
</p>
</div>
</div>
<div className="flex items-center gap-1.5 text-xs text-ink-700 font-mono">
<ShieldCheck className="h-4 w-4 text-success" />
<span className="font-bold">{asset.downloadsCount}</span>{" "}
downloads
</div>
</div>
))}
</div>
</div>
</div>
);
};