diff --git a/inventory-server/src/routes/categories.js b/inventory-server/src/routes/categories.js index 7e13de5..546804a 100644 --- a/inventory-server/src/routes/categories.js +++ b/inventory-server/src/routes/categories.js @@ -1,66 +1,10 @@ const express = require('express'); const router = express.Router(); -// Get categories with pagination, filtering, and sorting +// Get all categories router.get('/', async (req, res) => { const pool = req.app.locals.pool; 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 const [parentCategories] = await pool.query(` SELECT DISTINCT parent_category @@ -69,7 +13,7 @@ router.get('/', async (req, res) => { ORDER BY parent_category `); - // Get categories with metrics + // Get all categories with metrics const [categories] = await pool.query(` SELECT c.id as category_id, @@ -84,10 +28,8 @@ router.get('/', async (req, res) => { cm.status FROM categories c LEFT JOIN category_metrics cm ON c.id = cm.category_id - ${whereClause} - ORDER BY ${sortColumn} ${sortDirection} - LIMIT ? OFFSET ? - `, [...params, limit, offset]); + ORDER BY c.name ASC + `); // Get overall stats const [stats] = await pool.query(` @@ -116,11 +58,6 @@ router.get('/', async (req, res) => { totalValue: parseFloat(stats[0].totalValue || 0), avgMargin: parseFloat(stats[0].avgMargin || 0), avgGrowth: parseFloat(stats[0].avgGrowth || 0) - }, - pagination: { - total: countResult[0].total, - pages: Math.ceil(countResult[0].total / limit), - current: page, } }); } catch (error) { diff --git a/inventory-server/src/routes/vendors.js b/inventory-server/src/routes/vendors.js index c173e62..c44c518 100644 --- a/inventory-server/src/routes/vendors.js +++ b/inventory-server/src/routes/vendors.js @@ -5,63 +5,7 @@ const router = express.Router(); router.get('/', async (req, res) => { const pool = req.app.locals.pool; 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 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 + // Get all vendors with metrics const [vendors] = await pool.query(` SELECT DISTINCT p.vendor as name, @@ -77,13 +21,10 @@ router.get('/', async (req, res) => { END as status FROM products p LEFT JOIN vendor_metrics vm ON p.vendor = vm.vendor - ${whereClause} - AND p.vendor IS NOT NULL AND p.vendor != '' - ORDER BY ${sortColumn} ${sortDirection} - LIMIT ? OFFSET ? - `, [...params, limit, offset]); + WHERE p.vendor IS NOT NULL AND p.vendor != '' + `); - // Get cost metrics for these vendors + // Get cost metrics for all vendors const vendorNames = vendors.map(v => v.name); const [costMetrics] = await pool.query(` SELECT @@ -156,12 +97,6 @@ router.get('/', async (req, res) => { avgOnTimeDelivery: parseFloat(stats[0].avgOnTimeDelivery || 0), avgUnitCost: parseFloat(overallCostMetrics[0].avg_unit_cost || 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) { diff --git a/inventory/src/pages/Categories.tsx b/inventory/src/pages/Categories.tsx index c4b9ef2..4b86eeb 100644 --- a/inventory/src/pages/Categories.tsx +++ b/inventory/src/pages/Categories.tsx @@ -45,7 +45,9 @@ export function Categories() { const { data, isLoading } = useQuery({ queryKey: ["categories"], 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"); return response.json(); }, @@ -109,9 +111,11 @@ export function Categories() { }, [data?.categories, filters, sortColumn, sortDirection]); // Calculate pagination + const totalPages = Math.ceil(filteredData.length / 50); const paginatedData = useMemo(() => { - const startIndex = (page - 1) * 50; - return filteredData.slice(startIndex, startIndex + 50); + const start = (page - 1) * 50; + const end = start + 50; + return filteredData.slice(start, end); }, [filteredData, page]); // Calculate stats from filtered data @@ -327,7 +331,7 @@ export function Categories() { - {filteredData.length > 0 && ( + {totalPages > 1 && ( - {Array.from({ length: Math.ceil(filteredData.length / 50) }, (_, i) => ( + {Array.from({ length: totalPages }, (_, i) => ( { 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} /> diff --git a/inventory/src/pages/Vendors.tsx b/inventory/src/pages/Vendors.tsx index a718694..762cc27 100644 --- a/inventory/src/pages/Vendors.tsx +++ b/inventory/src/pages/Vendors.tsx @@ -6,7 +6,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@ import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; 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"; interface Vendor { @@ -28,6 +28,8 @@ interface VendorFilters { performance: string; } +const ITEMS_PER_PAGE = 50; + export function Vendors() { const [page, setPage] = useState(1); const [sortColumn, setSortColumn] = useState("name"); @@ -37,24 +39,11 @@ export function Vendors() { status: "all", performance: "all", }); - const [] = useState({ - column: 'name', - direction: 'asc' - }); const { data, isLoading } = useQuery({ - queryKey: ["vendors", page, filters, sortColumn, sortDirection], + queryKey: ["vendors"], queryFn: async () => { - const params = new URLSearchParams({ - page: page.toString(), - limit: '50', - search: filters.search, - status: filters.status, - performance: filters.performance, - sortColumn, - sortDirection - }); - const response = await fetch(`${config.apiUrl}/vendors?${params}`, { + const response = await fetch(`${config.apiUrl}/vendors`, { credentials: 'include' }); if (!response.ok) throw new Error("Failed to fetch vendors"); @@ -115,16 +104,12 @@ export function Vendors() { }, [data?.vendors, filters, sortColumn, sortDirection]); // Calculate pagination + const totalPages = Math.ceil(filteredData.length / ITEMS_PER_PAGE); const paginatedData = useMemo(() => { - if (!data?.vendors) return []; - return data.vendors; - }, [data?.vendors]); - - // Calculate stats from filtered data - const stats = useMemo(() => { - if (!data?.stats) return null; - return data.stats; - }, [data?.stats]); + const start = (page - 1) * ITEMS_PER_PAGE; + const end = start + ITEMS_PER_PAGE; + return filteredData.slice(start, end); + }, [filteredData, page]); const handleSort = (column: keyof Vendor) => { setSortDirection(prev => { @@ -147,7 +132,7 @@ export function Vendors() { transition={{ layout: { 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" @@ -173,9 +158,9 @@ export function Vendors() { Total Vendors -
{stats?.totalVendors ?? "..."}
+
{data?.stats?.totalVendors ?? "..."}

