/** * @license * SPDX-License-Identifier: Apache-2.0 */ import React from 'react'; import { Wallet, TrendingUp, Store, MapPin, Phone, AlertTriangle, Activity, Clock, ArrowUpRight, } from 'lucide-react'; import { useFiestaLocationSummary, useFiestaTenantLocations, useFiestaRevenueSummary, useFiestaOrderSummary } from '../services/fiestaQueries'; import { FIESTA_TENANT_ID } from '../services/fiestaApi'; interface DashboardViewProps { searchQuery: string; isCoimbatoreView: boolean; /** Fiesta merchant tenant to scope live store summaries to. */ tenantId?: number; } const ymd = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; const str = (v: unknown): string => (v == null ? '' : String(v)); export default function DashboardView({ searchQuery, tenantId = FIESTA_TENANT_ID }: DashboardViewProps) { // Live data — month-to-date order summary + tenant identity + store locations. const today = new Date(); const monthStart = new Date(today); monthStart.setDate(today.getDate() - 30); // Last 30 days to include May orders const fromdate = ymd(monthStart); const todate = ymd(today); // All scoped to the signed-in merchant's tenant. Store locations come from the // Fiesta source (the single source of truth used across the app) — it's already // deduped and stripped of test rows, unlike the raw Hasura tenant-locations feed. const summaryQ = useFiestaOrderSummary(tenantId, fromdate, todate); const locationsQ = useFiestaTenantLocations(tenantId); const revenueQ = useFiestaRevenueSummary({ tenantid: tenantId, fromdate, todate }); const s = summaryQ.data; const tenantName = s?.tenantname || `Tenant ${tenantId}`; // Revenue + profit come from the live invoice/financial insight. The endpoint // returns two distinct figures (revenue and profit); we surface both rather than // repeating one. When the tenant has no invoice records we show "—" instead of a // misleading ₹0. const insight = revenueQ.data as any; const money = (v: number | null) => (v == null ? '—' : `₹${Math.round(v).toLocaleString('en-IN')}`); const monthlyRevenue = insight ? Number(insight.grossrevenue || insight.overallrevenue || insight.revenue || 0) : null; const monthlyProfit = insight ? Number(insight.profit || insight.netrevenue || insight.margin || 0) : null; const locSummaryQ = useFiestaLocationSummary(tenantId, fromdate, todate); const summaries = locSummaryQ.data ?? []; // Region fulfillment — live month-to-date delivered ÷ total orders for the tenant. const ordersTotal = s?.total ?? 0; const ordersDelivered = s?.delivered ?? 0; const regionFulfillmentPct = ordersTotal > 0 ? (ordersDelivered / ordersTotal) * 100 : null; const locations = (locationsQ.data ?? []).filter((loc) => { if (!searchQuery) return true; const q = searchQuery.toLowerCase(); return ( str(loc.locationname).toLowerCase().includes(q) || str(loc.city).toLowerCase().includes(q) || str(loc.suburb).toLowerCase().includes(q) ); }); // KPI cards — orders from getordersummary, profit from getinvoiceinsight. const totalStoresCount = locations.length; const activeStoresCount = locations.filter(l => str(l.status).toLowerCase() === 'active').length; const inactiveStoresCount = totalStoresCount - activeStoresCount; const activePct = totalStoresCount > 0 ? Math.round((activeStoresCount / totalStoresCount) * 100) : 0; const circumference = 251.2; const dashOffset = circumference - (circumference * activePct) / 100; const kpis = [ { 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: revenueQ.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: revenueQ.isLoading, }, ]; const statusRows = [ { label: 'Active Outlets', value: activeStoresCount, dot: 'bg-emerald-500' }, { label: 'Inactive / Maintenance', value: inactiveStoresCount, dot: 'bg-zinc-400' }, ]; const loading = summaryQ.isLoading; const errored = summaryQ.isError; return (
Couldn't reach the live API.
The /hasura dev proxy loads at server start — stop and re-run npm run dev so the
secret and proxy are active.
{kpi.title}
{kpi.loading ? … : kpi.display}
{kpi.sub}