Rearrange dashboard to match IP
This commit is contained in:
@@ -26,18 +26,20 @@ router.get('/stock/metrics', async (req, res) => {
|
|||||||
FROM products
|
FROM products
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Get brand values in a separate query
|
// Get vendor stock values
|
||||||
const [brandValues] = await executeQuery(`
|
const [vendorValues] = await executeQuery(`
|
||||||
SELECT
|
SELECT
|
||||||
brand,
|
vendor,
|
||||||
COALESCE(SUM(stock_quantity * price), 0) as value
|
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
|
FROM products
|
||||||
WHERE brand IS NOT NULL
|
WHERE vendor IS NOT NULL
|
||||||
AND stock_quantity > 0
|
AND stock_quantity > 0
|
||||||
GROUP BY brand
|
GROUP BY vendor
|
||||||
HAVING value > 0
|
HAVING stock_cost > 0
|
||||||
ORDER BY value DESC
|
ORDER BY stock_cost DESC
|
||||||
LIMIT 8
|
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Format the response with explicit type conversion
|
// Format the response with explicit type conversion
|
||||||
@@ -47,9 +49,12 @@ router.get('/stock/metrics', async (req, res) => {
|
|||||||
totalStockUnits: parseInt(stockMetrics.total_units) || 0,
|
totalStockUnits: parseInt(stockMetrics.total_units) || 0,
|
||||||
totalStockCost: parseFloat(stockMetrics.total_cost) || 0,
|
totalStockCost: parseFloat(stockMetrics.total_cost) || 0,
|
||||||
totalStockRetail: parseFloat(stockMetrics.total_retail) || 0,
|
totalStockRetail: parseFloat(stockMetrics.total_retail) || 0,
|
||||||
brandRetailValue: brandValues.map(b => ({
|
vendorStock: vendorValues.map(v => ({
|
||||||
brand: b.brand,
|
vendor: v.vendor,
|
||||||
value: parseFloat(b.value) || 0
|
variants: parseInt(v.variant_count) || 0,
|
||||||
|
units: parseInt(v.stock_units) || 0,
|
||||||
|
cost: parseFloat(v.stock_cost) || 0,
|
||||||
|
retail: parseFloat(v.stock_retail) || 0
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -86,20 +91,19 @@ router.get('/purchase/metrics', async (req, res) => {
|
|||||||
JOIN products p ON po.product_id = p.product_id
|
JOIN products p ON po.product_id = p.product_id
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const [vendorValues] = await executeQuery(`
|
const [vendorOrders] = await executeQuery(`
|
||||||
SELECT
|
SELECT
|
||||||
po.vendor,
|
po.vendor,
|
||||||
COALESCE(SUM(CASE
|
COUNT(DISTINCT po.po_id) as order_count,
|
||||||
WHEN po.status = 'open'
|
COALESCE(SUM(po.ordered), 0) as ordered_units,
|
||||||
THEN po.ordered * po.cost_price
|
COALESCE(SUM(po.ordered * po.cost_price), 0) as order_cost,
|
||||||
ELSE 0
|
COALESCE(SUM(po.ordered * p.price), 0) as order_retail
|
||||||
END), 0) as value
|
|
||||||
FROM purchase_orders po
|
FROM purchase_orders po
|
||||||
|
JOIN products p ON po.product_id = p.product_id
|
||||||
WHERE po.status = 'open'
|
WHERE po.status = 'open'
|
||||||
GROUP BY po.vendor
|
GROUP BY po.vendor
|
||||||
HAVING value > 0
|
HAVING order_cost > 0
|
||||||
ORDER BY value DESC
|
ORDER BY order_cost DESC
|
||||||
LIMIT 8
|
|
||||||
`);
|
`);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
@@ -108,9 +112,12 @@ router.get('/purchase/metrics', async (req, res) => {
|
|||||||
onOrderUnits: parseInt(poMetrics.total_units) || 0,
|
onOrderUnits: parseInt(poMetrics.total_units) || 0,
|
||||||
onOrderCost: parseFloat(poMetrics.total_cost) || 0,
|
onOrderCost: parseFloat(poMetrics.total_cost) || 0,
|
||||||
onOrderRetail: parseFloat(poMetrics.total_retail) || 0,
|
onOrderRetail: parseFloat(poMetrics.total_retail) || 0,
|
||||||
vendorOrderValue: vendorValues.map(v => ({
|
vendorOrders: vendorOrders.map(v => ({
|
||||||
vendor: v.vendor,
|
vendor: v.vendor,
|
||||||
value: parseFloat(v.value) || 0
|
orders: parseInt(v.order_count) || 0,
|
||||||
|
units: parseInt(v.ordered_units) || 0,
|
||||||
|
cost: parseFloat(v.order_cost) || 0,
|
||||||
|
retail: parseFloat(v.order_retail) || 0
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -150,52 +157,45 @@ router.get('/replenishment/metrics', async (req, res) => {
|
|||||||
WHERE p.replenishable = true
|
WHERE p.replenishable = true
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Get category breakdown
|
// Get top variants to replenish
|
||||||
const [categories] = await executeQuery(`
|
const [variants] = await executeQuery(`
|
||||||
SELECT
|
SELECT
|
||||||
c.name as category,
|
p.product_id,
|
||||||
COUNT(DISTINCT CASE
|
p.title,
|
||||||
WHEN pm.stock_status IN ('Critical', 'Reorder')
|
p.stock_quantity as current_stock,
|
||||||
THEN p.product_id
|
pm.reorder_qty as replenish_qty,
|
||||||
END) as products,
|
(pm.reorder_qty * p.cost_price) as replenish_cost,
|
||||||
SUM(CASE
|
(pm.reorder_qty * p.price) as replenish_retail,
|
||||||
WHEN pm.stock_status IN ('Critical', 'Reorder')
|
pm.stock_status,
|
||||||
THEN pm.reorder_qty
|
DATE_FORMAT(pm.planning_period_end, '%b %d, %Y') as planning_period
|
||||||
ELSE 0
|
FROM products p
|
||||||
END) as units,
|
|
||||||
SUM(CASE
|
|
||||||
WHEN pm.stock_status IN ('Critical', 'Reorder')
|
|
||||||
THEN pm.reorder_qty * p.cost_price
|
|
||||||
ELSE 0
|
|
||||||
END) as cost,
|
|
||||||
SUM(CASE
|
|
||||||
WHEN pm.stock_status IN ('Critical', 'Reorder')
|
|
||||||
THEN pm.reorder_qty * p.price
|
|
||||||
ELSE 0
|
|
||||||
END) as retail
|
|
||||||
FROM categories c
|
|
||||||
JOIN product_categories pc ON c.id = pc.category_id
|
|
||||||
JOIN products p ON pc.product_id = p.product_id
|
|
||||||
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
|
||||||
GROUP BY c.id, c.name
|
AND pm.stock_status IN ('Critical', 'Reorder')
|
||||||
HAVING products > 0
|
ORDER BY
|
||||||
ORDER BY cost DESC
|
CASE pm.stock_status
|
||||||
LIMIT 8
|
WHEN 'Critical' THEN 1
|
||||||
|
WHEN 'Reorder' THEN 2
|
||||||
|
END,
|
||||||
|
replenish_cost DESC
|
||||||
|
LIMIT 5
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Format response
|
// Format response
|
||||||
const response = {
|
const response = {
|
||||||
productsToReplenish: parseInt(metrics.products_to_replenish) || 0,
|
productsToReplenish: parseInt(metrics.products_to_replenish) || 0,
|
||||||
totalUnitsToReplenish: parseInt(metrics.total_units_needed) || 0,
|
unitsToReplenish: parseInt(metrics.total_units_needed) || 0,
|
||||||
totalReplenishmentCost: parseFloat(metrics.total_cost) || 0,
|
replenishmentCost: parseFloat(metrics.total_cost) || 0,
|
||||||
totalReplenishmentRetail: parseFloat(metrics.total_retail) || 0,
|
replenishmentRetail: parseFloat(metrics.total_retail) || 0,
|
||||||
categoryData: categories.map(c => ({
|
topVariants: variants.map(v => ({
|
||||||
category: c.category,
|
id: v.product_id,
|
||||||
products: parseInt(c.products) || 0,
|
title: v.title,
|
||||||
units: parseInt(c.units) || 0,
|
currentStock: parseInt(v.current_stock) || 0,
|
||||||
cost: parseFloat(c.cost) || 0,
|
replenishQty: parseInt(v.replenish_qty) || 0,
|
||||||
retail: parseFloat(c.retail) || 0
|
replenishCost: parseFloat(v.replenish_cost) || 0,
|
||||||
|
replenishRetail: parseFloat(v.replenish_retail) || 0,
|
||||||
|
status: v.stock_status,
|
||||||
|
planningPeriod: v.planning_period
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -833,4 +833,56 @@ router.get('/inventory-health', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /dashboard/replenish/products
|
||||||
|
// Returns top products that need replenishment
|
||||||
|
router.get('/replenish/products', async (req, res) => {
|
||||||
|
const limit = Math.max(1, Math.min(100, parseInt(req.query.limit) || 50));
|
||||||
|
try {
|
||||||
|
const [products] = await executeQuery(`
|
||||||
|
SELECT
|
||||||
|
p.product_id,
|
||||||
|
p.SKU,
|
||||||
|
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,
|
||||||
|
CASE
|
||||||
|
WHEN pm.daily_sales_avg > 0
|
||||||
|
THEN FLOOR(p.stock_quantity / pm.daily_sales_avg)
|
||||||
|
ELSE NULL
|
||||||
|
END as days_until_stockout
|
||||||
|
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.reorder_qty > 0
|
||||||
|
ORDER BY
|
||||||
|
CASE pm.stock_status
|
||||||
|
WHEN 'Critical' THEN 1
|
||||||
|
WHEN 'Reorder' THEN 2
|
||||||
|
END,
|
||||||
|
replenish_cost DESC
|
||||||
|
LIMIT ?
|
||||||
|
`, [limit]);
|
||||||
|
|
||||||
|
// Format response
|
||||||
|
const response = products.map(p => ({
|
||||||
|
product_id: p.product_id,
|
||||||
|
SKU: p.SKU,
|
||||||
|
title: p.title,
|
||||||
|
current_stock: parseInt(p.current_stock) || 0,
|
||||||
|
replenish_qty: parseInt(p.replenish_qty) || 0,
|
||||||
|
replenish_cost: parseFloat(p.replenish_cost) || 0,
|
||||||
|
replenish_retail: parseFloat(p.replenish_retail) || 0,
|
||||||
|
days_until_stockout: p.days_until_stockout
|
||||||
|
}));
|
||||||
|
|
||||||
|
res.json(response);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching products to replenish:', err);
|
||||||
|
res.status(500).json({ error: 'Failed to fetch products to replenish' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@@ -10,9 +10,12 @@ interface PurchaseMetricsData {
|
|||||||
onOrderUnits: number
|
onOrderUnits: number
|
||||||
onOrderCost: number
|
onOrderCost: number
|
||||||
onOrderRetail: number
|
onOrderRetail: number
|
||||||
vendorOrderValue: {
|
vendorOrders: {
|
||||||
vendor: string
|
vendor: string
|
||||||
value: number
|
orders: number
|
||||||
|
units: number
|
||||||
|
cost: number
|
||||||
|
retail: number
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,28 +45,28 @@ export function PurchaseMetrics() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-lg font-medium">Purchase Orders Overview</CardTitle>
|
<CardTitle className="text-lg font-medium">Purchase Overview</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="grid grid-cols-2 gap-4 mb-6">
|
<div className="grid grid-cols-2 gap-4 mb-6">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-muted-foreground">Active POs</p>
|
<p className="text-sm font-medium text-muted-foreground">Active Orders</p>
|
||||||
<p className="text-2xl font-bold">{data?.activePurchaseOrders.toLocaleString() || 0}</p>
|
<p className="text-2xl font-bold">{data?.activePurchaseOrders.toLocaleString() || 0}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-muted-foreground">Overdue POs</p>
|
<p className="text-sm font-medium text-muted-foreground">Overdue Orders</p>
|
||||||
<p className="text-2xl font-bold text-destructive">{data?.overduePurchaseOrders.toLocaleString() || 0}</p>
|
<p className="text-2xl font-bold">{data?.overduePurchaseOrders.toLocaleString() || 0}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-muted-foreground">On Order Units</p>
|
<p className="text-sm font-medium text-muted-foreground">Units On Order</p>
|
||||||
<p className="text-2xl font-bold">{data?.onOrderUnits.toLocaleString() || 0}</p>
|
<p className="text-2xl font-bold">{data?.onOrderUnits.toLocaleString() || 0}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-muted-foreground">On Order Cost</p>
|
<p className="text-sm font-medium text-muted-foreground">Order Cost</p>
|
||||||
<p className="text-2xl font-bold">{formatCurrency(data?.onOrderCost || 0)}</p>
|
<p className="text-2xl font-bold">{formatCurrency(data?.onOrderCost || 0)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<p className="text-sm font-medium text-muted-foreground">On Order Retail</p>
|
<p className="text-sm font-medium text-muted-foreground">Order Retail</p>
|
||||||
<p className="text-2xl font-bold">{formatCurrency(data?.onOrderRetail || 0)}</p>
|
<p className="text-2xl font-bold">{formatCurrency(data?.onOrderRetail || 0)}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,8 +75,8 @@ export function PurchaseMetrics() {
|
|||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<PieChart>
|
<PieChart>
|
||||||
<Pie
|
<Pie
|
||||||
data={data?.vendorOrderValue || []}
|
data={data?.vendorOrders || []}
|
||||||
dataKey="value"
|
dataKey="cost"
|
||||||
nameKey="vendor"
|
nameKey="vendor"
|
||||||
cx="50%"
|
cx="50%"
|
||||||
cy="50%"
|
cy="50%"
|
||||||
@@ -81,7 +84,7 @@ export function PurchaseMetrics() {
|
|||||||
outerRadius={80}
|
outerRadius={80}
|
||||||
paddingAngle={2}
|
paddingAngle={2}
|
||||||
>
|
>
|
||||||
{data?.vendorOrderValue.map((entry, index) => (
|
{data?.vendorOrders?.map((entry, index) => (
|
||||||
<Cell
|
<Cell
|
||||||
key={entry.vendor}
|
key={entry.vendor}
|
||||||
fill={COLORS[index % COLORS.length]}
|
fill={COLORS[index % COLORS.length]}
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ interface StockMetricsData {
|
|||||||
totalStockUnits: number
|
totalStockUnits: number
|
||||||
totalStockCost: number
|
totalStockCost: number
|
||||||
totalStockRetail: number
|
totalStockRetail: number
|
||||||
brandRetailValue: {
|
vendorStock: {
|
||||||
brand: string
|
vendor: string
|
||||||
value: number
|
variants: number
|
||||||
|
units: number
|
||||||
|
cost: number
|
||||||
|
retail: number
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,25 +75,25 @@ export function StockMetrics() {
|
|||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<PieChart>
|
<PieChart>
|
||||||
<Pie
|
<Pie
|
||||||
data={data?.brandRetailValue || []}
|
data={data?.vendorStock || []}
|
||||||
dataKey="value"
|
dataKey="cost"
|
||||||
nameKey="brand"
|
nameKey="vendor"
|
||||||
cx="50%"
|
cx="50%"
|
||||||
cy="50%"
|
cy="50%"
|
||||||
innerRadius={60}
|
innerRadius={60}
|
||||||
outerRadius={80}
|
outerRadius={80}
|
||||||
paddingAngle={2}
|
paddingAngle={2}
|
||||||
>
|
>
|
||||||
{data?.brandRetailValue.map((entry, index) => (
|
{data?.vendorStock?.map((entry, index) => (
|
||||||
<Cell
|
<Cell
|
||||||
key={entry.brand}
|
key={entry.vendor}
|
||||||
fill={COLORS[index % COLORS.length]}
|
fill={COLORS[index % COLORS.length]}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value: number) => formatCurrency(value)}
|
formatter={(value: number) => formatCurrency(value)}
|
||||||
labelFormatter={(label: string) => `Brand: ${label}`}
|
labelFormatter={(label: string) => `Vendor: ${label}`}
|
||||||
/>
|
/>
|
||||||
</PieChart>
|
</PieChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
|
|||||||
77
inventory/src/components/dashboard/TopReplenishProducts.tsx
Normal file
77
inventory/src/components/dashboard/TopReplenishProducts.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||||
|
import config from "@/config"
|
||||||
|
import { formatCurrency } from "@/lib/utils"
|
||||||
|
|
||||||
|
interface ReplenishProduct {
|
||||||
|
product_id: number
|
||||||
|
SKU: string
|
||||||
|
title: string
|
||||||
|
current_stock: number
|
||||||
|
replenish_qty: number
|
||||||
|
replenish_cost: number
|
||||||
|
replenish_retail: number
|
||||||
|
days_until_stockout: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TopReplenishProducts() {
|
||||||
|
const { data } = useQuery<ReplenishProduct[]>({
|
||||||
|
queryKey: ["top-replenish-products"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await fetch(`${config.apiUrl}/dashboard/replenish/products?limit=50`)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch products to replenish")
|
||||||
|
}
|
||||||
|
return response.json()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg font-medium">Top Products to Replenish</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<ScrollArea className="h-[400px] w-full">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Product</TableHead>
|
||||||
|
<TableHead className="text-right">Current</TableHead>
|
||||||
|
<TableHead className="text-right">Replenish</TableHead>
|
||||||
|
<TableHead className="text-right">Cost</TableHead>
|
||||||
|
<TableHead className="text-right">Days</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{data?.map((product) => (
|
||||||
|
<TableRow key={product.product_id}>
|
||||||
|
<TableCell>
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">{product.title}</p>
|
||||||
|
<p className="text-sm text-muted-foreground">{product.SKU}</p>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{product.current_stock.toLocaleString()}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{product.replenish_qty.toLocaleString()}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{formatCurrency(product.replenish_cost)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{product.days_until_stockout ?? "N/A"}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</ScrollArea>
|
||||||
|
</CardContent>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,16 +1,12 @@
|
|||||||
import { Card } from "@/components/ui/card"
|
import { Card } from "@/components/ui/card"
|
||||||
import { InventoryHealthSummary } from "@/components/dashboard/InventoryHealthSummary"
|
|
||||||
import { LowStockAlerts } from "@/components/dashboard/LowStockAlerts"
|
|
||||||
import { TrendingProducts } from "@/components/dashboard/TrendingProducts"
|
|
||||||
import { VendorPerformance } from "@/components/dashboard/VendorPerformance"
|
|
||||||
import { KeyMetricsCharts } from "@/components/dashboard/KeyMetricsCharts"
|
|
||||||
import { StockMetrics } from "@/components/dashboard/StockMetrics"
|
import { StockMetrics } from "@/components/dashboard/StockMetrics"
|
||||||
import { PurchaseMetrics } from "@/components/dashboard/PurchaseMetrics"
|
import { PurchaseMetrics } from "@/components/dashboard/PurchaseMetrics"
|
||||||
import { ReplenishmentMetrics } from "@/components/dashboard/ReplenishmentMetrics"
|
import { ReplenishmentMetrics } from "@/components/dashboard/ReplenishmentMetrics"
|
||||||
import { ForecastMetrics } from "@/components/dashboard/ForecastMetrics"
|
import { TopReplenishProducts } from "@/components/dashboard/TopReplenishProducts"
|
||||||
import { OverstockMetrics } from "@/components/dashboard/OverstockMetrics"
|
import { OverstockMetrics } from "@/components/dashboard/OverstockMetrics"
|
||||||
import { TopOverstockedProducts } from "@/components/dashboard/TopOverstockedProducts"
|
import { TopOverstockedProducts } from "@/components/dashboard/TopOverstockedProducts"
|
||||||
import { BestSellers } from "@/components/dashboard/BestSellers"
|
import { BestSellers } from "@/components/dashboard/BestSellers"
|
||||||
|
import { ForecastMetrics } from "@/components/dashboard/ForecastMetrics"
|
||||||
import { SalesMetrics } from "@/components/dashboard/SalesMetrics"
|
import { SalesMetrics } from "@/components/dashboard/SalesMetrics"
|
||||||
import { motion } from "motion/react"
|
import { motion } from "motion/react"
|
||||||
|
|
||||||
@@ -22,59 +18,47 @@ export function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* First row - Stock and Purchase metrics */}
|
{/* First row - Stock and Purchase metrics */}
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 grid-cols-2">
|
||||||
<Card>
|
<Card className="col-span-1">
|
||||||
<StockMetrics />
|
<StockMetrics />
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card className="col-span-1">
|
||||||
<PurchaseMetrics />
|
<PurchaseMetrics />
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Second row - Replenishment and Overstock */}
|
{/* Second row - Replenishment section */}
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 grid-cols-3">
|
||||||
|
<Card className="col-span-2">
|
||||||
|
<TopReplenishProducts />
|
||||||
|
</Card>
|
||||||
|
<div className="col-span-1 grid gap-4 grid-rows-2">
|
||||||
<Card>
|
<Card>
|
||||||
<ReplenishmentMetrics />
|
<ReplenishmentMetrics />
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
|
||||||
<OverstockMetrics />
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Third row - Products to Replenish and Overstocked Products */}
|
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
|
||||||
<Card>
|
|
||||||
<LowStockAlerts />
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<TopOverstockedProducts />
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Fourth row - Sales and Forecast */}
|
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
|
||||||
<Card>
|
|
||||||
<SalesMetrics />
|
|
||||||
</Card>
|
|
||||||
<Card>
|
<Card>
|
||||||
<ForecastMetrics />
|
<ForecastMetrics />
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Fifth row - Best Sellers */}
|
{/* Third row - Overstock section */}
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4 grid-cols-3">
|
||||||
<Card>
|
<Card className="col-span-1">
|
||||||
<BestSellers />
|
<OverstockMetrics />
|
||||||
|
</Card>
|
||||||
|
<Card className="col-span-2">
|
||||||
|
<TopOverstockedProducts />
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sixth row - Vendor Performance and Trending Products */}
|
{/* Fourth row - Best Sellers and Sales */}
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 grid-cols-3">
|
||||||
<Card>
|
<Card className="col-span-2">
|
||||||
<VendorPerformance />
|
<BestSellers />
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card className="col-span-1">
|
||||||
<TrendingProducts />
|
<SalesMetrics />
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
Reference in New Issue
Block a user