- {stats?.activeVendors ?? "..."} active + {data?.stats?.activeVendors ?? "..."} active

@@ -186,10 +171,10 @@ export function Vendors() {
- ${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 }) : "..."}

- Avg unit cost: ${typeof stats?.avgUnitCost === 'number' ? stats.avgUnitCost.toFixed(2) : "..."} + Avg unit cost: ${typeof data?.stats?.avgUnitCost === 'number' ? data.stats.avgUnitCost.toFixed(2) : "..."}

@@ -199,9 +184,9 @@ export function Vendors() { Performance -
{typeof stats?.avgFillRate === 'number' ? stats.avgFillRate.toFixed(1) : "..."}%
+
{typeof data?.stats?.avgFillRate === 'number' ? data.stats.avgFillRate.toFixed(1) : "..."}%

- 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

@@ -211,7 +196,7 @@ export function Vendors() { Lead Time -
{typeof stats?.avgLeadTime === 'number' ? stats.avgLeadTime.toFixed(1) : "..."} days
+
{typeof data?.stats?.avgLeadTime === 'number' ? data.stats.avgLeadTime.toFixed(1) : "..."} days

Average delivery time

@@ -312,7 +297,7 @@ export function Vendors() { - {data?.pagination && data.pagination.total > 0 && ( + {totalPages > 1 && ( - {Array.from({ length: data.pagination.pages }, (_, i) => ( + {Array.from({ length: totalPages }, (_, i) => ( { 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} />