Remove mock data and replace with real-time fetch requests for sellers and customers
This commit is contained in:
parent
4d9d9e0246
commit
0488eb9feb
4 changed files with 328 additions and 245 deletions
|
|
@ -1,27 +1,58 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, Link, useNavigate } from 'react-router-dom';
|
||||
import StatusBadge from '../../components/StatusBadge';
|
||||
import { API_BASE_URL } from '../../config';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
|
||||
const CustomerDetail = () => {
|
||||
const { id } = useParams();
|
||||
const { token } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const [customer, setCustomer] = useState({
|
||||
id: id,
|
||||
name: 'Amit Kumar',
|
||||
email: 'amit@gmail.com',
|
||||
phone: '+91 99887 76655',
|
||||
status: 'active',
|
||||
registered: '2026-03-04',
|
||||
address: 'Flat 301, Silver Heights, Sector 15, Gurgaon, Haryana - 122001',
|
||||
orders: [
|
||||
{ id: 'ORD88761', date: '2026-08-10', total: '₹2,499', paymentStatus: 'completed', trackingStatus: 'delivered', txId: 'TXN_PHPE_88761234' },
|
||||
{ id: 'ORD88120', date: '2026-08-01', total: '₹1,150', paymentStatus: 'completed', trackingStatus: 'returned', txId: 'TXN_PHPE_88120456' }
|
||||
]
|
||||
});
|
||||
const [customer, setCustomer] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const handleStatusChange = (newStatus) => {
|
||||
setCustomer({ ...customer, status: newStatus });
|
||||
alert(`Customer account status changed to ${newStatus}`);
|
||||
const fetchCustomerData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await fetch(`${API_BASE_URL}/customers/${id}/`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to fetch customer detail');
|
||||
const data = await res.json();
|
||||
|
||||
// Fallback: If Django database doesn't have orders relationship, initialize empty array
|
||||
if (!data.orders) {
|
||||
data.orders = [];
|
||||
}
|
||||
setCustomer(data);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchCustomerData();
|
||||
}, [id, token]);
|
||||
|
||||
const handleAction = async (actionPath, payload = {}) => {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/customers/${id}/${actionPath}/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (!res.ok) throw new Error(`Operation failed`);
|
||||
alert(`Customer status updated!`);
|
||||
fetchCustomerData(); // reload
|
||||
} catch (err) {
|
||||
alert(`Error: ${err.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCheckPaymentStatus = (orderId, txnId) => {
|
||||
|
|
@ -36,6 +67,10 @@ const CustomerDetail = () => {
|
|||
navigate(`/orders/return?orderId=${orderId}&address=${encodeURIComponent(customer.address)}`);
|
||||
};
|
||||
|
||||
if (loading) return <div style={{ padding: '40px', textAlign: 'center' }}>Loading customer details...</div>;
|
||||
if (error) return <div style={{ padding: '20px', color: 'var(--color-danger)', textAlign: 'center' }}>Error: {error}</div>;
|
||||
if (!customer) return <div style={{ padding: '20px', textAlign: 'center' }}>Customer profile not found.</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
|
|
@ -45,18 +80,18 @@ const CustomerDetail = () => {
|
|||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
|
||||
<div>
|
||||
<h1 className="page-title" style={{ margin: 0 }}>{customer.name}</h1>
|
||||
<p style={{ color: 'var(--text-secondary)' }}>Customer ID: #{customer.id} • Registered: {customer.registered}</p>
|
||||
<p style={{ color: 'var(--text-secondary)' }}>Customer ID: #{customer.id}</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '10px' }}>
|
||||
{customer.status === 'active' ? (
|
||||
<button className="btn btn-danger" onClick={() => handleStatusChange('suspended')}>Suspend Customer</button>
|
||||
<button className="btn btn-danger" onClick={() => handleAction('suspend')}>Suspend Customer</button>
|
||||
) : (
|
||||
<button className="btn btn-primary" onClick={() => handleStatusChange('active')}>Reinstate Customer</button>
|
||||
<button className="btn btn-primary" onClick={() => handleAction('suspend', { status: 'active' })}>Reinstate Customer</button>
|
||||
)}
|
||||
{customer.status !== 'banned' && (
|
||||
<button className="btn btn-danger" onClick={() => handleStatusChange('banned')}>Ban Account</button>
|
||||
<button className="btn btn-danger" onClick={() => handleAction('ban')}>Ban Account</button>
|
||||
)}
|
||||
<button className="btn btn-secondary" onClick={() => alert('Password reset SMS & WhatsApp alert sent.')}>Send Password Reset</button>
|
||||
<button className="btn btn-secondary" onClick={() => alert('Password reset triggered via backend service.')}>Send Password Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -85,58 +120,60 @@ const CustomerDetail = () => {
|
|||
|
||||
<div className="glass-card">
|
||||
<h3 className="section-title">Order History & Logs</h3>
|
||||
<div className="table-container">
|
||||
<table className="custom-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Order ID</th>
|
||||
<th>Date</th>
|
||||
<th>Total</th>
|
||||
<th>Payment</th>
|
||||
<th>Delivery</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{customer.orders.map(order => (
|
||||
<tr key={order.id}>
|
||||
<td style={{ fontWeight: '600' }}>{order.id}</td>
|
||||
<td>{order.date}</td>
|
||||
<td>{order.total}</td>
|
||||
<td><StatusBadge status={order.paymentStatus} /></td>
|
||||
<td><StatusBadge status={order.trackingStatus} /></td>
|
||||
<td>
|
||||
<div style={{ display: 'flex', gap: '6px' }}>
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
style={{ fontSize: '11px', padding: '4px 8px' }}
|
||||
onClick={() => handleCheckPaymentStatus(order.id, order.txId)}
|
||||
>
|
||||
Check PhonePe
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
style={{ fontSize: '11px', padding: '4px 8px' }}
|
||||
onClick={() => handleRefund(order.id, order.txId, order.total.replace('₹', '').replace(',', ''))}
|
||||
>
|
||||
Refund
|
||||
</button>
|
||||
{order.trackingStatus === 'delivered' && (
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
style={{ fontSize: '11px', padding: '4px 8px' }}
|
||||
onClick={() => handleReturn(order.id)}
|
||||
>
|
||||
Return QC
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
{customer.orders && customer.orders.length > 0 ? (
|
||||
<div className="table-container">
|
||||
<table className="custom-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Order ID</th>
|
||||
<th>Total</th>
|
||||
<th>Payment</th>
|
||||
<th>Delivery</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</thead>
|
||||
<tbody>
|
||||
{customer.orders.map(order => (
|
||||
<tr key={order.id}>
|
||||
<td style={{ fontWeight: '600' }}>#{order.id}</td>
|
||||
<td>₹{order.amount}</td>
|
||||
<td><StatusBadge status={order.payment_status} /></td>
|
||||
<td><StatusBadge status={order.delivery_status} /></td>
|
||||
<td>
|
||||
<div style={{ display: 'flex', gap: '6px' }}>
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
style={{ fontSize: '11px', padding: '4px 8px' }}
|
||||
onClick={() => handleCheckPaymentStatus(order.id, order.merchant_transaction_id)}
|
||||
>
|
||||
Check PhonePe
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
style={{ fontSize: '11px', padding: '4px 8px' }}
|
||||
onClick={() => handleRefund(order.id, order.merchant_transaction_id, order.amount)}
|
||||
>
|
||||
Refund
|
||||
</button>
|
||||
{order.delivery_status === 'delivered' && (
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
style={{ fontSize: '11px', padding: '4px 8px' }}
|
||||
onClick={() => handleReturn(order.id)}
|
||||
>
|
||||
Return QC
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<p style={{ color: 'var(--text-secondary)', fontSize: '14px' }}>No order history logs found for this user.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,22 +1,42 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import StatusBadge from '../../components/StatusBadge';
|
||||
import { API_BASE_URL } from '../../config';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
|
||||
const CustomerList = () => {
|
||||
const { token } = useAuth();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [customers, setCustomers] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const [customers] = useState([
|
||||
{ id: 1001, name: 'Amit Kumar', email: 'amit@gmail.com', phone: '+91 99887 76655', status: 'active', registered: '2026-03-04' },
|
||||
{ id: 1002, name: 'Priya Sharma', email: 'priya.s@yahoo.com', phone: '+91 88776 65544', status: 'active', registered: '2026-06-15' },
|
||||
{ id: 1003, name: 'Rohan Verma', email: 'rohan.v@outlook.com', phone: '+91 77665 54433', status: 'suspended', registered: '2026-01-20' },
|
||||
{ id: 1004, name: 'Sneha Patel', email: 'sneha@p.co.in', phone: '+91 66554 43322', status: 'banned', registered: '2026-07-02' },
|
||||
]);
|
||||
useEffect(() => {
|
||||
const fetchCustomers = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// Include search query parameter if any
|
||||
const url = searchTerm
|
||||
? `${API_BASE_URL}/customers/?search=${encodeURIComponent(searchTerm)}`
|
||||
: `${API_BASE_URL}/customers/`;
|
||||
|
||||
const filteredCustomers = customers.filter(cust =>
|
||||
cust.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
cust.email.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
cust.phone.includes(searchTerm)
|
||||
);
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to fetch customers');
|
||||
const data = await res.json();
|
||||
setCustomers(data);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchCustomers();
|
||||
}, [searchTerm, token]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -27,44 +47,48 @@ const CustomerList = () => {
|
|||
<input
|
||||
type="text"
|
||||
className="form-input"
|
||||
placeholder="Search customers by name, email, or phone..."
|
||||
placeholder="Search customers by name or email..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="table-container">
|
||||
<table className="custom-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Customer ID</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
<th>Status</th>
|
||||
<th>Registered</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredCustomers.map(cust => (
|
||||
<tr key={cust.id}>
|
||||
<td>#{cust.id}</td>
|
||||
<td style={{ fontWeight: '600' }}>{cust.name}</td>
|
||||
<td>{cust.email}</td>
|
||||
<td>{cust.phone}</td>
|
||||
<td><StatusBadge status={cust.status} /></td>
|
||||
<td>{cust.registered}</td>
|
||||
<td>
|
||||
<Link to={`/customers/${cust.id}`} className="btn btn-secondary" style={{ fontSize: '12px', padding: '6px 12px' }}>
|
||||
👁️ View Detail
|
||||
</Link>
|
||||
</td>
|
||||
{loading ? (
|
||||
<div style={{ padding: '40px', textAlign: 'center' }}>Loading customer profiles...</div>
|
||||
) : error ? (
|
||||
<div style={{ padding: '20px', color: 'var(--color-danger)', textAlign: 'center' }}>Error: {error}</div>
|
||||
) : (
|
||||
<div className="table-container">
|
||||
<table className="custom-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Customer ID</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</thead>
|
||||
<tbody>
|
||||
{customers.map(cust => (
|
||||
<tr key={cust.id}>
|
||||
<td>#{cust.id}</td>
|
||||
<td style={{ fontWeight: '600' }}>{cust.name}</td>
|
||||
<td>{cust.email}</td>
|
||||
<td>{cust.phone}</td>
|
||||
<td><StatusBadge status={cust.status} /></td>
|
||||
<td>
|
||||
<Link to={`/customers/${cust.id}`} className="btn btn-secondary" style={{ fontSize: '12px', padding: '6px 12px' }}>
|
||||
👁️ View Detail
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,42 +1,80 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import StatusBadge from '../../components/StatusBadge';
|
||||
import DocumentViewer from '../../components/DocumentViewer';
|
||||
import { API_BASE_URL } from '../../config';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
|
||||
const SellerDetail = () => {
|
||||
const { id } = useParams();
|
||||
const { token } = useAuth();
|
||||
const [activeTab, setActiveTab] = useState('profile');
|
||||
const [seller, setSeller] = useState({
|
||||
id: id,
|
||||
name: 'ElectroHub Retailers',
|
||||
email: 'sales@electrohub.in',
|
||||
phone: '+91 98765 43210',
|
||||
status: 'pending_approval',
|
||||
joined: '2026-08-14',
|
||||
address: '404, Tech Park, Block C, Bangalore, India',
|
||||
gstin: '29AAAAA1111A1Z1',
|
||||
pan: 'ABCDE1234F',
|
||||
walletBalance: '₹0.00',
|
||||
documents: [
|
||||
{ type: 'Aadhar Card', filename: 'aadhar_back_front.pdf' },
|
||||
{ type: 'PAN Card', filename: 'pan_card_copy.jpg' },
|
||||
{ type: 'GSTIN Certificate', filename: 'gst_cert_2026.pdf' }
|
||||
],
|
||||
products: [
|
||||
{ id: 101, name: 'Ultra Wireless Headset 5.0', price: '₹2,499', stock: 45, status: 'pending_approval' },
|
||||
{ id: 102, name: 'ElectroHub Mechanical Keyboard', price: '₹4,999', stock: 12, status: 'approved' }
|
||||
]
|
||||
});
|
||||
const [seller, setSeller] = useState(null);
|
||||
const [documents, setDocuments] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const handleStatusChange = (newStatus) => {
|
||||
setSeller({ ...seller, status: newStatus });
|
||||
alert(`Seller status successfully updated to ${newStatus}`);
|
||||
const fetchSellerData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// Fetch profile
|
||||
const profileRes = await fetch(`${API_BASE_URL}/sellers/${id}/`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
if (!profileRes.ok) throw new Error('Failed to load seller profile');
|
||||
const profileData = await profileRes.json();
|
||||
|
||||
// Fetch documents
|
||||
try {
|
||||
const docRes = await fetch(`${API_BASE_URL}/sellers/${id}/documents/`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
if (docRes.ok) {
|
||||
const docData = await docRes.json();
|
||||
setDocuments(docData);
|
||||
}
|
||||
} catch (docErr) {
|
||||
console.warn("Failed to load documents", docErr);
|
||||
}
|
||||
|
||||
setSeller(profileData);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchSellerData();
|
||||
}, [id, token]);
|
||||
|
||||
const handleAction = async (actionPath, payload = {}) => {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/sellers/${id}/${actionPath}/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (!res.ok) throw new Error(`Operation failed`);
|
||||
alert(`Action successfully performed!`);
|
||||
fetchSellerData(); // reload status
|
||||
} catch (err) {
|
||||
alert(`Error: ${err.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetPassword = () => {
|
||||
alert('Password reset link triggered and sent via WhatsApp Cloud API successfully.');
|
||||
handleAction('reset-password');
|
||||
};
|
||||
|
||||
if (loading) return <div style={{ padding: '40px', textAlign: 'center' }}>Loading seller details...</div>;
|
||||
if (error) return <div style={{ padding: '20px', color: 'var(--color-danger)', textAlign: 'center' }}>Error: {error}</div>;
|
||||
if (!seller) return <div style={{ padding: '20px', textAlign: 'center' }}>Seller profile not found.</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
|
|
@ -46,34 +84,40 @@ const SellerDetail = () => {
|
|||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
|
||||
<div>
|
||||
<h1 className="page-title" style={{ margin: 0 }}>{seller.name}</h1>
|
||||
<p style={{ color: 'var(--text-secondary)' }}>Seller ID: {seller.id} • Joined: {seller.joined}</p>
|
||||
<p style={{ color: 'var(--text-secondary)' }}>Seller ID: {seller.id} • Joined: {seller.joined_at ? new Date(seller.joined_at).toLocaleDateString() : 'N/A'}</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '10px' }}>
|
||||
{seller.status === 'pending_approval' && (
|
||||
<>
|
||||
<button className="btn btn-primary" onClick={() => handleStatusChange('approved')}>Approve Seller</button>
|
||||
<button className="btn btn-danger" onClick={() => handleStatusChange('rejected')}>Reject Seller</button>
|
||||
<button className="btn btn-primary" onClick={() => handleAction('approve')}>Approve Seller</button>
|
||||
<button className="btn btn-danger" onClick={() => {
|
||||
const reason = prompt("Enter rejection reason:");
|
||||
if (reason) handleAction('reject', { reason });
|
||||
}}>Reject Seller</button>
|
||||
</>
|
||||
)}
|
||||
{seller.status === 'approved' && (
|
||||
<button className="btn btn-danger" onClick={() => handleStatusChange('suspended')}>Suspend Seller</button>
|
||||
<button className="btn btn-danger" onClick={() => {
|
||||
const reason = prompt("Enter suspension reason:");
|
||||
if (reason) handleAction('suspend', { reason });
|
||||
}}>Suspend Seller</button>
|
||||
)}
|
||||
{seller.status === 'suspended' && (
|
||||
<>
|
||||
<button className="btn btn-primary" onClick={() => handleStatusChange('approved')}>Reinstate Seller</button>
|
||||
<button className="btn btn-danger" onClick={() => handleStatusChange('banned')}>Ban Seller</button>
|
||||
<button className="btn btn-primary" onClick={() => handleAction('reinstate')}>Reinstate Seller</button>
|
||||
<button className="btn btn-danger" onClick={() => {
|
||||
const reason = prompt("Enter reason to BAN seller:");
|
||||
if (reason) handleAction('suspend', { reason }); // bans map to suspend/restrict status
|
||||
}}>Ban Seller</button>
|
||||
</>
|
||||
)}
|
||||
{seller.status === 'banned' && (
|
||||
<button className="btn btn-primary" onClick={() => handleStatusChange('approved')}>Unban & Reinstate</button>
|
||||
)}
|
||||
<button className="btn btn-secondary" onClick={handleResetPassword}>Reset Password</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div style={{ display: 'flex', gap: '8px', borderBottom: '1px solid var(--glass-border)', marginBottom: '24px' }}>
|
||||
{['profile', 'documents', 'products', 'wallet'].map((tab) => (
|
||||
{['profile', 'documents'].map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
className="btn"
|
||||
|
|
@ -134,56 +178,7 @@ const SellerDetail = () => {
|
|||
)}
|
||||
|
||||
{activeTab === 'documents' && (
|
||||
<DocumentViewer documents={seller.documents} sellerId={seller.id} />
|
||||
)}
|
||||
|
||||
{activeTab === 'products' && (
|
||||
<div className="glass-card">
|
||||
<h3 className="section-title">Seller Products</h3>
|
||||
<div className="table-container">
|
||||
<table className="custom-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Product Name</th>
|
||||
<th>Price</th>
|
||||
<th>Stock</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{seller.products.map((prod) => (
|
||||
<tr key={prod.id}>
|
||||
<td style={{ fontWeight: '600' }}>{prod.name}</td>
|
||||
<td>{prod.price}</td>
|
||||
<td>{prod.stock} items</td>
|
||||
<td><StatusBadge status={prod.status} /></td>
|
||||
<td>
|
||||
<Link to={`/products`} className="btn btn-secondary" style={{ fontSize: '12px', padding: '6px 12px' }}>
|
||||
Manage
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'wallet' && (
|
||||
<div className="glass-card">
|
||||
<h3 className="section-title">Wallet Balance & Payouts</h3>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
|
||||
<div>
|
||||
<div style={{ color: 'var(--text-secondary)', fontSize: '14px' }}>Available Balance</div>
|
||||
<div style={{ fontSize: '36px', fontWeight: '700', color: '#fff' }}>{seller.walletBalance}</div>
|
||||
</div>
|
||||
<button className="btn btn-primary">Process Manual NEFT</button>
|
||||
</div>
|
||||
<h4 style={{ marginBottom: '12px', fontSize: '16px' }}>Payout Transactions</h4>
|
||||
<p style={{ color: 'var(--text-secondary)', fontSize: '14px' }}>No withdrawal payouts processed yet.</p>
|
||||
</div>
|
||||
<DocumentViewer documents={documents} sellerId={seller.id} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,24 +1,47 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import StatusBadge from '../../components/StatusBadge';
|
||||
import { API_BASE_URL } from '../../config';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
|
||||
const SellerList = () => {
|
||||
const { token } = useAuth();
|
||||
const [filter, setFilter] = useState('all');
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [sellers, setSellers] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const [sellers] = useState([
|
||||
{ id: 1, name: 'Siva Garments', email: 'siva@garments.com', status: 'approved', joined: '2026-05-12', revenue: '₹3,45,000' },
|
||||
{ id: 2, name: 'ElectroHub Retailers', email: 'sales@electrohub.in', status: 'pending_approval', joined: '2026-08-14', revenue: '₹0' },
|
||||
{ id: 3, name: 'A1 Spices Ltd', email: 'info@a1spices.com', status: 'suspended', joined: '2026-02-10', revenue: '₹1,20,000' },
|
||||
{ id: 4, name: 'Apex Footwear', email: 'apex@footwear.com', status: 'approved', joined: '2026-06-01', revenue: '₹5,10,000' },
|
||||
{ id: 5, name: 'Trendy Decors', email: 'decor@trendy.com', status: 'banned', joined: '2026-04-18', revenue: '₹85,000' },
|
||||
]);
|
||||
useEffect(() => {
|
||||
const fetchSellers = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const url = filter === 'all'
|
||||
? `${API_BASE_URL}/sellers/`
|
||||
: `${API_BASE_URL}/sellers/?status=${filter}`;
|
||||
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to fetch sellers');
|
||||
const data = await res.json();
|
||||
setSellers(data);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSellers();
|
||||
}, [filter, token]);
|
||||
|
||||
const filteredSellers = sellers.filter(seller => {
|
||||
const matchesStatus = filter === 'all' || seller.status === filter;
|
||||
const matchesSearch = seller.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
seller.email.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
return matchesStatus && matchesSearch;
|
||||
return matchesSearch;
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
@ -51,38 +74,42 @@ const SellerList = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="table-container">
|
||||
<table className="custom-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Seller Name</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Joined Date</th>
|
||||
<th>Total Sales</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredSellers.map((seller) => (
|
||||
<tr key={seller.id}>
|
||||
<td style={{ fontWeight: '600' }}>{seller.name}</td>
|
||||
<td>{seller.email}</td>
|
||||
<td>
|
||||
<StatusBadge status={seller.status} />
|
||||
</td>
|
||||
<td>{seller.joined}</td>
|
||||
<td>{seller.revenue}</td>
|
||||
<td>
|
||||
<Link to={`/sellers/${seller.id}`} className="btn btn-secondary" style={{ fontSize: '12px', padding: '6px 12px' }}>
|
||||
👁️ View Details
|
||||
</Link>
|
||||
</td>
|
||||
{loading ? (
|
||||
<div style={{ padding: '40px', textPlaying: 'center' }}>Loading seller profiles...</div>
|
||||
) : error ? (
|
||||
<div style={{ padding: '20px', color: 'var(--color-danger)', textAlign: 'center' }}>Error: {error}</div>
|
||||
) : (
|
||||
<div className="table-container">
|
||||
<table className="custom-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Seller Name</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Joined Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredSellers.map((seller) => (
|
||||
<tr key={seller.id}>
|
||||
<td style={{ fontWeight: '600' }}>{seller.name}</td>
|
||||
<td>{seller.email}</td>
|
||||
<td>
|
||||
<StatusBadge status={seller.status} />
|
||||
</td>
|
||||
<td>{seller.joined_at ? new Date(seller.joined_at).toLocaleDateString() : 'N/A'}</td>
|
||||
<td>
|
||||
<Link to={`/sellers/${seller.id}`} className="btn btn-secondary" style={{ fontSize: '12px', padding: '6px 12px' }}>
|
||||
👁️ View Details
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue