From f38174ca2ab4bfa1ff48d2beec401128fccd0c35 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 17 Jan 2025 21:10:35 -0500 Subject: [PATCH] Fix and restyle stock and purchases components --- inventory-server/src/routes/dashboard.js | 86 ++++++++++++++----- .../components/dashboard/PurchaseMetrics.tsx | 26 ++++-- .../src/components/dashboard/StockMetrics.tsx | 57 ++++++------ 3 files changed, 113 insertions(+), 56 deletions(-) diff --git a/inventory-server/src/routes/dashboard.js b/inventory-server/src/routes/dashboard.js index 5bccb6e..d8e3f24 100644 --- a/inventory-server/src/routes/dashboard.js +++ b/inventory-server/src/routes/dashboard.js @@ -16,30 +16,60 @@ async function executeQuery(sql, params = []) { router.get('/stock/metrics', async (req, res) => { try { // Get stock metrics - const [stockMetrics] = await executeQuery(` + const [rows] = await executeQuery(` SELECT COALESCE(COUNT(*), 0) as total_products, COALESCE(COUNT(CASE WHEN stock_quantity > 0 THEN 1 END), 0) as products_in_stock, - COALESCE(SUM(stock_quantity), 0) as total_units, - COALESCE(SUM(stock_quantity * cost_price), 0) as total_cost, - COALESCE(SUM(stock_quantity * price), 0) as total_retail + COALESCE(SUM(CASE WHEN stock_quantity > 0 THEN stock_quantity END), 0) as total_units, + COALESCE(SUM(CASE WHEN stock_quantity > 0 THEN stock_quantity * cost_price END), 0) as total_cost, + COALESCE(SUM(CASE WHEN stock_quantity > 0 THEN stock_quantity * price END), 0) as total_retail FROM products `); + const stockMetrics = rows[0]; - // Get vendor stock values - const [vendorValues] = await executeQuery(` - SELECT - vendor, - COUNT(DISTINCT product_id) as variant_count, - COALESCE(SUM(stock_quantity), 0) as stock_units, - COALESCE(SUM(stock_quantity * cost_price), 0) as stock_cost, - COALESCE(SUM(stock_quantity * price), 0) as stock_retail - FROM products - WHERE vendor IS NOT NULL - AND stock_quantity > 0 - GROUP BY vendor - HAVING stock_cost > 0 - ORDER BY stock_cost DESC + console.log('Raw stockMetrics from database:', stockMetrics); + console.log('stockMetrics.total_products:', stockMetrics.total_products); + console.log('stockMetrics.products_in_stock:', stockMetrics.products_in_stock); + console.log('stockMetrics.total_units:', stockMetrics.total_units); + console.log('stockMetrics.total_cost:', stockMetrics.total_cost); + console.log('stockMetrics.total_retail:', stockMetrics.total_retail); + + // Get brand stock values with Other category + const [brandValues] = await executeQuery(` + WITH brand_totals AS ( + SELECT + brand, + COUNT(DISTINCT product_id) as variant_count, + COALESCE(SUM(stock_quantity), 0) as stock_units, + COALESCE(SUM(stock_quantity * cost_price), 0) as stock_cost, + COALESCE(SUM(stock_quantity * price), 0) as stock_retail + FROM products + WHERE brand IS NOT NULL + AND stock_quantity > 0 + GROUP BY brand + HAVING stock_cost > 0 + ), + other_brands AS ( + SELECT + 'Other' as brand, + SUM(variant_count) as variant_count, + SUM(stock_units) as stock_units, + SUM(stock_cost) as stock_cost, + SUM(stock_retail) as stock_retail + FROM brand_totals + WHERE stock_cost <= 5000 + ), + main_brands AS ( + SELECT * + FROM brand_totals + WHERE stock_cost > 5000 + ORDER BY stock_cost DESC + ) + SELECT * FROM main_brands + UNION ALL + SELECT * FROM other_brands + WHERE stock_cost > 0 + ORDER BY CASE WHEN brand = 'Other' THEN 1 ELSE 0 END, stock_cost DESC `); // Format the response with explicit type conversion @@ -49,8 +79,8 @@ router.get('/stock/metrics', async (req, res) => { totalStockUnits: parseInt(stockMetrics.total_units) || 0, totalStockCost: parseFloat(stockMetrics.total_cost) || 0, totalStockRetail: parseFloat(stockMetrics.total_retail) || 0, - vendorStock: vendorValues.map(v => ({ - vendor: v.vendor, + brandStock: brandValues.map(v => ({ + brand: v.brand, variants: parseInt(v.variant_count) || 0, units: parseInt(v.stock_units) || 0, cost: parseFloat(v.stock_cost) || 0, @@ -69,7 +99,7 @@ router.get('/stock/metrics', async (req, res) => { // Returns purchase order metrics by vendor router.get('/purchase/metrics', async (req, res) => { try { - const [poMetrics] = await executeQuery(` + const [rows] = await executeQuery(` SELECT COALESCE(COUNT(DISTINCT CASE WHEN po.status = 'open' THEN po.po_id END), 0) as active_pos, COALESCE(COUNT(DISTINCT CASE @@ -90,6 +120,14 @@ router.get('/purchase/metrics', async (req, res) => { FROM purchase_orders po JOIN products p ON po.product_id = p.product_id `); + const poMetrics = rows[0]; + + console.log('Raw poMetrics from database:', poMetrics); + console.log('poMetrics.active_pos:', poMetrics.active_pos); + console.log('poMetrics.overdue_pos:', poMetrics.overdue_pos); + console.log('poMetrics.total_units:', poMetrics.total_units); + console.log('poMetrics.total_cost:', poMetrics.total_cost); + console.log('poMetrics.total_retail:', poMetrics.total_retail); const [vendorOrders] = await executeQuery(` SELECT @@ -106,7 +144,7 @@ router.get('/purchase/metrics', async (req, res) => { ORDER BY order_cost DESC `); - res.json({ + const response = { activePurchaseOrders: parseInt(poMetrics.active_pos) || 0, overduePurchaseOrders: parseInt(poMetrics.overdue_pos) || 0, onOrderUnits: parseInt(poMetrics.total_units) || 0, @@ -119,7 +157,9 @@ router.get('/purchase/metrics', async (req, res) => { cost: parseFloat(v.order_cost) || 0, retail: parseFloat(v.order_retail) || 0 })) - }); + }; + + res.json(response); } catch (err) { console.error('Error fetching purchase metrics:', err); res.status(500).json({ error: 'Failed to fetch purchase metrics' }); diff --git a/inventory/src/components/dashboard/PurchaseMetrics.tsx b/inventory/src/components/dashboard/PurchaseMetrics.tsx index e6668f9..4a7ce67 100644 --- a/inventory/src/components/dashboard/PurchaseMetrics.tsx +++ b/inventory/src/components/dashboard/PurchaseMetrics.tsx @@ -83,17 +83,25 @@ const renderActiveShape = (props: any) => { export function PurchaseMetrics() { const [activeIndex, setActiveIndex] = useState(); - const { data } = useQuery({ + const { data, error, isLoading } = useQuery({ queryKey: ["purchase-metrics"], queryFn: async () => { + console.log('Fetching from:', `${config.apiUrl}/dashboard/purchase/metrics`); const response = await fetch(`${config.apiUrl}/dashboard/purchase/metrics`) if (!response.ok) { - throw new Error("Failed to fetch purchase metrics") + const text = await response.text(); + console.error('API Error:', text); + throw new Error(`Failed to fetch purchase metrics: ${response.status} ${response.statusText}`); } - return response.json() + const data = await response.json(); + console.log('API Response:', data); + return data; }, }) + if (isLoading) return
Loading...
; + if (error) return
Error loading purchase metrics
; + return ( <> @@ -108,41 +116,41 @@ export function PurchaseMetrics() {

Active Purchase Orders

-

{data?.activePurchaseOrders.toLocaleString() || 0}

+

{data?.activePurchaseOrders.toLocaleString() || 0}

Overdue Purchase Orders

-

{data?.overduePurchaseOrders.toLocaleString() || 0}

+

{data?.overduePurchaseOrders.toLocaleString() || 0}

On Order Units

-

{data?.onOrderUnits.toLocaleString() || 0}

+

{data?.onOrderUnits.toLocaleString() || 0}

On Order Cost

-

{formatCurrency(data?.onOrderCost || 0)}

+

{formatCurrency(data?.onOrderCost || 0)}

On Order Retail

-

{formatCurrency(data?.onOrderRetail || 0)}

+

{formatCurrency(data?.onOrderRetail || 0)}

-
Purchase Orders By Vendor
+
Purchase Orders By Vendor
diff --git a/inventory/src/components/dashboard/StockMetrics.tsx b/inventory/src/components/dashboard/StockMetrics.tsx index e806c8e..108a80a 100644 --- a/inventory/src/components/dashboard/StockMetrics.tsx +++ b/inventory/src/components/dashboard/StockMetrics.tsx @@ -3,7 +3,7 @@ import { CardHeader, CardTitle, CardContent } from "@/components/ui/card" import { PieChart, Pie, ResponsiveContainer, Cell, Tooltip, Sector } from "recharts" import config from "@/config" import { formatCurrency } from "@/lib/utils" -import { Package, Layers, DollarSign, ShoppingCart } from "lucide-react" // Importing icons +import { Package, Layers, DollarSign, ShoppingCart } from "lucide-react" import { useState } from "react" interface StockMetricsData { @@ -12,8 +12,8 @@ interface StockMetricsData { totalStockUnits: number totalStockCost: number totalStockRetail: number - vendorStock: { - vendor: string + brandStock: { + brand: string variants: number units: number cost: number @@ -33,10 +33,10 @@ const COLORS = [ ] const renderActiveShape = (props: any) => { - const { cx, cy, innerRadius, vendor, cost } = props; + const { cx, cy, innerRadius, brand, retail } = props; - // Split vendor name into words and create lines of max 12 chars - const words = vendor.split(' '); + // Split brand name into words and create lines of max 12 chars + const words = brand.split(' '); const lines: string[] = []; let currentLine = ''; @@ -73,7 +73,7 @@ const renderActiveShape = (props: any) => { fill="#000000" className="text-base font-medium" > - {formatCurrency(cost)} + {formatCurrency(retail)} {props.children} @@ -83,16 +83,24 @@ const renderActiveShape = (props: any) => { export function StockMetrics() { const [activeIndex, setActiveIndex] = useState(); - const { data } = useQuery({ + const { data, error, isLoading } = useQuery({ queryKey: ["stock-metrics"], queryFn: async () => { - const response = await fetch(`${config.apiUrl}/dashboard/stock/metrics`) + console.log('Fetching from:', `${config.apiUrl}/dashboard/stock/metrics`); + const response = await fetch(`${config.apiUrl}/dashboard/stock/metrics`); if (!response.ok) { - throw new Error("Failed to fetch stock metrics") + const text = await response.text(); + console.error('API Error:', text); + throw new Error(`Failed to fetch stock metrics: ${response.status} ${response.statusText}`); } - return response.json() + const data = await response.json(); + console.log('API Response:', data); + return data; }, - }) + }); + + if (isLoading) return
Loading...
; + if (error) return
Error loading stock metrics
; return ( <> @@ -108,48 +116,48 @@ export function StockMetrics() {

Products

-

{data?.totalProducts.toLocaleString() || 0}

+

{data?.totalProducts.toLocaleString() || 0}

Products In Stock

-

{data?.productsInStock.toLocaleString() || 0}

+

{data?.productsInStock.toLocaleString() || 0}

Stock Units

-

{data?.totalStockUnits.toLocaleString() || 0}

+

{data?.totalStockUnits.toLocaleString() || 0}

Stock Cost

-

{formatCurrency(data?.totalStockCost || 0)}

+

{formatCurrency(data?.totalStockCost || 0)}

Stock Retail

-

{formatCurrency(data?.totalStockRetail || 0)}

+

{formatCurrency(data?.totalStockRetail || 0)}

-
Stock Retail By Brand
+
Stock Retail By Brand
setActiveIndex(index)} onMouseLeave={() => setActiveIndex(undefined)} > - {data?.vendorStock?.map((entry, index) => ( + {data?.brandStock?.map((entry, index) => ( ))}