fix sales issue

This commit is contained in:
2026-08-06 18:13:59 +05:30
parent 32847159ae
commit f80c1d9f5d
13 changed files with 213 additions and 425 deletions

View File

@@ -4,40 +4,45 @@ 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';
import {useWorkspace} from '@/shared/providers/WorkspaceProvider';
import {getCommerceKpis} from '../services/commerceService';
export function CommerceKpiRow() {
const {storeId, range} = useWorkspace();
const kpis = getCommerceKpis(storeId, range);
const kpiList = [
{
label: 'Total Revenue',
value: 340000,
value: kpis.revenue.rawValue,
format: formatInrCompact,
icon: ICONS.revenue,
deltaPct: 14.2,
},
{
label: 'Total Orders',
value: 1284,
value: kpis.orders.rawValue,
format: formatCount,
icon: ICONS.purchases,
deltaPct: 8.5,
},
{
label: 'Avg Order Value',
value: 1420,
value: kpis.aov.rawValue,
format: formatInrCompact,
icon: ICONS.purchases,
deltaPct: 5.1,
},
{
label: 'Net Sales',
value: 318000,
value: kpis.netSales.rawValue,
format: formatInrCompact,
icon: ICONS.revenue,
deltaPct: 13.9,
},
{
label: 'Refunds',
value: 22000,
value: kpis.refunds.rawValue,
format: formatInrCompact,
icon: ICONS.alert,
deltaPct: -2.4,
@@ -45,7 +50,7 @@ export function CommerceKpiRow() {
},
{
label: 'Conversion',
value: 23.8,
value: kpis.conversion.rawValue,
format: (v: number) => formatPct(v, 1),
icon: ICONS.conversion,
deltaPct: 1.4,

View File

@@ -3,8 +3,13 @@
import {Heading, Text} from '@astryxdesign/core/Text';
import {MetricCard} from '@/shared/components/patterns/MetricCard';
import {formatInrCompact} from '@/shared/utils/format';
import {useWorkspace} from '@/shared/providers/WorkspaceProvider';
import {getCommerceKpis} from '../services/commerceService';
export function CommerceRevenueSummary() {
const {storeId, range} = useWorkspace();
const kpis = getCommerceKpis(storeId, range);
return (
<div className="rounded-xl border border-border bg-card p-4 space-y-4 shadow-sm">
<div className="border-b border-border pb-3">
@@ -19,26 +24,26 @@ export function CommerceRevenueSummary() {
<div className="grid grid-cols-2 gap-3">
<MetricCard
label="Gross Sales"
value={362000}
value={kpis.grossSales.rawValue}
format={formatInrCompact}
deltaPct={12.8}
/>
<MetricCard
label="Discounts & Promos"
value={22000}
value={kpis.discounts.rawValue}
format={formatInrCompact}
deltaPct={-4.1}
/>
<MetricCard
label="Refunds Processed"
value={22000}
value={kpis.refunds.rawValue}
format={formatInrCompact}
deltaPct={-2.4}
isRiseGood={false}
/>
<MetricCard
label="Net Realized Revenue"
value={318000}
value={kpis.netSales.rawValue}
format={formatInrCompact}
deltaPct={13.9}
/>

View File

@@ -1,127 +0,0 @@
'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 (
<div className="h-full w-full overflow-y-auto p-4 space-y-4 bg-background text-white">
{/* Pulse Header */}
<div className="flex items-center justify-between pb-2 border-b border-border">
<div>
<Heading level={3} className="text-sm font-bold text-white flex items-center gap-2">
<span className="size-2 rounded-full bg-emerald-500 animate-pulse" />
Business Pulse
</Heading>
<Text size="sm" color="secondary">
Live telemetry stream
</Text>
</div>
<Badge variant="success" label="Active" />
</div>
{/* Pulse Cards */}
<div className="space-y-3">
{/* Today's Revenue */}
<div className="rounded-xl border border-border bg-card p-3 flex justify-between items-center shadow-sm">
<div>
<div className="text-xs font-semibold text-zinc-400">Today's Revenue</div>
<div className="text-lg font-bold text-white tabular-nums">{COMMERCE_KPIS.revenue.value}</div>
</div>
<span className="text-xs font-bold text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded border border-emerald-500/20">
{COMMERCE_KPIS.revenue.change}
</span>
</div>
{/* Today's Orders */}
<div className="rounded-xl border border-border bg-card p-3 flex justify-between items-center shadow-sm">
<div>
<div className="text-xs font-semibold text-zinc-400">Today's Orders</div>
<div className="text-lg font-bold text-white tabular-nums">{COMMERCE_KPIS.orders.value}</div>
</div>
<span className="text-xs font-bold text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded border border-emerald-500/20">
{COMMERCE_KPIS.orders.change}
</span>
</div>
{/* Active Promotions */}
<div className="rounded-xl border border-border bg-card p-3 space-y-1 shadow-sm">
<div className="flex items-center justify-between text-xs font-bold text-white">
<span className="flex items-center gap-1.5">
<Icon icon={ICONS.activeRewards} size="sm" className="text-amber-400" />
Active Promotions
</span>
<Badge variant="info" label="3 Running" />
</div>
<div className="text-[11px] text-zinc-400 font-medium">
Weekend Double LYT & Espresso Bundle driving +14% lift
</div>
</div>
{/* Inventory Alerts */}
<div className="rounded-xl border border-amber-500/30 bg-amber-500/[0.04] p-3 space-y-1 shadow-sm">
<div className="flex items-center justify-between text-xs font-bold text-amber-300">
<span className="flex items-center gap-1.5">
<Icon icon={ICONS.alert} size="sm" className="text-amber-400" />
Inventory Alert
</span>
<Badge variant="warning" label="2 Items" />
</div>
<div className="text-[11px] text-zinc-400">
Cold Brew Pack (18 left) & Barista Mug (8 left)
</div>
</div>
{/* Payment Split */}
<div className="rounded-xl border border-border bg-card p-3 space-y-2 shadow-sm">
<div className="text-xs font-bold text-white flex justify-between">
<span>Payment Method Split</span>
<span className="text-emerald-400">UPI 58%</span>
</div>
<div className="h-1.5 w-full rounded-full bg-zinc-800 overflow-hidden flex">
{PAYMENT_BREAKDOWN_DATA.map((p) => (
<div
key={p.method}
style={{width: `${p.percentage}%`, backgroundColor: p.color}}
className="h-full"
/>
))}
</div>
</div>
{/* Top Store */}
<div className="rounded-xl border border-border bg-card p-3 space-y-1 shadow-sm">
<div className="text-xs font-semibold text-zinc-400">Top Performing Store</div>
<div className="text-sm font-bold text-white">{COMMERCE_KPIS.topStore?.value ?? 'Indiranagar Flagship'}</div>
<div className="text-[11px] text-zinc-400">{COMMERCE_KPIS.topStore?.subtitle ?? '9.1L Revenue (23.8% Conv)'}</div>
</div>
{/* Recent Transactions */}
<div className="rounded-xl border border-border bg-card p-3 space-y-2 shadow-sm">
<div className="text-xs font-bold text-white">Recent Transactions</div>
<div className="space-y-2 divide-y divide-zinc-800">
{[
{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) => (
<div key={t.id} className="pt-1.5 first:pt-0 flex items-center justify-between text-xs">
<div>
<div className="font-bold text-white">{t.code}</div>
<div className="text-[10px] text-zinc-400">{t.name}</div>
</div>
<div className="text-right">
<div className="font-bold text-white tabular-nums">{t.amount}</div>
<div className="text-[10px] text-zinc-400">{t.time}</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,38 +0,0 @@
'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 (
<div className="rounded-xl border border-border bg-card p-5 space-y-3 shadow-sm overflow-hidden">
<div className="flex items-center justify-between">
<div>
<Heading level={4} className="text-base font-bold text-white">
Predictive Revenue & Demand Forecast
</Heading>
<Text size="sm" color="secondary">
AI-driven 7-day revenue, demand and stock requirement projections
</Text>
</div>
<span className="text-xs font-bold text-indigo-300 bg-indigo-500/10 px-3 py-1 rounded-full border border-indigo-500/20">
96% Confidence Band
</span>
</div>
<div className="h-64 w-full pt-2">
<LineChartView
data={FORECAST_TREND_DATA}
xKey="date"
series={[
{key: 'actual', label: 'Actual Revenue (₹)'},
{key: 'forecast', label: 'Forecasted Projection (₹)'},
{key: 'upperBound', label: 'Upper Bound (₹)'},
]}
height="100%"
/>
</div>
</div>
);
}

View File

@@ -1,72 +0,0 @@
'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 (
<div className="rounded-xl border border-border bg-card p-5 space-y-4 shadow-sm overflow-hidden">
<div className="flex items-center justify-between">
<div>
<Heading level={4} className="text-base font-bold text-white">
Hourly Sales & Footfall Heatmap
</Heading>
<Text size="sm" color="secondary">
Sales volume intensity matrix mapped across days and operating hours
</Text>
</div>
<div className="flex items-center gap-2 text-xs font-semibold text-zinc-300">
<span>Low</span>
<div className="flex gap-1">
<span className="size-3 rounded bg-zinc-800" />
<span className="size-3 rounded bg-emerald-700/60" />
<span className="size-3 rounded bg-emerald-600/80" />
<span className="size-3 rounded bg-emerald-500" />
</div>
<span>Peak</span>
</div>
</div>
<div className="overflow-x-auto w-full pt-1">
<table className="w-full text-center text-xs border-collapse">
<thead>
<tr>
<th className="p-2 text-left text-zinc-400 font-bold">Day / Hour</th>
{HEATMAP_HOURS.map((h) => (
<th key={h} className="p-2 font-bold text-zinc-300">
{h}
</th>
))}
</tr>
</thead>
<tbody>
{HEATMAP_DAYS.map((day) => (
<tr key={day} className="border-t border-zinc-800/40">
<td className="p-2 text-left font-bold text-white">{day}</td>
{HEATMAP_HOURS.map((hour) => {
const styleClass = getIntensityBg(day, hour);
return (
<td key={hour} className="p-1">
<div
className={`h-8 rounded flex items-center justify-center text-[11px] transition-all hover:scale-105 ${styleClass}`}
>
{hour === '18:00' ? '98%' : hour === '14:00' ? '74%' : '42%'}
</div>
</td>
);
})}
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}

View File

@@ -2,9 +2,15 @@
import {Heading, Text} from '@astryxdesign/core/Text';
import {BarChartView} from '@/shared/components/charts/BarChartView';
import {ORDERS_TREND_DATA} from '../../services/commerceService';
import {useWorkspace} from '@/shared/providers/WorkspaceProvider';
import {getOrdersTrendData} from '../../services/commerceService';
export function OrdersChart() {
const {storeId, range} = useWorkspace();
const data = getOrdersTrendData(storeId, range);
const totalOrders = data.reduce((acc, curr) => acc + curr.Orders, 0);
return (
<div className="rounded-xl border border-border bg-card p-4 space-y-3 overflow-hidden shadow-sm">
<div className="flex items-center justify-between">
@@ -17,13 +23,13 @@ export function OrdersChart() {
</Text>
</div>
<span className="text-xs font-bold text-white bg-zinc-800 px-2.5 py-1 rounded-full border border-zinc-700">
1,284 Orders Today
{totalOrders.toLocaleString()} Orders
</span>
</div>
<div className="h-64 w-full pt-2">
<BarChartView
data={ORDERS_TREND_DATA}
data={data}
xKey="time"
series={[{key: 'Orders', label: 'Completed Orders'}]}
height="100%"

View File

@@ -3,13 +3,16 @@
import {useState} from 'react';
import {Heading, Text} from '@astryxdesign/core/Text';
import {Badge} from '@astryxdesign/core/Badge';
import {PAYMENT_BREAKDOWN_DATA} from '../../services/commerceService';
import {useWorkspace} from '@/shared/providers/WorkspaceProvider';
import {getPaymentBreakdownData} from '../../services/commerceService';
export function PaymentBreakdown() {
const {storeId, range} = useWorkspace();
const paymentData = getPaymentBreakdownData(storeId, range);
const [selectedMethod, setSelectedMethod] = useState<string | null>(null);
const activeItem = selectedMethod
? PAYMENT_BREAKDOWN_DATA.find((p) => p.method === selectedMethod)
? paymentData.find((p) => p.method === selectedMethod)
: null;
return (
@@ -37,7 +40,7 @@ export function PaymentBreakdown() {
>
All
</button>
{PAYMENT_BREAKDOWN_DATA.map((item) => (
{paymentData.map((item) => (
<button
key={item.method}
type="button"
@@ -66,7 +69,7 @@ export function PaymentBreakdown() {
{/* Visual Ring / Stack Bar */}
<div className="h-4 w-full rounded-full bg-zinc-800 overflow-hidden flex shadow-inner p-0.5">
{PAYMENT_BREAKDOWN_DATA.map((item) => {
{paymentData.map((item) => {
const isSelected = selectedMethod === null || selectedMethod === item.method;
return (
<div
@@ -88,7 +91,7 @@ export function PaymentBreakdown() {
{/* Interactive Breakdown Cards */}
<div className="grid grid-cols-2 gap-3 pt-1">
{PAYMENT_BREAKDOWN_DATA.map((item) => {
{paymentData.map((item) => {
const isSelected = selectedMethod === item.method;
return (
<div
@@ -145,7 +148,7 @@ export function PaymentBreakdown() {
/>
<span className="font-bold text-white">{activeItem.method} Channel Details:</span>
<span className="text-zinc-300">
{activeItem.amount} collected today across all active store POS terminals.
{activeItem.amount} collected across selected scope terminals.
</span>
</div>
<button

View File

@@ -2,9 +2,13 @@
import {Heading, Text} from '@astryxdesign/core/Text';
import {AreaChartView} from '@/shared/components/charts/AreaChartView';
import {REVENUE_TREND_DATA} from '../../services/commerceService';
import {useWorkspace} from '@/shared/providers/WorkspaceProvider';
import {getRevenueTrendData} from '../../services/commerceService';
export function RevenueChart() {
const {storeId, range} = useWorkspace();
const data = getRevenueTrendData(storeId, range);
return (
<div className="rounded-xl border border-border bg-card p-4 space-y-3 overflow-hidden shadow-sm">
<div className="flex items-center justify-between">
@@ -23,7 +27,7 @@ export function RevenueChart() {
<div className="h-64 w-full pt-2">
<AreaChartView
data={REVENUE_TREND_DATA}
data={data}
xKey="time"
series={[
{key: 'Revenue', label: 'Actual Revenue (₹)'},

View File

@@ -1,62 +0,0 @@
'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 (
<div className="rounded-xl border border-border bg-card p-5 space-y-4 shadow-sm overflow-hidden">
<div className="flex items-center justify-between">
<div>
<Heading level={4} className="text-base font-bold text-white">
Sales & Conversion Funnel
</Heading>
<Text size="sm" color="secondary">
Customer journey from initial store visit to completed transaction
</Text>
</div>
<div className="text-right">
<span className="text-sm font-bold text-emerald-400">23.8% Overall Conversion</span>
<div className="text-xs text-zinc-400 font-medium">2,951 of 12,400 converted</div>
</div>
</div>
{/* Funnel Visual Stack */}
<div className="space-y-2.5 pt-2">
{SALES_FUNNEL_STAGES.map((stage, idx) => {
const widthPercent = Math.max(25, (stage.count / maxCount) * 100);
return (
<div key={stage.stage} className="space-y-1">
<div className="flex items-center justify-between text-xs font-semibold">
<span className="text-white font-bold flex items-center gap-2">
<span className="size-5 rounded-full bg-zinc-800 border border-zinc-700 flex items-center justify-center text-[10px] text-zinc-300">
{idx + 1}
</span>
{stage.stage}
</span>
<span className="text-zinc-300 tabular-nums">
{stage.count.toLocaleString()} ({stage.conversionRate})
</span>
</div>
<div className="w-full bg-zinc-900/80 rounded-lg p-1.5 border border-zinc-800">
<div
className="h-8 rounded-md transition-all duration-500 flex items-center justify-between px-3 text-xs font-bold text-white shadow-sm"
style={{
width: `${widthPercent}%`,
backgroundColor: stage.color,
}}
>
<span className="truncate">{stage.label}</span>
<span className="tabular-nums">{stage.count.toLocaleString()}</span>
</div>
</div>
</div>
);
})}
</div>
</div>
);
}

View File

@@ -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 (
<div className="rounded-xl border border-border bg-card overflow-hidden shadow-sm space-y-3">
<div className="p-4 border-b border-border bg-card">
@@ -29,11 +33,11 @@ export function TopProductsTable() {
</tr>
</thead>
<tbody className="divide-y divide-zinc-800/60">
{TOP_PRODUCTS_DATA.map((prod) => (
{products.map((prod) => (
<tr key={prod.id} className="hover:bg-white/[0.02] transition-colors">
<td className="px-4 py-3.5 text-white font-semibold">{prod.name}</td>
<td className="px-4 py-3.5 text-zinc-300 font-medium">{prod.category}</td>
<td className="px-4 py-3.5 text-right text-white font-bold tabular-nums">{prod.unitsSold}</td>
<td className="px-4 py-3.5 text-right text-white font-bold tabular-nums">{prod.unitsSold.toLocaleString()}</td>
<td className="px-4 py-3.5 text-right text-white font-bold tabular-nums">{prod.revenue}</td>
<td className="px-4 py-3.5 text-right font-bold tabular-nums">
<span className={prod.growthDirection === 'up' ? 'text-emerald-400' : 'text-rose-400'}>

View File

@@ -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),
};
}

View File

@@ -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),
};
}
}

View File

@@ -1,58 +1,139 @@
import type {
BusinessAlert,
CommerceFilterState,
ForecastDataPoint,
FunnelStage,
HeatmapCell,
MetricCardData,
PaymentBreakdownItem,
TopProductRow,
} from '../types/commerce';
export const COMMERCE_KPIS: Record<string, MetricCardData> = {
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<string, number> = {
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<string, number> = {
'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[] = [
{