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

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query" import { useQuery } from "@tanstack/react-query"
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card" 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 config from "@/config"
import { formatCurrency } from "@/lib/utils" import { formatCurrency } from "@/lib/utils"
import { ClipboardList, AlertCircle, Layers, DollarSign, ShoppingCart } from "lucide-react" // Importing icons 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 import { Package, DollarSign, ShoppingCart } from "lucide-react" // Importing icons
interface ReplenishmentMetricsData { interface ReplenishmentMetricsData {
totalUnitsToReplenish: number productsToReplenish: number
totalReplenishmentCost: number unitsToReplenish: number
totalReplenishmentRetail: number replenishmentCost: number
replenishmentByCategory: { replenishmentRetail: number
category: string topVariants: {
units: number id: number
cost: number title: string
currentStock: number
replenishQty: number
replenishCost: number
replenishRetail: number
status: string
planningPeriod: string
}[] }[]
} }
export function ReplenishmentMetrics() { export function ReplenishmentMetrics() {
const { data } = useQuery<ReplenishmentMetricsData>({ const { data, error, isLoading } = useQuery<ReplenishmentMetricsData>({
queryKey: ["replenishment-metrics"], queryKey: ["replenishment-metrics"],
queryFn: async () => { queryFn: async () => {
console.log('Fetching from:', `${config.apiUrl}/dashboard/replenishment/metrics`);
const response = await fetch(`${config.apiUrl}/dashboard/replenishment/metrics`) const response = await fetch(`${config.apiUrl}/dashboard/replenishment/metrics`)
if (!response.ok) { 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 ( return (
<> <>
<CardHeader> <CardHeader>
@@ -39,21 +54,21 @@ export function ReplenishmentMetrics() {
<Package className="h-4 w-4 text-muted-foreground" /> <Package className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Units to Replenish</p> <p className="text-sm font-medium text-muted-foreground">Units to Replenish</p>
</div> </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>
<div className="flex items-baseline justify-between"> <div className="flex items-baseline justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<DollarSign className="h-4 w-4 text-muted-foreground" /> <DollarSign className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Replenishment Cost</p> <p className="text-sm font-medium text-muted-foreground">Replenishment Cost</p>
</div> </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>
<div className="flex items-baseline justify-between"> <div className="flex items-baseline justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<ShoppingCart className="h-4 w-4 text-muted-foreground" /> <ShoppingCart className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Replenishment Retail</p> <p className="text-sm font-medium text-muted-foreground">Replenishment Retail</p>
</div> </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>
</div> </div>
</CardContent> </CardContent>

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query" import { useQuery } from "@tanstack/react-query"
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card" 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 config from "@/config"
import { formatCurrency } from "@/lib/utils" import { formatCurrency } from "@/lib/utils"
import { Package, Layers, DollarSign, ShoppingCart } from "lucide-react" import { Package, Layers, DollarSign, ShoppingCart } from "lucide-react"