From ad71fe02a541ba312245facb2122032000f1411a Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 15 Jan 2025 13:59:38 -0500 Subject: [PATCH] Add 'Show Non-Replenishable' filter to Products page and update API to handle non-replenishable products --- inventory-server/src/routes/products.js | 61 +++++++++++++++---------- inventory/src/pages/Products.tsx | 21 ++++++++- 2 files changed, 57 insertions(+), 25 deletions(-) diff --git a/inventory-server/src/routes/products.js b/inventory-server/src/routes/products.js index 13d6b5a..56768a8 100755 --- a/inventory-server/src/routes/products.js +++ b/inventory-server/src/routes/products.js @@ -13,13 +13,18 @@ router.get('/', async (req, res) => { const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 50; const offset = (page - 1) * limit; - const sortColumn = req.query.sortColumn || 'title'; - const sortDirection = req.query.sortDirection === 'desc' ? 'DESC' : 'ASC'; + const sortColumn = req.query.sort || 'title'; + const sortDirection = req.query.order === 'desc' ? 'DESC' : 'ASC'; // Build the WHERE clause const conditions = ['p.visible = true']; const params = []; + // Add default replenishable filter unless explicitly showing non-replenishable + if (req.query.showNonReplenishable !== 'true') { + conditions.push('p.replenishable = true'); + } + // Handle text search filters if (req.query.search) { conditions.push('(p.title LIKE ? OR p.SKU LIKE ?)'); @@ -195,20 +200,23 @@ router.get('/', async (req, res) => { params.push(parseFloat(req.query.maxStockCoverage)); } - // Handle status filters + // Handle stock status filter if (req.query.stockStatus && req.query.stockStatus !== 'all') { conditions.push('pm.stock_status = ?'); params.push(req.query.stockStatus); } + // Combine all conditions with AND + const whereClause = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : ''; + // Get total count for pagination - const [countResult] = await pool.query( - `SELECT COUNT(DISTINCT p.product_id) as total - FROM products p - LEFT JOIN product_metrics pm ON p.product_id = pm.product_id - WHERE ${conditions.join(' AND ')}`, - params - ); + const countQuery = ` + SELECT COUNT(DISTINCT p.product_id) as total + FROM products p + LEFT JOIN product_metrics pm ON p.product_id = pm.product_id + ${whereClause} + `; + const [countResult] = await pool.query(countQuery, params); const total = countResult[0].total; // Get available filters @@ -229,19 +237,19 @@ router.get('/', async (req, res) => { p.product_id, COALESCE( (SELECT overstock_days FROM stock_thresholds st - JOIN product_categories pc ON st.category_id = pc.category_id - WHERE pc.product_id = p.product_id - AND st.vendor = p.vendor LIMIT 1), + WHERE st.category_id IN ( + SELECT pc.category_id + FROM product_categories pc + WHERE pc.product_id = p.product_id + ) + AND (st.vendor = p.vendor OR st.vendor IS NULL) + ORDER BY st.vendor IS NULL + LIMIT 1), (SELECT overstock_days FROM stock_thresholds st - JOIN product_categories pc ON st.category_id = pc.category_id - WHERE pc.product_id = p.product_id - AND st.vendor IS NULL LIMIT 1), - (SELECT overstock_days FROM stock_thresholds st - WHERE st.category_id IS NULL - AND st.vendor = p.vendor LIMIT 1), - (SELECT overstock_days FROM stock_thresholds st - WHERE st.category_id IS NULL - AND st.vendor IS NULL LIMIT 1), + WHERE st.category_id IS NULL + AND (st.vendor = p.vendor OR st.vendor IS NULL) + ORDER BY st.vendor IS NULL + LIMIT 1), 90 ) as target_days FROM products p @@ -283,13 +291,18 @@ router.get('/', async (req, res) => { LEFT JOIN product_categories pc ON p.product_id = pc.product_id LEFT JOIN categories c ON pc.category_id = c.id LEFT JOIN product_thresholds pt ON p.product_id = pt.product_id - WHERE ${conditions.join(' AND ')} + ${whereClause} GROUP BY p.product_id ORDER BY ${sortColumn} ${sortDirection} LIMIT ? OFFSET ? `; - const [rows] = await pool.query(query, [...params, limit, offset]); + // Add pagination params to the main query params + const queryParams = [...params, limit, offset]; + console.log('Query:', query.replace(/\s+/g, ' ')); + console.log('Params:', queryParams); + + const [rows] = await pool.query(query, queryParams); // Transform the results const products = rows.map(row => ({ diff --git a/inventory/src/pages/Products.tsx b/inventory/src/pages/Products.tsx index 37fe8e8..cbc3570 100644 --- a/inventory/src/pages/Products.tsx +++ b/inventory/src/pages/Products.tsx @@ -27,6 +27,8 @@ import { PaginationNext, PaginationPrevious, } from "@/components/ui/pagination" +import { Switch } from '@/components/ui/switch'; +import { Label } from '@/components/ui/label'; // Column definition type interface ColumnDef { @@ -104,6 +106,7 @@ export function Products() { const [selectedProductId, setSelectedProductId] = useState(null); const [activeView, setActiveView] = useState("all"); const [pageSize] = useState(50); + const [showNonReplenishable, setShowNonReplenishable] = useState(false); // Group columns by their group property const columnsByGroup = AVAILABLE_COLUMNS.reduce((acc, col) => { @@ -133,6 +136,11 @@ export function Products() { params.append('stockStatus', activeView); } + // Add showNonReplenishable param + if (showNonReplenishable) { + params.append('showNonReplenishable', 'true'); + } + // Add other filters Object.entries(filters).forEach(([key, value]) => { if (value !== undefined && value !== '') { @@ -149,7 +157,7 @@ export function Products() { // Query for products data const { data, isFetching } = useQuery({ - queryKey: ['products', currentPage, pageSize, sortColumn, sortDirection, activeView, filters], + queryKey: ['products', currentPage, pageSize, sortColumn, sortDirection, activeView, filters, showNonReplenishable], queryFn: fetchProducts, placeholderData: keepPreviousData, }); @@ -271,6 +279,17 @@ export function Products() { activeFilters={filters} />
+
+ { + setShowNonReplenishable(checked); + setCurrentPage(1); + }} + /> + +
{data?.pagination.total > 0 && (
{data.pagination.total.toLocaleString()} products