Change filter sort to client side for vendors/cats
This commit is contained in:
@@ -1,66 +1,10 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Get categories with pagination, filtering, and sorting
|
// Get all categories
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
const pool = req.app.locals.pool;
|
const pool = req.app.locals.pool;
|
||||||
try {
|
try {
|
||||||
const page = parseInt(req.query.page) || 1;
|
|
||||||
const limit = parseInt(req.query.limit) || 50;
|
|
||||||
const offset = (page - 1) * limit;
|
|
||||||
const search = req.query.search || '';
|
|
||||||
const parent = req.query.parent || 'all';
|
|
||||||
const performance = req.query.performance || 'all';
|
|
||||||
const sortColumn = req.query.sortColumn || 'name';
|
|
||||||
const sortDirection = req.query.sortDirection || 'asc';
|
|
||||||
|
|
||||||
// Build the WHERE clause based on filters
|
|
||||||
const whereConditions = [];
|
|
||||||
const params = [];
|
|
||||||
|
|
||||||
if (search) {
|
|
||||||
whereConditions.push('(LOWER(c.name) LIKE LOWER(?) OR LOWER(c.description) LIKE LOWER(?))');
|
|
||||||
params.push(`%${search}%`, `%${search}%`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent !== 'all') {
|
|
||||||
if (parent === 'none') {
|
|
||||||
whereConditions.push('c.parent_category IS NULL');
|
|
||||||
} else {
|
|
||||||
whereConditions.push('c.parent_category = ?');
|
|
||||||
params.push(parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (performance !== 'all') {
|
|
||||||
switch (performance) {
|
|
||||||
case 'high_growth':
|
|
||||||
whereConditions.push('cm.growth_rate >= 20');
|
|
||||||
break;
|
|
||||||
case 'growing':
|
|
||||||
whereConditions.push('cm.growth_rate >= 5 AND cm.growth_rate < 20');
|
|
||||||
break;
|
|
||||||
case 'stable':
|
|
||||||
whereConditions.push('cm.growth_rate >= -5 AND cm.growth_rate < 5');
|
|
||||||
break;
|
|
||||||
case 'declining':
|
|
||||||
whereConditions.push('cm.growth_rate < -5');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const whereClause = whereConditions.length > 0
|
|
||||||
? 'WHERE ' + whereConditions.join(' AND ')
|
|
||||||
: '';
|
|
||||||
|
|
||||||
// Get total count for pagination
|
|
||||||
const [countResult] = await pool.query(`
|
|
||||||
SELECT COUNT(DISTINCT c.id) as total
|
|
||||||
FROM categories c
|
|
||||||
LEFT JOIN category_metrics cm ON c.id = cm.category_id
|
|
||||||
${whereClause}
|
|
||||||
`, params);
|
|
||||||
|
|
||||||
// Get parent categories for filter dropdown
|
// Get parent categories for filter dropdown
|
||||||
const [parentCategories] = await pool.query(`
|
const [parentCategories] = await pool.query(`
|
||||||
SELECT DISTINCT parent_category
|
SELECT DISTINCT parent_category
|
||||||
@@ -69,7 +13,7 @@ router.get('/', async (req, res) => {
|
|||||||
ORDER BY parent_category
|
ORDER BY parent_category
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Get categories with metrics
|
// Get all categories with metrics
|
||||||
const [categories] = await pool.query(`
|
const [categories] = await pool.query(`
|
||||||
SELECT
|
SELECT
|
||||||
c.id as category_id,
|
c.id as category_id,
|
||||||
@@ -84,10 +28,8 @@ router.get('/', async (req, res) => {
|
|||||||
cm.status
|
cm.status
|
||||||
FROM categories c
|
FROM categories c
|
||||||
LEFT JOIN category_metrics cm ON c.id = cm.category_id
|
LEFT JOIN category_metrics cm ON c.id = cm.category_id
|
||||||
${whereClause}
|
ORDER BY c.name ASC
|
||||||
ORDER BY ${sortColumn} ${sortDirection}
|
`);
|
||||||
LIMIT ? OFFSET ?
|
|
||||||
`, [...params, limit, offset]);
|
|
||||||
|
|
||||||
// Get overall stats
|
// Get overall stats
|
||||||
const [stats] = await pool.query(`
|
const [stats] = await pool.query(`
|
||||||
@@ -116,11 +58,6 @@ router.get('/', async (req, res) => {
|
|||||||
totalValue: parseFloat(stats[0].totalValue || 0),
|
totalValue: parseFloat(stats[0].totalValue || 0),
|
||||||
avgMargin: parseFloat(stats[0].avgMargin || 0),
|
avgMargin: parseFloat(stats[0].avgMargin || 0),
|
||||||
avgGrowth: parseFloat(stats[0].avgGrowth || 0)
|
avgGrowth: parseFloat(stats[0].avgGrowth || 0)
|
||||||
},
|
|
||||||
pagination: {
|
|
||||||
total: countResult[0].total,
|
|
||||||
pages: Math.ceil(countResult[0].total / limit),
|
|
||||||
current: page,
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -5,63 +5,7 @@ const router = express.Router();
|
|||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
const pool = req.app.locals.pool;
|
const pool = req.app.locals.pool;
|
||||||
try {
|
try {
|
||||||
const page = parseInt(req.query.page) || 1;
|
// Get all vendors with metrics
|
||||||
const limit = parseInt(req.query.limit) || 50;
|
|
||||||
const offset = (page - 1) * limit;
|
|
||||||
const search = req.query.search || '';
|
|
||||||
const status = req.query.status || 'all';
|
|
||||||
const performance = req.query.performance || 'all';
|
|
||||||
const sortColumn = req.query.sortColumn || 'name';
|
|
||||||
const sortDirection = req.query.sortDirection || 'asc';
|
|
||||||
|
|
||||||
// Build the WHERE clause based on filters
|
|
||||||
const whereConditions = ['p.vendor IS NOT NULL AND p.vendor != \'\''];
|
|
||||||
const params = [];
|
|
||||||
|
|
||||||
if (search) {
|
|
||||||
whereConditions.push('LOWER(p.vendor) LIKE LOWER(?)');
|
|
||||||
params.push(`%${search}%`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status !== 'all') {
|
|
||||||
whereConditions.push(`
|
|
||||||
CASE
|
|
||||||
WHEN COALESCE(vm.total_orders, 0) > 0 AND COALESCE(vm.order_fill_rate, 0) >= 75 THEN 'active'
|
|
||||||
WHEN COALESCE(vm.total_orders, 0) > 0 THEN 'inactive'
|
|
||||||
ELSE 'pending'
|
|
||||||
END = ?
|
|
||||||
`);
|
|
||||||
params.push(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (performance !== 'all') {
|
|
||||||
switch (performance) {
|
|
||||||
case 'excellent':
|
|
||||||
whereConditions.push('COALESCE(vm.order_fill_rate, 0) >= 95');
|
|
||||||
break;
|
|
||||||
case 'good':
|
|
||||||
whereConditions.push('COALESCE(vm.order_fill_rate, 0) >= 85 AND COALESCE(vm.order_fill_rate, 0) < 95');
|
|
||||||
break;
|
|
||||||
case 'fair':
|
|
||||||
whereConditions.push('COALESCE(vm.order_fill_rate, 0) >= 75 AND COALESCE(vm.order_fill_rate, 0) < 85');
|
|
||||||
break;
|
|
||||||
case 'poor':
|
|
||||||
whereConditions.push('COALESCE(vm.order_fill_rate, 0) < 75');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const whereClause = 'WHERE ' + whereConditions.join(' AND ');
|
|
||||||
|
|
||||||
// Get total count for pagination
|
|
||||||
const [countResult] = await pool.query(`
|
|
||||||
SELECT COUNT(DISTINCT p.vendor) as total
|
|
||||||
FROM products p
|
|
||||||
LEFT JOIN vendor_metrics vm ON p.vendor = vm.vendor
|
|
||||||
${whereClause}
|
|
||||||
`, params);
|
|
||||||
|
|
||||||
// Get vendors with metrics
|
|
||||||
const [vendors] = await pool.query(`
|
const [vendors] = await pool.query(`
|
||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
p.vendor as name,
|
p.vendor as name,
|
||||||
@@ -77,13 +21,10 @@ router.get('/', async (req, res) => {
|
|||||||
END as status
|
END as status
|
||||||
FROM products p
|
FROM products p
|
||||||
LEFT JOIN vendor_metrics vm ON p.vendor = vm.vendor
|
LEFT JOIN vendor_metrics vm ON p.vendor = vm.vendor
|
||||||
${whereClause}
|
WHERE p.vendor IS NOT NULL AND p.vendor != ''
|
||||||
AND p.vendor IS NOT NULL AND p.vendor != ''
|
`);
|
||||||
ORDER BY ${sortColumn} ${sortDirection}
|
|
||||||
LIMIT ? OFFSET ?
|
|
||||||
`, [...params, limit, offset]);
|
|
||||||
|
|
||||||
// Get cost metrics for these vendors
|
// Get cost metrics for all vendors
|
||||||
const vendorNames = vendors.map(v => v.name);
|
const vendorNames = vendors.map(v => v.name);
|
||||||
const [costMetrics] = await pool.query(`
|
const [costMetrics] = await pool.query(`
|
||||||
SELECT
|
SELECT
|
||||||
@@ -156,12 +97,6 @@ router.get('/', async (req, res) => {
|
|||||||
avgOnTimeDelivery: parseFloat(stats[0].avgOnTimeDelivery || 0),
|
avgOnTimeDelivery: parseFloat(stats[0].avgOnTimeDelivery || 0),
|
||||||
avgUnitCost: parseFloat(overallCostMetrics[0].avg_unit_cost || 0),
|
avgUnitCost: parseFloat(overallCostMetrics[0].avg_unit_cost || 0),
|
||||||
totalSpend: parseFloat(overallCostMetrics[0].total_spend || 0)
|
totalSpend: parseFloat(overallCostMetrics[0].total_spend || 0)
|
||||||
},
|
|
||||||
pagination: {
|
|
||||||
total: parseInt(countResult[0].total || 0),
|
|
||||||
currentPage: page,
|
|
||||||
pages: Math.ceil(parseInt(countResult[0].total || 0) / limit),
|
|
||||||
limit
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ export function Categories() {
|
|||||||
const { data, isLoading } = useQuery({
|
const { data, isLoading } = useQuery({
|
||||||
queryKey: ["categories"],
|
queryKey: ["categories"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/categories`);
|
const response = await fetch(`${config.apiUrl}/categories`, {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
if (!response.ok) throw new Error("Failed to fetch categories");
|
if (!response.ok) throw new Error("Failed to fetch categories");
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
@@ -109,9 +111,11 @@ export function Categories() {
|
|||||||
}, [data?.categories, filters, sortColumn, sortDirection]);
|
}, [data?.categories, filters, sortColumn, sortDirection]);
|
||||||
|
|
||||||
// Calculate pagination
|
// Calculate pagination
|
||||||
|
const totalPages = Math.ceil(filteredData.length / 50);
|
||||||
const paginatedData = useMemo(() => {
|
const paginatedData = useMemo(() => {
|
||||||
const startIndex = (page - 1) * 50;
|
const start = (page - 1) * 50;
|
||||||
return filteredData.slice(startIndex, startIndex + 50);
|
const end = start + 50;
|
||||||
|
return filteredData.slice(start, end);
|
||||||
}, [filteredData, page]);
|
}, [filteredData, page]);
|
||||||
|
|
||||||
// Calculate stats from filtered data
|
// Calculate stats from filtered data
|
||||||
@@ -327,7 +331,7 @@ export function Categories() {
|
|||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{filteredData.length > 0 && (
|
{totalPages > 1 && (
|
||||||
<motion.div
|
<motion.div
|
||||||
layout="position"
|
layout="position"
|
||||||
transition={{ duration: 0.15 }}
|
transition={{ duration: 0.15 }}
|
||||||
@@ -345,7 +349,7 @@ export function Categories() {
|
|||||||
aria-disabled={page === 1}
|
aria-disabled={page === 1}
|
||||||
/>
|
/>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
{Array.from({ length: Math.ceil(filteredData.length / 50) }, (_, i) => (
|
{Array.from({ length: totalPages }, (_, i) => (
|
||||||
<PaginationItem key={i + 1}>
|
<PaginationItem key={i + 1}>
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
href="#"
|
href="#"
|
||||||
@@ -364,9 +368,9 @@ export function Categories() {
|
|||||||
href="#"
|
href="#"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (page < Math.ceil(filteredData.length / 50)) setPage(p => p + 1);
|
if (page < totalPages) setPage(p => p + 1);
|
||||||
}}
|
}}
|
||||||
aria-disabled={page >= Math.ceil(filteredData.length / 50)}
|
aria-disabled={page >= totalPages}
|
||||||
/>
|
/>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
</PaginationContent>
|
</PaginationContent>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@
|
|||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from "@/components/ui/pagination";
|
import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from "@/components/ui/pagination";
|
||||||
import { motion } from "motion/react";
|
import { motion } from "framer-motion";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
|
|
||||||
interface Vendor {
|
interface Vendor {
|
||||||
@@ -28,6 +28,8 @@ interface VendorFilters {
|
|||||||
performance: string;
|
performance: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ITEMS_PER_PAGE = 50;
|
||||||
|
|
||||||
export function Vendors() {
|
export function Vendors() {
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [sortColumn, setSortColumn] = useState<keyof Vendor>("name");
|
const [sortColumn, setSortColumn] = useState<keyof Vendor>("name");
|
||||||
@@ -37,24 +39,11 @@ export function Vendors() {
|
|||||||
status: "all",
|
status: "all",
|
||||||
performance: "all",
|
performance: "all",
|
||||||
});
|
});
|
||||||
const [] = useState({
|
|
||||||
column: 'name',
|
|
||||||
direction: 'asc'
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data, isLoading } = useQuery({
|
const { data, isLoading } = useQuery({
|
||||||
queryKey: ["vendors", page, filters, sortColumn, sortDirection],
|
queryKey: ["vendors"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const params = new URLSearchParams({
|
const response = await fetch(`${config.apiUrl}/vendors`, {
|
||||||
page: page.toString(),
|
|
||||||
limit: '50',
|
|
||||||
search: filters.search,
|
|
||||||
status: filters.status,
|
|
||||||
performance: filters.performance,
|
|
||||||
sortColumn,
|
|
||||||
sortDirection
|
|
||||||
});
|
|
||||||
const response = await fetch(`${config.apiUrl}/vendors?${params}`, {
|
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
if (!response.ok) throw new Error("Failed to fetch vendors");
|
if (!response.ok) throw new Error("Failed to fetch vendors");
|
||||||
@@ -115,16 +104,12 @@ export function Vendors() {
|
|||||||
}, [data?.vendors, filters, sortColumn, sortDirection]);
|
}, [data?.vendors, filters, sortColumn, sortDirection]);
|
||||||
|
|
||||||
// Calculate pagination
|
// Calculate pagination
|
||||||
|
const totalPages = Math.ceil(filteredData.length / ITEMS_PER_PAGE);
|
||||||
const paginatedData = useMemo(() => {
|
const paginatedData = useMemo(() => {
|
||||||
if (!data?.vendors) return [];
|
const start = (page - 1) * ITEMS_PER_PAGE;
|
||||||
return data.vendors;
|
const end = start + ITEMS_PER_PAGE;
|
||||||
}, [data?.vendors]);
|
return filteredData.slice(start, end);
|
||||||
|
}, [filteredData, page]);
|
||||||
// Calculate stats from filtered data
|
|
||||||
const stats = useMemo(() => {
|
|
||||||
if (!data?.stats) return null;
|
|
||||||
return data.stats;
|
|
||||||
}, [data?.stats]);
|
|
||||||
|
|
||||||
const handleSort = (column: keyof Vendor) => {
|
const handleSort = (column: keyof Vendor) => {
|
||||||
setSortDirection(prev => {
|
setSortDirection(prev => {
|
||||||
@@ -147,7 +132,7 @@ export function Vendors() {
|
|||||||
transition={{
|
transition={{
|
||||||
layout: {
|
layout: {
|
||||||
duration: 0.15,
|
duration: 0.15,
|
||||||
ease: [0.4, 0, 0.2, 1] // Material Design easing
|
ease: [0.4, 0, 0.2, 1]
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="container mx-auto py-6 space-y-4"
|
className="container mx-auto py-6 space-y-4"
|
||||||
@@ -173,9 +158,9 @@ export function Vendors() {
|
|||||||
<CardTitle className="text-sm font-medium">Total Vendors</CardTitle>
|
<CardTitle className="text-sm font-medium">Total Vendors</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">{stats?.totalVendors ?? "..."}</div>
|
<div className="text-2xl font-bold">{data?.stats?.totalVendors ?? "..."}</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{stats?.activeVendors ?? "..."} active
|
{data?.stats?.activeVendors ?? "..."} active
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -186,10 +171,10 @@ export function Vendors() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">
|
<div className="text-2xl font-bold">
|
||||||
${typeof stats?.totalSpend === 'number' ? stats.totalSpend.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 0 }) : "..."}
|
${typeof data?.stats?.totalSpend === 'number' ? data.stats.totalSpend.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 0 }) : "..."}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Avg unit cost: ${typeof stats?.avgUnitCost === 'number' ? stats.avgUnitCost.toFixed(2) : "..."}
|
Avg unit cost: ${typeof data?.stats?.avgUnitCost === 'number' ? data.stats.avgUnitCost.toFixed(2) : "..."}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -199,9 +184,9 @@ export function Vendors() {
|
|||||||
<CardTitle className="text-sm font-medium">Performance</CardTitle>
|
<CardTitle className="text-sm font-medium">Performance</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">{typeof stats?.avgFillRate === 'number' ? stats.avgFillRate.toFixed(1) : "..."}%</div>
|
<div className="text-2xl font-bold">{typeof data?.stats?.avgFillRate === 'number' ? data.stats.avgFillRate.toFixed(1) : "..."}%</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Fill rate / {typeof stats?.avgOnTimeDelivery === 'number' ? stats.avgOnTimeDelivery.toFixed(1) : "..."}% on-time
|
Fill rate / {typeof data?.stats?.avgOnTimeDelivery === 'number' ? data.stats.avgOnTimeDelivery.toFixed(1) : "..."}% on-time
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -211,7 +196,7 @@ export function Vendors() {
|
|||||||
<CardTitle className="text-sm font-medium">Lead Time</CardTitle>
|
<CardTitle className="text-sm font-medium">Lead Time</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">{typeof stats?.avgLeadTime === 'number' ? stats.avgLeadTime.toFixed(1) : "..."} days</div>
|
<div className="text-2xl font-bold">{typeof data?.stats?.avgLeadTime === 'number' ? data.stats.avgLeadTime.toFixed(1) : "..."} days</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Average delivery time
|
Average delivery time
|
||||||
</p>
|
</p>
|
||||||
@@ -312,7 +297,7 @@ export function Vendors() {
|
|||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{data?.pagination && data.pagination.total > 0 && (
|
{totalPages > 1 && (
|
||||||
<motion.div
|
<motion.div
|
||||||
layout="position"
|
layout="position"
|
||||||
transition={{ duration: 0.15 }}
|
transition={{ duration: 0.15 }}
|
||||||
@@ -330,7 +315,7 @@ export function Vendors() {
|
|||||||
aria-disabled={page === 1}
|
aria-disabled={page === 1}
|
||||||
/>
|
/>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
{Array.from({ length: data.pagination.pages }, (_, i) => (
|
{Array.from({ length: totalPages }, (_, i) => (
|
||||||
<PaginationItem key={i + 1}>
|
<PaginationItem key={i + 1}>
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
href="#"
|
href="#"
|
||||||
@@ -349,9 +334,9 @@ export function Vendors() {
|
|||||||
href="#"
|
href="#"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (page < data.pagination.pages) setPage(p => p + 1);
|
if (page < totalPages) setPage(p => p + 1);
|
||||||
}}
|
}}
|
||||||
aria-disabled={page >= data.pagination.pages}
|
aria-disabled={page >= totalPages}
|
||||||
/>
|
/>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
</PaginationContent>
|
</PaginationContent>
|
||||||
|
|||||||
Reference in New Issue
Block a user