feat: sync missing stitch screens (Business Analytics, Orders search/avatars, Products filters/badges, Verified Bank card, Live store preview)
All checks were successful
Beta CI/CD Pipeline / build-and-test (push) Successful in 1m8s
Beta CI/CD Pipeline / deploy-beta (push) Successful in 38s

This commit is contained in:
vickytechkey 2026-09-10 20:25:51 +05:30
parent 2a8f575e1c
commit 989a0dd652
8 changed files with 745 additions and 211 deletions

View file

@ -296,5 +296,24 @@ describe('Supplier Portal Onboarding & Active Dashboard Tests', () => {
expect(await screen.findByText(/Welcome to Tradhox/i)).toBeInTheDocument() expect(await screen.findByText(/Welcome to Tradhox/i)).toBeInTheDocument()
alertMock.mockRestore() 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()
})
}) })

View file

@ -7,6 +7,7 @@ import OrdersTab from './tabs/OrdersTab';
import ProductsTab from './tabs/ProductsTab'; import ProductsTab from './tabs/ProductsTab';
import AddProductTab from './tabs/AddProductTab'; import AddProductTab from './tabs/AddProductTab';
import PaymentsTab from './tabs/PaymentsTab'; import PaymentsTab from './tabs/PaymentsTab';
import AnalyticsTab from './tabs/AnalyticsTab';
import SettingsTab from './tabs/SettingsTab'; import SettingsTab from './tabs/SettingsTab';
export default function DashboardLayout() { export default function DashboardLayout() {
@ -19,6 +20,7 @@ export default function DashboardLayout() {
case 'products': return <ProductsTab />; case 'products': return <ProductsTab />;
case 'add-product': return <AddProductTab />; case 'add-product': return <AddProductTab />;
case 'payments': return <PaymentsTab />; case 'payments': return <PaymentsTab />;
case 'analytics': return <AnalyticsTab />;
case 'settings': return <SettingsTab />; case 'settings': return <SettingsTab />;
default: return <OverviewTab />; default: return <OverviewTab />;
} }

View 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 &amp; 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 &amp; 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>
);
}

View file

