Fix and restyle replenishmentmetrics component

This commit is contained in:
2025-01-17 21:41:43 -05:00
parent b6e95aada9
commit d85d387c1a
4 changed files with 67 additions and 46 deletions

View File

@@ -173,28 +173,25 @@ router.get('/replenishment/metrics', async (req, res) => {
// Get summary metrics
const [metrics] = await executeQuery(`
SELECT
COUNT(DISTINCT CASE
WHEN pm.stock_status IN ('Critical', 'Reorder')
THEN p.product_id
END) as products_to_replenish,
SUM(CASE
WHEN pm.stock_status IN ('Critical', 'Reorder')
THEN pm.reorder_qty
ELSE 0
END) as total_units_needed,
SUM(CASE
WHEN pm.stock_status IN ('Critical', 'Reorder')
THEN pm.reorder_qty * p.cost_price
ELSE 0
END) as total_cost,
SUM(CASE
WHEN pm.stock_status IN ('Critical', 'Reorder')
THEN pm.reorder_qty * p.price
ELSE 0
END) as total_retail
COUNT(DISTINCT p.product_id) as products_to_replenish,
COALESCE(SUM(CASE
WHEN p.stock_quantity < 0 THEN ABS(p.stock_quantity) + pm.reorder_qty
ELSE pm.reorder_qty
END), 0) as total_units_needed,
COALESCE(SUM(CASE
WHEN p.stock_quantity < 0 THEN (ABS(p.stock_quantity) + pm.reorder_qty) * p.cost_price
ELSE pm.reorder_qty * p.cost_price
END), 0) as total_cost,
COALESCE(SUM(CASE
WHEN p.stock_quantity < 0 THEN (ABS(p.stock_quantity) + pm.reorder_qty) * p.price
ELSE pm.reorder_qty * p.price
END), 0) as total_retail
FROM products p
JOIN product_metrics pm ON p.product_id = pm.product_id
WHERE p.replenishable = true
AND (pm.stock_status IN ('Critical', 'Reorder')
OR p.stock_quantity < 0)
AND pm.reorder_qty > 0
`);
// Get top variants to replenish
@@ -203,15 +200,25 @@ router.get('/replenishment/metrics', async (req, res) => {
p.product_id,
p.title,
p.stock_quantity as current_stock,
pm.reorder_qty as replenish_qty,
(pm.reorder_qty * p.cost_price) as replenish_cost,
(pm.reorder_qty * p.price) as replenish_retail,
pm.stock_status,
DATE_FORMAT(pm.planning_period_end, '%b %d, %Y') as planning_period
CASE
WHEN p.stock_quantity < 0 THEN ABS(p.stock_quantity) + pm.reorder_qty
ELSE pm.reorder_qty
END as replenish_qty,
CASE
WHEN p.stock_quantity < 0 THEN (ABS(p.stock_quantity) + pm.reorder_qty) * p.cost_price
ELSE pm.reorder_qty * p.cost_price
END as replenish_cost,
CASE
WHEN p.stock_quantity < 0 THEN (ABS(p.stock_quantity) + pm.reorder_qty) * p.price
ELSE pm.reorder_qty * p.price
END as replenish_retail,
pm.stock_status
FROM products p
JOIN product_metrics pm ON p.product_id = pm.product_id
WHERE p.replenishable = true
AND pm.stock_status IN ('Critical', 'Reorder')
AND (pm.stock_status IN ('Critical', 'Reorder')
OR p.stock_quantity < 0)
AND pm.reorder_qty > 0
ORDER BY
CASE pm.stock_status
WHEN 'Critical' THEN 1
@@ -223,10 +230,10 @@ router.get('/replenishment/metrics', async (req, res) => {
// Format response
const response = {
productsToReplenish: parseInt(metrics.products_to_replenish) || 0,
unitsToReplenish: parseInt(metrics.total_units_needed) || 0,
replenishmentCost: parseFloat(metrics.total_cost) || 0,
replenishmentRetail: parseFloat(metrics.total_retail) || 0,
productsToReplenish: parseInt(metrics[0].products_to_replenish) || 0,
unitsToReplenish: parseInt(metrics[0].total_units_needed) || 0,
replenishmentCost: parseFloat(metrics[0].total_cost) || 0,
replenishmentRetail: parseFloat(metrics[0].total_retail) || 0,
topVariants: variants.map(v => ({
id: v.product_id,
title: v.title,
@@ -234,8 +241,7 @@ router.get('/replenishment/metrics', async (req, res) => {
replenishQty: parseInt(v.replenish_qty) || 0,
replenishCost: parseFloat(v.replenish_cost) || 0,
replenishRetail: parseFloat(v.replenish_retail) || 0,
status: v.stock_status,
planningPeriod: v.planning_period
status: v.stock_status
}))
};

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query"
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
import { PieChart, Pie, ResponsiveContainer, Cell, Tooltip, Sector } from "recharts"
import { PieChart, Pie, ResponsiveContainer, Cell, Sector } from "recharts"
import config from "@/config"
import { formatCurrency } from "@/lib/utils"
import { ClipboardList, AlertCircle, Layers, DollarSign, ShoppingCart } from "lucide-react" // Importing icons

View File

@@ -5,28 +5,43 @@ import { formatCurrency } from "@/lib/utils"
import { Package, DollarSign, ShoppingCart } from "lucide-react" // Importing icons
interface ReplenishmentMetricsData {
totalUnitsToReplenish: number
totalReplenishmentCost: number
totalReplenishmentRetail: number
replenishmentByCategory: {
category: string
units: number
cost: number
productsToReplenish: number
unitsToReplenish: number
replenishmentCost: number
replenishmentRetail: number
topVariants: {
id: number
title: string
currentStock: number
replenishQty: number
replenishCost: number
replenishRetail: number
status: string
planningPeriod: string
}[]
}
export function ReplenishmentMetrics() {
const { data } = useQuery<ReplenishmentMetricsData>({
const { data, error, isLoading } = useQuery<ReplenishmentMetricsData>({
queryKey: ["replenishment-metrics"],
queryFn: async () => {
console.log('Fetching from:', `${config.apiUrl}/dashboard/replenishment/metrics`);
const response = await fetch(`${config.apiUrl}/dashboard/replenishment/metrics`)
if (!response.ok) {
throw new Error("Failed to fetch replenishment metrics")
const text = await response.text();
console.error('API Error:', text);
throw new Error(`Failed to fetch replenishment metrics: ${response.status} ${response.statusText} - ${text}`)
}
return response.json()
const data = await response.json();
console.log('API Response:', data);
return data;
},
})
if (isLoading) return <div className="p-8 text-center">Loading replenishment metrics...</div>;
if (error) return <div className="p-8 text-center text-red-500">Error: {error.message}</div>;
if (!data) return <div className="p-8 text-center">No replenishment data available</div>;
return (
<>
<CardHeader>
@@ -39,21 +54,21 @@ export function ReplenishmentMetrics() {
<Package className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Units to Replenish</p>
</div>
<p className="text-2xl font-bold">{data?.totalUnitsToReplenish.toLocaleString() || 0}</p>
<p className="text-lg font-bold">{data.unitsToReplenish.toLocaleString() || 0}</p>
</div>
<div className="flex items-baseline justify-between">
<div className="flex items-center gap-2">
<DollarSign className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Replenishment Cost</p>
</div>
<p className="text-2xl font-bold">{formatCurrency(data?.totalReplenishmentCost || 0)}</p>
<p className="text-lg font-bold">{formatCurrency(data.replenishmentCost || 0)}</p>
</div>
<div className="flex items-baseline justify-between">
<div className="flex items-center gap-2">
<ShoppingCart className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Replenishment Retail</p>
</div>
<p className="text-2xl font-bold">{formatCurrency(data?.totalReplenishmentRetail || 0)}</p>
<p className="text-lg font-bold">{formatCurrency(data.replenishmentRetail || 0)}</p>
</div>
</div>
</CardContent>

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query"
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
import { PieChart, Pie, ResponsiveContainer, Cell, Tooltip, Sector } from "recharts"
import { PieChart, Pie, ResponsiveContainer, Cell, Sector } from "recharts"
import config from "@/config"
import { formatCurrency } from "@/lib/utils"
import { Package, Layers, DollarSign, ShoppingCart } from "lucide-react"