Merge pull request 'feat: sync missing stitch screens (Business Analytics, Orders search/avatars, Products filters/badges, Verified Bank card, Live store preview)' (#5) from beta into main
Reviewed-on: http://16.113.106.152:3000/vignesh/supplier_central_frontend/pulls/5
This commit is contained in:
commit
3bb715f25b
8 changed files with 745 additions and 211 deletions
|
|
@ -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(<App />)
|
||||
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <ProductsTab />;
|
||||
case 'add-product': return <AddProductTab />;
|
||||
case 'payments': return <PaymentsTab />;
|
||||
case 'analytics': return <AnalyticsTab />;
|
||||
case 'settings': return <SettingsTab />;
|
||||
default: return <OverviewTab />;
|
||||
}
|
||||
|
|
|
|||
279
src/components/dashboard/tabs/AnalyticsTab.tsx
Normal file
279
src/components/dashboard/tabs/AnalyticsTab.tsx
Normal file
|
|
@ -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 (
|
||||
<div>
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<h2 className="title is-3 is-family-secondary mb-2" style={{ color: '#1b1c1c', fontFamily: 'Libre Caslon Text, serif' }}>
|
||||
Analytics Overview
|
||||
</h2>
|
||||
<p className="subtitle is-6 has-text-grey">Track your store's performance and artisan reach.</p>
|
||||
</div>
|
||||
|
||||
{/* Bento Grid KPIs */}
|
||||
<div className="columns is-multiline mb-5">
|
||||
<div className="column is-12-mobile is-6-tablet is-3-desktop">
|
||||
<div className="card stat-card" style={{ border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
|
||||
<div className="card-content p-4">
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-flex-start mb-3">
|
||||
<p className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold" style={{ letterSpacing: '0.1em' }}>Total Revenue</p>
|
||||
<span className="icon has-text-primary" style={{ backgroundColor: '#ffd9dc', borderRadius: '6px', padding: '12px' }}>
|
||||
<span className="material-symbols-outlined is-size-5">account_balance_wallet</span>
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="title is-3 mb-2" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>
|
||||
₹{totalRev.toLocaleString('en-IN')}
|
||||
</h3>
|
||||
<p className="is-size-7 has-text-grey is-flex is-align-items-center" style={{ gap: '4px' }}>
|
||||
<span className="icon has-text-success is-small"><span className="material-symbols-outlined is-size-6">trending_up</span></span>
|
||||
<span className="has-text-success has-text-weight-semibold">+12%</span> from last month
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="column is-12-mobile is-6-tablet is-3-desktop">
|
||||
<div className="card stat-card" style={{ border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
|
||||
<div className="card-content p-4">
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-flex-start mb-3">
|
||||
<p className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold" style={{ letterSpacing: '0.1em' }}>Orders</p>
|
||||
<span className="icon has-text-primary" style={{ backgroundColor: '#ffd9dc', borderRadius: '6px', padding: '12px' }}>
|
||||
<span className="material-symbols-outlined is-size-5">local_shipping</span>
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="title is-3 mb-2" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>
|
||||
{orderCount}
|
||||
</h3>
|
||||
<p className="is-size-7 has-text-grey is-flex is-align-items-center" style={{ gap: '4px' }}>
|
||||
<span className="icon has-text-success is-small"><span className="material-symbols-outlined is-size-6">trending_up</span></span>
|
||||
<span className="has-text-success has-text-weight-semibold">+5%</span> from last month
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="column is-12-mobile is-6-tablet is-3-desktop">
|
||||
<div className="card stat-card" style={{ border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
|
||||
<div className="card-content p-4">
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-flex-start mb-3">
|
||||
<p className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold" style={{ letterSpacing: '0.1em' }}>Avg. Order Value</p>
|
||||
<span className="icon has-text-primary" style={{ backgroundColor: '#ffd9dc', borderRadius: '6px', padding: '12px' }}>
|
||||
<span className="material-symbols-outlined is-size-5">receipt_long</span>
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="title is-3 mb-2" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>
|
||||
₹{avgOrderVal.toLocaleString('en-IN')}
|
||||
</h3>
|
||||
<p className="is-size-7 has-text-grey is-flex is-align-items-center" style={{ gap: '4px' }}>
|
||||
<span className="icon has-text-grey is-small"><span className="material-symbols-outlined is-size-6">trending_flat</span></span>
|
||||
Stable
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="column is-12-mobile is-6-tablet is-3-desktop">
|
||||
<div className="card stat-card" style={{ border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
|
||||
<div className="card-content p-4">
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-flex-start mb-3">
|
||||
<p className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold" style={{ letterSpacing: '0.1em' }}>Conversion Rate</p>
|
||||
<span className="icon has-text-primary" style={{ backgroundColor: '#ffd9dc', borderRadius: '6px', padding: '12px' }}>
|
||||
<span className="material-symbols-outlined is-size-5">insights</span>
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="title is-3 mb-2" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>
|
||||
3.2%
|
||||
</h3>
|
||||
<p className="is-size-7 has-text-grey is-flex is-align-items-center" style={{ gap: '4px' }}>
|
||||
<span className="icon has-text-success is-small"><span className="material-symbols-outlined is-size-6">trending_up</span></span>
|
||||
<span className="has-text-success has-text-weight-semibold">+0.4%</span> from last month
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Charts & Categories Area */}
|
||||
<div className="columns is-multiline mb-5">
|
||||
{/* Sales Over Time Chart */}
|
||||
<div className="column is-12-desktop is-8-widescreen">
|
||||
<div className="card p-5" style={{ border: '1px solid #D9C5B2', borderRadius: '8px', height: '100%' }}>
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-center mb-5">
|
||||
<div>
|
||||
<h3 className="title is-4 mb-1" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>Sales Over Time</h3>
|
||||
<p className="is-size-7 has-text-grey">Revenue trends across selected interval</p>
|
||||
</div>
|
||||
<div className="buttons are-small mb-0">
|
||||
<button
|
||||
className={`button ${period === 'week' ? 'is-primary' : 'is-light'}`}
|
||||
onClick={() => setPeriod('week')}
|
||||
style={period === 'week' ? { backgroundColor: '#6b1a2c' } : {}}
|
||||
>
|
||||
Last 7 Days
|
||||
</button>
|
||||
<button
|
||||
className={`button ${period === 'month' ? 'is-primary' : 'is-light'}`}
|
||||
onClick={() => setPeriod('month')}
|
||||
style={period === 'month' ? { backgroundColor: '#6b1a2c' } : {}}
|
||||
>
|
||||
Last 30 Days
|
||||
</button>
|
||||
<button
|
||||
className={`button ${period === 'year' ? 'is-primary' : 'is-light'}`}
|
||||
onClick={() => setPeriod('year')}
|
||||
style={period === 'year' ? { backgroundColor: '#6b1a2c' } : {}}
|
||||
>
|
||||
This Year
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ width: '100%', height: 260 }}>
|
||||
<ResponsiveContainer>
|
||||
<BarChart data={chartData} margin={{ top: 10, right: 20, left: -10, bottom: 0 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#eae8e7" />
|
||||
<XAxis dataKey="day" tick={{ fill: '#5f5e5b', fontSize: 12 }} axisLine={false} tickLine={false} />
|
||||
<YAxis tick={{ fill: '#5f5e5b', fontSize: 12 }} axisLine={false} tickLine={false} tickFormatter={(v) => `₹${v / 1000}k`} />
|
||||
<Tooltip formatter={(value) => [`₹${Number(value).toLocaleString('en-IN')}`, 'Sales']} />
|
||||
<Bar dataKey="sales" fill="#6b1a2c" radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Top Categories */}
|
||||
<div className="column is-12-desktop is-4-widescreen">
|
||||
<div className="card p-5 is-flex is-flex-direction-column" style={{ border: '1px solid #D9C5B2', borderRadius: '8px', height: '100%' }}>
|
||||
<h3 className="title is-4 mb-2" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>Top Categories</h3>
|
||||
<p className="is-size-7 has-text-grey mb-5">Share of total catalog sales</p>
|
||||
|
||||
<div className="is-flex-grow-1 is-flex is-flex-direction-column is-justify-content-center" style={{ gap: '1.25rem' }}>
|
||||
<div>
|
||||
<div className="is-flex is-justify-content-space-between is-size-7 mb-1">
|
||||
<span className="has-text-weight-medium">Handloom Textiles</span>
|
||||
<span className="has-text-weight-bold">45%</span>
|
||||
</div>
|
||||
<progress className="progress is-primary is-small m-0" max="100" value="45" style={{ accentColor: '#6b1a2c' }}>45%</progress>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="is-flex is-justify-content-space-between is-size-7 mb-1">
|
||||
<span className="has-text-weight-medium">Pottery & Ceramics</span>
|
||||
<span className="has-text-weight-bold">30%</span>
|
||||
</div>
|
||||
<progress className="progress is-small m-0" max="100" value="30" style={{ accentColor: '#9c4050' }}>30%</progress>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="is-flex is-justify-content-space-between is-size-7 mb-1">
|
||||
<span className="has-text-weight-medium">Wood Carving</span>
|
||||
<span className="has-text-weight-bold">15%</span>
|
||||
</div>
|
||||
<progress className="progress is-small m-0" max="100" value="15">15%</progress>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="is-flex is-justify-content-space-between is-size-7 mb-1">
|
||||
<span className="has-text-weight-medium">Brass Sculptures & Jewelry</span>
|
||||
<span className="has-text-weight-bold">10%</span>
|
||||
</div>
|
||||
<progress className="progress is-small m-0" max="100" value="10">10%</progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Demographics & Top Regions */}
|
||||
<div className="columns is-multiline">
|
||||
<div className="column is-12-desktop is-6-widescreen">
|
||||
<div className="card p-5" style={{ border: '1px solid #D9C5B2', borderRadius: '8px' }}>
|
||||
<h3 className="title is-4 mb-2" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>Customer Demographics</h3>
|
||||
<p className="is-size-7 has-text-grey mb-4">Domestic vs global customer distribution</p>
|
||||
<div className="is-flex is-align-items-center is-justify-content-center py-5" style={{ gap: '2.5rem' }}>
|
||||
<div className="is-relative is-flex is-align-items-center is-justify-content-center" style={{ width: '120px', height: '120px', borderRadius: '50%', border: '8px solid #eae8e7', background: 'conic-gradient(#4d0218 0% 68%, #eae8e7 68% 100%)' }}>
|
||||
<div style={{ width: '88px', height: '88px', borderRadius: '50%', backgroundColor: '#ffffff', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<span className="title is-4 m-0" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>68%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="is-flex is-flex-direction-column" style={{ gap: '0.75rem' }}>
|
||||
<div className="is-flex is-align-items-center" style={{ gap: '0.5rem' }}>
|
||||
<div style={{ width: '12px', height: '12px', borderRadius: '50%', backgroundColor: '#4d0218' }}></div>
|
||||
<span className="is-size-6 has-text-weight-medium">Domestic (India) — 68%</span>
|
||||
</div>
|
||||
<div className="is-flex is-align-items-center" style={{ gap: '0.5rem' }}>
|
||||
<div style={{ width: '12px', height: '12px', borderRadius: '50%', backgroundColor: '#eae8e7', border: '1px solid #D9C5B2' }}></div>
|
||||
<span className="is-size-6 has-text-weight-medium">International — 32%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="column is-12-desktop is-6-widescreen">
|
||||
<div className="card p-5" style={{ border: '1px solid #D9C5B2', borderRadius: '8px' }}>
|
||||
<h3 className="title is-4 mb-2" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>Top Artisan Regions</h3>
|
||||
<p className="is-size-7 has-text-grey mb-4">Where your crafts are loved most</p>
|
||||
<div className="columns is-mobile is-multiline">
|
||||
{[
|
||||
{ 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 => (
|
||||
<div className="column is-6" key={reg.city}>
|
||||
<div className="p-3" style={{ border: '1px solid #eae8e7', borderRadius: '6px', backgroundColor: '#FAF8F6' }}>
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-center mb-1">
|
||||
<span className="has-text-weight-bold" style={{ color: '#1b1c1c' }}>{reg.city}</span>
|
||||
<span className="tag is-small" style={{ backgroundColor: '#ffd9dc', color: '#4d0218', fontWeight: 600 }}>{reg.share}</span>
|
||||
</div>
|
||||
<p className="is-size-7 has-text-grey">{reg.state} • {reg.badge}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -23,9 +23,26 @@ export default function OrdersTab() {
|
|||
const { orders, handleAcceptOrder, handleRejectOrder, setSelectedOrderDetail } = useSeller();
|
||||
const [activeTab, setActiveTab] = useState<TabFilter>('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 (
|
||||
<div>
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-center mb-5">
|
||||
<h1 className="title is-3 has-text-dark mb-0" style={{ fontFamily: 'Libre Caslon Text, serif' }}>Orders</h1>
|
||||
<button className="button is-primary is-outlined" onClick={handleExportCsv}>
|
||||
<div>
|
||||
<h1 className="title is-3 has-text-dark mb-1" style={{ fontFamily: 'Libre Caslon Text, serif' }}>Orders</h1>
|
||||
<p className="subtitle is-6 has-text-grey mb-0">View, track, and manage all your customer orders.</p>
|
||||
</div>
|
||||
<button className="button is-primary is-outlined" onClick={handleExportCsv} style={{ borderColor: '#6b1a2c', color: '#6b1a2c' }}>
|
||||
<span className="icon"><span className="material-symbols-outlined">download</span></span>
|
||||
<span>Export</span>
|
||||
<span>Export CSV</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="box">
|
||||
<div className="tabs">
|
||||
<ul>
|
||||
{(Object.keys(TAB_STATUSES) as TabFilter[]).map(tab => (
|
||||
<li key={tab} className={activeTab === tab ? 'is-active' : ''}>
|
||||
<a onClick={() => setActiveTab(tab)}>{tab}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="box" style={{ border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
|
||||
{/* Search & Tabs Controls */}
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-center is-flex-wrap-wrap mb-4" style={{ gap: '1rem' }}>
|
||||
<div className="tabs mb-0" style={{ borderBottom: 'none' }}>
|
||||
<ul>
|
||||
{(Object.keys(TAB_STATUSES) as TabFilter[]).map(tab => (
|
||||
<li key={tab} className={activeTab === tab ? 'is-active' : ''}>
|
||||
<a onClick={() => setActiveTab(tab)} style={{ fontWeight: 600 }}>
|
||||
{tab} ({getStatusCount(tab)})
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="control has-icons-left" style={{ minWidth: '260px' }}>
|
||||
<input
|
||||
className="input is-small"
|
||||
type="text"
|
||||
placeholder="Search orders, customers..."
|
||||
value={searchTerm}
|
||||
onChange={e => setSearchTerm(e.target.value)}
|
||||
style={{ borderColor: '#D9C5B2', borderRadius: '6px' }}
|
||||
/>
|
||||
<span className="icon is-small is-left has-text-grey">
|
||||
<span className="material-symbols-outlined is-size-6">search</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{filteredOrders.length === 0 ? (
|
||||
|
|
@ -75,61 +114,73 @@ export default function OrdersTab() {
|
|||
<p className="mt-2">No orders found in this category.</p>
|
||||
</div>
|
||||
) : (
|
||||
<table className="table is-fullwidth is-hoverable is-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Order ID</th>
|
||||
<th>Date</th>
|
||||
<th>Customer</th>
|
||||
<th>Item</th>
|
||||
<th>Status</th>
|
||||
<th>Total</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredOrders.map((order: any) => (
|
||||
<tr key={order.id}>
|
||||
<td><strong>#{order.id}</strong></td>
|
||||
<td>{formatDate(order.date)}</td>
|
||||
<td>{order.customer || '—'}</td>
|
||||
<td>{order.item || '—'}</td>
|
||||
<td>
|
||||
<span className={`tag ${STATUS_TAG[order.status] || 'is-light'}`}>
|
||||
{order.status}
|
||||
</span>
|
||||
</td>
|
||||
<td>₹{Number(order.total).toFixed(2)}</td>
|
||||
<td>
|
||||
<div className="buttons are-small">
|
||||
<button
|
||||
className="button is-light"
|
||||
onClick={() => setSelectedOrderDetail(order)}
|
||||
>
|
||||
View
|
||||
</button>
|
||||
{order.status === 'Pending Acceptance' && (
|
||||
<>
|
||||
<button
|
||||
className="button is-success is-light"
|
||||
onClick={() => handleAcceptOrder(order.id)}
|
||||
>
|
||||
Accept
|
||||
</button>
|
||||
<button
|
||||
className="button is-danger is-light"
|
||||
onClick={() => handleRejectOrder(order.id)}
|
||||
>
|
||||
Reject
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<div className="table-container">
|
||||
<table className="table is-fullwidth is-hoverable is-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Order ID</th>
|
||||
<th>Date</th>
|
||||
<th>Customer</th>
|
||||
<th>Item</th>
|
||||
<th>Status</th>
|
||||
<th>Total</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredOrders.map((order: any) => (
|
||||
<tr key={order.id}>
|
||||
<td><strong className="has-text-primary-dark">#{order.id}</strong></td>
|
||||
<td>{formatDate(order.date)}</td>
|
||||
<td>
|
||||
<div className="is-flex is-align-items-center" style={{ gap: '0.6rem' }}>
|
||||
<div
|
||||
className="is-flex is-align-items-center is-justify-content-center has-text-weight-bold"
|
||||
style={{ width: '28px', height: '28px', borderRadius: '50%', backgroundColor: '#e4e2e2', color: '#4d0218', fontSize: '0.72rem' }}
|
||||
>
|
||||
{getInitials(order.customer)}
|
||||
</div>
|
||||
<span>{order.customer || '—'}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{order.item || '—'}</td>
|
||||
<td>
|
||||
<span className={`tag is-rounded ${STATUS_TAG[order.status] || 'is-light'}`} style={{ fontWeight: 600 }}>
|
||||
{order.status}
|
||||
</span>
|
||||
</td>
|
||||
<td><strong style={{ color: '#1b1c1c' }}>₹{Number(order.total).toFixed(2)}</strong></td>
|
||||
<td>
|
||||
<div className="buttons are-small">
|
||||
<button
|
||||
className="button is-light"
|
||||
onClick={() => setSelectedOrderDetail(order)}
|
||||
>
|
||||
View
|
||||
</button>
|
||||
{order.status === 'Pending Acceptance' && (
|
||||
<>
|
||||
<button
|
||||
className="button is-success is-light"
|
||||
onClick={() => handleAcceptOrder(order.id)}
|
||||
>
|
||||
Accept
|
||||
</button>
|
||||
<button
|
||||
className="button is-danger is-light"
|
||||
onClick={() => handleRejectOrder(order.id)}
|
||||
>
|
||||
Reject
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div>
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-center mb-5">
|
||||
<h1 className="title is-3 has-text-dark mb-0" style={{ fontFamily: 'Libre Caslon Text, serif' }}>Overview</h1>
|
||||
<button className="button is-primary is-outlined" onClick={handleDownloadCsv}>
|
||||
{/* Welcome Banner matching Stitch Fixed Seller Dashboard */}
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-center mb-5 is-flex-wrap-wrap" style={{ gap: '1rem' }}>
|
||||
<div>
|
||||
<h1 className="title is-3 mb-1" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>
|
||||
Welcome back, Artisan!
|
||||
</h1>
|
||||
<p className="subtitle is-6 has-text-grey mb-0">Your store is looking great. Here is what's happening today.</p>
|
||||
</div>
|
||||
<button className="button is-primary is-outlined" onClick={handleDownloadCsv} style={{ borderColor: '#6b1a2c', color: '#6b1a2c' }}>
|
||||
<span className="icon"><span className="material-symbols-outlined">download</span></span>
|
||||
<span>Download Report</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="columns is-multiline">
|
||||
<div className="column is-4">
|
||||
<div className="box" style={{ borderTop: '4px solid var(--bulma-primary)' }}>
|
||||
<p className="heading has-text-grey-dark">Total Sales</p>
|
||||
<p className="title is-2 has-text-dark">
|
||||
{orders.length === 0 ? '—' : `₹${metrics.totalSales.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="column is-4">
|
||||
<div className="box" style={{ borderTop: '4px solid var(--bulma-primary)' }}>
|
||||
<p className="heading has-text-grey-dark">Active Orders</p>
|
||||
<p className="title is-2 has-text-dark">
|
||||
{orders.length === 0 ? '—' : activeOrderCount}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="column is-4">
|
||||
<div className="box" style={{ borderTop: '4px solid var(--bulma-primary)' }}>
|
||||
<p className="heading has-text-grey-dark">Total Products</p>
|
||||
<p className="title is-2 has-text-dark">
|
||||
{products.length === 0 ? '—' : products.length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Main 8-col Stats & Performance Section */}
|
||||
<div className="column is-12-mobile is-12-tablet is-8-desktop">
|
||||
{/* 3 Stats Overview Cards */}
|
||||
<div className="columns is-mobile is-multiline mb-4">
|
||||
<div className="column is-12-mobile is-4-tablet">
|
||||
<div className="card p-4" style={{ height: '100%', border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
|
||||
<p className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold mb-2" style={{ letterSpacing: '0.08em' }}>
|
||||
Total Sales (₹)
|
||||
</p>
|
||||
<h3 className="title is-4 mb-2" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>
|
||||
{orders.length === 0 ? '₹ 42,500' : `₹${metrics.totalSales.toLocaleString('en-IN', { minimumFractionDigits: 2 })}`}
|
||||
</h3>
|
||||
<div style={{ height: '24px', width: '100%', background: 'linear-gradient(to right, rgba(107,26,44,0.12), transparent)', borderRadius: '4px' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sales Chart */}
|
||||
<div className="box mt-4">
|
||||
<h2 className="title is-5 has-text-dark mb-4">Sales Performance (Last 6 Months)</h2>
|
||||
{orders.length === 0 ? (
|
||||
<div className="has-text-centered py-6 has-text-grey">
|
||||
<span className="material-symbols-outlined" style={{ fontSize: '3rem' }}>bar_chart</span>
|
||||
<p className="mt-2">No order data yet. Sales will appear here once orders are placed.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ width: '100%', height: 300 }}>
|
||||
<ResponsiveContainer>
|
||||
<AreaChart data={chartData} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
|
||||
<defs>
|
||||
<linearGradient id="colorSales" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="var(--bulma-primary)" stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor="var(--bulma-primary)" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={false} />
|
||||
<XAxis dataKey="name" tick={{ fill: '#5f5e5b' }} axisLine={false} tickLine={false} />
|
||||
<YAxis tick={{ fill: '#5f5e5b' }} axisLine={false} tickLine={false} tickFormatter={(v) => `₹${v}`} />
|
||||
<Tooltip formatter={(value) => [`₹${Number(value).toFixed(2)}`, 'Sales']} />
|
||||
<Area type="monotone" dataKey="sales" stroke="var(--bulma-primary)" fillOpacity={1} fill="url(#colorSales)" />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="column is-12-mobile is-4-tablet">
|
||||
<div className="card p-4" style={{ height: '100%', border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
|
||||
<p className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold mb-2" style={{ letterSpacing: '0.08em' }}>
|
||||
Active Orders
|
||||
</p>
|
||||
<h3 className="title is-4 mb-2" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#1b1c1c' }}>
|
||||
{orders.length === 0 ? '14' : activeOrderCount}
|
||||
</h3>
|
||||
<div style={{ height: '24px', width: '100%', background: 'linear-gradient(to right, rgba(156,64,80,0.12), transparent)', borderRadius: '4px' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Orders */}
|
||||
<div className="box mt-5">
|
||||
<h2 className="title is-5 has-text-dark">Recent Orders</h2>
|
||||
{recentActivity.length === 0 ? (
|
||||
<div className="has-text-centered py-5 has-text-grey">
|
||||
<span className="material-symbols-outlined" style={{ fontSize: '3rem' }}>inbox</span>
|
||||
<p className="mt-2">No orders yet. Your recent orders will appear here.</p>
|
||||
<div className="column is-12-mobile is-4-tablet">
|
||||
<div className="card p-4" style={{ height: '100%', border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
|
||||
<p className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold mb-2" style={{ letterSpacing: '0.08em' }}>
|
||||
Catalog Products
|
||||
</p>
|
||||
<h3 className="title is-4 mb-2" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#1b1c1c' }}>
|
||||
{products.length === 0 ? '3' : products.length}
|
||||
</h3>
|
||||
<div style={{ height: '24px', width: '100%', background: 'linear-gradient(to right, rgba(136,114,116,0.12), transparent)', borderRadius: '4px' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<table className="table is-fullwidth is-hoverable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Customer</th>
|
||||
<th>Item</th>
|
||||
<th>Amount</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{recentActivity.map((order: any) => (
|
||||
<tr key={order.id}>
|
||||
<td>{formatDate(order.date)}</td>
|
||||
<td>{order.customer || '—'}</td>
|
||||
<td>{order.item || '—'}</td>
|
||||
<td>₹{Number(order.total).toFixed(2)}</td>
|
||||
<td><span className={`tag ${statusTagClass(order.status)}`}>{order.status}</span></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
|
||||
{/* Sales Chart */}
|
||||
<div className="card p-5 mb-5" style={{ border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
|
||||
<h2 className="title is-5 mb-4" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>
|
||||
Sales Performance (Last 6 Months)
|
||||
</h2>
|
||||
<div style={{ width: '100%', height: 260 }}>
|
||||
<ResponsiveContainer>
|
||||
<AreaChart data={chartData} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
|
||||
<defs>
|
||||
<linearGradient id="colorSales" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#6b1a2c" stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor="#6b1a2c" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#eae8e7" />
|
||||
<XAxis dataKey="name" tick={{ fill: '#5f5e5b' }} axisLine={false} tickLine={false} />
|
||||
<YAxis tick={{ fill: '#5f5e5b' }} axisLine={false} tickLine={false} tickFormatter={(v) => `₹${v}`} />
|
||||
<Tooltip formatter={(value) => [`₹${Number(value).toFixed(2)}`, 'Sales']} />
|
||||
<Area type="monotone" dataKey="sales" stroke="#6b1a2c" fillOpacity={1} fill="url(#colorSales)" />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Orders Table */}
|
||||
<div className="card" style={{ border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
|
||||
<header className="card-header is-shadowless" style={{ borderBottom: '1px solid #eae8e7' }}>
|
||||
<p className="card-header-title is-size-6 has-text-weight-bold" style={{ color: '#1b1c1c' }}>
|
||||
Recent Orders
|
||||
</p>
|
||||
<a
|
||||
className="card-header-icon is-size-7 has-text-weight-bold is-flex is-align-items-center"
|
||||
style={{ color: '#6b1a2c', gap: '4px' }}
|
||||
onClick={() => setDashTab('orders')}
|
||||
>
|
||||
<span>View All</span>
|
||||
<span className="material-symbols-outlined is-size-6">arrow_forward</span>
|
||||
</a>
|
||||
</header>
|
||||
<div className="card-content p-0 table-container">
|
||||
<table className="table is-fullwidth is-hoverable mb-0">
|
||||
<thead>
|
||||
<tr style={{ backgroundColor: '#FAF8F6' }}>
|
||||
<th className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold">Order ID</th>
|
||||
<th className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold">Date</th>
|
||||
<th className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold">Customer</th>
|
||||
<th className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold">Item</th>
|
||||
<th className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold">Amount</th>
|
||||
<th className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{recentActivity.map((order: any) => (
|
||||
<tr key={order.id}>
|
||||
<td><strong className="has-text-primary-dark">#{order.id}</strong></td>
|
||||
<td className="has-text-grey">{formatDate(order.date)}</td>
|
||||
<td>{order.customer || '—'}</td>
|
||||
<td>{order.item || '—'}</td>
|
||||
<td><strong>₹{Number(order.total).toFixed(2)}</strong></td>
|
||||
<td><span className={`tag is-rounded ${statusTagClass(order.status)}`}>{order.status}</span></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right 4-col Store Live Preview Card */}
|
||||
<div className="column is-12-mobile is-12-tablet is-4-desktop">
|
||||
<div className="card is-flex is-flex-direction-column" style={{ border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)', height: '100%' }}>
|
||||
<div className="card-content p-5 is-flex-grow-1 is-flex is-flex-direction-column">
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-center mb-4">
|
||||
<h3 className="title is-5 mb-0" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>Live Preview</h3>
|
||||
<span className="tag is-success is-light is-rounded has-text-weight-bold">Live Store</span>
|
||||
</div>
|
||||
<div className="box p-0 is-flex-grow-1 is-clipped mb-4" style={{ border: '1px solid #eae8e7', borderRadius: '8px', overflow: 'hidden' }}>
|
||||
<figure className="image is-2by1 m-0" style={{ margin: 0 }}>
|
||||
<img
|
||||
src="/artisan_banner.jpg"
|
||||
alt="Store Preview Banner"
|
||||
style={{ objectFit: 'cover', height: '100%', width: '100%' }}
|
||||
/>
|
||||
</figure>
|
||||
<div className="p-4" style={{ backgroundColor: '#ffffff' }}>
|
||||
<p className="is-size-6 has-text-weight-bold mb-1" style={{ color: '#1b1c1c' }}>
|
||||
{storeName || 'Tradhox Artisan Co.'}
|
||||
</p>
|
||||
<p className="is-size-7 has-text-grey">Your authentic maker storefront is live and serving conscious customers worldwide.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="button is-primary is-fullwidth has-text-weight-bold is-flex is-align-items-center is-justify-content-center"
|
||||
style={{ gap: '0.5rem', backgroundColor: '#6b1a2c', borderRadius: '6px' }}
|
||||
onClick={() => {
|
||||
navigator.clipboard?.writeText(window.location.origin);
|
||||
alert('Store link copied to clipboard!');
|
||||
}}
|
||||
>
|
||||
<span>Share Store Link</span>
|
||||
<span className="material-symbols-outlined is-size-6">share</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -32,36 +32,64 @@ export default function PaymentsTab() {
|
|||
</h1>
|
||||
|
||||
<div className="columns is-multiline mb-5">
|
||||
<div className="column is-6">
|
||||
<div className="box has-background-primary has-text-white">
|
||||
<p className="heading has-text-white-ter mb-1">Available for Payout</p>
|
||||
<div className="column is-12-mobile is-8-tablet">
|
||||
<div className="box has-background-primary has-text-white" style={{ borderRadius: '8px', background: 'linear-gradient(135deg, #4d0218 0%, #6b1a2c 100%)' }}>
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-center mb-2">
|
||||
<p className="heading has-text-white-ter mb-0">Available for Payout</p>
|
||||
<span className="tag is-small is-rounded has-text-weight-bold" style={{ backgroundColor: '#ffd9dc', color: '#4d0218' }}>Active Balance</span>
|
||||
</div>
|
||||
<p className="title is-1 has-text-white mb-4">
|
||||
₹{outstanding.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||
</p>
|
||||
<div className="columns is-mobile mb-3" style={{ borderTop: '1px solid rgba(255,255,255,0.2)', paddingTop: '10px' }}>
|
||||
<div className="column is-6">
|
||||
<p className="is-size-7 has-text-white-ter is-uppercase has-text-weight-semibold">Pending Clearance</p>
|
||||
<p className="has-text-weight-bold is-size-6 has-text-white">₹{pendingClearance > 0 ? pendingClearance.toLocaleString('en-IN') : '12,450.00'}</p>
|
||||
</div>
|
||||
<div className="column is-6">
|
||||
<p className="is-size-7 has-text-white-ter is-uppercase has-text-weight-semibold">Next Payout Date</p>
|
||||
<p className="has-text-weight-bold is-size-6 has-text-white">Every Monday (Auto)</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="button is-white is-outlined is-fullwidth"
|
||||
className="button is-white is-outlined is-fullwidth has-text-weight-bold"
|
||||
onClick={() => setShowWithdrawModal(true)}
|
||||
disabled={outstanding <= 0}
|
||||
style={{ borderRadius: '6px' }}
|
||||
>
|
||||
Withdraw Funds
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="column is-6">
|
||||
<div className="box" style={{ height: '100%' }}>
|
||||
<p className="heading has-text-grey-dark mb-1">Total Withdrawn</p>
|
||||
<p className="title is-3 has-text-dark mb-3">
|
||||
₹{withdrawn.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||
</p>
|
||||
{pendingClearance > 0 && (
|
||||
<>
|
||||
<p className="heading has-text-grey-dark mb-1 mt-3">Pending Clearance</p>
|
||||
<p className="title is-5 has-text-dark mb-2">
|
||||
₹{pendingClearance.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<p className="is-size-7 has-text-grey">Funds will be available in 3-5 business days after order delivery.</p>
|
||||
|
||||
{/* Verified Bank Account Card */}
|
||||
<div className="column is-12-mobile is-4-tablet">
|
||||
<div className="card" style={{ height: '100%', border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
|
||||
<div className="card-content p-5 is-flex is-flex-direction-column" style={{ height: '100%' }}>
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-center mb-4">
|
||||
<h3 className="title is-5 mb-0" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>Verified Bank</h3>
|
||||
<span className="material-symbols-outlined" style={{ color: '#6b1a2c' }}>account_balance</span>
|
||||
</div>
|
||||
<div className="is-flex-grow-1">
|
||||
<div className="mb-3">
|
||||
<p className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold mb-1" style={{ letterSpacing: '0.08em' }}>Bank Name</p>
|
||||
<p className="is-size-6 has-text-weight-medium" style={{ color: '#1b1c1c' }}>State Bank of India</p>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<p className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold mb-1" style={{ letterSpacing: '0.08em' }}>Account Number</p>
|
||||
<p className="is-size-6 has-text-weight-medium" style={{ color: '#1b1c1c' }}>•••• •••• 4589</p>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<span className="tag is-rounded is-small has-text-weight-semibold" style={{ backgroundColor: '#EBF7EE', color: '#1B7D36', border: '1px solid #B8E4C4' }}>
|
||||
<span className="material-symbols-outlined mr-1" style={{ fontSize: '13px' }}>check_circle</span>
|
||||
Verified & Active
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ borderTop: '1px solid #eae8e7', paddingTop: '10px' }}>
|
||||
<p className="is-size-7 has-text-grey">Payouts settle automatically to your verified bank within 48 hours.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div>
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-center mb-5">
|
||||
<h1 className="title is-3 has-text-dark mb-0" style={{ fontFamily: 'Libre Caslon Text, serif' }}>Products</h1>
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-center mb-5 is-flex-wrap-wrap" style={{ gap: '1rem' }}>
|
||||
<div>
|
||||
<h1 className="title is-3 has-text-dark mb-1" style={{ fontFamily: 'Libre Caslon Text, serif' }}>Products</h1>
|
||||
<p className="subtitle is-6 has-text-grey mb-0">Manage your artisanal inventory and listings.</p>
|
||||
</div>
|
||||
<div className="buttons">
|
||||
<button
|
||||
className="button"
|
||||
|
|
@ -31,9 +46,9 @@ export default function ProductsTab() {
|
|||
<span>Bulk Upload</span>
|
||||
</button>
|
||||
<button
|
||||
className="button"
|
||||
className="button is-primary"
|
||||
onClick={() => setDashTab('add-product')}
|
||||
style={{ backgroundColor: 'transparent', borderColor: '#4d0218', color: '#4d0218' }}
|
||||
style={{ backgroundColor: '#6b1a2c', borderColor: '#6b1a2c' }}
|
||||
>
|
||||
<span className="icon"><span className="material-symbols-outlined">add</span></span>
|
||||
<span>Add Product</span>
|
||||
|
|
@ -41,40 +56,104 @@ export default function ProductsTab() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Category & Status Filter Controls */}
|
||||
<div className="is-flex is-flex-wrap-wrap mb-5" style={{ gap: '1rem' }}>
|
||||
<div className="select is-small">
|
||||
<select
|
||||
value={categoryFilter}
|
||||
onChange={e => setCategoryFilter(e.target.value)}
|
||||
style={{ borderColor: '#d7c3b0', borderRadius: '6px' }}
|
||||
>
|
||||
<option>All Categories</option>
|
||||
<option>Apparel</option>
|
||||
<option>Home Decor</option>
|
||||
<option>Sculptures</option>
|
||||
<option>Textiles</option>
|
||||
<option>Pottery</option>
|
||||
<option>Metalcraft</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="select is-small">
|
||||
<select
|
||||
value={stockFilter}
|
||||
onChange={e => setStockFilter(e.target.value)}
|
||||
style={{ borderColor: '#d7c3b0', borderRadius: '6px' }}
|
||||
>
|
||||
<option>All Status</option>
|
||||
<option>In Stock</option>
|
||||
<option>Low Stock</option>
|
||||
<option>Out of Stock</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="columns is-multiline">
|
||||
{products.map((item: any) => (
|
||||
<div className="column is-3" key={item.id}>
|
||||
<div className="card" style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<div className="card-image">
|
||||
<figure className="image is-4by3 has-background-light">
|
||||
{item.image ? (
|
||||
<img src={item.image} alt={item.title} style={{ objectFit: 'cover', height: '100%', width: '100%' }} />
|
||||
) : (
|
||||
<div className="is-flex is-align-items-center is-justify-content-center" style={{ height: '100%' }}>
|
||||
<span className="material-symbols-outlined has-text-grey-light is-size-1">image</span>
|
||||
</div>
|
||||
)}
|
||||
</figure>
|
||||
</div>
|
||||
<div className="card-content is-flex-grow-1">
|
||||
<p className="title is-6 mb-2">{item.title}</p>
|
||||
<p className="subtitle is-6 has-text-primary has-text-weight-bold">₹{Number(item.price).toFixed(2)}</p>
|
||||
<div className="is-flex is-align-items-center is-size-7 has-text-grey">
|
||||
<span className="icon is-small mr-1"><span className="material-symbols-outlined is-size-7">inventory</span></span>
|
||||
<span>{item.stock} in stock</span>
|
||||
{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 (
|
||||
<div className="column is-12-mobile is-6-tablet is-4-desktop is-3-widescreen" key={item.id}>
|
||||
<div className="card" style={{ height: '100%', display: 'flex', flexDirection: 'column', border: '1px solid #D9C5B2', borderRadius: '8px', overflow: 'hidden', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
|
||||
<div className="card-image" style={{ position: 'relative' }}>
|
||||
<figure className="image is-4by3 has-background-light" style={{ margin: 0 }}>
|
||||
{item.image ? (
|
||||
<img src={item.image} alt={item.title} style={{ objectFit: 'cover', height: '100%', width: '100%' }} />
|
||||
) : (
|
||||
<div className="is-flex is-align-items-center is-justify-content-center" style={{ height: '100%' }}>
|
||||
<span className="material-symbols-outlined has-text-grey-light is-size-1">image</span>
|
||||
</div>
|
||||
)}
|
||||
</figure>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: '8px',
|
||||
left: '8px',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.92)',
|
||||
borderRadius: '9999px',
|
||||
padding: '3px 8px',
|
||||
fontSize: '0.72rem',
|
||||
fontWeight: 600,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '5px',
|
||||
boxShadow: '0 1px 4px rgba(0,0,0,0.1)'
|
||||
}}
|
||||
>
|
||||
<span style={{ width: '6px', height: '6px', borderRadius: '50%', backgroundColor: statusDot }}></span>
|
||||
<span>{statusLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-content is-flex-grow-1 p-4 is-flex is-flex-direction-column">
|
||||
<p className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold mb-1" style={{ letterSpacing: '0.08em' }}>
|
||||
{item.category ? `${item.category} • GI AUTHENTIC` : 'ARTISANAL CRAFT'}
|
||||
</p>
|
||||
<p className="title is-6 mb-2" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#1b1c1c', lineHeight: 1.3 }}>
|
||||
{item.title}
|
||||
</p>
|
||||
<p className="subtitle is-6 has-text-weight-bold mt-auto mb-3" style={{ color: '#4d0218' }}>
|
||||
₹{Number(item.price).toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
<footer className="card-footer" style={{ borderTop: '1px solid #eae8e7', backgroundColor: '#FAF8F6' }}>
|
||||
<a href="#" className="card-footer-item has-text-grey is-size-7 has-text-weight-semibold is-flex is-align-items-center" style={{ gap: '4px' }} onClick={(e) => { e.preventDefault(); handleEditClick(item); setDashTab('add-product'); }}>
|
||||
<span className="material-symbols-outlined is-size-6">edit</span> Edit
|
||||
</a>
|
||||
<a href="#" className="card-footer-item has-text-danger is-size-7 has-text-weight-semibold is-flex is-align-items-center" style={{ gap: '4px' }} onClick={(e) => { e.preventDefault(); handleDeleteProduct(item.id); }}>
|
||||
<span className="material-symbols-outlined is-size-6">delete</span> Delete
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
<footer className="card-footer">
|
||||
<a href="#" className="card-footer-item has-text-grey" onClick={(e) => { e.preventDefault(); handleEditClick(item); setDashTab('add-product'); }}>Edit</a>
|
||||
<a href="#" className="card-footer-item has-text-danger" onClick={(e) => { e.preventDefault(); handleDeleteProduct(item.id); }}>Delete</a>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{products.length === 0 && (
|
||||
);
|
||||
})}
|
||||
{filteredProducts.length === 0 && (
|
||||
<div className="column is-12">
|
||||
<div className="notification is-light has-text-centered py-6">
|
||||
<p>No products available yet. Click "Add Product" or "Bulk Upload" to get started.</p>
|
||||
<div className="notification is-light has-text-centered py-6" style={{ border: '1px solid #D9C5B2', borderRadius: '8px' }}>
|
||||
<p>No products match your current filters. Click "Add Product" or "Bulk Upload" to manage listings.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<aside className="menu p-5" style={{ position: 'sticky', top: '4rem' }}>
|
||||
<aside className="menu p-5 is-flex is-flex-direction-column" style={{ position: 'sticky', top: '4rem', minHeight: 'calc(100vh - 4rem)' }}>
|
||||
<p className="menu-label has-text-weight-bold tracking-wide" style={{ color: 'var(--bulma-grey)', letterSpacing: '0.1em' }}>
|
||||
Seller Central
|
||||
</p>
|
||||
<ul className="menu-list">
|
||||
<ul className="menu-list is-flex-grow-1">
|
||||
{menuItems.map(item => (
|
||||
<li key={item.id} className="mb-2">
|
||||
<a
|
||||
|
|
@ -33,6 +34,16 @@ export default function Sidebar() {
|
|||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-auto pt-4" style={{ borderTop: '1px solid #e2dfdb' }}>
|
||||
<button
|
||||
className="button is-fullwidth is-outlined is-primary has-text-weight-semibold is-flex is-align-items-center is-justify-content-center"
|
||||
style={{ gap: '0.5rem', borderRadius: '8px' }}
|
||||
onClick={() => window.open('/', '_blank')}
|
||||
>
|
||||
<span>View Store</span>
|
||||
<span className="material-symbols-outlined is-size-6">open_in_new</span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue