Remove mock data from Orders and Payments tabs; wire real API data
This commit is contained in:
parent
b61b4bca23
commit
84f07711c3
3 changed files with 243 additions and 66 deletions
|
|
@ -148,6 +148,6 @@ describe('Supplier Portal Onboarding & Active Dashboard Tests', () => {
|
||||||
fireEvent.click(walletTabBtn)
|
fireEvent.click(walletTabBtn)
|
||||||
|
|
||||||
expect(screen.getByText(/Available for Payout/i)).toBeInTheDocument()
|
expect(screen.getByText(/Available for Payout/i)).toBeInTheDocument()
|
||||||
expect(screen.getByText(/Recent Transactions/i)).toBeInTheDocument()
|
expect(screen.getByText(/Transaction History/i)).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,58 @@
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { useSeller } from '../../../context/SellerContext';
|
||||||
|
|
||||||
|
type TabFilter = 'All Orders' | 'Pending' | 'Shipped' | 'Completed';
|
||||||
|
|
||||||
|
const TAB_STATUSES: Record<TabFilter, string[]> = {
|
||||||
|
'All Orders': [],
|
||||||
|
'Pending': ['Pending Acceptance'],
|
||||||
|
'Shipped': ['Ready to Ship', 'Shipped'],
|
||||||
|
'Completed': ['Delivered', 'Cancelled', 'Rejected'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATUS_TAG: Record<string, string> = {
|
||||||
|
'Pending Acceptance': 'is-warning is-light',
|
||||||
|
'Ready to Ship': 'is-primary is-light',
|
||||||
|
'Shipped': 'is-info is-light',
|
||||||
|
'Delivered': 'is-success is-light',
|
||||||
|
'Cancelled': 'is-danger is-light',
|
||||||
|
'Rejected': 'is-danger is-light',
|
||||||
|
};
|
||||||
|
|
||||||
export default function OrdersTab() {
|
export default function OrdersTab() {
|
||||||
|
const { orders, handleAcceptOrder, handleRejectOrder, setSelectedOrderDetail } = useSeller();
|
||||||
|
const [activeTab, setActiveTab] = useState<TabFilter>('All Orders');
|
||||||
|
|
||||||
|
const filteredOrders = TAB_STATUSES[activeTab].length === 0
|
||||||
|
? orders
|
||||||
|
: orders.filter((o: any) => TAB_STATUSES[activeTab].includes(o.status));
|
||||||
|
|
||||||
|
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 handleExportCsv = () => {
|
||||||
|
const header = 'Order ID,Date,Customer,Item,Qty,Status,Total\n';
|
||||||
|
const rows = orders.map((o: any) =>
|
||||||
|
`${o.id},${o.date},${o.customer},${o.item},${o.quantity},${o.status},${o.total}`
|
||||||
|
).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', 'orders_export.csv');
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
};
|
||||||
|
|
||||||
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>
|
<h1 className="title is-3 has-text-dark mb-0" style={{ fontFamily: 'Libre Caslon Text, serif' }}>Orders</h1>
|
||||||
<button className="button is-primary is-outlined">
|
<button className="button is-primary is-outlined" onClick={handleExportCsv}>
|
||||||
<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</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -14,42 +61,76 @@ export default function OrdersTab() {
|
||||||
<div className="box">
|
<div className="box">
|
||||||
<div className="tabs">
|
<div className="tabs">
|
||||||
<ul>
|
<ul>
|
||||||
<li className="is-active"><a>All Orders</a></li>
|
{(Object.keys(TAB_STATUSES) as TabFilter[]).map(tab => (
|
||||||
<li><a>Pending</a></li>
|
<li key={tab} className={activeTab === tab ? 'is-active' : ''}>
|
||||||
<li><a>Shipped</a></li>
|
<a onClick={() => setActiveTab(tab)}>{tab}</a>
|
||||||
<li><a>Completed</a></li>
|
</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{filteredOrders.length === 0 ? (
|
||||||
|
<div className="has-text-centered py-6 has-text-grey">
|
||||||
|
<span className="material-symbols-outlined" style={{ fontSize: '3rem' }}>inventory_2</span>
|
||||||
|
<p className="mt-2">No orders found in this category.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<table className="table is-fullwidth is-hoverable is-striped">
|
<table className="table is-fullwidth is-hoverable is-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Order ID</th>
|
<th>Order ID</th>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th>Customer</th>
|
<th>Customer</th>
|
||||||
|
<th>Item</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Total</th>
|
<th>Total</th>
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{[
|
{filteredOrders.map((order: any) => (
|
||||||
{ id: '#1045', date: 'Oct 24, 2023', customer: 'Jane Doe', status: 'Processing', type: 'info', total: '$120.00' },
|
|
||||||
{ id: '#1044', date: 'Oct 22, 2023', customer: 'Alex Johnson', status: 'Shipped', type: 'warning', total: '$45.50' },
|
|
||||||
{ id: '#1043', date: 'Oct 20, 2023', customer: 'Sarah Williams', status: 'Completed', type: 'success', total: '$210.00' }
|
|
||||||
].map(order => (
|
|
||||||
<tr key={order.id}>
|
<tr key={order.id}>
|
||||||
<td><strong>{order.id}</strong></td>
|
<td><strong>#{order.id}</strong></td>
|
||||||
<td>{order.date}</td>
|
<td>{formatDate(order.date)}</td>
|
||||||
<td>{order.customer}</td>
|
<td>{order.customer || '—'}</td>
|
||||||
<td><span className={`tag is-${order.type} is-light`}>{order.status}</span></td>
|
<td>{order.item || '—'}</td>
|
||||||
<td>{order.total}</td>
|
|
||||||
<td>
|
<td>
|
||||||
<button className="button is-small is-light">View</button>
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,146 @@
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { useSeller } from '../../../context/SellerContext';
|
||||||
|
|
||||||
export default function PaymentsTab() {
|
export default function PaymentsTab() {
|
||||||
|
const { wallet, withdrawAmount, setWithdrawAmount, handleWithdrawRequest } = useSeller();
|
||||||
|
|
||||||
|
const outstanding = Number(wallet.outstanding || 0);
|
||||||
|
const withdrawn = Number(wallet.withdrawn || 0);
|
||||||
|
const transactions = wallet.transactions || wallet.history || [];
|
||||||
|
|
||||||
|
// Pending = orders delivered but not yet in wallet (approximated as 0 unless backend provides it)
|
||||||
|
const pendingClearance = Number(wallet.pending_clearance || 0);
|
||||||
|
|
||||||
|
const [showWithdrawModal, setShowWithdrawModal] = useState(false);
|
||||||
|
|
||||||
|
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 handleWithdraw = async () => {
|
||||||
|
await handleWithdrawRequest();
|
||||||
|
setShowWithdrawModal(false);
|
||||||
|
setWithdrawAmount('');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="title is-3 has-text-dark mb-5" style={{ fontFamily: 'Libre Caslon Text, serif' }}>Payments & Earnings</h1>
|
<h1 className="title is-3 has-text-dark mb-5" style={{ fontFamily: 'Libre Caslon Text, serif' }}>
|
||||||
|
Payments & Earnings
|
||||||
|
</h1>
|
||||||
|
|
||||||
<div className="columns is-multiline mb-5">
|
<div className="columns is-multiline mb-5">
|
||||||
<div className="column is-6">
|
<div className="column is-6">
|
||||||
<div className="box has-background-primary has-text-white">
|
<div className="box has-background-primary has-text-white">
|
||||||
<p className="heading has-text-white-ter mb-1">Available for Payout</p>
|
<p className="heading has-text-white-ter mb-1">Available for Payout</p>
|
||||||
<p className="title is-1 has-text-white mb-4">$1,240.00</p>
|
<p className="title is-1 has-text-white mb-4">
|
||||||
<button className="button is-white is-outlined is-fullwidth">Withdraw Funds</button>
|
₹{outstanding.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className="button is-white is-outlined is-fullwidth"
|
||||||
|
onClick={() => setShowWithdrawModal(true)}
|
||||||
|
disabled={outstanding <= 0}
|
||||||
|
>
|
||||||
|
Withdraw Funds
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="column is-6">
|
<div className="column is-6">
|
||||||
<div className="box" style={{ height: '100%' }}>
|
<div className="box" style={{ height: '100%' }}>
|
||||||
<p className="heading has-text-grey-dark mb-1">Pending Clearance</p>
|
<p className="heading has-text-grey-dark mb-1">Total Withdrawn</p>
|
||||||
<p className="title is-3 has-text-dark mb-4">$320.00</p>
|
<p className="title is-3 has-text-dark mb-3">
|
||||||
|
₹{withdrawn.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||||
|
</p>
|
||||||
|
{pendingClearance > 0 && (
|
||||||
|
<>
|
||||||
|
<p className="heading has-text-grey-dark mb-1 mt-3">Pending Clearance</p>
|
||||||
|
<p className="title is-5 has-text-dark mb-2">
|
||||||
|
₹{pendingClearance.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<p className="is-size-7 has-text-grey">Funds will be available in 3-5 business days after order delivery.</p>
|
<p className="is-size-7 has-text-grey">Funds will be available in 3-5 business days after order delivery.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Withdraw Modal */}
|
||||||
|
{showWithdrawModal && (
|
||||||
|
<div className="modal is-active">
|
||||||
|
<div className="modal-background" onClick={() => setShowWithdrawModal(false)} />
|
||||||
|
<div className="modal-card">
|
||||||
|
<header className="modal-card-head">
|
||||||
|
<p className="modal-card-title">Withdraw Funds</p>
|
||||||
|
<button className="delete" onClick={() => setShowWithdrawModal(false)} />
|
||||||
|
</header>
|
||||||
|
<section className="modal-card-body">
|
||||||
|
<p className="mb-3 has-text-grey">
|
||||||
|
Available: ₹{outstanding.toLocaleString('en-IN', { minimumFractionDigits: 2 })}
|
||||||
|
</p>
|
||||||
|
<div className="field">
|
||||||
|
<label className="label">Amount (₹)</label>
|
||||||
|
<div className="control">
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max={outstanding}
|
||||||
|
placeholder="Enter amount"
|
||||||
|
value={withdrawAmount}
|
||||||
|
onChange={e => setWithdrawAmount(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<footer className="modal-card-foot">
|
||||||
|
<button className="button is-primary" onClick={handleWithdraw}>Confirm Withdrawal</button>
|
||||||
|
<button className="button" onClick={() => setShowWithdrawModal(false)}>Cancel</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="box">
|
<div className="box">
|
||||||
<h2 className="title is-5 has-text-dark">Recent Transactions</h2>
|
<h2 className="title is-5 has-text-dark">Transaction History</h2>
|
||||||
<table className="table is-fullwidth">
|
{transactions.length === 0 ? (
|
||||||
|
<div className="has-text-centered py-5 has-text-grey">
|
||||||
|
<span className="material-symbols-outlined" style={{ fontSize: '3rem' }}>receipt_long</span>
|
||||||
|
<p className="mt-2">No transactions yet.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<table className="table is-fullwidth is-hoverable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
<th>Amount</th>
|
<th>Amount</th>
|
||||||
|
<th>Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
{transactions.map((tx: any) => {
|
||||||
<td>Oct 20, 2023</td>
|
const amount = Number(tx.amount || 0);
|
||||||
<td>Payout to Bank Account (...1234)</td>
|
const isDebit = tx.type === 'withdrawal' || amount < 0;
|
||||||
<td className="has-text-danger">-$500.00</td>
|
return (
|
||||||
</tr>
|
<tr key={tx.id}>
|
||||||
<tr>
|
<td>{formatDate(tx.date)}</td>
|
||||||
<td>Oct 19, 2023</td>
|
<td>{tx.description || (isDebit ? 'Payout Withdrawal' : 'Order Revenue')}</td>
|
||||||
<td>Order #1042 Revenue</td>
|
<td className={isDebit ? 'has-text-danger' : 'has-text-success'}>
|
||||||
<td className="has-text-success">+$85.00</td>
|
{isDebit ? '-' : '+'}₹{Math.abs(amount).toFixed(2)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span className={`tag ${tx.status === 'Transferred' ? 'is-success is-light' : 'is-warning is-light'}`}>
|
||||||
|
{tx.status || 'Pending'}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue