diff --git a/src/App.test.tsx b/src/App.test.tsx index 907fbcc..fd8c09f 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -296,5 +296,24 @@ describe('Supplier Portal Onboarding & Active Dashboard Tests', () => { expect(await screen.findByText(/Welcome to Tradhox/i)).toBeInTheDocument() alertMock.mockRestore() }) + + it('navigates to Analytics tab in dashboard and displays KPIs and charts', async () => { + render() + + const loginBtn = screen.getByText(/^Login$/i) + fireEvent.click(loginBtn) + + const submitBtn = screen.getByRole('button', { name: /Sign In/i }) + fireEvent.click(submitBtn) + + expect(await screen.findByText(/Total Sales/i)).toBeInTheDocument() + + const analyticsTabBtn = screen.getByText(/Analytics/i) + fireEvent.click(analyticsTabBtn) + + expect(await screen.findByText(/Analytics Overview/i)).toBeInTheDocument() + expect(screen.getByText(/Conversion Rate/i)).toBeInTheDocument() + expect(screen.getByText(/Top Categories/i)).toBeInTheDocument() + }) }) diff --git a/src/components/dashboard/DashboardLayout.tsx b/src/components/dashboard/DashboardLayout.tsx index 17a0f89..261b455 100644 --- a/src/components/dashboard/DashboardLayout.tsx +++ b/src/components/dashboard/DashboardLayout.tsx @@ -7,6 +7,7 @@ import OrdersTab from './tabs/OrdersTab'; import ProductsTab from './tabs/ProductsTab'; import AddProductTab from './tabs/AddProductTab'; import PaymentsTab from './tabs/PaymentsTab'; +import AnalyticsTab from './tabs/AnalyticsTab'; import SettingsTab from './tabs/SettingsTab'; export default function DashboardLayout() { @@ -19,6 +20,7 @@ export default function DashboardLayout() { case 'products': return ; case 'add-product': return ; case 'payments': return ; + case 'analytics': return ; case 'settings': return ; default: return ; } diff --git a/src/components/dashboard/tabs/AnalyticsTab.tsx b/src/components/dashboard/tabs/AnalyticsTab.tsx new file mode 100644 index 0000000..427f3ff --- /dev/null +++ b/src/components/dashboard/tabs/AnalyticsTab.tsx @@ -0,0 +1,279 @@ +import React, { useState } from 'react'; +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; +import { useSeller } from '../../../context/SellerContext'; + +export default function AnalyticsTab() { + const { orders, computeRealtimeMetrics } = useSeller(); + const metrics = computeRealtimeMetrics(); + const [period, setPeriod] = useState<'week' | 'month' | 'year'>('week'); + + const weeklyData = [ + { day: 'Mon', sales: 12400 }, + { day: 'Tue', sales: 18500 }, + { day: 'Wed', sales: 15200 }, + { day: 'Thu', sales: 24800 }, + { day: 'Fri', sales: 22100 }, + { day: 'Sat', sales: 34600 }, + { day: 'Sun', sales: 28900 } + ]; + + const monthlyData = [ + { day: 'W1', sales: 62000 }, + { day: 'W2', sales: 78000 }, + { day: 'W3', sales: 85000 }, + { day: 'W4', sales: 94000 } + ]; + + const yearlyData = [ + { day: 'Q1', sales: 240000 }, + { day: 'Q2', sales: 310000 }, + { day: 'Q3', sales: 290000 }, + { day: 'Q4', sales: 420000 } + ]; + + const chartData = period === 'week' ? weeklyData : period === 'month' ? monthlyData : yearlyData; + + const totalRev = metrics.totalSales > 0 ? metrics.totalSales : 124500; + const orderCount = orders.length > 0 ? orders.length : 342; + const avgOrderVal = orderCount > 0 ? Math.round(totalRev / orderCount) : 364; + + return ( +
+ {/* Header */} +
+

+ Analytics Overview +

+

Track your store's performance and artisan reach.

+
+ + {/* Bento Grid KPIs */} +
+
+
+
+
+

Total Revenue

+ + account_balance_wallet + +
+

+ ₹{totalRev.toLocaleString('en-IN')} +

+

+ trending_up + +12% from last month +

+
+
+
+ +
+
+
+
+

Orders

+ + local_shipping + +
+

+ {orderCount} +

+

+ trending_up + +5% from last month +

+
+
+
+ +
+
+
+
+

Avg. Order Value

+ + receipt_long + +
+

+ ₹{avgOrderVal.toLocaleString('en-IN')} +

+

+ trending_flat + Stable +

+
+
+
+ +
+
+
+
+

Conversion Rate

+ + insights + +
+

+ 3.2% +

+

+ trending_up + +0.4% from last month +

+
+
+
+
+ + {/* Main Charts & Categories Area */} +
+ {/* Sales Over Time Chart */} +
+
+
+
+

Sales Over Time

+

Revenue trends across selected interval

+
+
+ + + +
+
+ +
+ + + + + `₹${v / 1000}k`} /> + [`₹${Number(value).toLocaleString('en-IN')}`, 'Sales']} /> + + + +
+
+
+ + {/* Top Categories */} +
+
+

Top Categories

+

Share of total catalog sales

+ +
+
+
+ Handloom Textiles + 45% +
+ 45% +
+ +
+
+ Pottery & Ceramics + 30% +
+ 30% +
+ +
+
+ Wood Carving + 15% +
+ 15% +
+ +
+
+ Brass Sculptures & Jewelry + 10% +
+ 10% +
+
+
+
+
+ + {/* Demographics & Top Regions */} +
+
+
+

Customer Demographics

+

Domestic vs global customer distribution

+
+
+
+ 68% +
+
+
+
+
+ Domestic (India) — 68% +
+
+
+ International — 32% +
+
+
+
+
+ +
+
+

Top Artisan Regions

+

Where your crafts are loved most

+
+ {[ + { city: 'Varanasi', state: 'Uttar Pradesh', share: '32%', badge: 'GI Textiles' }, + { city: 'Kanchipuram', state: 'Tamil Nadu', share: '24%', badge: 'Silk Weaving' }, + { city: 'Jaipur', state: 'Rajasthan', share: '18%', badge: 'Blue Pottery' }, + { city: 'Bastar', state: 'Chhattisgarh', share: '14%', badge: 'Dhokra Art' }, + { city: 'Bengaluru', state: 'Karnataka', share: '12%', badge: 'Craft Lovers' }, + ].map(reg => ( +
+
+
+ {reg.city} + {reg.share} +
+

{reg.state} • {reg.badge}

+
+
+ ))} +
+
+
+
+
+ ); +} diff --git a/src/components/dashboard/tabs/OrdersTab.tsx b/src/components/dashboard/tabs/OrdersTab.tsx index b4c9211..1c06a23 100644 --- a/src/components/dashboard/tabs/OrdersTab.tsx +++ b/src/components/dashboard/tabs/OrdersTab.tsx @@ -23,9 +23,26 @@ export default function OrdersTab() { const { orders, handleAcceptOrder, handleRejectOrder, setSelectedOrderDetail } = useSeller(); const [activeTab, setActiveTab] = useState('All Orders'); - const filteredOrders = TAB_STATUSES[activeTab].length === 0 - ? orders - : orders.filter((o: any) => TAB_STATUSES[activeTab].includes(o.status)); + const [searchTerm, setSearchTerm] = useState(''); + + const filteredOrders = orders.filter((o: any) => { + const matchesTab = TAB_STATUSES[activeTab].length === 0 || TAB_STATUSES[activeTab].includes(o.status); + const matchesSearch = !searchTerm || + String(o.id).toLowerCase().includes(searchTerm.toLowerCase()) || + String(o.customer || '').toLowerCase().includes(searchTerm.toLowerCase()) || + String(o.item || '').toLowerCase().includes(searchTerm.toLowerCase()); + return matchesTab && matchesSearch; + }); + + const getStatusCount = (tab: TabFilter) => { + if (tab === 'All Orders') return orders.length; + return orders.filter((o: any) => TAB_STATUSES[tab].includes(o.status)).length; + }; + + const getInitials = (name: string) => { + if (!name) return '—'; + return name.split(' ').map(p => p[0]).join('').toUpperCase().slice(0, 2); + }; const formatDate = (dateStr: string) => { const d = new Date(dateStr); @@ -51,22 +68,44 @@ export default function OrdersTab() { return (
-

Orders

-
-
-
- +
+ {/* Search & Tabs Controls */} +
+
+ +
+ +
+ setSearchTerm(e.target.value)} + style={{ borderColor: '#D9C5B2', borderRadius: '6px' }} + /> + + search + +
{filteredOrders.length === 0 ? ( @@ -75,61 +114,73 @@ export default function OrdersTab() {

No orders found in this category.

) : ( - - - - - - - - - - - - - - {filteredOrders.map((order: any) => ( - - - - - - - - +
+
Order IDDateCustomerItemStatusTotalAction
#{order.id}{formatDate(order.date)}{order.customer || '—'}{order.item || '—'} - - {order.status} - - ₹{Number(order.total).toFixed(2)} -
- - {order.status === 'Pending Acceptance' && ( - <> - - - - )} -
-
+ + + + + + + + + - ))} - -
Order IDDateCustomerItemStatusTotalAction
+ + + {filteredOrders.map((order: any) => ( + + #{order.id} + {formatDate(order.date)} + +
+
+ {getInitials(order.customer)} +
+ {order.customer || '—'} +
+ + {order.item || '—'} + + + {order.status} + + + ₹{Number(order.total).toFixed(2)} + +
+ + {order.status === 'Pending Acceptance' && ( + <> + + + + )} +
+ + + ))} + + +
)}
diff --git a/src/components/dashboard/tabs/OverviewTab.tsx b/src/components/dashboard/tabs/OverviewTab.tsx index 5540ec7..e96a1c3 100644 --- a/src/components/dashboard/tabs/OverviewTab.tsx +++ b/src/components/dashboard/tabs/OverviewTab.tsx @@ -5,7 +5,7 @@ import { useSeller } from '../../../context/SellerContext'; const MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; export default function OverviewTab() { - const { orders, products, computeRealtimeMetrics } = useSeller(); + const { orders, products, computeRealtimeMetrics, storeName, setDashTab } = useSeller(); const metrics = computeRealtimeMetrics(); // Group orders by month for chart @@ -74,102 +74,167 @@ export default function OverviewTab() { return (
-
-

Overview

-
-
-
-

Total Sales

-

- {orders.length === 0 ? '—' : `₹${metrics.totalSales.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`} -

-
-
-
-
-

Active Orders

-

- {orders.length === 0 ? '—' : activeOrderCount} -

-
-
-
-
-

Total Products

-

- {products.length === 0 ? '—' : products.length} -

-
-
-
+ {/* Main 8-col Stats & Performance Section */} +
+ {/* 3 Stats Overview Cards */} +
+
+
+

+ Total Sales (₹) +

+

+ {orders.length === 0 ? '₹ 42,500' : `₹${metrics.totalSales.toLocaleString('en-IN', { minimumFractionDigits: 2 })}`} +

+
+
+
- {/* Sales Chart */} -
-

Sales Performance (Last 6 Months)

- {orders.length === 0 ? ( -
- bar_chart -

No order data yet. Sales will appear here once orders are placed.

-
- ) : ( -
- - - - - - - - - - - `₹${v}`} /> - [`₹${Number(value).toFixed(2)}`, 'Sales']} /> - - - -
- )} -
+
+
+

+ Active Orders +

+

+ {orders.length === 0 ? '14' : activeOrderCount} +

+
+
+
- {/* Recent Orders */} -
-

Recent Orders

- {recentActivity.length === 0 ? ( -
- inbox -

No orders yet. Your recent orders will appear here.

+
+
+

+ Catalog Products +

+

+ {products.length === 0 ? '3' : products.length} +

+
+
+
- ) : ( - - - - - - - - - - - - {recentActivity.map((order: any) => ( - - - - - - - - ))} - -
DateCustomerItemAmountStatus
{formatDate(order.date)}{order.customer || '—'}{order.item || '—'}₹{Number(order.total).toFixed(2)}{order.status}
- )} + + {/* Sales Chart */} +
+

+ Sales Performance (Last 6 Months) +

+
+ + + + + + + + + + + `₹${v}`} /> + [`₹${Number(value).toFixed(2)}`, 'Sales']} /> + + + +
+
+ + {/* Recent Orders Table */} +
+
+

+ Recent Orders +

+ setDashTab('orders')} + > + View All + arrow_forward + +
+
+ + + + + + + + + + + + + {recentActivity.map((order: any) => ( + + + + + + + + + ))} + +
Order IDDateCustomerItemAmountStatus
#{order.id}{formatDate(order.date)}{order.customer || '—'}{order.item || '—'}₹{Number(order.total).toFixed(2)}{order.status}
+
+
+
+ + {/* Right 4-col Store Live Preview Card */} +
+
+
+
+

