udpates on the ui changesand api integration

This commit is contained in:
Gokul
2026-06-09 11:25:29 +05:30
parent 7dbae96b5f
commit 9f25c5f60a
26 changed files with 4324 additions and 2639 deletions

View File

@@ -5,15 +5,15 @@
import React from 'react';
import {
ShoppingBag,
PackageCheck,
Wallet,
TrendingUp,
Store,
MapPin,
Phone,
Sparkles,
AlertTriangle,
Activity,
Clock,
ArrowUpRight,
} from 'lucide-react';
import { useOrderSummary, useTenantInfo, useTenantLocations, useInvoiceInsight } from '../services/queries';
import { DEFAULT_TENANT_ID, DEFAULT_CONFIG_ID } from '../services/api';
@@ -81,10 +81,42 @@ export default function DashboardView({ searchQuery }: DashboardViewProps) {
const dashOffset = circumference - (circumference * activePct) / 100;
const kpis = [
{ title: 'ACTIVE OUTLETS', display: `${activeStoresCount} / ${totalStoresCount}`, icon: Store, chip: 'bg-purple-50 text-[#581c87]', loading: locationsQ.isLoading },
{ title: 'REGION FULFILLMENT', display: regionFulfillmentPct == null ? '—' : `${regionFulfillmentPct.toFixed(1)}%`, icon: Sparkles, chip: 'bg-emerald-50 text-emerald-600', loading: summaryQ.isLoading },
{ title: 'MONTHLY REVENUE', display: money(monthlyRevenue), icon: Wallet, chip: 'bg-sky-50 text-sky-600', loading: insightQ.isLoading },
{ title: 'MONTHLY PROFIT', display: money(monthlyProfit), icon: TrendingUp, chip: 'bg-emerald-50 text-emerald-600', loading: insightQ.isLoading },
{
title: 'ACTIVE OUTLETS',
display: `${activeStoresCount} / ${totalStoresCount}`,
sub: `${activePct}% of the network is live`,
icon: Store,
bar: 'from-purple-500 to-indigo-500',
chip: 'bg-purple-50 text-purple-650 ring-purple-100',
loading: locationsQ.isLoading,
},
{
title: 'REGION FULFILLMENT',
display: regionFulfillmentPct == null ? '—' : `${regionFulfillmentPct.toFixed(1)}%`,
sub: `${ordersDelivered.toLocaleString('en-IN')} of ${ordersTotal.toLocaleString('en-IN')} orders delivered`,
icon: Activity,
bar: 'from-indigo-500 to-sky-500',
chip: 'bg-indigo-50 text-indigo-600 ring-indigo-100',
loading: summaryQ.isLoading,
},
{
title: 'MONTHLY REVENUE',
display: money(monthlyRevenue),
sub: 'Gross billed · month-to-date',
icon: Wallet,
bar: 'from-sky-500 to-cyan-500',
chip: 'bg-sky-50 text-sky-600 ring-sky-100',
loading: insightQ.isLoading,
},
{
title: 'MONTHLY PROFIT',
display: money(monthlyProfit),
sub: 'Net margin · month-to-date',
icon: TrendingUp,
bar: 'from-emerald-500 to-teal-500',
chip: 'bg-emerald-50 text-emerald-600 ring-emerald-100',
loading: insightQ.isLoading,
},
];
const statusRows = [
@@ -97,35 +129,57 @@ export default function DashboardView({ searchQuery }: DashboardViewProps) {
return (
<div className="space-y-lg animate-in fade-in duration-500 relative">
{/* Scope banner */}
<div className="bg-[#faf5ff] border border-purple-100 rounded-xl p-md flex items-center justify-between shadow-[0_1px_3px_rgba(0,0,0,0.05)]">
<div className="flex items-center gap-sm">
<Sparkles size={16} className="text-[#581c87]" />
<span className="font-sans text-xs text-zinc-700 font-medium">
Live operations data for <strong>{tenantName}</strong> · {fromdate} {todate}
</span>
{/* ── Immersive Executive Banner (cover image + slate→purple gradient overlay) ── */}
<div className="relative rounded-2xl p-6 md:p-8 text-white shadow-xl border border-purple-500/20 overflow-hidden animate-in fade-in duration-300">
{/* Cover image background & decorative glow */}
<div className="absolute inset-0 z-0 overflow-hidden rounded-2xl">
<img
src="https://images.unsplash.com/photo-1551288049-bebda4e38f71?auto=format&fit=crop&w=1400&q=80"
alt="Executive operations dashboard"
className="w-full h-full object-cover object-center opacity-40"
/>
<div className="absolute inset-0 bg-gradient-to-r from-slate-950 via-slate-900/90 to-purple-950/80" />
<div className="absolute top-0 right-0 w-72 h-72 bg-purple-500/10 rounded-full blur-3xl -mr-20 -mt-20 pointer-events-none" />
<div className="absolute bottom-0 left-0 w-56 h-56 bg-indigo-500/10 rounded-full blur-2xl -ml-20 -mb-20 pointer-events-none" />
</div>
</div>
{/* Header */}
<div className="flex justify-between items-end">
<div>
<h1 className="font-sans font-bold text-3xl tracking-tight text-[#0f172a]">Executive Command Center</h1>
<div className="flex items-center gap-2 mt-1">
<p className="text-zinc-500 font-sans text-sm">Month-to-date order operations, pulled live from the API.</p>
{loading ? (
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-zinc-400 uppercase tracking-wide">
<span className="w-1.5 h-1.5 rounded-full bg-zinc-300 animate-pulse" /> Loading
{/* Content row */}
<div className="relative z-10 flex flex-col md:flex-row md:items-center justify-between gap-lg">
<div>
<h1 className="font-sans font-bold text-2xl md:text-3xl tracking-tight text-white flex items-center gap-2.5">
Executive Command Center
<span className="text-[10px] text-purple-200 font-bold bg-purple-900/60 border border-purple-500/30 px-2 py-0.5 rounded-full uppercase tracking-wider animate-pulse">
Live Core
</span>
) : errored ? (
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-rose-600 uppercase tracking-wide" title="Restart the dev server so the /hasura proxy is active.">
<span className="w-1.5 h-1.5 rounded-full bg-rose-500" /> Live data unavailable
</span>
) : (
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-emerald-600 uppercase tracking-wide">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500" /> Live · {tenantName}
</span>
)}
</h1>
<p className="text-slate-300 font-sans text-sm mt-2 leading-relaxed whitespace-nowrap">
Month-to-date order operations for <strong className="text-white font-semibold">{tenantName}</strong>, pulled live from the API.
</p>
<div className="mt-4">
{loading ? (
<span className="inline-flex items-center gap-1.5 text-[11px] font-bold text-slate-300 uppercase tracking-wide">
<span className="w-2 h-2 rounded-full bg-slate-400 animate-pulse" /> Syncing live data
</span>
) : errored ? (
<span className="inline-flex items-center gap-1.5 text-[11px] font-bold text-rose-300 uppercase tracking-wide" title="Restart the dev server so the /hasura proxy is active.">
<span className="w-2 h-2 rounded-full bg-rose-400" /> Live data unavailable
</span>
) : (
<span className="inline-flex items-center gap-1.5 text-[11px] font-bold text-emerald-300 uppercase tracking-wide">
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse" /> Live · {tenantName}
</span>
)}
</div>
</div>
{/* Reporting scope panel */}
<div className="flex flex-col items-start md:items-end gap-2 shrink-0">
<div className="inline-flex items-center gap-2 bg-white/10 backdrop-blur-md border border-white/15 rounded-xl px-3.5 py-2.5 shadow-sm">
<Clock size={14} className="text-purple-300" />
<span className="text-xs font-bold font-mono text-white tracking-tight">{fromdate} {todate}</span>
</div>
<span className="text-[10px] text-slate-400 uppercase tracking-widest font-bold">Month-to-date reporting scope</span>
</div>
</div>
</div>
@@ -144,23 +198,31 @@ export default function DashboardView({ searchQuery }: DashboardViewProps) {
</div>
)}
{/* KPI cards — all live from getordersummary */}
{/* KPI cards — all live from getordersummary / getinvoiceinsight */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-gutter">
{kpis.map((kpi) => {
const Icon = kpi.icon;
return (
<div
key={kpi.title}
className="group relative flex flex-col bg-white border border-[#eceef2] rounded-xl p-3 shadow-[0_1px_2px_rgba(16,24,40,0.04)] transition-all duration-200 hover:-translate-y-0.5 hover:border-purple-300 hover:shadow-[0_8px_22px_rgba(16,24,40,0.08)]"
className="group relative flex flex-col overflow-hidden bg-white border border-slate-200/70 rounded-2xl p-5 shadow-[0_1px_2px_rgba(16,24,40,0.04)] transition-all duration-300 hover:-translate-y-1 hover:border-purple-200 hover:shadow-[0_16px_36px_rgba(16,24,40,0.10)]"
>
<div className={`h-7 w-7 rounded-lg flex items-center justify-center ${kpi.chip}`}>
<Icon size={14} />
{/* Gradient accent bar */}
<span className={`absolute inset-x-0 top-0 h-1 bg-gradient-to-r ${kpi.bar}`} />
<div className="flex items-start justify-between">
<div className={`h-11 w-11 rounded-xl flex items-center justify-center ring-1 group-hover:scale-110 transition-transform duration-300 ${kpi.chip}`}>
<Icon size={19} />
</div>
<ArrowUpRight size={16} className="text-slate-300 group-hover:text-purple-400 transition-colors" />
</div>
<p className="text-[10px] font-semibold text-zinc-400 tracking-wider uppercase font-sans mt-3">
<p className="text-[10px] font-bold text-slate-400 tracking-widest uppercase font-sans mt-4">
{kpi.title}
</p>
<p className="font-sans font-bold text-2xl leading-tight text-[#0f172a] tracking-tight mt-0.5">
{kpi.loading ? <span className="text-zinc-300">…</span> : kpi.display}
<p className="font-sans font-extrabold text-[28px] leading-tight text-slate-900 tracking-tight mt-1">
{kpi.loading ? <span className="text-slate-300">…</span> : kpi.display}
</p>
<p className="text-[11px] text-slate-400 font-medium mt-1.5 leading-snug">
{kpi.sub}
</p>
</div>
);
@@ -170,115 +232,140 @@ export default function DashboardView({ searchQuery }: DashboardViewProps) {
{/* Order status + store locations */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-gutter">
{/* Store Node Status donut (live) */}
<div className="bg-white border border-[#e2e8f0] rounded-xl p-md flex flex-col shadow-[0_1px_3px_rgba(0,0,0,0.05)]">
<div>
<h3 className="font-sans font-bold text-base text-[#0f172a]">Store Outlet Status</h3>
<p className="text-zinc-500 text-xs font-sans mt-0.5">Active share of all registered store nodes.</p>
<div className="bg-white border border-slate-200/70 rounded-2xl p-lg flex flex-col shadow-[0_1px_3px_rgba(16,24,40,0.05)]">
<div className="flex items-start gap-2.5">
<div className="h-9 w-9 rounded-xl bg-emerald-50 text-emerald-600 ring-1 ring-emerald-100 flex items-center justify-center shrink-0">
<Activity size={17} />
</div>
<div>
<h3 className="font-sans font-bold text-base text-slate-900 tracking-tight">Store Outlet Status</h3>
<p className="text-slate-500 text-xs font-sans mt-0.5">Active share of all registered nodes.</p>
</div>
</div>
<div className="my-md flex justify-center items-center">
<div className="relative w-40 h-40 flex items-center justify-center">
<svg className="w-full h-full transform -rotate-90" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="transparent" stroke="#eceef0" strokeWidth="8" />
<div className="my-lg flex justify-center items-center">
<div className="relative w-44 h-44 flex items-center justify-center">
{/* soft glow behind the ring */}
<div className="absolute w-32 h-32 rounded-full bg-emerald-400/10 blur-2xl" />
<svg className="w-full h-full transform -rotate-90 relative" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="transparent" stroke="#eef2f6" strokeWidth="9" />
<circle
cx="50"
cy="50"
r="40"
fill="transparent"
stroke="#10b981"
strokeWidth="8"
stroke="url(#dashGradient)"
strokeWidth="9"
strokeDasharray={circumference}
strokeDashoffset={dashOffset}
strokeLinecap="round"
className="transition-all duration-700"
/>
<defs>
<linearGradient id="dashGradient" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stopColor="#10b981" />
<stop offset="100%" stopColor="#0d9488" />
</linearGradient>
</defs>
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center">
<span className="font-sans font-bold text-3xl text-[#0f172a] tracking-tight">{activePct}%</span>
<span className="text-[10px] text-emerald-600 uppercase tracking-widest font-semibold mt-1">Active</span>
<span className="font-sans font-extrabold text-4xl text-slate-900 tracking-tight">{activePct}%</span>
<span className="text-[10px] text-emerald-600 uppercase tracking-widest font-bold mt-1">Active</span>
</div>
</div>
</div>
<div className="divide-y divide-[#f1f5f9] text-xs">
<div className="divide-y divide-slate-100 text-xs">
{statusRows.map((r) => (
<div key={r.label} className="flex justify-between items-center py-2">
<span className="flex items-center gap-1.5 text-zinc-500">
<div key={r.label} className="flex justify-between items-center py-2.5">
<span className="flex items-center gap-2 text-slate-500 font-medium">
<span className={`w-2.5 h-2.5 rounded-full ${r.dot}`} />
{r.label}
</span>
<span className="font-mono font-bold text-zinc-700">{r.value.toLocaleString('en-IN')}</span>
<span className="font-mono font-bold text-slate-700">{r.value.toLocaleString('en-IN')}</span>
</div>
))}
<div className="flex justify-between items-center py-2">
<span className="text-zinc-500 font-semibold">Total Nodes</span>
<span className="font-mono font-bold text-[#581c87]">{totalStoresCount.toLocaleString('en-IN')}</span>
<div className="flex justify-between items-center pt-2.5">
<span className="text-slate-600 font-bold">Total Nodes</span>
<span className="font-mono font-extrabold text-purple-650">{totalStoresCount.toLocaleString('en-IN')}</span>
</div>
</div>
</div>
{/* Store locations (live) */}
<div className="lg:col-span-2 bg-white border border-[#e2e8f0] rounded-xl p-md shadow-[0_1px_3px_rgba(0,0,0,0.05)]">
<div className="flex justify-between items-center mb-md pb-xs border-b border-[#f1f5f9]">
<h3 className="font-sans font-bold text-base text-[#0f172a] flex items-center gap-2">
<Store size={16} className="text-[#581c87]" /> Store Locations
<div className="lg:col-span-2 bg-white border border-slate-200/70 rounded-2xl p-lg shadow-[0_1px_3px_rgba(16,24,40,0.05)]">
<div className="flex justify-between items-center mb-md pb-sm border-b border-slate-100">
<h3 className="font-sans font-bold text-base text-slate-900 flex items-center gap-2.5 tracking-tight">
<span className="h-9 w-9 rounded-xl bg-purple-50 text-purple-650 ring-1 ring-purple-100 flex items-center justify-center">
<Store size={17} />
</span>
Store Locations
</h3>
<span className="text-[10px] text-[#581c87] uppercase font-bold bg-purple-50 px-2 py-0.5 rounded tracking-wide border border-purple-100">
<span className="text-[10px] text-purple-650 uppercase font-bold bg-purple-50 px-2.5 py-1 rounded-lg tracking-wide border border-purple-100">
{locationsQ.isLoading ? 'Loading' : `${locations.length} Outlet${locations.length === 1 ? '' : 's'}`}
</span>
</div>
{locationsQ.isLoading ? (
<div className="text-center py-xl text-zinc-400 text-xs">Loading store locations…</div>
<div className="text-center py-xl text-slate-400 text-xs">Loading store locations…</div>
) : locations.length === 0 ? (
<div className="text-center py-xl text-zinc-400 text-xs">No store locations found for this tenant.</div>
<div className="text-center py-xl text-slate-400 text-xs">No store locations found for this tenant.</div>
) : (
<div className="space-y-sm max-h-80 overflow-y-auto">
<div className="space-y-sm max-h-80 overflow-y-auto pr-1">
{locations.map((loc, i) => {
const sum = summaries.find((s) => s.locationid === Number(loc.locationid));
const deliveries = sum?.delivered ?? 0;
const orders = Math.max(sum?.delivered ?? 0, sum?.total ?? 0);
const isActive = str(loc.status).toLowerCase() === 'active';
const name = str(loc.locationname);
return (
<div
key={str(loc.locationid) || i}
className="p-sm border border-[#e2e8f0] rounded-lg bg-[#f8fafc]/40 flex justify-between items-start gap-md hover:border-purple-200 transition-colors animate-in fade-in"
className="group p-3.5 border border-slate-200/70 rounded-xl bg-slate-50/40 flex justify-between items-start gap-md hover:border-purple-200 hover:bg-white hover:shadow-[0_6px_18px_rgba(16,24,40,0.06)] transition-all animate-in fade-in"
>
<div className="min-w-0">
<p className="font-sans font-semibold text-sm text-[#0f172a] truncate">{str(loc.locationname)}</p>
<p className="text-[11px] text-zinc-500 mt-0.5 flex items-center gap-1">
<MapPin size={11} className="shrink-0 text-zinc-400" />
<span className="truncate">{str(loc.address) || `${str(loc.suburb)}, ${str(loc.city)}`}</span>
</p>
{str(loc.contactno) && (
<p className="text-[11px] text-zinc-500 mt-0.5 flex items-center gap-1">
<Phone size={11} className="shrink-0 text-zinc-400" />
{str(loc.contactno)}
<div className="flex items-start gap-3 min-w-0">
{/* Outlet initial badge */}
<div className="h-9 w-9 shrink-0 rounded-lg bg-gradient-to-br from-purple-500 to-indigo-500 text-white flex items-center justify-center font-bold text-xs uppercase shadow-sm">
{name.slice(0, 2) || ''}
</div>
<div className="min-w-0">
<p className="font-sans font-semibold text-sm text-slate-900 truncate">{name}</p>
<p className="text-[11px] text-slate-500 mt-0.5 flex items-center gap-1">
<MapPin size={11} className="shrink-0 text-slate-400" />
<span className="truncate">{str(loc.address) || `${str(loc.suburb)}, ${str(loc.city)}`}</span>
</p>
)}
{/* Node-specific Orders and Dispatches */}
<div className="flex items-center gap-3 mt-2.5">
<span className="text-[10px] bg-purple-50 text-[#581c87] font-semibold px-2 py-0.5 rounded border border-purple-100/50">
{orders} Orders
</span>
<span className="text-[10px] bg-emerald-50 text-emerald-700 font-semibold px-2 py-0.5 rounded border border-emerald-100/50">
{deliveries} Dispatched
</span>
{orders > 0 && (
<span className="text-[10px] text-zinc-400 font-medium">
{Math.round((deliveries / orders) * 100)}% Fulfilled
</span>
{str(loc.contactno) && (
<p className="text-[11px] text-slate-500 mt-0.5 flex items-center gap-1">
<Phone size={11} className="shrink-0 text-slate-400" />
{str(loc.contactno)}
</p>
)}
{/* Node-specific Orders and Dispatches */}
<div className="flex items-center gap-2 mt-2.5 flex-wrap">
<span className="text-[10px] bg-purple-50 text-purple-650 font-bold px-2 py-0.5 rounded-md border border-purple-100/60">
{orders} Orders
</span>
<span className="text-[10px] bg-emerald-50 text-emerald-700 font-bold px-2 py-0.5 rounded-md border border-emerald-100/60">
{deliveries} Dispatched
</span>
{orders > 0 && (
<span className="text-[10px] text-slate-400 font-semibold">
{Math.round((deliveries / orders) * 100)}% Fulfilled
</span>
)}
</div>
</div>
</div>
<span
className={`shrink-0 px-1.5 py-0.5 rounded text-[9px] font-bold uppercase ${
str(loc.status).toLowerCase() === 'active'
className={`shrink-0 inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-[9px] font-bold uppercase tracking-wide ${
isActive
? 'text-emerald-600 bg-emerald-50 border border-emerald-100'
: 'text-zinc-500 bg-zinc-100'
: 'text-slate-500 bg-slate-100 border border-slate-200'
}`}
>
<span className={`w-1.5 h-1.5 rounded-full ${isActive ? 'bg-emerald-500' : 'bg-slate-400'}`} />
{str(loc.status) || ''}
</span>
</div>