From bae8c575bc9d1c671cb39a6cc2a8a31929194fea Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 13 Feb 2026 23:18:45 -0500 Subject: [PATCH] Overview tweaks --- inventory-server/src/routes/dashboard.js | 62 ++++++++++--------- .../components/overview/ForecastMetrics.tsx | 4 +- .../components/overview/OverstockMetrics.tsx | 14 ++--- .../product-editor/ImageManager.tsx | 2 +- 4 files changed, 43 insertions(+), 39 deletions(-) diff --git a/inventory-server/src/routes/dashboard.js b/inventory-server/src/routes/dashboard.js index df9dbb5..77cf7c4 100644 --- a/inventory-server/src/routes/dashboard.js +++ b/inventory-server/src/routes/dashboard.js @@ -21,8 +21,8 @@ router.get('/stock/metrics', async (req, res) => { COALESCE(COUNT(*), 0)::integer as total_products, COALESCE(COUNT(CASE WHEN current_stock > 0 THEN 1 END), 0)::integer as products_in_stock, COALESCE(SUM(CASE WHEN current_stock > 0 THEN current_stock END), 0)::integer as total_units, - ROUND(COALESCE(SUM(CASE WHEN current_stock > 0 THEN current_stock_cost END), 0)::numeric, 3) as total_cost, - ROUND(COALESCE(SUM(CASE WHEN current_stock > 0 THEN current_stock_retail END), 0)::numeric, 3) as total_retail + ROUND(COALESCE(SUM(CASE WHEN current_stock > 0 THEN current_stock_cost END), 0)::numeric, 2) as total_cost, + ROUND(COALESCE(SUM(CASE WHEN current_stock > 0 THEN current_stock_retail END), 0)::numeric, 2) as total_retail FROM product_metrics WHERE is_visible = true `); @@ -34,21 +34,21 @@ router.get('/stock/metrics', async (req, res) => { COALESCE(brand, 'Unbranded') as brand, COUNT(DISTINCT pid)::integer as variant_count, COALESCE(SUM(current_stock), 0)::integer as stock_units, - ROUND(COALESCE(SUM(current_stock_cost), 0)::numeric, 3) as stock_cost, - ROUND(COALESCE(SUM(current_stock_retail), 0)::numeric, 3) as stock_retail + ROUND(COALESCE(SUM(current_stock_cost), 0)::numeric, 2) as stock_cost, + ROUND(COALESCE(SUM(current_stock_retail), 0)::numeric, 2) as stock_retail FROM product_metrics WHERE current_stock > 0 AND is_visible = true GROUP BY COALESCE(brand, 'Unbranded') - HAVING ROUND(COALESCE(SUM(current_stock_cost), 0)::numeric, 3) > 0 + HAVING ROUND(COALESCE(SUM(current_stock_cost), 0)::numeric, 2) > 0 ), other_brands AS ( SELECT 'Other' as brand, SUM(variant_count)::integer as variant_count, SUM(stock_units)::integer as stock_units, - ROUND(SUM(stock_cost)::numeric, 3) as stock_cost, - ROUND(SUM(stock_retail)::numeric, 3) as stock_retail + ROUND(SUM(stock_cost)::numeric, 2) as stock_cost, + ROUND(SUM(stock_retail)::numeric, 2) as stock_retail FROM brand_totals WHERE stock_cost <= 5000 ), @@ -154,7 +154,10 @@ router.get('/purchase/metrics', async (req, res) => { vendor, SUM(on_order_qty)::integer AS units, ROUND(SUM(on_order_cost)::numeric, 2) AS cost, - ROUND(SUM(on_order_retail)::numeric, 2) AS retail + ROUND(SUM(on_order_retail)::numeric, 2) AS retail, + SUM(SUM(on_order_qty)::integer) OVER () AS total_units, + ROUND(SUM(SUM(on_order_cost)) OVER ()::numeric, 2) AS total_cost, + ROUND(SUM(SUM(on_order_retail)) OVER ()::numeric, 2) AS total_retail FROM product_metrics WHERE is_visible = true AND on_order_qty > 0 GROUP BY vendor @@ -169,9 +172,10 @@ router.get('/purchase/metrics', async (req, res) => { retail: parseFloat(v.retail) || 0 })); - const onOrderUnits = vendorOrders.reduce((sum, v) => sum + v.units, 0); - const onOrderCost = vendorOrders.reduce((sum, v) => sum + v.cost, 0); - const onOrderRetail = vendorOrders.reduce((sum, v) => sum + v.retail, 0); + const firstRow = vendorRows[0]; + const onOrderUnits = firstRow ? parseInt(firstRow.total_units) || 0 : 0; + const onOrderCost = firstRow ? parseFloat(firstRow.total_cost) || 0 : 0; + const onOrderRetail = firstRow ? parseFloat(firstRow.total_retail) || 0 : 0; // Format response to match PurchaseMetricsData interface const response = { @@ -199,8 +203,8 @@ router.get('/replenishment/metrics', async (req, res) => { SELECT COUNT(DISTINCT pm.pid)::integer as products_to_replenish, COALESCE(SUM(pm.replenishment_units), 0)::integer as total_units_needed, - ROUND(COALESCE(SUM(pm.replenishment_cost), 0)::numeric, 3) as total_cost, - ROUND(COALESCE(SUM(pm.replenishment_retail), 0)::numeric, 3) as total_retail + ROUND(COALESCE(SUM(pm.replenishment_cost), 0)::numeric, 2) as total_cost, + ROUND(COALESCE(SUM(pm.replenishment_retail), 0)::numeric, 2) as total_retail FROM product_metrics pm WHERE pm.is_visible = true AND pm.is_replenishable = true @@ -216,8 +220,8 @@ router.get('/replenishment/metrics', async (req, res) => { pm.title, pm.current_stock::integer as current_stock, pm.replenishment_units::integer as replenish_qty, - ROUND(pm.replenishment_cost::numeric, 3) as replenish_cost, - ROUND(pm.replenishment_retail::numeric, 3) as replenish_retail, + ROUND(pm.replenishment_cost::numeric, 2) as replenish_cost, + ROUND(pm.replenishment_retail::numeric, 2) as replenish_retail, pm.status, pm.planning_period_days::text as planning_period FROM product_metrics pm @@ -552,7 +556,7 @@ router.get('/forecast/metrics', async (req, res) => { return res.json({ forecastSales: Math.round(totalUnits), - forecastRevenue: totalRevenue.toFixed(2), + forecastRevenue: parseFloat(totalRevenue.toFixed(2)), confidenceLevel, dailyForecasts, dailyForecastsByPhase, @@ -611,7 +615,7 @@ router.get('/forecast/metrics', async (req, res) => { res.json({ forecastSales: Math.round(dailyUnits * days), - forecastRevenue: (dailyRevenue * days).toFixed(2), + forecastRevenue: parseFloat((dailyRevenue * days).toFixed(2)), confidenceLevel: 0, dailyForecasts, categoryForecasts: categoryRows.map(c => ({ @@ -794,10 +798,10 @@ router.get('/overstock/metrics', async (req, res) => { if (parseInt(countCheck.overstock_count) === 0) { return res.json({ overstockedProducts: 0, - total_excess_units: 0, - total_excess_cost: 0, - total_excess_retail: 0, - category_data: [] + totalExcessUnits: 0, + totalExcessCost: 0, + totalExcessRetail: 0, + categoryData: [] }); } @@ -806,8 +810,8 @@ router.get('/overstock/metrics', async (req, res) => { SELECT COUNT(DISTINCT pid)::integer as total_overstocked, SUM(overstocked_units)::integer as total_excess_units, - ROUND(SUM(overstocked_cost)::numeric, 3) as total_excess_cost, - ROUND(SUM(overstocked_retail)::numeric, 3) as total_excess_retail + ROUND(SUM(overstocked_cost)::numeric, 2) as total_excess_cost, + ROUND(SUM(overstocked_retail)::numeric, 2) as total_excess_retail FROM product_metrics WHERE status = 'Overstock' AND is_visible = true @@ -819,8 +823,8 @@ router.get('/overstock/metrics', async (req, res) => { c.name as category_name, COUNT(DISTINCT pm.pid)::integer as overstocked_products, SUM(pm.overstocked_units)::integer as total_excess_units, - ROUND(SUM(pm.overstocked_cost)::numeric, 3) as total_excess_cost, - ROUND(SUM(pm.overstocked_retail)::numeric, 3) as total_excess_retail + ROUND(SUM(pm.overstocked_cost)::numeric, 2) as total_excess_cost, + ROUND(SUM(pm.overstocked_retail)::numeric, 2) as total_excess_retail FROM categories c JOIN product_categories pc ON c.cat_id = pc.cat_id JOIN product_metrics pm ON pc.pid = pm.pid @@ -850,10 +854,10 @@ router.get('/overstock/metrics', async (req, res) => { // Format response with explicit type conversion const response = { overstockedProducts: parseInt(summaryMetrics.total_overstocked) || 0, - total_excess_units: parseInt(summaryMetrics.total_excess_units) || 0, - total_excess_cost: parseFloat(summaryMetrics.total_excess_cost) || 0, - total_excess_retail: parseFloat(summaryMetrics.total_excess_retail) || 0, - category_data: categoryData.map(cat => ({ + totalExcessUnits: parseInt(summaryMetrics.total_excess_units) || 0, + totalExcessCost: parseFloat(summaryMetrics.total_excess_cost) || 0, + totalExcessRetail: parseFloat(summaryMetrics.total_excess_retail) || 0, + categoryData: categoryData.map(cat => ({ category: cat.category_name, products: parseInt(cat.overstocked_products) || 0, units: parseInt(cat.total_excess_units) || 0, diff --git a/inventory/src/components/overview/ForecastMetrics.tsx b/inventory/src/components/overview/ForecastMetrics.tsx index fb4a9e8..5c04b05 100644 --- a/inventory/src/components/overview/ForecastMetrics.tsx +++ b/inventory/src/components/overview/ForecastMetrics.tsx @@ -44,7 +44,7 @@ interface DailyPhaseData { interface ForecastData { forecastSales: number - forecastRevenue: string + forecastRevenue: number confidenceLevel: number dailyForecasts: { date: string @@ -129,7 +129,7 @@ export function ForecastMetrics() {

Forecast Revenue

{isLoading || !data ? : ( -

{formatCurrency(Number(data.forecastRevenue) || 0)}

+

{formatCurrency(data.forecastRevenue)}

)} diff --git a/inventory/src/components/overview/OverstockMetrics.tsx b/inventory/src/components/overview/OverstockMetrics.tsx index e537957..3d4e006 100644 --- a/inventory/src/components/overview/OverstockMetrics.tsx +++ b/inventory/src/components/overview/OverstockMetrics.tsx @@ -17,10 +17,10 @@ interface PhaseBreakdown { interface OverstockMetricsData { overstockedProducts: number - total_excess_units: number - total_excess_cost: number - total_excess_retail: number - category_data: { + totalExcessUnits: number + totalExcessCost: number + totalExcessRetail: number + categoryData: { category: string products: number units: number @@ -69,7 +69,7 @@ export function OverstockMetrics() {

Overstocked Units

{isLoading || !data ? : ( -

{data.total_excess_units.toLocaleString()}

+

{data.totalExcessUnits.toLocaleString()}

)}
@@ -78,7 +78,7 @@ export function OverstockMetrics() {

Overstocked Cost

{isLoading || !data ? : ( -

{formatCurrency(data.total_excess_cost)}

+

{formatCurrency(data.totalExcessCost)}

)}
@@ -87,7 +87,7 @@ export function OverstockMetrics() {

Overstocked Retail

{isLoading || !data ? : ( -

{formatCurrency(data.total_excess_retail)}

+

{formatCurrency(data.totalExcessRetail)}

)} {data?.phaseBreakdown && data.phaseBreakdown.length > 0 && ( diff --git a/inventory/src/components/product-editor/ImageManager.tsx b/inventory/src/components/product-editor/ImageManager.tsx index 9e290af..536c856 100644 --- a/inventory/src/components/product-editor/ImageManager.tsx +++ b/inventory/src/components/product-editor/ImageManager.tsx @@ -175,7 +175,7 @@ function SortableImageCell({ src={src} alt={`Image ${image.iid}`} className={cn( - "w-full h-full object-cover pointer-events-none select-none", + "w-full h-full object-contain pointer-events-none select-none", isMain ? "rounded-lg" : "rounded-md" )} draggable={false}