Add 'Show Non-Replenishable' filter to Products page and update API to handle non-replenishable products

This commit is contained in:
2025-01-15 13:59:38 -05:00
parent 6b9fdcb162
commit ad71fe02a5
2 changed files with 57 additions and 25 deletions

View File

@@ -13,13 +13,18 @@ router.get('/', async (req, res) => {
const page = parseInt(req.query.page) || 1; const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 50; const limit = parseInt(req.query.limit) || 50;
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
const sortColumn = req.query.sortColumn || 'title'; const sortColumn = req.query.sort || 'title';
const sortDirection = req.query.sortDirection === 'desc' ? 'DESC' : 'ASC'; const sortDirection = req.query.order === 'desc' ? 'DESC' : 'ASC';
// Build the WHERE clause // Build the WHERE clause
const conditions = ['p.visible = true']; const conditions = ['p.visible = true'];
const params = []; 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 // Handle text search filters
if (req.query.search) { if (req.query.search) {
conditions.push('(p.title LIKE ? OR p.SKU LIKE ?)'); conditions.push('(p.title LIKE ? OR p.SKU LIKE ?)');
@@ -195,20 +200,23 @@ router.get('/', async (req, res) => {
params.push(parseFloat(req.query.maxStockCoverage)); params.push(parseFloat(req.query.maxStockCoverage));
} }
// Handle status filters // Handle stock status filter
if (req.query.stockStatus && req.query.stockStatus !== 'all') { if (req.query.stockStatus && req.query.stockStatus !== 'all') {
conditions.push('pm.stock_status = ?'); conditions.push('pm.stock_status = ?');
params.push(req.query.stockStatus); params.push(req.query.stockStatus);
} }
// Combine all conditions with AND
const whereClause = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : '';
// Get total count for pagination // Get total count for pagination
const [countResult] = await pool.query( const countQuery = `
`SELECT COUNT(DISTINCT p.product_id) as total SELECT COUNT(DISTINCT p.product_id) as total
FROM products p FROM products p
LEFT JOIN product_metrics pm ON p.product_id = pm.product_id LEFT JOIN product_metrics pm ON p.product_id = pm.product_id
WHERE ${conditions.join(' AND ')}`, ${whereClause}
params `;
); const [countResult] = await pool.query(countQuery, params);
const total = countResult[0].total; const total = countResult[0].total;
// Get available filters // Get available filters
@@ -229,19 +237,19 @@ router.get('/', async (req, res) => {
p.product_id, p.product_id,
COALESCE( COALESCE(
(SELECT overstock_days FROM stock_thresholds st (SELECT overstock_days FROM stock_thresholds st
JOIN product_categories pc ON st.category_id = pc.category_id WHERE st.category_id IN (
WHERE pc.product_id = p.product_id SELECT pc.category_id
AND st.vendor = p.vendor LIMIT 1), 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 (SELECT overstock_days FROM stock_thresholds st
JOIN product_categories pc ON st.category_id = pc.category_id WHERE st.category_id IS NULL
WHERE pc.product_id = p.product_id AND (st.vendor = p.vendor OR st.vendor IS NULL)
AND st.vendor IS NULL LIMIT 1), ORDER BY st.vendor IS NULL
(SELECT overstock_days FROM stock_thresholds st LIMIT 1),
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),
90 90
) as target_days ) as target_days
FROM products p 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 product_categories pc ON p.product_id = pc.product_id
LEFT JOIN categories c ON pc.category_id = c.id LEFT JOIN categories c ON pc.category_id = c.id
LEFT JOIN product_thresholds pt ON p.product_id = pt.product_id LEFT JOIN product_thresholds pt ON p.product_id = pt.product_id
WHERE ${conditions.join(' AND ')} ${whereClause}
GROUP BY p.product_id GROUP BY p.product_id
ORDER BY ${sortColumn} ${sortDirection} ORDER BY ${sortColumn} ${sortDirection}
LIMIT ? OFFSET ? 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 // Transform the results
const products = rows.map(row => ({ const products = rows.map(row => ({

View File

@@ -27,6 +27,8 @@ import {
PaginationNext, PaginationNext,
PaginationPrevious, PaginationPrevious,
} from "@/components/ui/pagination" } from "@/components/ui/pagination"
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
// Column definition type // Column definition type
interface ColumnDef { interface ColumnDef {
@@ -104,6 +106,7 @@ export function Products() {
const [selectedProductId, setSelectedProductId] = useState<number | null>(null); const [selectedProductId, setSelectedProductId] = useState<number | null>(null);
const [activeView, setActiveView] = useState("all"); const [activeView, setActiveView] = useState("all");
const [pageSize] = useState(50); const [pageSize] = useState(50);
const [showNonReplenishable, setShowNonReplenishable] = useState(false);
// Group columns by their group property // Group columns by their group property
const columnsByGroup = AVAILABLE_COLUMNS.reduce((acc, col) => { const columnsByGroup = AVAILABLE_COLUMNS.reduce((acc, col) => {
@@ -133,6 +136,11 @@ export function Products() {
params.append('stockStatus', activeView); params.append('stockStatus', activeView);
} }
// Add showNonReplenishable param
if (showNonReplenishable) {
params.append('showNonReplenishable', 'true');
}
// Add other filters // Add other filters
Object.entries(filters).forEach(([key, value]) => { Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && value !== '') { if (value !== undefined && value !== '') {
@@ -149,7 +157,7 @@ export function Products() {
// Query for products data // Query for products data
const { data, isFetching } = useQuery({ const { data, isFetching } = useQuery({
queryKey: ['products', currentPage, pageSize, sortColumn, sortDirection, activeView, filters], queryKey: ['products', currentPage, pageSize, sortColumn, sortDirection, activeView, filters, showNonReplenishable],
queryFn: fetchProducts, queryFn: fetchProducts,
placeholderData: keepPreviousData, placeholderData: keepPreviousData,
}); });
@@ -271,6 +279,17 @@ export function Products() {
activeFilters={filters} activeFilters={filters}
/> />
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex items-center space-x-2">
<Switch
id="show-non-replenishable"
checked={showNonReplenishable}
onCheckedChange={(checked) => {
setShowNonReplenishable(checked);
setCurrentPage(1);
}}
/>
<Label htmlFor="show-non-replenishable">Show Non-Replenishable</Label>
</div>
{data?.pagination.total > 0 && ( {data?.pagination.total > 0 && (
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
{data.pagination.total.toLocaleString()} products {data.pagination.total.toLocaleString()} products