- );
-}
diff --git a/src/features/commerce/components/analytics/TopProductsTable.tsx b/src/features/commerce/components/analytics/TopProductsTable.tsx
index 9c8895e..d07de78 100644
--- a/src/features/commerce/components/analytics/TopProductsTable.tsx
+++ b/src/features/commerce/components/analytics/TopProductsTable.tsx
@@ -2,9 +2,13 @@
import {Heading, Text} from '@astryxdesign/core/Text';
import {Badge} from '@astryxdesign/core/Badge';
-import {TOP_PRODUCTS_DATA} from '../../services/commerceService';
+import {useWorkspace} from '@/shared/providers/WorkspaceProvider';
+import {getTopProductsData} from '../../services/commerceService';
export function TopProductsTable() {
+ const {storeId, range} = useWorkspace();
+ const products = getTopProductsData(storeId, range);
+
return (
@@ -29,11 +33,11 @@ export function TopProductsTable() {
- {TOP_PRODUCTS_DATA.map((prod) => (
+ {products.map((prod) => (
| {prod.name} |
{prod.category} |
- {prod.unitsSold} |
+ {prod.unitsSold.toLocaleString()} |
{prod.revenue} |
diff --git a/src/features/commerce/hooks/useCommerce.ts b/src/features/commerce/hooks/useCommerce.ts
index c56065c..4d19325 100644
--- a/src/features/commerce/hooks/useCommerce.ts
+++ b/src/features/commerce/hooks/useCommerce.ts
@@ -4,9 +4,9 @@ import {useCallback, useEffect, useState} from 'react';
import type {CommerceFilterState} from '../types/commerce';
import {
BUSINESS_ALERTS,
- COMMERCE_KPIS,
- PAYMENT_BREAKDOWN_DATA,
- TOP_PRODUCTS_DATA,
+ getCommerceKpis,
+ getPaymentBreakdownData,
+ getTopProductsData,
} from '../services/commerceService';
import {exportData} from '@/shared/utils/export/exportManager';
@@ -49,14 +49,16 @@ export function useCommerce() {
}
}, []);
+ const kpis = getCommerceKpis(filters.storeId, filters.dateRange);
+
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},
+ {metric: 'Total Revenue', value: kpis.revenue.title, change: kpis.revenue.change},
+ {metric: 'Total Orders', value: kpis.orders.title, change: kpis.orders.change},
+ {metric: 'Average Order Value', value: kpis.aov.title, change: kpis.aov.change},
+ {metric: 'Net Sales', value: kpis.netSales.title, change: kpis.netSales.change},
+ {metric: 'Refunds', value: kpis.refunds.title, change: kpis.refunds.change},
+ {metric: 'Conversion Rate', value: kpis.conversion.title, change: kpis.conversion.change},
];
await exportData({
@@ -71,7 +73,7 @@ export function useCommerce() {
data: dataRows,
format,
});
- }, [filters]);
+ }, [filters, kpis]);
return {
filters,
@@ -81,9 +83,9 @@ export function useCommerce() {
panelWidth,
updatePanelWidth,
handleExport,
- kpis: COMMERCE_KPIS,
+ kpis,
alerts: BUSINESS_ALERTS,
- topProducts: TOP_PRODUCTS_DATA,
- paymentSplits: PAYMENT_BREAKDOWN_DATA,
+ topProducts: getTopProductsData(filters.storeId, filters.dateRange),
+ paymentSplits: getPaymentBreakdownData(filters.storeId, filters.dateRange),
};
}
diff --git a/src/features/commerce/repositories/commerceRepository.ts b/src/features/commerce/repositories/commerceRepository.ts
index ba1befc..7916b50 100644
--- a/src/features/commerce/repositories/commerceRepository.ts
+++ b/src/features/commerce/repositories/commerceRepository.ts
@@ -1,28 +1,25 @@
import {
- COMMERCE_KPIS,
- FORECAST_TREND_DATA,
- ORDERS_TREND_DATA,
- PAYMENT_BREAKDOWN_DATA,
- REVENUE_TREND_DATA,
- SALES_FUNNEL_STAGES,
STORE_PERFORMANCE_DATA,
- TOP_PRODUCTS_DATA,
+ getCommerceKpis,
+ getOrdersTrendData,
+ getPaymentBreakdownData,
+ getRevenueTrendData,
+ getTopProductsData,
} 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));
+ const storeId = filters.storeId || 'all';
+ const range = filters.dateRange || '7d';
+
return {
- kpis: COMMERCE_KPIS,
- revenueTrend: REVENUE_TREND_DATA,
- ordersTrend: ORDERS_TREND_DATA,
- funnel: SALES_FUNNEL_STAGES,
- paymentBreakdown: PAYMENT_BREAKDOWN_DATA,
+ kpis: getCommerceKpis(storeId, range),
+ revenueTrend: getRevenueTrendData(storeId, range),
+ ordersTrend: getOrdersTrendData(storeId, range),
+ paymentBreakdown: getPaymentBreakdownData(storeId, range),
storePerformance: STORE_PERFORMANCE_DATA,
- topProducts: TOP_PRODUCTS_DATA,
- forecast: FORECAST_TREND_DATA,
+ topProducts: getTopProductsData(storeId, range),
};
}
}
diff --git a/src/features/commerce/services/commerceService.ts b/src/features/commerce/services/commerceService.ts
index 3a397b9..ed969b7 100644
--- a/src/features/commerce/services/commerceService.ts
+++ b/src/features/commerce/services/commerceService.ts
@@ -1,58 +1,139 @@
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)'},
+const STORE_MULTIPLIERS: Record = {
+ all: 1.0,
+ 'blr-indiranagar': 0.42,
+ 'blr-koramangala': 0.28,
+ 'blr-whitefield': 0.18,
+ 'che-anna-nagar': 0.12,
+ 'hyd-jubilee': 0.1,
};
-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},
-];
+const RANGE_MULTIPLIERS: Record = {
+ '7d': 1.0,
+ '30d': 3.8,
+ '90d': 11.2,
+ mtd: 2.1,
+ ytd: 24.5,
+ custom: 1.5,
+};
-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},
-];
+function getMult(storeId: string, range: string): {storeMult: number; rangeMult: number; totalMult: number} {
+ const storeMult = STORE_MULTIPLIERS[storeId] ?? 1.0;
+ const rangeMult = RANGE_MULTIPLIERS[range] ?? 1.0;
+ return {storeMult, rangeMult, totalMult: storeMult * rangeMult};
+}
-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 function getCommerceKpis(storeId = 'all', range = '7d') {
+ const {totalMult, storeMult} = getMult(storeId, range);
-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'},
-];
+ const baseRevenue = 340000 * totalMult;
+ const baseOrders = Math.round(1284 * totalMult);
+ const baseAov = Math.round(1420 * (0.9 + storeMult * 0.25));
+ const baseNetSales = 318000 * totalMult;
+ const baseRefunds = 22000 * totalMult;
+ const baseConversion = Number((23.8 * (0.85 + storeMult * 0.3)).toFixed(1));
+
+ return {
+ revenue: {
+ title: 'Total Revenue',
+ rawValue: baseRevenue,
+ change: '+14.2% vs prev',
+ trendDirection: 'up' as const,
+ subtitle: 'Net revenue across selected store',
+ },
+ orders: {
+ title: 'Total Orders',
+ rawValue: baseOrders,
+ change: '+8.5%',
+ trendDirection: 'up' as const,
+ subtitle: 'Completed order volume',
+ },
+ aov: {
+ title: 'Avg Order Value',
+ rawValue: baseAov,
+ change: '+5.1%',
+ trendDirection: 'up' as const,
+ subtitle: 'Average spend per cart',
+ },
+ netSales: {
+ title: 'Net Sales',
+ rawValue: baseNetSales,
+ change: '+13.9%',
+ trendDirection: 'up' as const,
+ subtitle: 'Revenue after discounts',
+ },
+ refunds: {
+ title: 'Refunds',
+ rawValue: baseRefunds,
+ change: '-2.4%',
+ trendDirection: 'down' as const,
+ subtitle: 'Total returns rate',
+ },
+ conversion: {
+ title: 'Conversion',
+ rawValue: baseConversion,
+ change: '+1.4%',
+ trendDirection: 'up' as const,
+ subtitle: 'Visits to purchase ratio',
+ },
+ grossSales: {
+ title: 'Gross Sales',
+ rawValue: baseRevenue * 1.065,
+ change: '+12.8%',
+ trendDirection: 'up' as const,
+ },
+ discounts: {
+ title: 'Discounts & Promos',
+ rawValue: baseRefunds,
+ change: '-4.1%',
+ trendDirection: 'down' as const,
+ },
+ };
+}
+
+export function getRevenueTrendData(storeId = 'all', range = '7d') {
+ const {totalMult} = getMult(storeId, range);
+ return [
+ {time: '08:00', Revenue: Math.round(18000 * totalMult), Target: Math.round(15000 * totalMult)},
+ {time: '10:00', Revenue: Math.round(35000 * totalMult), Target: Math.round(30000 * totalMult)},
+ {time: '12:00', Revenue: Math.round(72000 * totalMult), Target: Math.round(60000 * totalMult)},
+ {time: '14:00', Revenue: Math.round(95000 * totalMult), Target: Math.round(80000 * totalMult)},
+ {time: '16:00', Revenue: Math.round(82000 * totalMult), Target: Math.round(75000 * totalMult)},
+ {time: '18:00', Revenue: Math.round(110000 * totalMult), Target: Math.round(90000 * totalMult)},
+ {time: '20:00', Revenue: Math.round(68000 * totalMult), Target: Math.round(60000 * totalMult)},
+ ];
+}
+
+export function getOrdersTrendData(storeId = 'all', range = '7d') {
+ const {totalMult} = getMult(storeId, range);
+ return [
+ {time: '08:00', Orders: Math.max(1, Math.round(42 * totalMult))},
+ {time: '10:00', Orders: Math.max(1, Math.round(88 * totalMult))},
+ {time: '12:00', Orders: Math.max(1, Math.round(165 * totalMult))},
+ {time: '14:00', Orders: Math.max(1, Math.round(210 * totalMult))},
+ {time: '16:00', Orders: Math.max(1, Math.round(184 * totalMult))},
+ {time: '18:00', Orders: Math.max(1, Math.round(245 * totalMult))},
+ {time: '20:00', Orders: Math.max(1, Math.round(135 * totalMult))},
+ ];
+}
+
+export function getPaymentBreakdownData(storeId = 'all', range = '7d'): PaymentBreakdownItem[] {
+ const {totalMult} = getMult(storeId, range);
+ const totalInr = 340000 * totalMult;
+
+ return [
+ {method: 'UPI', amount: `₹${(totalInr * 0.58).toLocaleString('en-IN', {maximumFractionDigits: 0})}`, percentage: 58, color: '#10b981'},
+ {method: 'Credit & Debit Cards', amount: `₹${(totalInr * 0.28).toLocaleString('en-IN', {maximumFractionDigits: 0})}`, percentage: 28, color: '#3b82f6'},
+ {method: 'Store Credit / LYT', amount: `₹${(totalInr * 0.10).toLocaleString('en-IN', {maximumFractionDigits: 0})}`, percentage: 10, color: '#f59e0b'},
+ {method: 'Cash & POS', amount: `₹${(totalInr * 0.04).toLocaleString('en-IN', {maximumFractionDigits: 0})}`, percentage: 4, color: '#8b5cf6'},
+ ];
+}
export const STORE_PERFORMANCE_DATA = [
{store: 'Indiranagar Flagship', Revenue: 910000},
@@ -61,36 +142,16 @@ export const STORE_PERFORMANCE_DATA = [
{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 function getTopProductsData(storeId = 'all', range = '7d'): TopProductRow[] {
+ const {totalMult} = getMult(storeId, range);
+ return [
+ {id: 'tp1', name: 'Signature Espresso Blend (1kg)', category: 'Beverage & Beans', unitsSold: Math.round(482 * totalMult), revenue: `₹${(144600 * totalMult).toLocaleString('en-IN', {maximumFractionDigits: 0})}`, growth: '+18.4%', growthDirection: 'up', stockCount: 142, stockStatus: 'in_stock'},
+ {id: 'tp2', name: 'Artisanal Cold Brew Pack (6x)', category: 'Ready to Drink', unitsSold: Math.round(310 * totalMult), revenue: `₹${(93000 * totalMult).toLocaleString('en-IN', {maximumFractionDigits: 0})}`, growth: '+12.1%', growthDirection: 'up', stockCount: 18, stockStatus: 'low_stock'},
+ {id: 'tp3', name: 'Ceramic Barista Mug (Matte Black)', category: 'Merchandise', unitsSold: Math.round(215 * totalMult), revenue: `₹${(75250 * totalMult).toLocaleString('en-IN', {maximumFractionDigits: 0})}`, growth: '+8.9%', growthDirection: 'up', stockCount: 8, stockStatus: 'low_stock'},
+ {id: 'tp4', name: 'Vanilla Bean Syrup (750ml)', category: 'Syrups', unitsSold: Math.round(184 * totalMult), revenue: `₹${(46000 * totalMult).toLocaleString('en-IN', {maximumFractionDigits: 0})}`, growth: '+5.4%', growthDirection: 'up', stockCount: 85, stockStatus: 'in_stock'},
+ {id: 'tp5', name: 'French Press Brewer (8-Cup)', category: 'Equipment', unitsSold: Math.round(94 * totalMult), revenue: `₹${(37600 * totalMult).toLocaleString('en-IN', {maximumFractionDigits: 0})}`, growth: '-2.1%', growthDirection: 'down', stockCount: 0, stockStatus: 'out_of_stock'},
+ ];
+}
export const BUSINESS_ALERTS: BusinessAlert[] = [
{
|