supplier_central_frontend/src/components/dashboard/tabs/OverviewTab.tsx

242 lines
13 KiB
TypeScript
Raw Normal View History

import React, { useMemo } from 'react';
2026-08-21 05:13:19 +00:00
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { useSeller } from '../../../context/SellerContext';
2026-08-21 05:13:19 +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() {
const { orders, products, computeRealtimeMetrics, storeName, setDashTab } = useSeller();
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 = () => {
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);
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>
{/* 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">
{/* 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>
{/* 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>
2026-08-21 05:13:19 +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>
</div>
</div>
2026-08-21 05:13:19 +00:00
</div>
</div>
);
}