Remove mock data and replace with real-time fetch requests for sellers and customers
All checks were successful
Beta CI/CD Pipeline / build-and-test (push) Successful in 14s
Beta CI/CD Pipeline / deploy-beta (push) Successful in 6s

This commit is contained in:
vickytechkey 2026-08-15 19:54:23 +05:30
parent 4d9d9e0246
commit 0488eb9feb
4 changed files with 328 additions and 245 deletions

View file

@ -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>

View file

@ -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>
);

View file

@ -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>
);

View file

@ -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>
);