/** * @license * SPDX-License-Identifier: Apache-2.0 */ import React, { useState, useEffect } from 'react'; import { TrendingUp, TrendingDown, Download, Filter, ChevronDown, TrendingUp as TrendUp, TrendingDown as TrendDown, Equal as TrendFlat, ChevronLeft, ChevronRight, Info, Activity, Package, ShoppingBag, AlertTriangle, MapPin, Calendar } from 'lucide-react'; import { LeaderboardNode } from '../types'; import { useFiestaOrderSummary, useFiestaLocationSummary, useFiestaOrderInsight, useFiestaStockStatement, } from '../services/fiestaQueries'; import { FIESTA_TENANT_ID, FIESTA_PRIMARY_LOCATION_ID, num as fnum, str as fstr, ymd } from '../services/fiestaApi'; import { stockRowToProduct } from '../services/fiestaMappers'; import AwaitingApi from './AwaitingApi'; interface ReportsViewProps { searchQuery: string; isCoimbatoreView: boolean; setIsCoimbatoreView: (val: boolean) => void; tenantId?: number; } const MONTH_KEYS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dece']; const MONTH_LABELS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; export default function ReportsView({ searchQuery, isCoimbatoreView, setIsCoimbatoreView, tenantId = FIESTA_TENANT_ID }: ReportsViewProps) { const [selectedTimeframe, setSelectedTimeframe] = useState('This Year (YTD)'); const [selectedRegion, setSelectedRegion] = useState<'all' | 'coimbatore' | 'chennai' | 'bangalore'>('all'); const [stockFilter, setStockFilter] = useState<'All' | 'Healthy' | 'Low Stock' | 'Critical'>('All'); const [selectedCell, setSelectedCell] = useState<{ day: string; hour: string; val: number } | null>(null); const [currentPage, setCurrentPage] = useState(1); const [chartMetric, setChartMetric] = useState<'orders' | 'revenue' | 'cancelled' | 'skus'>('orders'); const [expandedProductId, setExpandedProductId] = useState(null); const [exportingFormat, setExportingFormat] = useState<'PDF' | 'CSV' | null>(null); const [exportProgress, setExportProgress] = useState(0); // Dropdown open states const [showTimeframeDropdown, setShowTimeframeDropdown] = useState(false); const [showRegionDropdown, setShowRegionDropdown] = useState(false); // Sync state with parent component's CoimbatoreView flag useEffect(() => { if (isCoimbatoreView && selectedRegion !== 'coimbatore') { setSelectedRegion('coimbatore'); } else if (!isCoimbatoreView && selectedRegion === 'coimbatore') { setSelectedRegion('all'); } }, [isCoimbatoreView]); const handleRegionChange = (region: 'all' | 'coimbatore' | 'chennai' | 'bangalore') => { setSelectedRegion(region); if (region === 'coimbatore') { setIsCoimbatoreView(true); } else { setIsCoimbatoreView(false); } }; // ── Live analytics (Fiesta) ─────────────────────────────────────────────── const today = new Date(); const yearStart = new Date(today.getFullYear(), 0, 1); const todate = ymd(today); // Previous equal-length window (same number of days immediately before the // current YTD window) so we can derive a REAL orders/cancelled delta. const periodDays = Math.round((today.getTime() - yearStart.getTime()) / 86400000); const prevEnd = new Date(yearStart.getTime() - 86400000); const prevStart = new Date(prevEnd.getTime() - periodDays * 86400000); const summaryQ = useFiestaOrderSummary(tenantId, ymd(yearStart), todate); const prevSummaryQ = useFiestaOrderSummary(tenantId, ymd(prevStart), ymd(prevEnd)); const locSummaryQ = useFiestaLocationSummary(tenantId); const insightQ = useFiestaOrderInsight(tenantId); const stockQ = useFiestaStockStatement({ tenantid: tenantId, locationid: FIESTA_PRIMARY_LOCATION_ID, keyword: '', pageno: 1, pagesize: 100, }); const s = summaryQ.data; const prevS = prevSummaryQ.data; const activeSkus = (stockQ.data ?? []).length; // Real period-over-period % change (null when we can't compute it yet). const pctChange = (current: number, previous: number): number | null => { if (previous <= 0) return null; return ((current - previous) / previous) * 100; }; const ordersDelta = s && prevS ? pctChange(s.total, prevS.total) : null; const cancelledDelta = s && prevS ? pctChange(s.cancelled, prevS.cancelled) : null; const fmtDelta = (d: number) => `${d >= 0 ? '+' : ''}${d.toFixed(1)}%`; // Dynamic sparkline generator helper const getSparkPath = (values: number[], width: number, height: number) => { const max = Math.max(...values); const min = Math.min(...values); const range = max - min || 1; return values.map((val, idx) => { const x = (idx / (values.length - 1)) * width; const y = height - ((val - min) / range) * height; return `${idx === 0 ? 'M' : 'L'} ${x} ${y}`; }).join(' '); }; // Tab thematic config const getChartColors = () => { switch (chartMetric) { case 'orders': return { stroke: 'url(#chart-indigo-grad)', fill: 'url(#chart-indigo-area)', hoverCircle: '#4f46e5', activeLine: '#6366f1', pointFill: '#818cf8' }; case 'revenue': return { stroke: 'url(#chart-emerald-grad)', fill: 'url(#chart-emerald-area)', hoverCircle: '#059669', activeLine: '#10b981', pointFill: '#34d399' }; case 'cancelled': return { stroke: 'url(#chart-rose-grad)', fill: 'url(#chart-rose-area)', hoverCircle: '#e11d48', activeLine: '#f43f5e', pointFill: '#fb7185' }; case 'skus': return { stroke: 'url(#chart-sky-grad)', fill: 'url(#chart-sky-area)', hoverCircle: '#0284c7', activeLine: '#0ea5e9', pointFill: '#38bdf8' }; } }; const theme = getChartColors(); // Live KPI values (tenant-wide; region scaling removed — no per-region API). const totalOrdersVal = s?.total ?? 0; const deliveredVal = s?.delivered ?? 0; const cancelledVal = s?.cancelled ?? 0; const activeSkusVal = activeSkus; // KPI Row Configuration. `awaiting` cards have no live value (rendered via // AwaitingApi). `trend` is only set where a REAL delta could be derived. const reportsKPIs = [ { id: 'orders' as const, title: 'Orders', value: totalOrdersVal.toLocaleString('en-IN'), trend: ordersDelta !== null ? fmtDelta(ordersDelta) : null, status: `${deliveredVal.toLocaleString('en-IN')} filled`, isPositive: ordersDelta === null ? true : ordersDelta >= 0, spark: [30, 45, 35, 60, 55, 70, 65, 80], color: 'indigo', awaiting: false, }, { // Revenue: no revenue API ([R1]) — render AwaitingApi instead of a value. id: 'revenue' as const, title: 'Revenue', value: '', trend: null, status: '', isPositive: true, spark: [20, 30, 25, 45, 40, 55, 50, 68], color: 'emerald', awaiting: true, }, { id: 'cancelled' as const, title: 'Cancelled', value: cancelledVal.toLocaleString('en-IN'), // Lower cancellations is good, so a negative delta is "positive". trend: cancelledDelta !== null ? fmtDelta(cancelledDelta) : null, status: `${(s?.created ?? 0).toLocaleString('en-IN')} active`, isPositive: cancelledDelta === null ? false : cancelledDelta <= 0, spark: [15, 10, 8, 12, 5, 9, 4, 3], color: 'rose', awaiting: false, }, { id: 'skus' as const, title: 'Active SKUs', value: activeSkusVal.toLocaleString('en-IN'), // SKU delta value itself was fabricated — show no trend chip. trend: null, status: `All verified`, isPositive: true, spark: [50, 50, 55, 60, 60, 68, 70, 72], color: 'sky', awaiting: false, }, ]; // Helper matching local hubs belonging to Coimbatore const isCoimbatoreNode = (name: string) => { const coimbatoreZones = ['gandhipuram', 'rs puram', 'peelamedu', 'saravanampatti', 'coimbatore']; return coimbatoreZones.some(zone => name.toLowerCase().includes(zone)); }; const getFilteredLocations = () => { const rawLocations = [...(locSummaryQ.data ?? [])]; // Only Coimbatore can be filtered from live data; Chennai/Bangalore have no // live tenant locations (their stub data was removed). Selecting them yields // an empty list rather than fabricated hubs. if (selectedRegion === 'coimbatore') { return rawLocations.filter(r => isCoimbatoreNode(r.locationname || '')); } if (selectedRegion === 'chennai' || selectedRegion === 'bangalore') { return []; } return rawLocations; }; const filteredLocations = getFilteredLocations(); // Leaderboard — outlets ranked by total live orders. const leaderboard: LeaderboardNode[] = (() => { let rows = [...filteredLocations]; rows = rows.sort((a, b) => b.total - a.total).slice(0, 4); const max = rows.length ? rows[0].total : 0; return rows.map((r, i) => ({ rank: String(i + 1).padStart(2, '0'), name: r.locationname || `Location ${r.locationid}`, percentage: max > 0 ? Math.round((r.total / max) * 100) : 0, // Live order count drives the ranking/bar. No per-node revenue API yet, so the // label shows the real order count (not fabricated rupees) — revenue lands with [R1]. revenue: `${r.total.toLocaleString()} ord`, })); })(); const currentLeaderboard = leaderboard; // Monthly order distribution per outlet — live only (useFiestaOrderInsight // already covers all the tenant's locations). The Chennai/Bangalore stub rows // were removed; selecting those regions filters the live rows to none. const insightRows = (() => { let rows = (insightQ.data ?? []); if (selectedRegion === 'coimbatore') { rows = rows.filter(r => isCoimbatoreNode(fstr(r.locationname))); } else if (selectedRegion === 'chennai' || selectedRegion === 'bangalore') { rows = []; } return rows.map((r) => ({ name: fstr(r.locationname) || `Location ${fstr(r.locationid)}`, months: (r.ordermonths ?? {}) as Record, })); })(); const heatmapMax = Math.max( 1, ...insightRows.flatMap((row) => MONTH_KEYS.map((k) => fnum(row.months[k]))), ); // Live product performance matrix. const liveProducts = (stockQ.data ?? []).map(stockRowToProduct); const filteredProducts = liveProducts.filter((prod) => { const matchesSearch = prod.name.toLowerCase().includes(searchQuery.toLowerCase()) || prod.sku.toLowerCase().includes(searchQuery.toLowerCase()); const matchesStock = stockFilter === 'All' ? true : prod.stockStatus === stockFilter; return matchesSearch && matchesStock; }); // Heatmap cell color gradient scale (multi-stop violet theme) const getHeatmapColorClass = (val: number) => { const ratio = val / heatmapMax; if (ratio < 0.15) { return 'bg-slate-50 text-zinc-400 border border-slate-100/50 hover:bg-slate-100 hover:text-zinc-650 hover:scale-105'; } if (ratio <= 0.4) { return 'bg-purple-50 text-purple-700 border border-purple-100/50 hover:bg-purple-100 hover:scale-105'; } if (ratio <= 0.7) { return 'bg-purple-200/50 text-purple-900 border border-purple-200/40 hover:bg-purple-200 hover:scale-105'; } if (ratio <= 0.9) { return 'bg-[#7c3aed]/20 text-[#7c3aed] border border-[#7c3aed]/20 hover:bg-[#7c3aed]/30 hover:scale-105'; } return 'bg-gradient-to-br from-[#7c3aed] to-[#581c87] text-white border-none shadow-sm hover:scale-108 hover:shadow-md hover:brightness-110'; }; // Triggers progress bar simulated exporting const startExportSim = (format: 'PDF' | 'CSV') => { setExportingFormat(format); setExportProgress(0); const interval = setInterval(() => { setExportProgress(prev => { if (prev >= 100) { clearInterval(interval); setTimeout(() => { setExportingFormat(null); alert(`${format} ledger exported successfully.`); }, 450); return 100; } return prev + 10; }); }, 120); }; return (
{/* Immersive Background Blur Blobs */}
{/* ── Immersive Analytics Banner (With Data Cover Image & Slate Gradient Overlay) ── */}
{/* Cover Image Background & Decor (wrapped in overflow-hidden to keep rounded corner clip, while allowing dropdown overflow) */}
Reports Dashboard Banner
{/* Background decorative glowing circles */}
{/* Content Row */}

