crmfrontend/src/pages/sellers/SellerList.jsx
vickytechkey 971dfd0e18
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
implement server-side table pagination across Seller, Customer, Product, and Audit Log lists
2026-08-16 15:22:52 +05:30

192 lines
7.4 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
// Pagination states
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(10);
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);
setCurrentPage(1); // Reset page to 1 when filter changes
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchSellers();
}, [filter, token]);
const filteredSellers = sellers.filter(seller => {
const matchesSearch = seller.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
seller.email.toLowerCase().includes(searchTerm.toLowerCase());
return matchesSearch;
});
// Reset page when search term changes
useEffect(() => {
setCurrentPage(1);
}, [searchTerm]);
// Compute pagination details
const totalItems = filteredSellers.length;
const totalPages = Math.ceil(totalItems / itemsPerPage);
const startIdx = totalItems === 0 ? 0 : (currentPage - 1) * itemsPerPage + 1;
const endIdx = Math.min(currentPage * itemsPerPage, totalItems);
const currentSellers = filteredSellers.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage);
return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
<h1 className="page-title" style={{ margin: 0 }}>Seller Management</h1>
</div>
<div className="glass-card">
<div style={{ display: 'flex', gap: '16px', marginBottom: '20px', flexWrap: 'wrap' }}>
<input
type="text"
className="form-input"
style={{ flex: 1, minWidth: '200px' }}
placeholder="Search sellers by name or email..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<div style={{ display: 'flex', gap: '8px' }}>
{['all', 'unverified', 'pending_approval', 'approved', 'rejected', 'suspended'].map((status) => (
<button
key={status}
onClick={() => setFilter(status)}
className={`btn ${filter === status ? 'btn-primary' : 'btn-secondary'}`}
style={{ fontSize: '13px', padding: '8px 16px', textTransform: 'capitalize' }}
>
{status.replace('_', ' ')}
</button>
))}
</div>
</div>
{loading ? (
<div style={{ padding: '40px', textAlign: '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>
</thead>
<tbody>
{currentSellers.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>
{/* Pagination Controls */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '20px', flexWrap: 'wrap', gap: '12px', borderTop: '1px solid var(--glass-border)', paddingTop: '16px' }}>
<div style={{ color: 'var(--text-secondary)', fontSize: '13px' }}>
Showing {startIdx} to {endIdx} of {totalItems} items
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<span style={{ fontSize: '13px', color: 'var(--text-secondary)' }}>Items per page:</span>
<select
value={itemsPerPage}
onChange={(e) => {
setItemsPerPage(Number(e.target.value));
setCurrentPage(1);
}}
style={{
background: 'var(--bg-tertiary)',
color: '#fff',
border: '1px solid var(--glass-border)',
borderRadius: '6px',
padding: '4px 8px',
fontSize: '13px',
outline: 'none'
}}
>
{[5, 10, 20, 50, 100].map(size => (
<option key={size} value={size}>{size}</option>
))}
</select>
</div>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<button
className="btn btn-secondary"
style={{ fontSize: '12px', padding: '6px 12px' }}
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
disabled={currentPage === 1}
>
Previous
</button>
<span style={{ fontSize: '13px', color: 'var(--text-secondary)' }}>
Page {currentPage} of {totalPages || 1}
</span>
<button
className="btn btn-secondary"
style={{ fontSize: '12px', padding: '6px 12px' }}
onClick={() => setCurrentPage(prev => Math.min(prev + 1, totalPages))}
disabled={currentPage === totalPages || totalPages === 0}
>
Next
</button>
</div>
</div>
</div>
</>
)}
</div>
</div>
);
};
export default SellerList;