2026-08-21 06:27:14 +00:00
|
|
|
import React, { useMemo } from 'react';
|
2026-08-21 05:13:19 +00:00
|
|
|
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
2026-08-21 06:27:14 +00:00
|
|
|
import { useSeller } from '../../../context/SellerContext';
|
2026-08-21 05:13:19 +00:00
|
|
|
|
2026-08-21 06:27:14 +00:00
|
|
|
const MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
2026-08-21 05:13:19 +00:00
|
|
|
|
|
|
|
|
export default function OverviewTab() {
|
2026-09-10 14:55:51 +00:00
|
|
|
const { orders, products, computeRealtimeMetrics, storeName, setDashTab } = useSeller();
|
2026-08-21 06:27:14 +00:00
|
|
|
const metrics = computeRealtimeMetrics();
|
|
|
|
|
|
|
|
|
|
// Group orders by month for chart
|
|
|
|
|
const chartData = useMemo(() => {
|
|
|
|
|
const monthlySales: Record<number, number> = {};
|
|
|
|
|
orders
|
|
|
|
|
.filter((o: any) => o.status !== 'Cancelled' && o.status !== 'Rejected')
|
|
|
|
|
.forEach((o: any) => {
|
|
|
|
|
const date = new Date(o.date);
|
|
|
|
|
if (!isNaN(date.getTime())) {
|
|
|
|
|
const month = date.getMonth();
|
|
|
|
|
monthlySales[month] = (monthlySales[month] || 0) + Number(o.total);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Show last 6 months
|
|
|
|
|
const currentMonth = new Date().getMonth();
|
|
|
|
|
const months = [];
|
|
|
|
|
for (let i = 5; i >= 0; i--) {
|
|
|
|
|
const idx = (currentMonth - i + 12) % 12;
|
|
|
|
|
months.push({ name: MONTH_NAMES[idx], sales: monthlySales[idx] || 0 });
|
|
|
|
|
}
|
|
|
|
|
return months;
|
|
|
|
|
}, [orders]);
|
|
|
|
|
|
|
|
|
|
const activeOrderCount = orders.filter((o: any) =>
|
|
|
|
|
!['Cancelled', 'Rejected', 'Delivered'].includes(o.status)
|
|
|
|
|
).length;
|
|
|
|
|
|
|
|
|
|
const recentActivity = useMemo(() => {
|
|
|
|
|
return [...orders]
|
|
|
|
|
.sort((a: any, b: any) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
|
|
|
|
.slice(0, 5);
|
|
|
|
|
}, [orders]);
|
|
|
|
|
|
|
|
|
|
const formatDate = (dateStr: string) => {
|
|
|
|
|
const d = new Date(dateStr);
|
|
|
|
|
if (isNaN(d.getTime())) return dateStr;
|
|
|
|
|
return d.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const statusTagClass = (status: string) => {
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 'Delivered': return 'is-success is-light';
|
|
|
|
|
case 'Shipped': return 'is-info is-light';
|
|
|
|
|
case 'Ready to Ship': return 'is-primary is-light';
|
|
|
|
|
case 'Pending Acceptance': return 'is-warning is-light';
|
|
|
|
|
case 'Rejected':
|
|
|
|
|
case 'Cancelled': return 'is-danger is-light';
|
|
|
|
|
default: return 'is-light';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-21 05:13:19 +00:00
|
|
|
const handleDownloadCsv = () => {
|
2026-08-21 06:27:14 +00:00
|
|
|
const header = 'Month,Sales\n';
|
|
|
|
|
const rows = chartData.map(row => `${row.name},${row.sales}`).join('\n');
|
|
|
|
|
const blob = new Blob([header + rows], { type: 'text/csv;charset=utf-8;' });
|
2026-08-21 05:13:19 +00:00
|
|
|
const url = URL.createObjectURL(blob);
|
2026-08-21 06:27:14 +00:00
|
|
|
const link = document.createElement('a');
|
|
|
|
|
link.setAttribute('href', url);
|
|
|
|
|
link.setAttribute('download', 'sales_report.csv');
|
2026-08-21 05:13:19 +00:00
|
|
|
document.body.appendChild(link);
|
|
|
|
|
link.click();
|
|
|
|
|
document.body.removeChild(link);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
2026-09-10 14:55:51 +00:00
|
|
|
{/* 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' }}>
|
2026-08-21 05:13:19 +00:00
|
|
|
<span className="icon"><span className="material-symbols-outlined">download</span></span>
|
|
|
|
|
<span>Download Report</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="columns is-multiline">
|
2026-09-10 14:55:51 +00:00
|
|
|
{/* 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>
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<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>
|
2026-08-21 05:13:19 +00:00
|
|
|
</div>
|
2026-08-21 06:27:14 +00:00
|
|
|
|
2026-09-10 14:55:51 +00:00
|
|
|
{/* 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>
|
2026-08-21 06:27:14 +00:00
|
|
|
</div>
|
2026-09-10 14:55:51 +00:00
|
|
|
|
|
|
|
|
{/* 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>
|
2026-08-21 06:27:14 +00:00
|
|
|
</div>
|
2026-09-10 14:55:51 +00:00
|
|
|
</div>
|
2026-08-21 05:13:19 +00:00
|
|
|
|
2026-09-10 14:55:51 +00:00
|
|
|
{/* 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>
|
2026-08-21 06:27:14 +00:00
|
|
|
</div>
|
2026-09-10 14:55:51 +00:00
|
|
|
</div>
|
2026-08-21 05:13:19 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|