Business Intelligence Center Live Core

{/* Action picker filters inside the banner */}
{/* Custom Timeframe Dropdown */}
{showTimeframeDropdown && ( <>
setShowTimeframeDropdown(false)} />
{['This Month', 'This Year (YTD)', 'Last 12 Months', 'All Time'].map((opt) => ( ))}
)}
{/* Custom Region Dropdown */}
{showRegionDropdown && ( <>
setShowRegionDropdown(false)} />
{[ { id: 'all' as const, label: 'All Regions (12 Hubs)' }, { id: 'coimbatore' as const, label: 'Coimbatore (5 Hubs)' }, { id: 'chennai' as const, label: 'Chennai (4 Hubs)' }, { id: 'bangalore' as const, label: 'Bangalore (3 Hubs)' } ].map((opt) => ( ))}
)}
{/* Export PDF action */}
{/* Small cards metrics grid relative to reports (similar to StoreDetailView) */}
{/* Card 1: Active Region */}
Active Region

{selectedRegion === 'all' ? 'All Regions' : selectedRegion === 'coimbatore' ? 'Coimbatore' : selectedRegion === 'chennai' ? 'Chennai' : 'Bangalore'}

{filteredLocations.length} hubs active

{/* Card 2: Selected Horizon */}
Time Horizon