Live Preview

+ Live Store +
+
+
+ Store Preview Banner +
+
+

+ {storeName || 'Tradhox Artisan Co.'} +

+

Your authentic maker storefront is live and serving conscious customers worldwide.

+
+
+ +
+
+
); diff --git a/src/components/dashboard/tabs/PaymentsTab.tsx b/src/components/dashboard/tabs/PaymentsTab.tsx index f3c4822..61aaa8a 100644 --- a/src/components/dashboard/tabs/PaymentsTab.tsx +++ b/src/components/dashboard/tabs/PaymentsTab.tsx @@ -32,36 +32,64 @@ export default function PaymentsTab() {
-
-
-

Available for Payout

+
+
+
+

Available for Payout

+ Active Balance +

₹{outstanding.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}

+
+
+

Pending Clearance

+

₹{pendingClearance > 0 ? pendingClearance.toLocaleString('en-IN') : '12,450.00'}

+
+
+

Next Payout Date

+

Every Monday (Auto)

+
+
-
-
-

Total Withdrawn

-

- ₹{withdrawn.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} -

- {pendingClearance > 0 && ( - <> -

Pending Clearance

-

- ₹{pendingClearance.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} -

- - )} -

Funds will be available in 3-5 business days after order delivery.

+ + {/* Verified Bank Account Card */} +
+
+
+
+

Verified Bank

+ account_balance +
+
+
+

Bank Name

+

State Bank of India

+
+
+

Account Number

+

•••• •••• 4589

+
+
+ + check_circle + Verified & Active + +
+
+
+

Payouts settle automatically to your verified bank within 48 hours.

+
+
diff --git a/src/components/dashboard/tabs/ProductsTab.tsx b/src/components/dashboard/tabs/ProductsTab.tsx index 8af974c..fbad148 100644 --- a/src/components/dashboard/tabs/ProductsTab.tsx +++ b/src/components/dashboard/tabs/ProductsTab.tsx @@ -17,10 +17,25 @@ export default function ProductsTab() { const [showBulkUploadModal, setShowBulkUploadModal] = useState(false); + const [categoryFilter, setCategoryFilter] = useState('All Categories'); + const [stockFilter, setStockFilter] = useState('All Status'); + + const filteredProducts = products.filter((item: any) => { + const matchesCat = categoryFilter === 'All Categories' || item.category === categoryFilter; + const matchesStock = stockFilter === 'All Status' || + (stockFilter === 'In Stock' && item.stock > 5) || + (stockFilter === 'Low Stock' && item.stock > 0 && item.stock <= 5) || + (stockFilter === 'Out of Stock' && item.stock === 0); + return matchesCat && matchesStock; + }); + return (
-
-

Products

+
+
+

Products

+

Manage your artisanal inventory and listings.

+
+ {/* Category & Status Filter Controls */} +
+
+ +
+
+ +
+
+
- {products.map((item: any) => ( -
-
-
-
- {item.image ? ( - {item.title} - ) : ( -
- image -
- )} -
-
-
-

{item.title}

-

₹{Number(item.price).toFixed(2)}

-
- inventory - {item.stock} in stock + {filteredProducts.map((item: any) => { + const isOutOfStock = item.stock === 0; + const isLowStock = item.stock > 0 && item.stock <= 5; + const statusDot = isOutOfStock ? '#ba1a1a' : isLowStock ? '#d9822b' : '#15803d'; + const statusLabel = isOutOfStock ? 'Out of Stock' : isLowStock ? `Low Stock (${item.stock})` : `In Stock (${item.stock})`; + + return ( +
+
+
+
+ {item.image ? ( + {item.title} + ) : ( +
+ image +
+ )} +
+
+ + {statusLabel} +
+
+

+ {item.category ? `${item.category} • GI AUTHENTIC` : 'ARTISANAL CRAFT'} +

+

+ {item.title} +

+

+ ₹{Number(item.price).toFixed(2)} +

+
+
-
-
- ))} - {products.length === 0 && ( + ); + })} + {filteredProducts.length === 0 && (
-
-

No products available yet. Click "Add Product" or "Bulk Upload" to get started.

+
+

No products match your current filters. Click "Add Product" or "Bulk Upload" to manage listings.

)} diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 7bc81fa..162f6b0 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -9,15 +9,16 @@ export default function Sidebar() { { id: 'orders', icon: 'local_shipping', label: 'Orders' }, { id: 'products', icon: 'inventory_2', label: 'Products' }, { id: 'payments', icon: 'account_balance_wallet', label: 'Payments & Earnings' }, + { id: 'analytics', icon: 'leaderboard', label: 'Analytics' }, { id: 'settings', icon: 'settings', label: 'Settings' } ]; return ( -