feat: integrate API-backed product list and dashboard statistics with authentication headers
All checks were successful
Beta CI/CD Pipeline / build-and-test (push) Successful in 15s
Beta CI/CD Pipeline / deploy-beta (push) Successful in 7s

This commit is contained in:
vickytechkey 2026-08-16 10:24:53 +05:30
parent 0488eb9feb
commit 13bc5f9078
2 changed files with 164 additions and 76 deletions

View file

@ -1,16 +1,49 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend } from 'chart.js';
import { Line } from 'react-chartjs-2';
import { API_BASE_URL } from '../config';
import { useAuth } from '../context/AuthContext';
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
const Dashboard = () => {
const { token } = useAuth();
const [stats, setStats] = useState({
total_sellers: 0,
pending_approvals: 0,
total_revenue: 0,
active_products: 0,
refunds_issued: 0
});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchStats = async () => {
try {
const res = await fetch(`${API_BASE_URL}/dashboard/`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (res.ok) {
const data = await res.json();
setStats(data);
}
} catch (err) {
console.error("Failed to load dashboard stats:", err);
} finally {
setLoading(false);
}
};
fetchStats();
}, [token]);
const statCards = [
{ label: 'Total Sellers', value: '142', change: '+12% this month', icon: '👤' },
{ label: 'Pending Approvals', value: '8', change: 'Require review', icon: '⏳' },
{ label: 'Total Revenue', value: '₹12,45,800', change: '+18% this month', icon: '💰' },
{ label: 'Active Products', value: '1,840', change: '+5% this week', icon: '📦' },
{ label: 'Refunds Issued', value: '₹42,500', change: '8 processed', icon: '🔄' },
{ label: 'Total Sellers', value: stats.total_sellers.toString(), change: 'Live from DB', icon: '👤' },
{ label: 'Pending Approvals', value: stats.pending_approvals.toString(), change: stats.pending_approvals > 0 ? 'Action required' : 'Up to date', icon: '⏳' },
{ label: 'Total Revenue', value: `₹${stats.total_revenue.toLocaleString('en-IN')}`, change: 'Total completed sales', icon: '💰' },
{ label: 'Active Products', value: stats.active_products.toString(), change: 'Live in catalog', icon: '📦' },
{ label: 'Refunds Issued', value: `₹${stats.refunds_issued.toLocaleString('en-IN')}`, change: 'Processed refunds', icon: '🔄' },
];
const chartData = {
@ -18,7 +51,7 @@ const Dashboard = () => {
datasets: [
{
label: 'Platform Sales (₹ Lakhs)',
data: [2.5, 3.8, 3.2, 5.4, 6.1, 7.8, 9.2, 12.45],
data: [2.5, 3.8, 3.2, 5.4, 6.1, 7.8, 9.2, stats.total_revenue / 100000],
borderColor: '#8b5cf6',
backgroundColor: 'rgba(139, 92, 246, 0.2)',
tension: 0.4,
@ -49,6 +82,10 @@ const Dashboard = () => {
},
};
if (loading) {
return <div style={{ padding: '40px', textAlign: 'center' }}>Loading dashboard metrics...</div>;
}
return (
<div>
<h1 className="page-title">Dashboard Overview</h1>
@ -85,7 +122,7 @@ const Dashboard = () => {
borderRadius: 'var(--border-radius)',
fontSize: '14px'
}}>
<div style={{ fontWeight: '600', marginBottom: '4px' }}>Validate 8 New Sellers</div>
<div style={{ fontWeight: '600', marginBottom: '4px' }}>Validate New Sellers</div>
<p style={{ color: 'var(--text-secondary)', fontSize: '12px' }}>Verify uploaded KYC Aadhar and PAN records.</p>
</div>
<div style={{
@ -106,7 +143,7 @@ const Dashboard = () => {
fontSize: '14px'
}}>
<div style={{ fontWeight: '600', marginBottom: '4px' }}>Shadowfax Reverse Log</div>
<p style={{ color: 'var(--text-secondary)', fontSize: '12px' }}>2 returned items failed doorstep quality check (QC).</p>
<p style={{ color: 'var(--text-secondary)', fontSize: '12px' }}>Check failed doorstep quality check (QC) status.</p>
</div>
</div>
</div>

View file

@ -1,30 +1,75 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import StatusBadge from '../../components/StatusBadge';
import { API_BASE_URL } from '../../config';
import { useAuth } from '../../context/AuthContext';
const ProductList = () => {
const { token } = useAuth();
const [searchTerm, setSearchTerm] = useState('');
const [products, setProducts] = useState([
{ id: 101, name: 'Ultra Wireless Headset 5.0', seller: 'ElectroHub Retailers', price: '₹2,499', stock: 45, status: 'pending_approval' },
{ id: 102, name: 'ElectroHub Mechanical Keyboard', seller: 'ElectroHub Retailers', price: '₹4,999', stock: 12, status: 'approved' },
{ id: 201, name: 'Pure Organic Honey (500g)', seller: 'A1 Spices Ltd', price: '₹350', stock: 150, status: 'approved' },
{ id: 301, name: 'Air Running Shoes (Red)', seller: 'Apex Footwear', price: '₹1,899', stock: 8, status: 'on_hold' },
]);
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const handleStatusChange = (prodId, newStatus) => {
setProducts(products.map(p => p.id === prodId ? { ...p, status: newStatus } : p));
alert(`Product status changed to ${newStatus}`);
const fetchProducts = async () => {
try {
setLoading(true);
const res = await fetch(`${API_BASE_URL}/products/`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!res.ok) throw new Error('Failed to fetch products');
const data = await res.json();
setProducts(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const handleRemove = (prodId) => {
useEffect(() => {
fetchProducts();
}, [token]);
const handleStatusChange = async (prodId, action) => {
try {
const res = await fetch(`${API_BASE_URL}/products/${prodId}/${action}/`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (!res.ok) throw new Error(`Failed to perform action: ${action}`);
alert(`Product action '${action}' completed successfully`);
fetchProducts();
} catch (err) {
alert(err.message);
}
};
const handleRemove = async (prodId) => {
if (window.confirm('Are you sure you want to permanently remove this product?')) {
setProducts(products.filter(p => p.id !== prodId));
alert('Product permanently deleted');
try {
const res = await fetch(`${API_BASE_URL}/products/${prodId}/`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!res.ok) throw new Error('Failed to delete product');
alert('Product permanently deleted');
fetchProducts();
} catch (err) {
alert(err.message);
}
}
};
const filteredProducts = products.filter(p =>
p.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
p.seller.toLowerCase().includes(searchTerm.toLowerCase())
(p.supplier_name && p.supplier_name.toLowerCase().includes(searchTerm.toLowerCase()))
);
return (
@ -42,60 +87,66 @@ const ProductList = () => {
/>
</div>
<div className="table-container">
<table className="custom-table">
<thead>
<tr>
<th>ID</th>
<th>Product Title</th>
<th>Seller</th>
<th>Price</th>
<th>Stock</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{filteredProducts.map(prod => (
<tr key={prod.id}>
<td>#{prod.id}</td>
<td style={{ fontWeight: '600' }}>{prod.name}</td>
<td>{prod.seller}</td>
<td>{prod.price}</td>
<td>{prod.stock} items</td>
<td><StatusBadge status={prod.status} /></td>
<td>
<div style={{ display: 'flex', gap: '6px' }}>
{prod.status === 'pending_approval' && (
<>
<button className="btn btn-primary" style={{ fontSize: '11px', padding: '4px 8px' }} onClick={() => handleStatusChange(prod.id, 'approved')}>
Approve
</button>
<button className="btn btn-danger" style={{ fontSize: '11px', padding: '4px 8px' }} onClick={() => handleStatusChange(prod.id, 'rejected')}>
Reject
</button>
</>
)}
{prod.status === 'approved' && (
<button className="btn btn-secondary" style={{ fontSize: '11px', padding: '4px 8px' }} onClick={() => handleStatusChange(prod.id, 'on_hold')}>
Hold Item
</button>
)}
{prod.status === 'on_hold' && (
<button className="btn btn-primary" style={{ fontSize: '11px', padding: '4px 8px' }} onClick={() => handleStatusChange(prod.id, 'approved')}>
Activate
</button>
)}
<button className="btn btn-danger" style={{ fontSize: '11px', padding: '4px 8px', background: 'transparent', border: '1px solid var(--color-danger)', color: 'var(--color-danger)' }} onClick={() => handleRemove(prod.id)}>
Remove
</button>
</div>
</td>
{loading ? (
<div style={{ padding: '40px', textAlign: 'center' }}>Loading products catalog...</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>ID</th>
<th>Product Title</th>
<th>Seller</th>
<th>Price</th>
<th>Stock</th>
<th>Status</th>
<th>Actions</th>
</tr>
))}
</tbody>
</table>
</div>
</thead>
<tbody>
{filteredProducts.map(prod => (
<tr key={prod.id}>
<td>#{prod.id}</td>
<td style={{ fontWeight: '600' }}>{prod.name}</td>
<td>{prod.supplier_name || 'N/A'}</td>
<td>₹{parseFloat(prod.price).toLocaleString('en-IN')}</td>
<td>{prod.stock} items</td>
<td><StatusBadge status={prod.status} /></td>
<td>
<div style={{ display: 'flex', gap: '6px' }}>
{prod.status === 'pending_approval' && (
<>
<button className="btn btn-primary" style={{ fontSize: '11px', padding: '4px 8px' }} onClick={() => handleStatusChange(prod.id, 'approve')}>
Approve
</button>
<button className="btn btn-danger" style={{ fontSize: '11px', padding: '4px 8px' }} onClick={() => handleStatusChange(prod.id, 'hold')}>
Reject/Hold
</button>
</>
)}
{prod.status === 'approved' && (
<button className="btn btn-secondary" style={{ fontSize: '11px', padding: '4px 8px' }} onClick={() => handleStatusChange(prod.id, 'hold')}>
Hold Item
</button>
)}
{prod.status === 'on_hold' && (
<button className="btn btn-primary" style={{ fontSize: '11px', padding: '4px 8px' }} onClick={() => handleStatusChange(prod.id, 'approve')}>
Activate
</button>
)}
<button className="btn btn-danger" style={{ fontSize: '11px', padding: '4px 8px', background: 'transparent', border: '1px solid var(--color-danger)', color: 'var(--color-danger)' }} onClick={() => handleRemove(prod.id)}>
Remove
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
);