{selectedTimeframe}

Historical Period

{/* Card 3: Total Segment Orders */}
Total Orders

{totalOrdersVal.toLocaleString('en-IN')}

Segment Volume

{/* Card 4: Gross Revenue — no revenue API ([R1]) */}
Gross Revenue
{/* Primary KPI Row - 4 Key Tab buttons with Sparklines */}
{reportsKPIs.map((kpi) => { const isActive = chartMetric === kpi.id; // Theme-based conditional classes let activeStyles = ''; let strokeColor = ''; let IconComponent = ShoppingBag; let iconBg = ''; let iconTextColor = ''; if (kpi.color === 'indigo') { activeStyles = isActive ? 'border-indigo-500 bg-indigo-50/10 shadow-[0_12px_30px_rgba(99,102,241,0.15)] ring-1 ring-indigo-500 scale-102 z-10' : 'hover:border-indigo-200 border-[#e2e8f0] bg-white/70 hover:bg-white hover:shadow-[0_8px_20px_rgba(99,102,241,0.05)]'; strokeColor = '#6366f1'; IconComponent = ShoppingBag; iconBg = 'bg-indigo-50 border border-indigo-100 shadow-[0_0_12px_rgba(99,102,241,0.2)]'; iconTextColor = 'text-indigo-600'; } else if (kpi.color === 'emerald') { activeStyles = isActive ? 'border-emerald-500 bg-emerald-50/10 shadow-[0_12px_30px_rgba(16,185,129,0.15)] ring-1 ring-emerald-500 scale-102 z-10' : 'hover:border-emerald-200 border-[#e2e8f0] bg-white/70 hover:bg-white hover:shadow-[0_8px_20px_rgba(16,185,129,0.05)]'; strokeColor = '#10b981'; IconComponent = Activity; iconBg = 'bg-emerald-50 border border-emerald-100 shadow-[0_0_12px_rgba(16,185,129,0.2)]'; iconTextColor = 'text-emerald-600'; } else if (kpi.color === 'rose') { activeStyles = isActive ? 'border-rose-500 bg-rose-50/10 shadow-[0_12px_30px_rgba(244,63,94,0.15)] ring-1 ring-rose-500 scale-102 z-10' : 'hover:border-rose-200 border-[#e2e8f0] bg-white/70 hover:bg-white hover:shadow-[0_8px_20px_rgba(244,63,94,0.05)]'; strokeColor = '#f43f5e'; IconComponent = AlertTriangle; iconBg = 'bg-rose-50 border border-rose-100 shadow-[0_0_12px_rgba(244,63,94,0.2)]'; iconTextColor = 'text-rose-600'; } else { activeStyles = isActive ? 'border-sky-500 bg-sky-50/10 shadow-[0_12px_30px_rgba(14,165,233,0.15)] ring-1 ring-sky-500 scale-102 z-10' : 'hover:border-sky-200 border-[#e2e8f0] bg-white/70 hover:bg-white hover:shadow-[0_8px_20px_rgba(14,165,233,0.05)]'; strokeColor = '#0ea5e9'; IconComponent = Package; iconBg = 'bg-sky-50 border border-sky-100 shadow-[0_0_12px_rgba(14,165,233,0.2)]'; iconTextColor = 'text-sky-600'; } return ( ); })}
{/* Main Interactive Charts & Insights Bento Segment */}
{/* Interactive Main Graph Card — Full width (12 cols) */}
Interactive Graph

{chartMetric === 'orders' ? 'Total Orders Velocity Trend' : chartMetric === 'revenue' ? 'Revenue Expansion Trajectory' : chartMetric === 'cancelled' ? 'Order Cancellation Frequency' : 'Catalogue Active SKUs Growth'}

Live Sync
{/* Plotted Area — no time-series API ([R2]) for orders/revenue/skus. The metric tabs (KPI cards above) still switch the card title; the chart body itself shows the awaiting-backend placeholder. */}
{/* Bento split maps (Heatmap grid & Leaderboard) */}
{/* Revenue Heatmap table - 8 Cols */}
Monthly Order Distribution
Busiest Month
{selectedCell && (
{selectedCell.day} · {selectedCell.hour} : {selectedCell.val} orders
)} {insightRows.length === 0 ? (
{insightQ.isLoading ? 'Loading monthly order distribution…' : 'No order insight available for this region.'}
) : ( {MONTH_LABELS.map((m) => ( ))} {insightRows.map((row, idx) => ( {MONTH_KEYS.map((key, mIdx) => { const val = fnum(row.months[key]); return ( ); })} ))}
Outlet Name {m}
{row.name}
)}
{/* Leaderboard nodes bar list - 4 Cols */}
Top Performing Nodes
{currentLeaderboard.length === 0 ? (
No active nodes in this region.
) : ( currentLeaderboard.map((node, index) => { let badgeStyle = ''; let badgeContent: React.ReactNode = node.rank; if (index === 0) { badgeStyle = 'bg-gradient-to-tr from-amber-300 via-yellow-400 to-amber-500 text-amber-955 border-amber-200 shadow-md shadow-amber-500/20'; badgeContent = '🥇'; } else if (index === 1) { badgeStyle = 'bg-gradient-to-tr from-slate-200 via-zinc-300 to-slate-400 text-zinc-800 border-zinc-100 shadow-md shadow-zinc-400/10'; badgeContent = '🥈'; } else if (index === 2) { badgeStyle = 'bg-gradient-to-tr from-amber-600 via-amber-700 to-orange-850 text-amber-50 border-amber-500 shadow-md shadow-amber-800/20'; badgeContent = '🥉'; } else { badgeStyle = 'bg-slate-100 text-zinc-505 border-slate-200'; } return (
{/* Ranking circle avatar */}
{badgeContent}
{node.name} {node.revenue}
); }) )}
{/* Detailed Performance Matrix table */}
{/* Table header with filters control */}

Product Performance Matrix

{/* Quick interactive filter pills */}
{(['All', 'Healthy', 'Low Stock', 'Critical'] as const).map((filter) => ( ))}
{/* Matrix Data table */}
{filteredProducts.length === 0 ? ( ) : ( filteredProducts.map((prod) => { const isExpanded = expandedProductId === prod.id; return ( setExpandedProductId(isExpanded ? null : prod.id)} className={`hover:bg-[#f2f4f6]/40 transition-all font-medium text-zinc-700 cursor-pointer ${isExpanded ? 'bg-slate-50/50' : ''}`} > {/* Expanded details row */} {isExpanded && ( )} ); }) )}
Product Name SKU ID Units Sold Revenue Stock Status Trend index
No matching items matching stock filter criteria.
{prod.name}
{prod.name} {prod.category}
{prod.sku} {prod.unitsSold.toLocaleString()} ₹{prod.revenue.toLocaleString()} {prod.stockStatus} {prod.trend === 'up' ? ( ) : prod.trend === 'down' ? ( ) : ( )}
{/* Per-product stock & location breakdown has no live API ([R3]); the previously fabricated unit counts, hub split, bin code, audit date and barcode are replaced with the awaiting-backend placeholder. */}
{/* Matrix table pagination */}
Showing 1-{filteredProducts.length} of {liveProducts.length} live products
{/* EXPORTING DIALOG MODAL */} {exportingFormat && (

Generating {exportingFormat} Report

Compiling database records and SVG vector curves...

{/* Progress track */}
{exportProgress}%
)}
); }