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 { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
||||||
|
import { useSeller } from '../../../context/SellerContext';
|
||||||
|
|
||||||
const data = [
|
const MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
{ name: 'Jan', sales: 4000 },
|
|
||||||
{ name: 'Feb', sales: 3000 },
|
|
||||||
{ name: 'Mar', sales: 5000 },
|
|
||||||
{ name: 'Apr', sales: 2780 },
|
|
||||||
{ name: 'May', sales: 8890 },
|
|
||||||
{ name: 'Jun', sales: 14500 },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function OverviewTab() {
|
export default function OverviewTab() {
|
||||||
const handleDownloadCsv = () => {
|
const { orders, products, computeRealtimeMetrics } = useSeller();
|
||||||
// Generate CSV string
|
const metrics = computeRealtimeMetrics();
|
||||||
const header = "Month,Sales\n";
|
|
||||||
const rows = data.map(row => `${row.name},${row.sales}`).join("\n");
|
|
||||||
const csvContent = header + rows;
|
|
||||||
|
|
||||||
// Create Blob and trigger download
|
// Group orders by month for chart
|
||||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
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 url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement("a");
|
const link = document.createElement('a');
|
||||||
link.setAttribute("href", url);
|
link.setAttribute('href', url);
|
||||||
link.setAttribute("download", "sales_report.csv");
|
link.setAttribute('download', 'sales_report.csv');
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
|
|
@ -42,32 +86,41 @@ export default function OverviewTab() {
|
||||||
<div className="column is-4">
|
<div className="column is-4">
|
||||||
<div className="box" style={{ borderTop: '4px solid var(--bulma-primary)' }}>
|
<div className="box" style={{ borderTop: '4px solid var(--bulma-primary)' }}>
|
||||||
<p className="heading has-text-grey-dark">Total Sales</p>
|
<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>
|
</div>
|
||||||
<div className="column is-4">
|
<div className="column is-4">
|
||||||
<div className="box" style={{ borderTop: '4px solid var(--bulma-primary)' }}>
|
<div className="box" style={{ borderTop: '4px solid var(--bulma-primary)' }}>
|
||||||
<p className="heading has-text-grey-dark">Active Orders</p>
|
<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>
|
</div>
|
||||||
<div className="column is-4">
|
<div className="column is-4">
|
||||||
<div className="box" style={{ borderTop: '4px solid var(--bulma-primary)' }}>
|
<div className="box" style={{ borderTop: '4px solid var(--bulma-primary)' }}>
|
||||||
<p className="heading has-text-grey-dark">Page Views</p>
|
<p className="heading has-text-grey-dark">Total Products</p>
|
||||||
<p className="title is-2 has-text-dark">1,204</p>
|
<p className="title is-2 has-text-dark">
|
||||||
|
{products.length === 0 ? '—' : products.length}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sales Chart */}
|
{/* Sales Chart */}
|
||||||
<div className="box mt-4">
|
<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 }}>
|
<div style={{ width: '100%', height: 300 }}>
|
||||||
<ResponsiveContainer>
|
<ResponsiveContainer>
|
||||||
<AreaChart
|
<AreaChart data={chartData} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
|
||||||
data={data}
|
|
||||||
margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
|
|
||||||
>
|
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="colorSales" x1="0" y1="0" x2="0" y2="1">
|
<linearGradient id="colorSales" x1="0" y1="0" x2="0" y2="1">
|
||||||
<stop offset="5%" stopColor="var(--bulma-primary)" stopOpacity={0.8} />
|
<stop offset="5%" stopColor="var(--bulma-primary)" stopOpacity={0.8} />
|
||||||
|
|
@ -76,40 +129,47 @@ export default function OverviewTab() {
|
||||||
</defs>
|
</defs>
|
||||||
<CartesianGrid strokeDasharray="3 3" vertical={false} />
|
<CartesianGrid strokeDasharray="3 3" vertical={false} />
|
||||||
<XAxis dataKey="name" tick={{ fill: '#5f5e5b' }} axisLine={false} tickLine={false} />
|
<XAxis dataKey="name" tick={{ fill: '#5f5e5b' }} axisLine={false} tickLine={false} />
|
||||||
<YAxis tick={{fill: '#5f5e5b'}} axisLine={false} tickLine={false} tickFormatter={(value) => `$${value}`} />
|
<YAxis tick={{ fill: '#5f5e5b' }} axisLine={false} tickLine={false} tickFormatter={(v) => `₹${v}`} />
|
||||||
<Tooltip formatter={(value) => [`$${value}`, 'Sales']} />
|
<Tooltip formatter={(value) => [`₹${Number(value).toFixed(2)}`, 'Sales']} />
|
||||||
<Area type="monotone" dataKey="sales" stroke="var(--bulma-primary)" fillOpacity={1} fill="url(#colorSales)" />
|
<Area type="monotone" dataKey="sales" stroke="var(--bulma-primary)" fillOpacity={1} fill="url(#colorSales)" />
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Recent Orders */}
|
||||||
<div className="box mt-5">
|
<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">
|
<table className="table is-fullwidth is-hoverable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th>Customer</th>
|
<th>Customer</th>
|
||||||
<th>Action</th>
|
<th>Item</th>
|
||||||
|
<th>Amount</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
{recentActivity.map((order: any) => (
|
||||||
<td>Oct 24, 2023</td>
|
<tr key={order.id}>
|
||||||
<td>Jane Doe</td>
|
<td>{formatDate(order.date)}</td>
|
||||||
<td>Placed Order #1045</td>
|
<td>{order.customer || '—'}</td>
|
||||||
<td><span className="tag is-info is-light">Processing</span></td>
|
<td>{order.item || '—'}</td>
|
||||||
</tr>
|
<td>₹{Number(order.total).toFixed(2)}</td>
|
||||||
<tr>
|
<td><span className={`tag ${statusTagClass(order.status)}`}>{order.status}</span></td>
|
||||||
<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>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,69 @@
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useSeller } from '../../../context/SellerContext';
|
import { useSeller } from '../../../context/SellerContext';
|
||||||
|
|
||||||
export default function SettingsTab() {
|
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 (
|
return (
|
||||||
<div className="columns is-centered">
|
<div className="columns is-centered">
|
||||||
<div className="column is-8">
|
<div className="column is-8">
|
||||||
<h1 className="title is-3 has-text-dark mb-5" style={{ fontFamily: 'Libre Caslon Text, serif' }}>Settings</h1>
|
<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">
|
<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">
|
<div className="field">
|
||||||
<label className="label">Store Name</label>
|
<label className="label">Store Name</label>
|
||||||
<div className="control">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -30,20 +78,31 @@ export default function SettingsTab() {
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label className="label">Store Description</label>
|
<label className="label">Store Description</label>
|
||||||
<div className="control">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="box mb-5">
|
<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="columns">
|
||||||
<div className="column is-half">
|
<div className="column is-half">
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label className="label">Support Email</label>
|
<label className="label">Support Email</label>
|
||||||
<div className="control">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -51,23 +110,38 @@ export default function SettingsTab() {
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label className="label">Support Phone</label>
|
<label className="label">Support Phone</label>
|
||||||
<div className="control">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="box mb-5">
|
<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>
|
<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="field">
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<div className="file is-boxed">
|
<div className="file is-boxed">
|
||||||
<label className="file-label is-flex-direction-column is-align-items-center">
|
<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-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="file-icon mb-2 has-text-primary">
|
||||||
<span className="material-symbols-outlined is-size-3">upload_file</span>
|
<span className="material-symbols-outlined is-size-3">upload_file</span>
|
||||||
|
|
@ -83,8 +157,15 @@ export default function SettingsTab() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="is-flex is-justify-content-flex-end">
|
<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>
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue