Fix and restyle replenishmentmetrics component
This commit is contained in:
@@ -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
|
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user