From 3f966ceac387376acea396defd50e3bfc6fab57a Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 16 Jan 2025 19:53:47 -0500 Subject: [PATCH] Fix vendor and category page loading issues --- inventory-server/src/routes/categories.js | 22 ++++++++++++++++----- inventory-server/src/routes/vendors.js | 22 ++++++++++++++++----- inventory/src/pages/Categories.tsx | 24 +++++++++++------------ inventory/src/pages/Vendors.tsx | 24 +++++++++++------------ 4 files changed, 58 insertions(+), 34 deletions(-) diff --git a/inventory-server/src/routes/categories.js b/inventory-server/src/routes/categories.js index a83d4cb..bfa3d0a 100644 --- a/inventory-server/src/routes/categories.js +++ b/inventory-server/src/routes/categories.js @@ -94,17 +94,29 @@ router.get('/', async (req, res) => { SELECT COUNT(DISTINCT c.id) as totalCategories, COUNT(DISTINCT CASE WHEN cm.status = 'active' THEN c.id END) as activeCategories, - SUM(cm.total_value) as totalValue, - AVG(cm.avg_margin) as avgMargin, - AVG(cm.growth_rate) as avgGrowth + COALESCE(SUM(cm.total_value), 0) as totalValue, + COALESCE(ROUND(AVG(NULLIF(cm.avg_margin, 0)), 1), 0) as avgMargin, + COALESCE(ROUND(AVG(NULLIF(cm.growth_rate, 0)), 1), 0) as avgGrowth FROM categories c LEFT JOIN category_metrics cm ON c.id = cm.category_id `); res.json({ - categories, + categories: categories.map(cat => ({ + ...cat, + product_count: parseInt(cat.product_count || 0), + total_value: parseFloat(cat.total_value || 0), + avg_margin: parseFloat(cat.avg_margin || 0), + turnover_rate: parseFloat(cat.turnover_rate || 0), + growth_rate: parseFloat(cat.growth_rate || 0) + })), parentCategories: parentCategories.map(p => p.parent_category), - stats: stats[0], + stats: { + ...stats[0], + 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), diff --git a/inventory-server/src/routes/vendors.js b/inventory-server/src/routes/vendors.js index 8688a77..0bacf66 100644 --- a/inventory-server/src/routes/vendors.js +++ b/inventory-server/src/routes/vendors.js @@ -84,16 +84,28 @@ router.get('/', async (req, res) => { SELECT COUNT(DISTINCT p.vendor) as totalVendors, COUNT(DISTINCT CASE WHEN vm.status = 'active' THEN p.vendor END) as activeVendors, - AVG(vm.avg_lead_time_days) as avgLeadTime, - AVG(vm.order_fill_rate) as avgFillRate, - AVG(vm.on_time_delivery_rate) as avgOnTimeDelivery + COALESCE(ROUND(AVG(NULLIF(vm.avg_lead_time_days, 0)), 1), 0) as avgLeadTime, + COALESCE(ROUND(AVG(NULLIF(vm.order_fill_rate, 0)), 1), 0) as avgFillRate, + COALESCE(ROUND(AVG(NULLIF(vm.on_time_delivery_rate, 0)), 1), 0) as avgOnTimeDelivery FROM products p LEFT JOIN vendor_metrics vm ON p.vendor = vm.vendor `); res.json({ - vendors, - stats: stats[0], + vendors: vendors.map(vendor => ({ + ...vendor, + avg_lead_time_days: parseFloat(vendor.avg_lead_time_days || 0), + on_time_delivery_rate: parseFloat(vendor.on_time_delivery_rate || 0), + order_fill_rate: parseFloat(vendor.order_fill_rate || 0), + total_orders: parseInt(vendor.total_orders || 0), + active_products: parseInt(vendor.active_products || 0) + })), + stats: { + ...stats[0], + avgLeadTime: parseFloat(stats[0].avgLeadTime || 0), + avgFillRate: parseFloat(stats[0].avgFillRate || 0), + avgOnTimeDelivery: parseFloat(stats[0].avgOnTimeDelivery || 0) + }, pagination: { total: countResult[0].total, pages: Math.ceil(countResult[0].total / limit), diff --git a/inventory/src/pages/Categories.tsx b/inventory/src/pages/Categories.tsx index c440b1d..6ecb17b 100644 --- a/inventory/src/pages/Categories.tsx +++ b/inventory/src/pages/Categories.tsx @@ -97,9 +97,9 @@ export function Categories() { Total Categories -
{data?.stats.totalCategories ?? "..."}
+
{data?.stats?.totalCategories ?? "..."}

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

@@ -109,7 +109,7 @@ export function Categories() { Total Value -
{data?.stats.totalValue ? formatCurrency(data.stats.totalValue) : "..."}
+
{data?.stats?.totalValue ? formatCurrency(data.stats.totalValue) : "..."}

Inventory value

@@ -121,7 +121,7 @@ export function Categories() { Avg Margin -
{data?.stats.avgMargin?.toFixed(1) ?? "..."}%
+
{typeof data?.stats?.avgMargin === 'number' ? data.stats.avgMargin.toFixed(1) : "..."}%

Across all categories

@@ -133,7 +133,7 @@ export function Categories() { Avg Growth -
{data?.stats.avgGrowth?.toFixed(1) ?? "..."}%
+
{typeof data?.stats?.avgGrowth === 'number' ? data.stats.avgGrowth.toFixed(1) : "..."}%

Year over year

@@ -203,21 +203,21 @@ export function Categories() { Loading categories... - ) : data?.categories.map((category: Category) => ( + ) : data?.categories?.map((category: Category) => (
{category.name}
{category.description}
{category.parent_category || "—"} - {category.product_count.toLocaleString()} - {formatCurrency(category.total_value)} - {category.avg_margin.toFixed(1)}% - {category.turnover_rate.toFixed(1)}x + {category.product_count?.toLocaleString() ?? 0} + {formatCurrency(category.total_value ?? 0)} + {typeof category.avg_margin === 'number' ? category.avg_margin.toFixed(1) : "0.0"}% + {typeof category.turnover_rate === 'number' ? category.turnover_rate.toFixed(1) : "0.0"}x
- {category.growth_rate.toFixed(1)}% - {getPerformanceBadge(category.growth_rate)} + {typeof category.growth_rate === 'number' ? category.growth_rate.toFixed(1) : "0.0"}% + {getPerformanceBadge(category.growth_rate ?? 0)}
{category.status} diff --git a/inventory/src/pages/Vendors.tsx b/inventory/src/pages/Vendors.tsx index 3167159..75e4694 100644 --- a/inventory/src/pages/Vendors.tsx +++ b/inventory/src/pages/Vendors.tsx @@ -89,9 +89,9 @@ export function Vendors() { Total Vendors -
{data?.stats.totalVendors ?? "..."}
+
{data?.stats?.totalVendors ?? "..."}

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

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

Across all vendors

@@ -113,7 +113,7 @@ export function Vendors() { Fill Rate -
{data?.stats.avgFillRate?.toFixed(1) ?? "..."}%
+
{typeof data?.stats?.avgFillRate === 'number' ? data.stats.avgFillRate.toFixed(1) : "..."}%

Average order fill rate

@@ -125,7 +125,7 @@ export function Vendors() { On-Time Delivery -
{data?.stats.avgOnTimeDelivery?.toFixed(1) ?? "..."}%
+
{typeof data?.stats?.avgOnTimeDelivery === 'number' ? data.stats.avgOnTimeDelivery.toFixed(1) : "..."}%

Average on-time rate

@@ -194,7 +194,7 @@ export function Vendors() { Loading vendors...
- ) : data?.vendors.map((vendor: Vendor) => ( + ) : data?.vendors?.map((vendor: Vendor) => ( {vendor.name} @@ -202,16 +202,16 @@ export function Vendors() {
{vendor.email}
{vendor.status} - {vendor.avg_lead_time_days.toFixed(1)} days - {vendor.on_time_delivery_rate.toFixed(1)}% + {typeof vendor.avg_lead_time_days === 'number' ? vendor.avg_lead_time_days.toFixed(1) : "0.0"} days + {typeof vendor.on_time_delivery_rate === 'number' ? vendor.on_time_delivery_rate.toFixed(1) : "0.0"}%
- {vendor.order_fill_rate.toFixed(1)}% - {getPerformanceBadge(vendor.order_fill_rate)} + {typeof vendor.order_fill_rate === 'number' ? vendor.order_fill_rate.toFixed(1) : "0.0"}% + {getPerformanceBadge(vendor.order_fill_rate ?? 0)}
- {vendor.total_orders.toLocaleString()} - {vendor.active_products.toLocaleString()} + {vendor.total_orders?.toLocaleString() ?? 0} + {vendor.active_products?.toLocaleString() ?? 0}
))} {!isLoading && !data?.vendors.length && (