diff --git a/src/app/(workspace)/commerce/page.tsx b/src/app/(workspace)/commerce/page.tsx
new file mode 100644
index 0000000..f3eadbd
--- /dev/null
+++ b/src/app/(workspace)/commerce/page.tsx
@@ -0,0 +1,10 @@
+import {CommerceWorkspace} from '@/features/commerce/components/CommerceWorkspace';
+
+export const metadata = {
+ title: 'Commerce | Loyaly Merchant OS',
+ description: 'AI-first commerce workspace for sales, orders, revenue and business performance telemetry.',
+};
+
+export default function CommercePage() {
+ return ;
+}
diff --git a/src/features/commerce/components/CommerceKpiRow.tsx b/src/features/commerce/components/CommerceKpiRow.tsx
new file mode 100644
index 0000000..0e9f847
--- /dev/null
+++ b/src/features/commerce/components/CommerceKpiRow.tsx
@@ -0,0 +1,70 @@
+'use client';
+
+import {Grid} from '@astryxdesign/core/Grid';
+import {MetricCard} from '@/shared/components/patterns/MetricCard';
+import {ICONS} from '@/shared/utils/icons';
+import {formatCount, formatInrCompact, formatPct} from '@/shared/utils/format';
+
+export function CommerceKpiRow() {
+ const kpiList = [
+ {
+ label: 'Total Revenue',
+ value: 340000,
+ format: formatInrCompact,
+ icon: ICONS.revenue,
+ deltaPct: 14.2,
+ },
+ {
+ label: 'Total Orders',
+ value: 1284,
+ format: formatCount,
+ icon: ICONS.purchases,
+ deltaPct: 8.5,
+ },
+ {
+ label: 'Avg Order Value',
+ value: 1420,
+ format: formatInrCompact,
+ icon: ICONS.purchases,
+ deltaPct: 5.1,
+ },
+ {
+ label: 'Net Sales',
+ value: 318000,
+ format: formatInrCompact,
+ icon: ICONS.revenue,
+ deltaPct: 13.9,
+ },
+ {
+ label: 'Refunds',
+ value: 22000,
+ format: formatInrCompact,
+ icon: ICONS.alert,
+ deltaPct: -2.4,
+ isRiseGood: false,
+ },
+ {
+ label: 'Conversion',
+ value: 23.8,
+ format: (v: number) => formatPct(v, 1),
+ icon: ICONS.conversion,
+ deltaPct: 1.4,
+ },
+ ];
+
+ return (
+
+ {kpiList.map((kpi) => (
+
+ ))}
+
+ );
+}
diff --git a/src/features/commerce/components/CommerceRecentOrders.tsx b/src/features/commerce/components/CommerceRecentOrders.tsx
new file mode 100644
index 0000000..f22ae98
--- /dev/null
+++ b/src/features/commerce/components/CommerceRecentOrders.tsx
@@ -0,0 +1,53 @@
+'use client';
+
+import {Heading, Text} from '@astryxdesign/core/Text';
+import {Badge} from '@astryxdesign/core/Badge';
+import {Icon} from '@astryxdesign/core/Icon';
+import {ICONS} from '@/shared/utils/icons';
+
+const RECENT_ORDERS = [
+ {id: 'o1', code: '#LYT-9842', customer: 'Aarav Sharma', store: 'Indiranagar Flagship', amount: '₹2,450', method: 'UPI', status: 'completed', time: '2m ago'},
+ {id: 'o2', code: '#LYT-9841', customer: 'Priya Patel', store: 'Koramangala 80ft', amount: '₹1,890', method: 'Credit Card', status: 'completed', time: '5m ago'},
+ {id: 'o3', code: '#LYT-9840', customer: 'Rohan Mehta', store: 'Jayanagar 4th Block', amount: '₹3,120', method: 'Shopify', status: 'completed', time: '12m ago'},
+ {id: 'o4', code: '#LYT-9839', customer: 'Ananya Roy', store: 'Whitefield Main', amount: '₹950', method: 'UPI', status: 'completed', time: '18m ago'},
+ {id: 'o5', code: '#LYT-9838', customer: 'Kabir Verma', store: 'Indiranagar Flagship', amount: '₹4,200', method: 'Card', status: 'completed', time: '24m ago'},
+];
+
+export function CommerceRecentOrders() {
+ return (
+
+
+
+
+
+ Recent Orders
+
+
+ Real-time completed orders across your network
+
+
+
+
+
+
+ {RECENT_ORDERS.map((ord) => (
+
+
+
+ {ord.code}
+ • {ord.customer}
+
+
+ {ord.store} ({ord.method})
+
+
+
+
{ord.amount}
+
{ord.time}
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/features/commerce/components/CommerceRevenueSummary.tsx b/src/features/commerce/components/CommerceRevenueSummary.tsx
new file mode 100644
index 0000000..703152d
--- /dev/null
+++ b/src/features/commerce/components/CommerceRevenueSummary.tsx
@@ -0,0 +1,48 @@
+'use client';
+
+import {Heading, Text} from '@astryxdesign/core/Text';
+import {MetricCard} from '@/shared/components/patterns/MetricCard';
+import {formatInrCompact} from '@/shared/utils/format';
+
+export function CommerceRevenueSummary() {
+ return (
+
+
+
+ Revenue Summary
+
+
+ Financial performance rollup for the selected period
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/features/commerce/components/CommerceSalesInsights.tsx b/src/features/commerce/components/CommerceSalesInsights.tsx
new file mode 100644
index 0000000..b1e38c7
--- /dev/null
+++ b/src/features/commerce/components/CommerceSalesInsights.tsx
@@ -0,0 +1,72 @@
+'use client';
+
+import {Heading, Text} from '@astryxdesign/core/Text';
+import {Badge} from '@astryxdesign/core/Badge';
+import {Icon} from '@astryxdesign/core/Icon';
+import {ICONS} from '@/shared/utils/icons';
+
+const SALES_INSIGHTS = [
+ {
+ id: 'si1',
+ title: 'Evening Peak Surge',
+ description: 'Evening hours (17:30 - 19:30) generated 38% of total daily revenue.',
+ intent: 'positive',
+ badge: 'Growth Opportunity',
+ },
+ {
+ id: 'si2',
+ title: 'Stock Reorder Warning',
+ description: 'Artisanal Cold Brew Pack (18 left) is approaching stockout threshold.',
+ intent: 'warning',
+ badge: 'Action Needed',
+ },
+ {
+ id: 'si3',
+ title: 'Indiranagar Conversion Leader',
+ description: 'Indiranagar delivered +18.4% MoM conversion growth after digital pick-up lanes.',
+ intent: 'positive',
+ badge: 'Best Performer',
+ },
+];
+
+export function CommerceSalesInsights() {
+ return (
+
+
+
+
+
+ Sales Insights
+
+
+ Automated intelligence highlights & telemetry warnings
+
+
+
+
+
+ {SALES_INSIGHTS.map((item) => (
+
+
+ {item.title}
+
+
+
+ {item.description}
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/features/commerce/components/CommerceWorkspace.tsx b/src/features/commerce/components/CommerceWorkspace.tsx
new file mode 100644
index 0000000..c0998ff
--- /dev/null
+++ b/src/features/commerce/components/CommerceWorkspace.tsx
@@ -0,0 +1,61 @@
+'use client';
+
+import {VStack} from '@astryxdesign/core/Layout';
+import {Grid} from '@astryxdesign/core/Grid';
+import {PageHeader} from '@/shared/components/primitives/PageHeader';
+import {ScopeControls} from '@/shared/components/scope/ScopeControls';
+import {useScopeLabel} from '@/features/stores/hooks/useStoreDirectory';
+import {CommerceKpiRow} from './CommerceKpiRow';
+import {RevenueChart} from './analytics/RevenueChart';
+import {OrdersChart} from './analytics/OrdersChart';
+import {PaymentBreakdown} from './analytics/PaymentBreakdown';
+import {StorePerformanceChart} from './analytics/StorePerformanceChart';
+import {TopProductsTable} from './analytics/TopProductsTable';
+import {CommerceSalesInsights} from './CommerceSalesInsights';
+import {CommerceRevenueSummary} from './CommerceRevenueSummary';
+import {CommerceRecentOrders} from './CommerceRecentOrders';
+
+export function CommerceWorkspace() {
+ const scopeLabel = useScopeLabel();
+
+ return (
+
+ {/* Sticky Header matching Dashboard visual language */}
+
+
+ {/* 1 — Headline KPI Cards Row */}
+
+
+ {/* 2 — Primary Sales Trends (Revenue Trend & Orders Trend) */}
+
+
+
+
+
+ {/* 3 — Revenue Breakdown & Store Performance */}
+
+
+
+
+
+ {/* 4 — Top Selling Products Leaderboard & Automated Sales Insights */}
+
+
+
+
+
+ {/* 5 — Financial Revenue Rollup & Recent Orders Live Feed */}
+
+
+
+
+
+ );
+}
diff --git a/src/features/commerce/components/analytics/BusinessAlerts.tsx b/src/features/commerce/components/analytics/BusinessAlerts.tsx
new file mode 100644
index 0000000..461f4a7
--- /dev/null
+++ b/src/features/commerce/components/analytics/BusinessAlerts.tsx
@@ -0,0 +1,77 @@
+'use client';
+
+import {Heading, Text} from '@astryxdesign/core/Text';
+import {Badge} from '@astryxdesign/core/Badge';
+import {Icon} from '@astryxdesign/core/Icon';
+import {ICONS} from '@/shared/utils/icons';
+import {BUSINESS_ALERTS} from '../../services/commerceService';
+
+export function BusinessAlerts() {
+ return (
+
+
+ Real-Time Business & Operational Alerts
+
+
+
+ {BUSINESS_ALERTS.map((alert) => {
+ const isCritical = alert.severity === 'critical';
+ const isWarning = alert.severity === 'warning';
+ const isSuccess = alert.severity === 'success';
+
+ const borderClass = isCritical
+ ? 'border-rose-500/30 bg-rose-500/[0.03]'
+ : isWarning
+ ? 'border-amber-500/30 bg-amber-500/[0.03]'
+ : isSuccess
+ ? 'border-emerald-500/30 bg-emerald-500/[0.03]'
+ : 'border-blue-500/30 bg-blue-500/[0.03]';
+
+ return (
+
+
+
+
+
+ {alert.title}
+
+ {alert.description}
+
+
+
+
+
+
+
+
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/features/commerce/components/analytics/BusinessPulsePanel.tsx b/src/features/commerce/components/analytics/BusinessPulsePanel.tsx
new file mode 100644
index 0000000..cedeea4
--- /dev/null
+++ b/src/features/commerce/components/analytics/BusinessPulsePanel.tsx
@@ -0,0 +1,127 @@
+'use client';
+
+import {Heading, Text} from '@astryxdesign/core/Text';
+import {Badge} from '@astryxdesign/core/Badge';
+import {Icon} from '@astryxdesign/core/Icon';
+import {ICONS} from '@/shared/utils/icons';
+import {COMMERCE_KPIS, PAYMENT_BREAKDOWN_DATA} from '../../services/commerceService';
+
+export function BusinessPulsePanel() {
+ return (
+
+ {/* Pulse Header */}
+
+
+
+
+ Business Pulse
+
+
+ Live telemetry stream
+
+
+
+
+
+ {/* Pulse Cards */}
+
+ {/* Today's Revenue */}
+
+
+
Today's Revenue
+
{COMMERCE_KPIS.revenue.value}
+
+
+ {COMMERCE_KPIS.revenue.change}
+
+
+
+ {/* Today's Orders */}
+
+
+
Today's Orders
+
{COMMERCE_KPIS.orders.value}
+
+
+ {COMMERCE_KPIS.orders.change}
+
+
+
+ {/* Active Promotions */}
+
+
+
+
+ Active Promotions
+
+
+
+
+ Weekend Double LYT & Espresso Bundle driving +14% lift
+
+
+
+ {/* Inventory Alerts */}
+
+
+
+
+ Inventory Alert
+
+
+
+
+ Cold Brew Pack (18 left) & Barista Mug (8 left)
+
+
+
+ {/* Payment Split */}
+
+
+ Payment Method Split
+ UPI 58%
+
+
+ {PAYMENT_BREAKDOWN_DATA.map((p) => (
+
+ ))}
+
+
+
+ {/* Top Store */}
+
+
Top Performing Store
+
{COMMERCE_KPIS.topStore?.value ?? 'Indiranagar Flagship'}
+
{COMMERCE_KPIS.topStore?.subtitle ?? '₹9.1L Revenue (23.8% Conv)'}
+
+
+ {/* Recent Transactions */}
+
+
Recent Transactions
+
+ {[
+ {id: 't1', code: '#LYT-9842', name: 'Aarav Sharma', amount: '₹2,450', time: '2m ago'},
+ {id: 't2', code: '#LYT-9841', name: 'Priya Patel', amount: '₹1,890', time: '5m ago'},
+ {id: 't3', code: '#LYT-9840', name: 'Rohan Mehta', amount: '₹3,120', time: '12m ago'},
+ ].map((t) => (
+
+
+
+
{t.amount}
+
{t.time}
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/features/commerce/components/analytics/ForecastChart.tsx b/src/features/commerce/components/analytics/ForecastChart.tsx
new file mode 100644
index 0000000..ba634f7
--- /dev/null
+++ b/src/features/commerce/components/analytics/ForecastChart.tsx
@@ -0,0 +1,38 @@
+'use client';
+
+import {Heading, Text} from '@astryxdesign/core/Text';
+import {LineChartView} from '@/shared/components/charts/LineChartView';
+import {FORECAST_TREND_DATA} from '../../services/commerceService';
+
+export function ForecastChart() {
+ return (
+
+
+
+
+ Predictive Revenue & Demand Forecast
+
+
+ AI-driven 7-day revenue, demand and stock requirement projections
+
+
+
+ 96% Confidence Band
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/features/commerce/components/analytics/HeatmapChart.tsx b/src/features/commerce/components/analytics/HeatmapChart.tsx
new file mode 100644
index 0000000..697b3ad
--- /dev/null
+++ b/src/features/commerce/components/analytics/HeatmapChart.tsx
@@ -0,0 +1,72 @@
+'use client';
+
+import {Heading, Text} from '@astryxdesign/core/Text';
+import {HEATMAP_DAYS, HEATMAP_HOURS} from '../../services/commerceService';
+
+function getIntensityBg(day: string, hour: string): string {
+ if (hour === '18:00') return 'bg-emerald-500 text-black font-bold';
+ if (hour === '12:00' || hour === '14:00') return 'bg-emerald-600/80 text-white font-semibold';
+ if (day === 'Sat' || day === 'Sun') return 'bg-emerald-700/60 text-white font-medium';
+ return 'bg-zinc-800/80 text-zinc-400';
+}
+
+export function HeatmapChart() {
+ return (
+
+
+
+
+ Hourly Sales & Footfall Heatmap
+
+
+ Sales volume intensity matrix mapped across days and operating hours
+
+
+
+
Low
+
+
+
+
+
+
+
Peak
+
+
+
+
+
+
+
+ | Day / Hour |
+ {HEATMAP_HOURS.map((h) => (
+
+ {h}
+ |
+ ))}
+
+
+
+ {HEATMAP_DAYS.map((day) => (
+
+ | {day} |
+ {HEATMAP_HOURS.map((hour) => {
+ const styleClass = getIntensityBg(day, hour);
+ return (
+
+
+ {hour === '18:00' ? '98%' : hour === '14:00' ? '74%' : '42%'}
+
+ |
+ );
+ })}
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/features/commerce/components/analytics/OperationalFlowcharts.tsx b/src/features/commerce/components/analytics/OperationalFlowcharts.tsx
new file mode 100644
index 0000000..d9d12b4
--- /dev/null
+++ b/src/features/commerce/components/analytics/OperationalFlowcharts.tsx
@@ -0,0 +1,109 @@
+'use client';
+
+import {Heading, Text} from '@astryxdesign/core/Text';
+
+const ORDER_FLOW = [
+ {step: 'Customer', detail: 'Identified / Guest'},
+ {step: 'Purchase', detail: 'Cart Checkout'},
+ {step: 'Payment', detail: 'UPI / Card Auth'},
+ {step: 'Order', detail: 'KDS & POS Created'},
+ {step: 'Packing', detail: 'Fulfillment Prep'},
+ {step: 'Delivery', detail: 'Dispatch / Pickup'},
+ {step: 'Completed', detail: 'Ledger Settled'},
+];
+
+const REVENUE_FLOW = [
+ {step: 'Visitor', detail: 'Store / Web Traffic'},
+ {step: 'Conversion', detail: 'Checkout Rate'},
+ {step: 'Order', detail: 'Gross Volume'},
+ {step: 'Revenue', detail: 'Net Payment'},
+ {step: 'Profit', detail: 'Margin Settled'},
+];
+
+const INVENTORY_FLOW = [
+ {step: 'Supplier', detail: 'PO Placed'},
+ {step: 'Warehouse', detail: 'Stock Received'},
+ {step: 'Store', detail: 'Shelf Allocation'},
+ {step: 'Customer', detail: 'Item Fulfilled'},
+];
+
+export function OperationalFlowcharts() {
+ return (
+
+
+
+ Operational Flow Diagrams
+
+
+ Visual operational pipeline lifecycle maps for orders, revenue settlements and inventory movement
+
+
+
+ {/* Order Flow Diagram */}
+
+
+ 1. Order Lifecycle Flow
+
+
+
+ {ORDER_FLOW.map((node, i) => (
+
+
+
{node.step}
+
{node.detail}
+
+ {i < ORDER_FLOW.length - 1 ? (
+
→
+ ) : null}
+
+ ))}
+
+
+
+
+ {/* Revenue Flow Diagram */}
+
+
+ 2. Revenue Settlement Flow
+
+
+
+ {REVENUE_FLOW.map((node, i) => (
+
+
+
{node.step}
+
{node.detail}
+
+ {i < REVENUE_FLOW.length - 1 ? (
+
→
+ ) : null}
+
+ ))}
+
+
+
+
+ {/* Inventory Flow Diagram */}
+
+
+ 3. Inventory & Supply Chain Flow
+
+
+
+ {INVENTORY_FLOW.map((node, i) => (
+
+
+
{node.step}
+
{node.detail}
+
+ {i < INVENTORY_FLOW.length - 1 ? (
+
→
+ ) : null}
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/features/commerce/components/analytics/OrdersChart.tsx b/src/features/commerce/components/analytics/OrdersChart.tsx
new file mode 100644
index 0000000..e23cac8
--- /dev/null
+++ b/src/features/commerce/components/analytics/OrdersChart.tsx
@@ -0,0 +1,34 @@
+'use client';
+
+import {Heading, Text} from '@astryxdesign/core/Text';
+import {BarChartView} from '@/shared/components/charts/BarChartView';
+import {ORDERS_TREND_DATA} from '../../services/commerceService';
+
+export function OrdersChart() {
+ return (
+
+
+
+
+ Orders Volume Trend
+
+
+ Completed order count grouped by hourly buckets
+
+
+
+ 1,284 Orders Today
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/features/commerce/components/analytics/PaymentBreakdown.tsx b/src/features/commerce/components/analytics/PaymentBreakdown.tsx
new file mode 100644
index 0000000..5fa870d
--- /dev/null
+++ b/src/features/commerce/components/analytics/PaymentBreakdown.tsx
@@ -0,0 +1,162 @@
+'use client';
+
+import {useState} from 'react';
+import {Heading, Text} from '@astryxdesign/core/Text';
+import {Badge} from '@astryxdesign/core/Badge';
+import {PAYMENT_BREAKDOWN_DATA} from '../../services/commerceService';
+
+export function PaymentBreakdown() {
+ const [selectedMethod, setSelectedMethod] = useState(null);
+
+ const activeItem = selectedMethod
+ ? PAYMENT_BREAKDOWN_DATA.find((p) => p.method === selectedMethod)
+ : null;
+
+ return (
+
+
+
+
+ Payment Methods Breakdown
+
+
+ Revenue share distribution by payment channel
+
+
+
+ {/* Interactive Channel Filter Buttons */}
+
+
+ {PAYMENT_BREAKDOWN_DATA.map((item) => (
+
+ ))}
+
+
+
+ {/* Visual Ring / Stack Bar */}
+
+ {PAYMENT_BREAKDOWN_DATA.map((item) => {
+ const isSelected = selectedMethod === null || selectedMethod === item.method;
+ return (
+
+ setSelectedMethod(selectedMethod === item.method ? null : item.method)
+ }
+ style={{
+ width: `${item.percentage}%`,
+ backgroundColor: item.color,
+ opacity: isSelected ? 1 : 0.25,
+ }}
+ className="h-full cursor-pointer transition-all duration-300 first:rounded-l-full last:rounded-r-full hover:brightness-125"
+ title={`${item.method}: ${item.percentage}% (${item.amount}) — Click to filter`}
+ />
+ );
+ })}
+
+
+ {/* Interactive Breakdown Cards */}
+
+ {PAYMENT_BREAKDOWN_DATA.map((item) => {
+ const isSelected = selectedMethod === item.method;
+ return (
+
+ setSelectedMethod(isSelected ? null : item.method)
+ }
+ className={`flex items-center justify-between p-3 rounded-xl cursor-pointer transition-all border ${
+ isSelected
+ ? 'bg-zinc-800/90 shadow-md ring-1'
+ : selectedMethod !== null
+ ? 'bg-zinc-900/40 border-zinc-800/60 opacity-60 hover:opacity-100'
+ : 'bg-zinc-900/60 border-zinc-800 hover:border-zinc-700'
+ }`}
+ style={{
+ borderColor: isSelected ? item.color : undefined,
+ }}
+ >
+
+
+
+
+ {item.method}
+
+
+ {item.percentage}% share
+
+
+
+
+
+
+ {item.amount}
+
+ {isSelected ? (
+
+ ) : null}
+
+
+ );
+ })}
+
+
+ {/* Selected Channel Deep-Dive Banner */}
+ {activeItem ? (
+
+
+
+ {activeItem.method} Channel Details:
+
+ {activeItem.amount} collected today across all active store POS terminals.
+
+
+
+
+ ) : null}
+
+ );
+}
diff --git a/src/features/commerce/components/analytics/RevenueChart.tsx b/src/features/commerce/components/analytics/RevenueChart.tsx
new file mode 100644
index 0000000..dab0453
--- /dev/null
+++ b/src/features/commerce/components/analytics/RevenueChart.tsx
@@ -0,0 +1,37 @@
+'use client';
+
+import {Heading, Text} from '@astryxdesign/core/Text';
+import {AreaChartView} from '@/shared/components/charts/AreaChartView';
+import {REVENUE_TREND_DATA} from '../../services/commerceService';
+
+export function RevenueChart() {
+ return (
+
+
+
+
+ Revenue Trend
+
+
+ Hourly revenue performance against targets
+
+
+
+ +14.2% Above Target
+
+
+
+
+
+ );
+}
diff --git a/src/features/commerce/components/analytics/SalesFunnel.tsx b/src/features/commerce/components/analytics/SalesFunnel.tsx
new file mode 100644
index 0000000..a03a066
--- /dev/null
+++ b/src/features/commerce/components/analytics/SalesFunnel.tsx
@@ -0,0 +1,62 @@
+'use client';
+
+import {Heading, Text} from '@astryxdesign/core/Text';
+import {SALES_FUNNEL_STAGES} from '../../services/commerceService';
+
+export function SalesFunnel() {
+ const maxCount = SALES_FUNNEL_STAGES[0]?.count || 1;
+
+ return (
+
+
+
+
+ Sales & Conversion Funnel
+
+
+ Customer journey from initial store visit to completed transaction
+
+
+
+
23.8% Overall Conversion
+
2,951 of 12,400 converted
+
+
+
+ {/* Funnel Visual Stack */}
+
+ {SALES_FUNNEL_STAGES.map((stage, idx) => {
+ const widthPercent = Math.max(25, (stage.count / maxCount) * 100);
+ return (
+
+
+
+
+ {idx + 1}
+
+ {stage.stage}
+
+
+ {stage.count.toLocaleString()} ({stage.conversionRate})
+
+
+
+
+
+ {stage.label}
+ {stage.count.toLocaleString()}
+
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/features/commerce/components/analytics/StorePerformanceChart.tsx b/src/features/commerce/components/analytics/StorePerformanceChart.tsx
new file mode 100644
index 0000000..e37c357
--- /dev/null
+++ b/src/features/commerce/components/analytics/StorePerformanceChart.tsx
@@ -0,0 +1,43 @@
+'use client';
+
+import {Heading, Text} from '@astryxdesign/core/Text';
+import {STORE_PERFORMANCE_DATA} from '../../services/commerceService';
+
+export function StorePerformanceChart() {
+ const maxRevenue = Math.max(...STORE_PERFORMANCE_DATA.map((s) => s.Revenue)) || 1;
+
+ return (
+
+
+
+ Top Performing Outlets
+
+
+ Store revenue comparison across your network
+
+
+
+
+ {STORE_PERFORMANCE_DATA.map((store) => {
+ const widthPercent = (store.Revenue / maxRevenue) * 100;
+ return (
+
+
+ {store.store}
+
+ ₹{(store.Revenue / 100000).toFixed(1)}L
+
+
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/features/commerce/components/analytics/TopProductsTable.tsx b/src/features/commerce/components/analytics/TopProductsTable.tsx
new file mode 100644
index 0000000..9c8895e
--- /dev/null
+++ b/src/features/commerce/components/analytics/TopProductsTable.tsx
@@ -0,0 +1,62 @@
+'use client';
+
+import {Heading, Text} from '@astryxdesign/core/Text';
+import {Badge} from '@astryxdesign/core/Badge';
+import {TOP_PRODUCTS_DATA} from '../../services/commerceService';
+
+export function TopProductsTable() {
+ return (
+
+
+
+ Top Selling Products Leaderboard
+
+
+ Product sales volume, revenue growth and real-time inventory counts
+
+
+
+
+
+
+
+ | Product Name |
+ Category |
+ Units Sold |
+ Total Revenue |
+ Growth |
+ Stock Status |
+
+
+
+ {TOP_PRODUCTS_DATA.map((prod) => (
+
+ | {prod.name} |
+ {prod.category} |
+ {prod.unitsSold} |
+ {prod.revenue} |
+
+
+ {prod.growth}
+
+ |
+
+
+ |
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/features/commerce/components/filters/CommerceFilterToolbar.tsx b/src/features/commerce/components/filters/CommerceFilterToolbar.tsx
new file mode 100644
index 0000000..334bb0a
--- /dev/null
+++ b/src/features/commerce/components/filters/CommerceFilterToolbar.tsx
@@ -0,0 +1,82 @@
+'use client';
+
+import {HStack} from '@astryxdesign/core/Layout';
+import {Button} from '@astryxdesign/core/Button';
+import {Icon} from '@astryxdesign/core/Icon';
+import {DownloadDropdown} from '@/shared/components/patterns/DownloadDropdown';
+import {ICONS} from '@/shared/utils/icons';
+import type {CommerceFilterState} from '../../types/commerce';
+
+export function CommerceFilterToolbar({
+ filters,
+ onFilterChange,
+ onExport,
+ onCompare,
+}: {
+ filters: CommerceFilterState;
+ onFilterChange: (f: CommerceFilterState) => void;
+ onExport: (format: 'pdf' | 'excel' | 'csv') => void;
+ onCompare?: () => void;
+}) {
+ return (
+
+ {/* Date Range Selector */}
+
+ {(['today', '7d', '30d', '90d'] as const).map((r) => (
+
+ ))}
+
+
+ {/* Store Filter */}
+
+
+ {/* Compare Button */}
+ {onCompare ? (
+ }
+ onClick={onCompare}
+ />
+ ) : null}
+
+ {/* Export Dropdown */}
+
+
+ );
+}
diff --git a/src/features/commerce/hooks/useCommerce.ts b/src/features/commerce/hooks/useCommerce.ts
new file mode 100644
index 0000000..c56065c
--- /dev/null
+++ b/src/features/commerce/hooks/useCommerce.ts
@@ -0,0 +1,89 @@
+'use client';
+
+import {useCallback, useEffect, useState} from 'react';
+import type {CommerceFilterState} from '../types/commerce';
+import {
+ BUSINESS_ALERTS,
+ COMMERCE_KPIS,
+ PAYMENT_BREAKDOWN_DATA,
+ TOP_PRODUCTS_DATA,
+} from '../services/commerceService';
+import {exportData} from '@/shared/utils/export/exportManager';
+
+const STORAGE_WIDTH_KEY = 'loyaly.commerce.pulsePanelWidth';
+const DEFAULT_PANEL_WIDTH = 340;
+
+export function useCommerce() {
+ const [filters, setFilters] = useState
({
+ dateRange: 'today',
+ storeId: 'all',
+ category: 'all',
+ paymentMethod: 'all',
+ salesChannel: 'all',
+ });
+
+ const [isPanelOpen, setIsPanelOpen] = useState(true);
+ const [panelWidth, setPanelWidth] = useState(DEFAULT_PANEL_WIDTH);
+
+ useEffect(() => {
+ try {
+ const saved = localStorage.getItem(STORAGE_WIDTH_KEY);
+ if (saved) {
+ const parsed = parseInt(saved, 10);
+ if (!isNaN(parsed) && parsed >= 280 && parsed <= 500) {
+ setPanelWidth(parsed);
+ }
+ }
+ } catch {
+ /* ignore */
+ }
+ }, []);
+
+ const updatePanelWidth = useCallback((width: number) => {
+ const clamped = Math.max(280, Math.min(width, 500));
+ setPanelWidth(clamped);
+ try {
+ localStorage.setItem(STORAGE_WIDTH_KEY, clamped.toString());
+ } catch {
+ /* ignore */
+ }
+ }, []);
+
+ const handleExport = useCallback(async (format: 'pdf' | 'excel' | 'csv') => {
+ const dataRows = [
+ {metric: 'Total Revenue', value: COMMERCE_KPIS.revenue.value, change: COMMERCE_KPIS.revenue.change},
+ {metric: 'Total Orders', value: COMMERCE_KPIS.orders.value, change: COMMERCE_KPIS.orders.change},
+ {metric: 'Average Order Value', value: COMMERCE_KPIS.aov.value, change: COMMERCE_KPIS.aov.change},
+ {metric: 'Net Sales', value: COMMERCE_KPIS.netSales.value, change: COMMERCE_KPIS.netSales.change},
+ {metric: 'Refunds', value: COMMERCE_KPIS.refunds.value, change: COMMERCE_KPIS.refunds.change},
+ {metric: 'Conversion Rate', value: COMMERCE_KPIS.conversion.value, change: COMMERCE_KPIS.conversion.change},
+ ];
+
+ await exportData({
+ filename: `Commerce_Analytics_${new Date().toISOString().slice(0, 10)}`,
+ title: 'Loyaly Commerce Business Intelligence Report',
+ subtitle: `Generated on ${new Date().toLocaleDateString()} • Date Range: ${filters.dateRange.toUpperCase()}`,
+ columns: [
+ {key: 'metric', header: 'Metric Name'},
+ {key: 'value', header: 'Current Value'},
+ {key: 'change', header: 'Trend / Delta'},
+ ],
+ data: dataRows,
+ format,
+ });
+ }, [filters]);
+
+ return {
+ filters,
+ setFilters,
+ isPanelOpen,
+ setIsPanelOpen,
+ panelWidth,
+ updatePanelWidth,
+ handleExport,
+ kpis: COMMERCE_KPIS,
+ alerts: BUSINESS_ALERTS,
+ topProducts: TOP_PRODUCTS_DATA,
+ paymentSplits: PAYMENT_BREAKDOWN_DATA,
+ };
+}
diff --git a/src/features/commerce/repositories/commerceRepository.ts b/src/features/commerce/repositories/commerceRepository.ts
new file mode 100644
index 0000000..ba1befc
--- /dev/null
+++ b/src/features/commerce/repositories/commerceRepository.ts
@@ -0,0 +1,30 @@
+import {
+ COMMERCE_KPIS,
+ FORECAST_TREND_DATA,
+ ORDERS_TREND_DATA,
+ PAYMENT_BREAKDOWN_DATA,
+ REVENUE_TREND_DATA,
+ SALES_FUNNEL_STAGES,
+ STORE_PERFORMANCE_DATA,
+ TOP_PRODUCTS_DATA,
+} from '../services/commerceService';
+import type {CommerceFilterState} from '../types/commerce';
+
+export class CommerceRepository {
+ async getAnalytics(filters: CommerceFilterState) {
+ // Simulates an API call
+ await new Promise((r) => setTimeout(r, 100));
+ return {
+ kpis: COMMERCE_KPIS,
+ revenueTrend: REVENUE_TREND_DATA,
+ ordersTrend: ORDERS_TREND_DATA,
+ funnel: SALES_FUNNEL_STAGES,
+ paymentBreakdown: PAYMENT_BREAKDOWN_DATA,
+ storePerformance: STORE_PERFORMANCE_DATA,
+ topProducts: TOP_PRODUCTS_DATA,
+ forecast: FORECAST_TREND_DATA,
+ };
+ }
+}
+
+export const commerceRepository = new CommerceRepository();
diff --git a/src/features/commerce/services/commerceService.ts b/src/features/commerce/services/commerceService.ts
new file mode 100644
index 0000000..3a397b9
--- /dev/null
+++ b/src/features/commerce/services/commerceService.ts
@@ -0,0 +1,128 @@
+import type {
+ BusinessAlert,
+ CommerceFilterState,
+ ForecastDataPoint,
+ FunnelStage,
+ HeatmapCell,
+ MetricCardData,
+ PaymentBreakdownItem,
+ TopProductRow,
+} from '../types/commerce';
+
+export const COMMERCE_KPIS: Record = {
+ revenue: {title: 'Total Revenue', value: '₹3,40,000', change: '+14.2% vs prev', trendDirection: 'up', subtitle: 'Net revenue across active stores'},
+ orders: {title: 'Total Orders', value: '1,284', change: '+8.5%', trendDirection: 'up', subtitle: 'Completed order volume'},
+ aov: {title: 'Average Order Value', value: '₹1,420', change: '+5.1%', trendDirection: 'up', subtitle: 'Average spend per cart'},
+ netSales: {title: 'Net Sales', value: '₹3,18,000', change: '+13.9%', trendDirection: 'up', subtitle: 'Revenue after discounts'},
+ refunds: {title: 'Refunds & Returns', value: '₹22,000', change: '-2.4%', trendDirection: 'down', subtitle: '6.4% total return rate'},
+ conversion: {title: 'Store Conversion', value: '23.8%', change: '+1.4%', trendDirection: 'up', subtitle: 'Visits to purchase ratio'},
+ topStore: {title: 'Top Performing Store', value: 'Indiranagar Flagship', change: '+18.4%', trendDirection: 'up', subtitle: '₹9.1L Revenue (23.8% Conv)'},
+};
+
+export const REVENUE_TREND_DATA = [
+ {time: '08:00', Revenue: 18000, Target: 15000},
+ {time: '10:00', Revenue: 35000, Target: 30000},
+ {time: '12:00', Revenue: 72000, Target: 60000},
+ {time: '14:00', Revenue: 95000, Target: 80000},
+ {time: '16:00', Revenue: 82000, Target: 75000},
+ {time: '18:00', Revenue: 110000, Target: 90000},
+ {time: '20:00', Revenue: 68000, Target: 60000},
+];
+
+export const ORDERS_TREND_DATA = [
+ {time: '08:00', Orders: 42},
+ {time: '10:00', Orders: 88},
+ {time: '12:00', Orders: 165},
+ {time: '14:00', Orders: 210},
+ {time: '16:00', Orders: 184},
+ {time: '18:00', Orders: 245},
+ {time: '20:00', Orders: 135},
+];
+
+export const SALES_FUNNEL_STAGES: FunnelStage[] = [
+ {stage: 'Visitors', count: 12400, label: 'Store & Web Visitors', conversionRate: '100%', color: '#3b82f6'},
+ {stage: 'Product Views', count: 8680, label: 'Browsed Items', conversionRate: '70%', color: '#6366f1'},
+ {stage: 'Add to Cart', count: 4340, label: 'Cart Created', conversionRate: '50%', color: '#8b5cf6'},
+ {stage: 'Checkout Initiated', count: 3100, label: 'Entered Checkout', conversionRate: '71.4%', color: '#ec4899'},
+ {stage: 'Purchased', count: 2951, label: 'Order Completed', conversionRate: '95.2%', color: '#10b981'},
+];
+
+export const PAYMENT_BREAKDOWN_DATA: PaymentBreakdownItem[] = [
+ {method: 'UPI', amount: '₹1,97,200', percentage: 58, color: '#10b981'},
+ {method: 'Credit & Debit Cards', amount: '₹95,200', percentage: 28, color: '#3b82f6'},
+ {method: 'Store Credit / LYT', amount: '₹34,000', percentage: 10, color: '#f59e0b'},
+ {method: 'Cash & POS', amount: '₹13,600', percentage: 4, color: '#8b5cf6'},
+];
+
+export const STORE_PERFORMANCE_DATA = [
+ {store: 'Indiranagar Flagship', Revenue: 910000},
+ {store: 'Koramangala 80ft', Revenue: 520000},
+ {store: 'Jayanagar 4th Block', Revenue: 400000},
+ {store: 'Whitefield Main', Revenue: 330000},
+];
+
+export const TOP_PRODUCTS_DATA: TopProductRow[] = [
+ {id: 'tp1', name: 'Signature Espresso Blend (1kg)', category: 'Beverage & Beans', unitsSold: 482, revenue: '₹1,44,600', growth: '+18.4%', growthDirection: 'up', stockCount: 142, stockStatus: 'in_stock'},
+ {id: 'tp2', name: 'Artisanal Cold Brew Pack (6x)', category: 'Ready to Drink', unitsSold: 310, revenue: '₹93,000', growth: '+12.1%', growthDirection: 'up', stockCount: 18, stockStatus: 'low_stock'},
+ {id: 'tp3', name: 'Ceramic Barista Mug (Matte Black)', category: 'Merchandise', unitsSold: 215, revenue: '₹75,250', growth: '+8.9%', growthDirection: 'up', stockCount: 8, stockStatus: 'low_stock'},
+ {id: 'tp4', name: 'Vanilla Bean Syrup (750ml)', category: 'Syrups', unitsSold: 184, revenue: '₹46,000', growth: '+5.4%', growthDirection: 'up', stockCount: 85, stockStatus: 'in_stock'},
+ {id: 'tp5', name: 'French Press Brewer (8-Cup)', category: 'Equipment', unitsSold: 94, revenue: '₹37,600', growth: '-2.1%', growthDirection: 'down', stockCount: 0, stockStatus: 'out_of_stock'},
+];
+
+export const HEATMAP_HOURS = ['08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00'];
+export const HEATMAP_DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
+
+export const HEATMAP_DATA: HeatmapCell[] = [
+ {day: 'Mon', hour: '12:00', value: 65},
+ {day: 'Mon', hour: '18:00', value: 85},
+ {day: 'Fri', hour: '18:00', value: 98},
+ {day: 'Sat', hour: '14:00', value: 92},
+ {day: 'Sat', hour: '18:00', value: 100},
+ {day: 'Sun', hour: '12:00', value: 78},
+ {day: 'Sun', hour: '18:00', value: 95},
+];
+
+export const FORECAST_TREND_DATA: ForecastDataPoint[] = [
+ {date: 'Mon', actual: 280000, forecast: 275000, lowerBound: 260000, upperBound: 290000},
+ {date: 'Tue', actual: 310000, forecast: 300000, lowerBound: 285000, upperBound: 315000},
+ {date: 'Wed', actual: 295000, forecast: 290000, lowerBound: 275000, upperBound: 305000},
+ {date: 'Thu', actual: 340000, forecast: 330000, lowerBound: 310000, upperBound: 350000},
+ {date: 'Fri (Est)', forecast: 380000, lowerBound: 355000, upperBound: 405000},
+ {date: 'Sat (Est)', forecast: 420000, lowerBound: 390000, upperBound: 450000},
+ {date: 'Sun (Est)', forecast: 390000, lowerBound: 360000, upperBound: 420000},
+];
+
+export const BUSINESS_ALERTS: BusinessAlert[] = [
+ {
+ id: 'ba1',
+ type: 'low_stock',
+ title: 'Low Stock Reorder Alert',
+ description: 'Ceramic Barista Mug has only 8 units left at Koramangala 80ft.',
+ actionText: 'Reorder Stock',
+ severity: 'critical',
+ },
+ {
+ id: 'ba2',
+ type: 'refund_spike',
+ title: 'Refund Spike Detected',
+ description: 'Whitefield store processed 4 return requests in the last 2 hours.',
+ actionText: 'Review Return Audit',
+ severity: 'warning',
+ },
+ {
+ id: 'ba3',
+ type: 'best_seller',
+ title: 'Best Seller Momentum',
+ description: 'Signature Espresso Blend sales spiked +18.4% above weekly baseline.',
+ actionText: 'Promote Banner',
+ severity: 'success',
+ },
+ {
+ id: 'ba4',
+ type: 'promotion_opportunity',
+ title: 'Weekend Upsell Opportunity',
+ description: 'Combine Cold Brew Pack with Syrups for a +₹250 AOV expansion.',
+ actionText: 'Launch Campaign',
+ severity: 'info',
+ },
+];
diff --git a/src/features/commerce/types/commerce.ts b/src/features/commerce/types/commerce.ts
new file mode 100644
index 0000000..50c01d4
--- /dev/null
+++ b/src/features/commerce/types/commerce.ts
@@ -0,0 +1,72 @@
+export interface CommerceFilterState {
+ dateRange: 'today' | '7d' | '30d' | '90d' | 'custom';
+ storeId: string;
+ category: string;
+ paymentMethod: string;
+ salesChannel: string;
+}
+
+export interface MetricCardData {
+ title: string;
+ value: string;
+ change: string;
+ trendDirection: 'up' | 'down' | 'neutral';
+ subtitle?: string;
+}
+
+export interface FunnelStage {
+ stage: string;
+ count: number;
+ label: string;
+ conversionRate: string;
+ color: string;
+}
+
+export interface PaymentBreakdownItem {
+ method: string;
+ amount: string;
+ percentage: number;
+ color: string;
+}
+
+export interface TopProductRow {
+ id: string;
+ name: string;
+ category: string;
+ unitsSold: number;
+ revenue: string;
+ growth: string;
+ growthDirection: 'up' | 'down';
+ stockCount: number;
+ stockStatus: 'in_stock' | 'low_stock' | 'out_of_stock';
+}
+
+export interface HeatmapCell {
+ day: string;
+ hour: string;
+ value: number; // intensity 0 - 100
+}
+
+export interface ForecastDataPoint {
+ date: string;
+ actual?: number;
+ forecast: number;
+ lowerBound?: number;
+ upperBound?: number;
+}
+
+export interface BusinessAlert {
+ id: string;
+ type: 'low_stock' | 'revenue_drop' | 'refund_spike' | 'best_seller' | 'promotion_opportunity';
+ title: string;
+ description: string;
+ actionText: string;
+ severity: 'critical' | 'warning' | 'info' | 'success';
+}
+
+export interface OperationalFlowStep {
+ step: string;
+ label: string;
+ detail: string;
+ iconName: string;
+}
diff --git a/src/shared/components/patterns/MetricCard.tsx b/src/shared/components/patterns/MetricCard.tsx
index 4253ab8..fdc73e3 100644
--- a/src/shared/components/patterns/MetricCard.tsx
+++ b/src/shared/components/patterns/MetricCard.tsx
@@ -58,7 +58,7 @@ export function MetricCard({
-
+
{/*
Text, NOT Heading. A KPI value is data, not a section title —
marking it up as an h2 puts "32.7k" into the document outline
@@ -66,11 +66,13 @@ export function MetricCard({
numbers. `display-3` gives the visual weight without the
semantics.
*/}
-
+
{deltaPct !== undefined ? (
-
+
+
+
) : null}
diff --git a/src/shared/layouts/workspace/nav-config.ts b/src/shared/layouts/workspace/nav-config.ts
index de9c3ee..2be8400 100644
--- a/src/shared/layouts/workspace/nav-config.ts
+++ b/src/shared/layouts/workspace/nav-config.ts
@@ -14,6 +14,7 @@ export interface NavEntry {
*/
export const PRIMARY_NAV: NavEntry[] = [
{label: 'Dashboard', href: '/dashboard', icon: ICONS.dashboard},
+ {label: 'Commerce', href: '/commerce', icon: ICONS.commerce},
{label: 'Store', href: '/stores', icon: ICONS.stores},
{label: 'Lyts', href: '/lyts', icon: ICONS.lyts},
{label: 'Leaderboard', href: '/staff', icon: ICONS.staff},
diff --git a/src/shared/utils/icons.ts b/src/shared/utils/icons.ts
index a785262..3775a16 100644
--- a/src/shared/utils/icons.ts
+++ b/src/shared/utils/icons.ts
@@ -78,6 +78,7 @@ export const ICONS = {
minimize: Minimize2,
// Navigation
dashboard: LayoutDashboard,
+ commerce: ShoppingCart,
stores: Store,
lyts: Gift,
staff: Users,