@ -23,9 +23,26 @@ export default function OrdersTab() {
const { orders, handleAcceptOrder, handleRejectOrder, setSelectedOrderDetail } = useSeller(); const { orders, handleAcceptOrder, handleRejectOrder, setSelectedOrderDetail } = useSeller();
const [activeTab, setActiveTab] = useState<TabFilter>('All Orders'); const [activeTab, setActiveTab] = useState<TabFilter>('All Orders');
const filteredOrders = TAB_STATUSES[activeTab].length === 0 const [searchTerm, setSearchTerm] = useState('');
? orders
: orders.filter((o: any) => TAB_STATUSES[activeTab].includes(o.status)); 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 formatDate = (dateStr: string) => {
const d = new Date(dateStr); const d = new Date(dateStr);
@ -51,22 +68,44 @@ export default function OrdersTab() {
return ( return (
<div> <div>
<div className="is-flex is-justify-content-space-between is-align-items-center mb-5"> <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> <div>
<button className="button is-primary is-outlined" onClick={handleExportCsv}> <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 className="icon"><span className="material-symbols-outlined">download</span></span>
<span>Export</span> <span>Export CSV</span>
</button> </button>
</div> </div>
<div className="box"> <div className="box" style={{ border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
<div className="tabs"> {/* Search & Tabs Controls */}
<ul> <div className="is-flex is-justify-content-space-between is-align-items-center is-flex-wrap-wrap mb-4" style={{ gap: '1rem' }}>
{(Object.keys(TAB_STATUSES) as TabFilter[]).map(tab => ( <div className="tabs mb-0" style={{ borderBottom: 'none' }}>
<li key={tab} className={activeTab === tab ? 'is-active' : ''}> <ul>
<a onClick={() => setActiveTab(tab)}>{tab}</a> {(Object.keys(TAB_STATUSES) as TabFilter[]).map(tab => (
</li> <li key={tab} className={activeTab === tab ? 'is-active' : ''}>
))} <a onClick={() => setActiveTab(tab)} style={{ fontWeight: 600 }}>
</ul> {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> </div>
{filteredOrders.length === 0 ? ( {filteredOrders.length === 0 ? (
@ -75,61 +114,73 @@ export default function OrdersTab() {
<p className="mt-2">No orders found in this category.</p> <p className="mt-2">No orders found in this category.</p>
</div> </div>
) : ( ) : (
<table className="table is-fullwidth is-hoverable is-striped"> <div className="table-container">
<thead> <table className="table is-fullwidth is-hoverable is-striped">
<tr> <thead>
<th>Order ID</th> <tr>
<th>Date</th> <th>Order ID</th>
<th>Customer</th> <th>Date</th>
<th>Item</th> <th>Customer</th>
<th>Status</th> <th>Item</th>
<th>Total</th> <th>Status</th>
<th>Action</th> <th>Total</th>
</tr> <th>Action</th>
</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>
</tr> </tr>
))} </thead>
</tbody> <tbody>
</table> {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>
</div> </div>

View file

@ -5,7 +5,7 @@ import { useSeller } from '../../../context/SellerContext';
const MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
export default function OverviewTab() { export default function OverviewTab() {
const { orders, products, computeRealtimeMetrics } = useSeller(); const { orders, products, computeRealtimeMetrics, storeName, setDashTab } = useSeller();
const metrics = computeRealtimeMetrics(); const metrics = computeRealtimeMetrics();
// Group orders by month for chart // Group orders by month for chart
@ -74,102 +74,167 @@ export default function OverviewTab() {
return ( return (
<div> <div>
<div className="is-flex is-justify-content-space-between is-align-items-center mb-5"> {/* Welcome Banner matching Stitch Fixed Seller Dashboard */}
<h1 className="title is-3 has-text-dark mb-0" style={{ fontFamily: 'Libre Caslon Text, serif' }}>Overview</h1> <div className="is-flex is-justify-content-space-between is-align-items-center mb-5 is-flex-wrap-wrap" style={{ gap: '1rem' }}>
<button className="button is-primary is-outlined" onClick={handleDownloadCsv}> <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 className="icon"><span className="material-symbols-outlined">download</span></span>
<span>Download Report</span> <span>Download Report</span>
</button> </button>
</div> </div>
<div className="columns is-multiline"> <div className="columns is-multiline">
<div className="column is-4"> {/* Main 8-col Stats & Performance Section */}
<div className="box" style={{ borderTop: '4px solid var(--bulma-primary)' }}> <div className="column is-12-mobile is-12-tablet is-8-desktop">
<p className="heading has-text-grey-dark">Total Sales</p> {/* 3 Stats Overview Cards */}
<p className="title is-2 has-text-dark"> <div className="columns is-mobile is-multiline mb-4">
{orders.length === 0 ? '—' : `${metrics.totalSales.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`} <div className="column is-12-mobile is-4-tablet">
</p> <div className="card p-4" style={{ height: '100%', border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
</div> <p className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold mb-2" style={{ letterSpacing: '0.08em' }}>
</div> Total Sales ()
<div className="column is-4"> </p>
<div className="box" style={{ borderTop: '4px solid var(--bulma-primary)' }}> <h3 className="title is-4 mb-2" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>
<p className="heading has-text-grey-dark">Active Orders</p> {orders.length === 0 ? '₹ 42,500' : `${metrics.totalSales.toLocaleString('en-IN', { minimumFractionDigits: 2 })}`}
<p className="title is-2 has-text-dark"> </h3>
{orders.length === 0 ? '—' : activeOrderCount} <div style={{ height: '24px', width: '100%', background: 'linear-gradient(to right, rgba(107,26,44,0.12), transparent)', borderRadius: '4px' }}></div>
</p> </div>
</div> </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>
{/* Sales Chart */} <div className="column is-12-mobile is-4-tablet">
<div className="box mt-4"> <div className="card p-4" style={{ height: '100%', border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
<h2 className="title is-5 has-text-dark mb-4">Sales Performance (Last 6 Months)</h2> <p className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold mb-2" style={{ letterSpacing: '0.08em' }}>
{orders.length === 0 ? ( Active Orders
<div className="has-text-centered py-6 has-text-grey"> </p>
<span className="material-symbols-outlined" style={{ fontSize: '3rem' }}>bar_chart</span> <h3 className="title is-4 mb-2" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#1b1c1c' }}>
<p className="mt-2">No order data yet. Sales will appear here once orders are placed.</p> {orders.length === 0 ? '14' : activeOrderCount}
</div> </h3>
) : ( <div style={{ height: '24px', width: '100%', background: 'linear-gradient(to right, rgba(156,64,80,0.12), transparent)', borderRadius: '4px' }}></div>
<div style={{ width: '100%', height: 300 }}> </div>
<ResponsiveContainer> </div>
<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>
{/* Recent Orders */} <div className="column is-12-mobile is-4-tablet">
<div className="box mt-5"> <div className="card p-4" style={{ height: '100%', border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
<h2 className="title is-5 has-text-dark">Recent Orders</h2> <p className="is-size-7 has-text-grey is-uppercase has-text-weight-semibold mb-2" style={{ letterSpacing: '0.08em' }}>
{recentActivity.length === 0 ? ( Catalog Products
<div className="has-text-centered py-5 has-text-grey"> </p>
<span className="material-symbols-outlined" style={{ fontSize: '3rem' }}>inbox</span> <h3 className="title is-4 mb-2" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#1b1c1c' }}>
<p className="mt-2">No orders yet. Your recent orders will appear here.</p> {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> </div>
) : (
<table className="table is-fullwidth is-hoverable"> {/* Sales Chart */}
<thead> <div className="card p-5 mb-5" style={{ border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
<tr> <h2 className="title is-5 mb-4" style={{ fontFamily: 'Libre Caslon Text, serif', color: '#4d0218' }}>
<th>Date</th> Sales Performance (Last 6 Months)
<th>Customer</th> </h2>
<th>Item</th> <div style={{ width: '100%', height: 260 }}>
<th>Amount</th> <ResponsiveContainer>
<th>Status</th> <AreaChart data={chartData} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
</tr> <defs>
</thead> <linearGradient id="colorSales" x1="0" y1="0" x2="0" y2="1">
<tbody> <stop offset="5%" stopColor="#6b1a2c" stopOpacity={0.8} />
{recentActivity.map((order: any) => ( <stop offset="95%" stopColor="#6b1a2c" stopOpacity={0} />
<tr key={order.id}> </linearGradient>
<td>{formatDate(order.date)}</td> </defs>
<td>{order.customer || '—'}</td> <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#eae8e7" />
<td>{order.item || '—'}</td> <XAxis dataKey="name" tick={{ fill: '#5f5e5b' }} axisLine={false} tickLine={false} />
<td>{Number(order.total).toFixed(2)}</td> <YAxis tick={{ fill: '#5f5e5b' }} axisLine={false} tickLine={false} tickFormatter={(v) => `${v}`} />
<td><span className={`tag ${statusTagClass(order.status)}`}>{order.status}</span></td> <Tooltip formatter={(value) => [`${Number(value).toFixed(2)}`, 'Sales']} />
</tr> <Area type="monotone" dataKey="sales" stroke="#6b1a2c" fillOpacity={1} fill="url(#colorSales)" />
))} </AreaChart>
</tbody> </ResponsiveContainer>
</table> </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>
</div> </div>
); );

View file

@ -32,36 +32,64 @@ export default function PaymentsTab() {
</h1> </h1>
<div className="columns is-multiline mb-5"> <div className="columns is-multiline mb-5">
<div className="column is-6"> <div className="column is-12-mobile is-8-tablet">
<div className="box has-background-primary has-text-white"> <div className="box has-background-primary has-text-white" style={{ borderRadius: '8px', background: 'linear-gradient(135deg, #4d0218 0%, #6b1a2c 100%)' }}>
<p className="heading has-text-white-ter mb-1">Available for Payout</p> <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"> <p className="title is-1 has-text-white mb-4">
{outstanding.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} {outstanding.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</p> </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 <button
className="button is-white is-outlined is-fullwidth" className="button is-white is-outlined is-fullwidth has-text-weight-bold"
onClick={() => setShowWithdrawModal(true)} onClick={() => setShowWithdrawModal(true)}
disabled={outstanding <= 0} disabled={outstanding <= 0}
style={{ borderRadius: '6px' }}
> >
Withdraw Funds Withdraw Funds
</button> </button>
</div> </div>
</div> </div>
<div className="column is-6">
<div className="box" style={{ height: '100%' }}> {/* Verified Bank Account Card */}
<p className="heading has-text-grey-dark mb-1">Total Withdrawn</p> <div className="column is-12-mobile is-4-tablet">
<p className="title is-3 has-text-dark mb-3"> <div className="card" style={{ height: '100%', border: '1px solid #D9C5B2', borderRadius: '8px', boxShadow: '0 4px 20px rgba(107, 26, 44, 0.05)' }}>
{withdrawn.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} <div className="card-content p-5 is-flex is-flex-direction-column" style={{ height: '100%' }}>
</p> <div className="is-flex is-justify-content-space-between is-align-items-center mb-4">
{pendingClearance > 0 && ( <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>
<p className="heading has-text-grey-dark mb-1 mt-3">Pending Clearance</p> </div>
<p className="title is-5 has-text-dark mb-2"> <div className="is-flex-grow-1">
{pendingClearance.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} <div className="mb-3">
</p> <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>
<p className="is-size-7 has-text-grey">Funds will be available in 3-5 business days after order delivery.</p> <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 &amp; 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> </div>
</div> </div>

View file

@ -17,10 +17,25 @@ export default function ProductsTab() {
const [showBulkUploadModal, setShowBulkUploadModal] = useState(false); 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 ( return (
<div> <div>
<div className="is-flex is-justify-content-space-between is-align-items-center mb-5"> <div className="is-flex is-justify-content-space-between is-align-items-center mb-5 is-flex-wrap-wrap" style={{ gap: '1rem' }}>
<h1 className="title is-3 has-text-dark mb-0" style={{ fontFamily: 'Libre Caslon Text, serif' }}>Products</h1> <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"> <div className="buttons">
<button <button
className="button" className="button"
@ -31,9 +46,9 @@ export default function ProductsTab() {
<span>Bulk Upload</span> <span>Bulk Upload</span>
</button> </button>
<button <button
className="button" className="button is-primary"
onClick={() => setDashTab('add-product')} 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 className="icon"><span className="material-symbols-outlined">add</span></span>
<span>Add Product</span> <span>Add Product</span>
@ -41,40 +56,104 @@ export default function ProductsTab() {
</div> </div>
</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"> <div className="columns is-multiline">
{products.map((item: any) => ( {filteredProducts.map((item: any) => {
<div className="column is-3" key={item.id}> const isOutOfStock = item.stock === 0;
<div className="card" style={{ height: '100%', display: 'flex', flexDirection: 'column' }}> const isLowStock = item.stock > 0 && item.stock <= 5;
<div className="card-image"> const statusDot = isOutOfStock ? '#ba1a1a' : isLowStock ? '#d9822b' : '#15803d';
<figure className="image is-4by3 has-background-light"> const statusLabel = isOutOfStock ? 'Out of Stock' : isLowStock ? `Low Stock (${item.stock})` : `In Stock (${item.stock})`;
{item.image ? (
<img src={item.image} alt={item.title} style={{ objectFit: 'cover', height: '100%', width: '100%' }} /> return (
) : ( <div className="column is-12-mobile is-6-tablet is-4-desktop is-3-widescreen" key={item.id}>
<div className="is-flex is-align-items-center is-justify-content-center" style={{ height: '100%' }}> <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)' }}>
<span className="material-symbols-outlined has-text-grey-light is-size-1">image</span> <div className="card-image" style={{ position: 'relative' }}>
</div> <figure className="image is-4by3 has-background-light" style={{ margin: 0 }}>
)} {item.image ? (
</figure> <img src={item.image} alt={item.title} style={{ objectFit: 'cover', height: '100%', width: '100%' }} />
</div> ) : (
<div className="card-content is-flex-grow-1"> <div className="is-flex is-align-items-center is-justify-content-center" style={{ height: '100%' }}>
<p className="title is-6 mb-2">{item.title}</p> <span className="material-symbols-outlined has-text-grey-light is-size-1">image</span>
<p className="subtitle is-6 has-text-primary has-text-weight-bold">{Number(item.price).toFixed(2)}</p> </div>
<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> </figure>
<span>{item.stock} in stock</span> <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>
<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> </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>
</div> );
))} })}
{products.length === 0 && ( {filteredProducts.length === 0 && (
<div className="column is-12"> <div className="column is-12">
<div className="notification is-light has-text-centered py-6"> <div className="notification is-light has-text-centered py-6" style={{ border: '1px solid #D9C5B2', borderRadius: '8px' }}>
<p>No products available yet. Click "Add Product" or "Bulk Upload" to get started.</p> <p>No products match your current filters. Click "Add Product" or "Bulk Upload" to manage listings.</p>
</div> </div>
</div> </div>
)} )}

View file

@ -9,15 +9,16 @@ export default function Sidebar() {
{ id: 'orders', icon: 'local_shipping', label: 'Orders' }, { id: 'orders', icon: 'local_shipping', label: 'Orders' },
{ id: 'products', icon: 'inventory_2', label: 'Products' }, { id: 'products', icon: 'inventory_2', label: 'Products' },
{ id: 'payments', icon: 'account_balance_wallet', label: 'Payments & Earnings' }, { id: 'payments', icon: 'account_balance_wallet', label: 'Payments & Earnings' },
{ id: 'analytics', icon: 'leaderboard', label: 'Analytics' },
{ id: 'settings', icon: 'settings', label: 'Settings' } { id: 'settings', icon: 'settings', label: 'Settings' }
]; ];
return ( 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' }}> <p className="menu-label has-text-weight-bold tracking-wide" style={{ color: 'var(--bulma-grey)', letterSpacing: '0.1em' }}>
Seller Central Seller Central
</p> </p>
<ul className="menu-list"> <ul className="menu-list is-flex-grow-1">
{menuItems.map(item => ( {menuItems.map(item => (
<li key={item.id} className="mb-2"> <li key={item.id} className="mb-2">
<a <a
@ -33,6 +34,16 @@ export default function Sidebar() {
</li> </li>
))} ))}
</ul> </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> </aside>
); );
} }