Rearrange dashboard to match IP
This commit is contained in:
@@ -10,9 +10,12 @@ interface PurchaseMetricsData {
|
||||
onOrderUnits: number
|
||||
onOrderCost: number
|
||||
onOrderRetail: number
|
||||
vendorOrderValue: {
|
||||
vendorOrders: {
|
||||
vendor: string
|
||||
value: number
|
||||
orders: number
|
||||
units: number
|
||||
cost: number
|
||||
retail: number
|
||||
}[]
|
||||
}
|
||||
|
||||
@@ -42,28 +45,28 @@ export function PurchaseMetrics() {
|
||||
return (
|
||||
<>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg font-medium">Purchase Orders Overview</CardTitle>
|
||||
<CardTitle className="text-lg font-medium">Purchase Overview</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-4 mb-6">
|
||||
<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>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">Overdue POs</p>
|
||||
<p className="text-2xl font-bold text-destructive">{data?.overduePurchaseOrders.toLocaleString() || 0}</p>
|
||||
<p className="text-sm font-medium text-muted-foreground">Overdue Orders</p>
|
||||
<p className="text-2xl font-bold">{data?.overduePurchaseOrders.toLocaleString() || 0}</p>
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,8 +75,8 @@ export function PurchaseMetrics() {
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={data?.vendorOrderValue || []}
|
||||
dataKey="value"
|
||||
data={data?.vendorOrders || []}
|
||||
dataKey="cost"
|
||||
nameKey="vendor"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
@@ -81,7 +84,7 @@ export function PurchaseMetrics() {
|
||||
outerRadius={80}
|
||||
paddingAngle={2}
|
||||
>
|
||||
{data?.vendorOrderValue.map((entry, index) => (
|
||||
{data?.vendorOrders?.map((entry, index) => (
|
||||
<Cell
|
||||
key={entry.vendor}
|
||||
fill={COLORS[index % COLORS.length]}
|
||||
|
||||
@@ -10,9 +10,12 @@ interface StockMetricsData {
|
||||
totalStockUnits: number
|
||||
totalStockCost: number
|
||||
totalStockRetail: number
|
||||
brandRetailValue: {
|
||||
brand: string
|
||||
value: number
|
||||
vendorStock: {
|
||||
vendor: string
|
||||
variants: number
|
||||
units: number
|
||||
cost: number
|
||||
retail: number
|
||||
}[]
|
||||
}
|
||||
|
||||
@@ -72,25 +75,25 @@ export function StockMetrics() {
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={data?.brandRetailValue || []}
|
||||
dataKey="value"
|
||||
nameKey="brand"
|
||||
data={data?.vendorStock || []}
|
||||
dataKey="cost"
|
||||
nameKey="vendor"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
innerRadius={60}
|
||||
outerRadius={80}
|
||||
paddingAngle={2}
|
||||
>
|
||||
{data?.brandRetailValue.map((entry, index) => (
|
||||
{data?.vendorStock?.map((entry, index) => (
|
||||
<Cell
|
||||
key={entry.brand}
|
||||
key={entry.vendor}
|
||||
fill={COLORS[index % COLORS.length]}
|
||||
/>
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip
|
||||
formatter={(value: number) => formatCurrency(value)}
|
||||
labelFormatter={(label: string) => `Brand: ${label}`}
|
||||
labelFormatter={(label: string) => `Vendor: ${label}`}
|
||||
/>
|
||||
</PieChart>
|
||||
</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 { 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 { PurchaseMetrics } from "@/components/dashboard/PurchaseMetrics"
|
||||
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 { TopOverstockedProducts } from "@/components/dashboard/TopOverstockedProducts"
|
||||
import { BestSellers } from "@/components/dashboard/BestSellers"
|
||||
import { ForecastMetrics } from "@/components/dashboard/ForecastMetrics"
|
||||
import { SalesMetrics } from "@/components/dashboard/SalesMetrics"
|
||||
import { motion } from "motion/react"
|
||||
|
||||
@@ -22,59 +18,47 @@ export function Dashboard() {
|
||||
</div>
|
||||
|
||||
{/* First row - Stock and Purchase metrics */}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Card>
|
||||
<div className="grid gap-4 grid-cols-2">
|
||||
<Card className="col-span-1">
|
||||
<StockMetrics />
|
||||
</Card>
|
||||
<Card>
|
||||
<Card className="col-span-1">
|
||||
<PurchaseMetrics />
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Second row - Replenishment and Overstock */}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Card>
|
||||
<ReplenishmentMetrics />
|
||||
</Card>
|
||||
<Card>
|
||||
<OverstockMetrics />
|
||||
{/* Second row - Replenishment section */}
|
||||
<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>
|
||||
<ReplenishmentMetrics />
|
||||
</Card>
|
||||
<Card>
|
||||
<ForecastMetrics />
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Third row - Products to Replenish and Overstocked Products */}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Card>
|
||||
<LowStockAlerts />
|
||||
{/* Third row - Overstock section */}
|
||||
<div className="grid gap-4 grid-cols-3">
|
||||
<Card className="col-span-1">
|
||||
<OverstockMetrics />
|
||||
</Card>
|
||||
<Card>
|
||||
<Card className="col-span-2">
|
||||
<TopOverstockedProducts />
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Fourth row - Sales and Forecast */}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Card>
|
||||
<SalesMetrics />
|
||||
</Card>
|
||||
<Card>
|
||||
<ForecastMetrics />
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Fifth row - Best Sellers */}
|
||||
<div className="grid gap-4">
|
||||
<Card>
|
||||
{/* Fourth row - Best Sellers and Sales */}
|
||||
<div className="grid gap-4 grid-cols-3">
|
||||
<Card className="col-span-2">
|
||||
<BestSellers />
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Sixth row - Vendor Performance and Trending Products */}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Card>
|
||||
<VendorPerformance />
|
||||
</Card>
|
||||
<Card>
|
||||
<TrendingProducts />
|
||||
<Card className="col-span-1">
|
||||
<SalesMetrics />
|
||||
</Card>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user