Remove mock data from Overview; fix Settings save button
This commit is contained in:
parent
114fa4706c
commit
91971379c5
2 changed files with 275 additions and 134 deletions
|
|
@ -1,28 +1,72 @@
|
|||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
||||
import { useSeller } from '../../../context/SellerContext';
|
||||
|
||||
const data = [
|
||||
{ name: 'Jan', sales: 4000 },
|
||||
{ name: 'Feb', sales: 3000 },
|
||||
{ name: 'Mar', sales: 5000 },
|
||||
{ name: 'Apr', sales: 2780 },
|
||||
{ name: 'May', sales: 8890 },
|
||||
{ name: 'Jun', sales: 14500 },
|
||||
];
|
||||
const MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
|
||||
export default function OverviewTab() {
|
||||
const handleDownloadCsv = () => {
|
||||
// Generate CSV string
|
||||
const header = "Month,Sales\n";
|
||||
const rows = data.map(row => `${row.name},${row.sales}`).join("\n");
|
||||
const csvContent = header + rows;
|
||||
const { orders, products, computeRealtimeMetrics } = useSeller();
|
||||
const metrics = computeRealtimeMetrics();
|
||||
|
||||
// Create Blob and trigger download
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
// 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';
|
||||
}
|
||||
};
|
||||
|
||||
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;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.setAttribute("href", url);
|
||||
link.setAttribute("download", "sales_report.csv");
|
||||
const link = document.createElement('a');
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', 'sales_report.csv');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
|
@ -42,32 +86,41 @@ export default function OverviewTab() {
|
|||
<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">$14,500</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">23</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">Page Views</p>
|
||||
<p className="title is-2 has-text-dark">1,204</p>
|
||||
<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="box mt-4">
|
||||
<h2 className="title is-5 has-text-dark mb-4">Sales Performance (YTD)</h2>
|
||||
<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={data}
|
||||
margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
|
||||
>
|
||||
<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} />
|
||||
|
|
@ -76,40 +129,47 @@ export default function OverviewTab() {
|
|||
</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={(value) => `$${value}`} />
|
||||
<Tooltip formatter={(value) => [`$${value}`, 'Sales']} />
|
||||
<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="box mt-5">
|
||||
<h2 className="title is-5 has-text-dark">Recent Activity</h2>
|
||||
<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>
|
||||
) : (
|
||||
<table className="table is-fullwidth is-hoverable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Customer</th>
|
||||
<th>Action</th>
|
||||
<th>Item</th>
|
||||
<th>Amount</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Oct 24, 2023</td>
|
||||
<td>Jane Doe</td>
|
||||
<td>Placed Order #1045</td>
|
||||
<td><span className="tag is-info is-light">Processing</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Oct 23, 2023</td>
|
||||
<td>John Smith</td>
|
||||
<td>Left a 5-star review</td>
|
||||
<td><span className="tag is-success is-light">Completed</span></td>
|
||||
{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>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,21 +1,69 @@
|
|||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useSeller } from '../../../context/SellerContext';
|
||||
|
||||
export default function SettingsTab() {
|
||||
const { storeSlug, supportEmail, supportPhone } = useSeller();
|
||||
const {
|
||||
storeName, setStoreName,
|
||||
storeSlug,
|
||||
businessBio, setBusinessBio,
|
||||
supportEmail, setSupportEmail,
|
||||
supportPhone, setSupportPhone,
|
||||
saveProfileBackend,
|
||||
uploadDocument,
|
||||
} = useSeller();
|
||||
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [saveSuccess, setSaveSuccess] = useState(false);
|
||||
const [saveError, setSaveError] = useState('');
|
||||
|
||||
const handleSave = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsSaving(true);
|
||||
setSaveSuccess(false);
|
||||
setSaveError('');
|
||||
try {
|
||||
await saveProfileBackend(true);
|
||||
setSaveSuccess(true);
|
||||
setTimeout(() => setSaveSuccess(false), 3000);
|
||||
} catch (err: any) {
|
||||
setSaveError(err.message || 'Failed to save settings.');
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="columns is-centered">
|
||||
<div className="column is-8">
|
||||
<h1 className="title is-3 has-text-dark mb-5" style={{ fontFamily: 'Libre Caslon Text, serif' }}>Settings</h1>
|
||||
|
||||
{saveSuccess && (
|
||||
<div className="notification is-success is-light mb-4">
|
||||
<button className="delete" onClick={() => setSaveSuccess(false)} />
|
||||
Settings saved successfully!
|
||||
</div>
|
||||
)}
|
||||
{saveError && (
|
||||
<div className="notification is-danger is-light mb-4">
|
||||
<button className="delete" onClick={() => setSaveError('')} />
|
||||
{saveError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSave}>
|
||||
<div className="box mb-5">
|
||||
<h2 className="title is-5 has-text-primary border-bottom pb-2" style={{ borderBottom: '1px solid #eee' }}>Store Profile</h2>
|
||||
<h2 className="title is-5 has-text-primary pb-2" style={{ borderBottom: '1px solid #eee' }}>Store Profile</h2>
|
||||
|
||||
<div className="field">
|
||||
<label className="label">Store Name</label>
|
||||
<div className="control">
|
||||
<input className="input" type="text" defaultValue="My Artisan Store" />
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
value={storeName}
|
||||
onChange={e => setStoreName(e.target.value)}
|
||||
placeholder="Your store name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -30,20 +78,31 @@ export default function SettingsTab() {
|
|||
<div className="field">
|
||||
<label className="label">Store Description</label>
|
||||
<div className="control">
|
||||
<textarea className="textarea" placeholder="Tell buyers about your craft..."></textarea>
|
||||
<textarea
|
||||
className="textarea"
|
||||
placeholder="Tell buyers about your craft..."
|
||||
value={businessBio}
|
||||
onChange={e => setBusinessBio(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="box mb-5">
|
||||
<h2 className="title is-5 has-text-primary border-bottom pb-2" style={{ borderBottom: '1px solid #eee' }}>Contact Information</h2>
|
||||
<h2 className="title is-5 has-text-primary pb-2" style={{ borderBottom: '1px solid #eee' }}>Contact Information</h2>
|
||||
|
||||
<div className="columns">
|
||||
<div className="column is-half">
|
||||
<div className="field">
|
||||
<label className="label">Support Email</label>
|
||||
<div className="control">
|
||||
<input className="input" type="email" defaultValue={supportEmail} />
|
||||
<input
|
||||
className="input"
|
||||
type="email"
|
||||
value={supportEmail}
|
||||
onChange={e => setSupportEmail(e.target.value)}
|
||||
placeholder="support@example.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -51,23 +110,38 @@ export default function SettingsTab() {
|
|||
<div className="field">
|
||||
<label className="label">Support Phone</label>
|
||||
<div className="control">
|
||||
<input className="input" type="tel" defaultValue={supportPhone} />
|
||||
<input
|
||||
className="input"
|
||||
type="tel"
|
||||
value={supportPhone}
|
||||
onChange={e => setSupportPhone(e.target.value)}
|
||||
placeholder="+91 XXXXX XXXXX"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="box mb-5">
|
||||
<h2 className="title is-5 has-text-primary border-bottom pb-2" style={{ borderBottom: '1px solid #eee' }}>Business Documents</h2>
|
||||
<h2 className="title is-5 has-text-primary pb-2" style={{ borderBottom: '1px solid #eee' }}>Business Documents</h2>
|
||||
<p className="has-text-grey-dark mb-4">Upload any additional documents (like business licenses, identity proof, or craft certifications).</p>
|
||||
|
||||
<div className="field">
|
||||
<div className="control">
|
||||
<div className="file is-boxed">
|
||||
<label className="file-label is-flex-direction-column is-align-items-center">
|
||||
<input className="file-input" type="file" name="business_doc" />
|
||||
<input
|
||||
className="file-input"
|
||||
type="file"
|
||||
name="business_doc"
|
||||
accept=".pdf,.jpg,.jpeg,.png"
|
||||
onChange={e => {
|
||||
if (e.target.files?.[0]) {
|
||||
uploadDocument(e.target.files[0], 'document');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span className="file-cta is-flex is-flex-direction-column is-align-items-center py-4 px-6 has-background-white" style={{ border: '1px solid var(--bulma-primary)' }}>
|
||||
<span className="file-icon mb-2 has-text-primary">
|
||||
<span className="material-symbols-outlined is-size-3">upload_file</span>
|
||||
|
|
@ -83,8 +157,15 @@ export default function SettingsTab() {
|
|||
</div>
|
||||
|
||||
<div className="is-flex is-justify-content-flex-end">
|
||||
<button className="button is-primary is-outlined">Save Changes</button>
|
||||
<button
|
||||
type="submit"
|
||||
className={`button is-primary is-outlined ${isSaving ? 'is-loading' : ''}`}
|
||||
disabled={isSaving}
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue