Fix data in product detail
This commit is contained in:
@@ -14,47 +14,118 @@ import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContai
|
||||
import config from "@/config";
|
||||
|
||||
interface Product {
|
||||
product_id: string;
|
||||
product_id: number;
|
||||
title: string;
|
||||
sku: string;
|
||||
SKU: string;
|
||||
barcode: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
|
||||
// Inventory fields
|
||||
stock_quantity: number;
|
||||
price: number;
|
||||
regular_price: number;
|
||||
cost_price: number;
|
||||
vendor: string;
|
||||
brand: string;
|
||||
moq: number;
|
||||
uom: number;
|
||||
managing_stock: boolean;
|
||||
replenishable: boolean;
|
||||
|
||||
// Pricing fields
|
||||
price: string | number;
|
||||
regular_price: string | number;
|
||||
cost_price: string | number;
|
||||
landing_cost_price: string | number | null;
|
||||
|
||||
// Categorization
|
||||
categories: string[];
|
||||
tags: string[];
|
||||
options: Record<string, any>;
|
||||
|
||||
// Vendor info
|
||||
vendor: string;
|
||||
vendor_reference: string;
|
||||
brand: string;
|
||||
|
||||
// URLs
|
||||
permalink: string;
|
||||
image: string;
|
||||
|
||||
// Metrics
|
||||
daily_sales_avg: number;
|
||||
weekly_sales_avg: number;
|
||||
monthly_sales_avg: number;
|
||||
days_of_inventory: number;
|
||||
reorder_point: number;
|
||||
safety_stock: number;
|
||||
avg_margin_percent: number;
|
||||
total_revenue: number;
|
||||
inventory_value: number;
|
||||
turnover_rate: number;
|
||||
abc_class: string;
|
||||
stock_status: string;
|
||||
metrics: {
|
||||
// Sales metrics
|
||||
daily_sales_avg: number;
|
||||
weekly_sales_avg: number;
|
||||
monthly_sales_avg: number;
|
||||
|
||||
// Inventory metrics
|
||||
days_of_inventory: number;
|
||||
reorder_point: number;
|
||||
safety_stock: number;
|
||||
stock_status: string;
|
||||
abc_class: string;
|
||||
|
||||
// Financial metrics
|
||||
avg_margin_percent: number;
|
||||
total_revenue: number;
|
||||
inventory_value: number;
|
||||
turnover_rate: number;
|
||||
gmroi: number;
|
||||
cost_of_goods_sold: number;
|
||||
gross_profit: number;
|
||||
|
||||
// Lead time metrics
|
||||
avg_lead_time_days: number;
|
||||
current_lead_time: number;
|
||||
target_lead_time: number;
|
||||
lead_time_status: string;
|
||||
};
|
||||
|
||||
// Vendor performance
|
||||
vendor_performance?: {
|
||||
avg_lead_time_days: number;
|
||||
on_time_delivery_rate: number;
|
||||
order_fill_rate: number;
|
||||
total_orders: number;
|
||||
total_late_orders: number;
|
||||
total_purchase_value: number;
|
||||
avg_order_value: number;
|
||||
};
|
||||
|
||||
// Time series data
|
||||
monthly_sales: Array<{
|
||||
monthly_sales?: Array<{
|
||||
month: string;
|
||||
quantity: number;
|
||||
revenue: number;
|
||||
cost: number;
|
||||
avg_price: number;
|
||||
profit_margin: number;
|
||||
inventory_value: number;
|
||||
quantity_growth: number;
|
||||
revenue_growth: number;
|
||||
}>;
|
||||
recent_orders: Array<{
|
||||
|
||||
recent_orders?: Array<{
|
||||
date: string;
|
||||
order_number: string;
|
||||
quantity: number;
|
||||
price: number;
|
||||
discount: number;
|
||||
tax: number;
|
||||
shipping: number;
|
||||
customer: string;
|
||||
status: string;
|
||||
payment_method: string;
|
||||
}>;
|
||||
recent_purchases: Array<{
|
||||
|
||||
recent_purchases?: Array<{
|
||||
date: string;
|
||||
expected_date: string;
|
||||
received_date: string | null;
|
||||
po_id: string;
|
||||
ordered: number;
|
||||
received: number;
|
||||
status: string;
|
||||
cost_price: number;
|
||||
notes: string;
|
||||
lead_time_days: number | null;
|
||||
}>;
|
||||
}
|
||||
|
||||
@@ -64,24 +135,61 @@ interface ProductDetailProps {
|
||||
}
|
||||
|
||||
export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
||||
const { data: product, isLoading } = useQuery<Product>({
|
||||
const { data: product, isLoading: isLoadingProduct } = useQuery<Product>({
|
||||
queryKey: ["product", productId],
|
||||
queryFn: async () => {
|
||||
if (!productId) return null;
|
||||
console.log('Fetching product details for:', productId);
|
||||
|
||||
const response = await fetch(`${config.apiUrl}/products/${productId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch product details");
|
||||
}
|
||||
return response.json();
|
||||
const data = await response.json();
|
||||
console.log('Product data:', data);
|
||||
return data;
|
||||
},
|
||||
enabled: !!productId,
|
||||
});
|
||||
|
||||
// Separate query for time series data
|
||||
const { data: timeSeriesData, isLoading: isLoadingTimeSeries } = useQuery({
|
||||
queryKey: ["product-time-series", productId],
|
||||
queryFn: async () => {
|
||||
if (!productId) return null;
|
||||
const response = await fetch(`${config.apiUrl}/products/${productId}/time-series`);
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch time series data");
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log('Time series data:', data);
|
||||
return data;
|
||||
},
|
||||
enabled: !!productId,
|
||||
});
|
||||
|
||||
const isLoading = isLoadingProduct || isLoadingTimeSeries;
|
||||
|
||||
// Helper function to format price values
|
||||
const formatPrice = (price: string | number | null | undefined): string => {
|
||||
if (price === null || price === undefined) return 'N/A';
|
||||
const numericPrice = typeof price === 'string' ? parseFloat(price) : price;
|
||||
return typeof numericPrice === 'number' ? numericPrice.toFixed(2) : 'N/A';
|
||||
};
|
||||
|
||||
// Combine product and time series data
|
||||
const combinedData = product && timeSeriesData ? {
|
||||
...product,
|
||||
monthly_sales: timeSeriesData.monthly_sales,
|
||||
recent_orders: timeSeriesData.recent_orders,
|
||||
recent_purchases: timeSeriesData.recent_purchases
|
||||
} : product;
|
||||
|
||||
if (!productId) return null;
|
||||
|
||||
return (
|
||||
<Drawer open={!!productId} onOpenChange={(open) => !open && onClose()}>
|
||||
<DrawerContent className="h-[85vh]">
|
||||
<DrawerContent className="h-[90vh] md:h-[90vh] overflow-y-auto">
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>
|
||||
{isLoading ? (
|
||||
@@ -92,21 +200,22 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
||||
</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
{isLoading ? (
|
||||
"\u00A0" // Non-breaking space for loading state
|
||||
"\u00A0"
|
||||
) : (
|
||||
`SKU: ${product?.sku}`
|
||||
`SKU: ${product?.SKU} | Stock: ${product?.stock_quantity}`
|
||||
)}
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
|
||||
<div className="px-4">
|
||||
<div className="px-4 pb-8">
|
||||
<Tabs defaultValue="overview" className="w-full">
|
||||
<TabsList className="w-full justify-start">
|
||||
<TabsList className="w-full justify-start mb-4 sticky top-0 bg-background z-10">
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="inventory">Inventory</TabsTrigger>
|
||||
<TabsTrigger value="sales">Sales</TabsTrigger>
|
||||
<TabsTrigger value="purchase">Purchase History</TabsTrigger>
|
||||
<TabsTrigger value="metrics">Performance Metrics</TabsTrigger>
|
||||
<TabsTrigger value="financial">Financial</TabsTrigger>
|
||||
<TabsTrigger value="vendor">Vendor</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="overview" className="space-y-4">
|
||||
@@ -130,7 +239,23 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Categories</dt>
|
||||
<dd>{Array.isArray(product?.categories) ? product.categories.join(", ") : "N/A"}</dd>
|
||||
<dd className="flex flex-wrap gap-2">
|
||||
{product?.categories?.map(category => (
|
||||
<span key={category} className="inline-flex items-center rounded-md bg-muted px-2 py-1 text-xs font-medium ring-1 ring-inset ring-muted">
|
||||
{category}
|
||||
</span>
|
||||
)) || "N/A"}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Tags</dt>
|
||||
<dd className="flex flex-wrap gap-2">
|
||||
{product?.tags?.map(tag => (
|
||||
<span key={tag} className="inline-flex items-center rounded-md bg-muted px-2 py-1 text-xs font-medium ring-1 ring-inset ring-muted">
|
||||
{tag}
|
||||
</span>
|
||||
)) || "N/A"}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</Card>
|
||||
@@ -140,18 +265,74 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
||||
<dl className="space-y-2">
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Price</dt>
|
||||
<dd>${typeof product?.price === 'number' ? product.price.toFixed(2) : 'N/A'}</dd>
|
||||
<dd>${formatPrice(product?.price)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Regular Price</dt>
|
||||
<dd>${typeof product?.regular_price === 'number' ? product.regular_price.toFixed(2) : 'N/A'}</dd>
|
||||
<dd>${formatPrice(product?.regular_price)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Cost Price</dt>
|
||||
<dd>${typeof product?.cost_price === 'number' ? product.cost_price.toFixed(2) : 'N/A'}</dd>
|
||||
<dd>${formatPrice(product?.cost_price)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Landing Cost</dt>
|
||||
<dd>${formatPrice(product?.landing_cost_price)}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</Card>
|
||||
|
||||
<Card className="p-4">
|
||||
<h3 className="font-semibold mb-2">Stock Status</h3>
|
||||
<dl className="space-y-2">
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Current Stock</dt>
|
||||
<dd className="text-2xl font-semibold">{product?.stock_quantity}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Status</dt>
|
||||
<dd>{product?.metrics?.stock_status}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Days of Stock</dt>
|
||||
<dd>{product?.metrics?.days_of_inventory} days</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</Card>
|
||||
|
||||
<Card className="p-4">
|
||||
<h3 className="font-semibold mb-2">Sales Velocity</h3>
|
||||
<dl className="space-y-2">
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Daily Sales</dt>
|
||||
<dd>{product?.metrics?.daily_sales_avg?.toFixed(1)} units</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Weekly Sales</dt>
|
||||
<dd>{product?.metrics?.weekly_sales_avg?.toFixed(1)} units</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Monthly Sales</dt>
|
||||
<dd>{product?.metrics?.monthly_sales_avg?.toFixed(1)} units</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</Card>
|
||||
|
||||
<Card className="p-4">
|
||||
<h3 className="font-semibold mb-2">Sales Trend</h3>
|
||||
<div className="h-64">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={combinedData?.monthly_sales || []}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="month" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Line type="monotone" dataKey="quantity" stroke="#8884d8" name="Quantity" />
|
||||
<Line type="monotone" dataKey="revenue" stroke="#82ca9d" name="Revenue" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
@@ -170,11 +351,11 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Days of Inventory</dt>
|
||||
<dd className="text-2xl font-semibold">{product?.days_of_inventory || 0}</dd>
|
||||
<dd className="text-2xl font-semibold">{product?.metrics?.days_of_inventory || 0}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Status</dt>
|
||||
<dd className="text-2xl font-semibold">{product?.stock_status || "N/A"}</dd>
|
||||
<dd className="text-2xl font-semibold">{product?.metrics?.stock_status || "N/A"}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</Card>
|
||||
@@ -184,15 +365,15 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
||||
<dl className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Reorder Point</dt>
|
||||
<dd>{product?.reorder_point || 0}</dd>
|
||||
<dd>{product?.metrics?.reorder_point || 0}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Safety Stock</dt>
|
||||
<dd>{product?.safety_stock || 0}</dd>
|
||||
<dd>{product?.metrics?.safety_stock || 0}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">ABC Class</dt>
|
||||
<dd>{product?.abc_class || "N/A"}</dd>
|
||||
<dd>{product?.metrics?.abc_class || "N/A"}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</Card>
|
||||
@@ -206,28 +387,45 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-4">
|
||||
<h3 className="font-semibold mb-2">Sales Metrics</h3>
|
||||
<dl className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Daily Sales Avg</dt>
|
||||
<dd>{product?.daily_sales_avg?.toFixed(2) || 0}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Weekly Sales Avg</dt>
|
||||
<dd>{product?.weekly_sales_avg?.toFixed(2) || 0}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Monthly Sales Avg</dt>
|
||||
<dd>{product?.monthly_sales_avg?.toFixed(2) || 0}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<h3 className="font-semibold mb-2">Recent Orders</h3>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Date</TableHead>
|
||||
<TableHead>Order #</TableHead>
|
||||
<TableHead>Customer</TableHead>
|
||||
<TableHead>Quantity</TableHead>
|
||||
<TableHead>Price</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{combinedData?.recent_orders?.map((order: NonNullable<Product['recent_orders']>[number]) => (
|
||||
<TableRow key={order.order_number}>
|
||||
<TableCell>{order.date}</TableCell>
|
||||
<TableCell>{order.order_number}</TableCell>
|
||||
<TableCell>{order.customer}</TableCell>
|
||||
<TableCell>{order.quantity}</TableCell>
|
||||
<TableCell>${formatPrice(order.price)}</TableCell>
|
||||
<TableCell>{order.status}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{(!combinedData?.recent_orders || combinedData.recent_orders.length === 0) && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="text-center text-muted-foreground">
|
||||
No recent orders
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
|
||||
<Card className="p-4">
|
||||
<h3 className="font-semibold mb-2">Monthly Sales Trend</h3>
|
||||
<div className="h-64">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={product?.monthly_sales || []}>
|
||||
<LineChart data={combinedData?.monthly_sales || []}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="month" />
|
||||
<YAxis yAxisId="left" />
|
||||
@@ -239,30 +437,6 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-4">
|
||||
<h3 className="font-semibold mb-2">Recent Orders</h3>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Date</TableHead>
|
||||
<TableHead>Order #</TableHead>
|
||||
<TableHead>Quantity</TableHead>
|
||||
<TableHead>Price</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{product?.recent_orders?.map((order) => (
|
||||
<TableRow key={order.order_number}>
|
||||
<TableCell>{order.date}</TableCell>
|
||||
<TableCell>{order.order_number}</TableCell>
|
||||
<TableCell>{order.quantity}</TableCell>
|
||||
<TableCell>${order.price.toFixed(2)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
@@ -282,18 +456,27 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
||||
<TableHead>Ordered</TableHead>
|
||||
<TableHead>Received</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Lead Time</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{product?.recent_purchases?.map((po) => (
|
||||
{combinedData?.recent_purchases?.map((po: NonNullable<Product['recent_purchases']>[number]) => (
|
||||
<TableRow key={po.po_id}>
|
||||
<TableCell>{po.date}</TableCell>
|
||||
<TableCell>{po.po_id}</TableCell>
|
||||
<TableCell>{po.ordered}</TableCell>
|
||||
<TableCell>{po.received}</TableCell>
|
||||
<TableCell>{po.status}</TableCell>
|
||||
<TableCell>{po.lead_time_days ? `${po.lead_time_days} days` : 'N/A'}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{(!combinedData?.recent_purchases || combinedData.recent_purchases.length === 0) && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="text-center text-muted-foreground">
|
||||
No recent purchase orders
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
@@ -301,47 +484,108 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="metrics" className="space-y-4">
|
||||
<TabsContent value="financial" className="space-y-4">
|
||||
{isLoading ? (
|
||||
<Skeleton className="h-48 w-full" />
|
||||
) : (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-4">
|
||||
<Card className="p-4">
|
||||
<h3 className="font-semibold mb-2">Financial Metrics</h3>
|
||||
<dl className="space-y-2">
|
||||
<h3 className="font-semibold mb-2">Financial Overview</h3>
|
||||
<dl className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Total Revenue</dt>
|
||||
<dd>${product?.total_revenue?.toFixed(2) || '0.00'}</dd>
|
||||
<dt className="text-sm text-muted-foreground">Gross Profit</dt>
|
||||
<dd className="text-2xl font-semibold">${formatPrice(product?.metrics.gross_profit)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">GMROI</dt>
|
||||
<dd className="text-2xl font-semibold">{product?.metrics.gmroi.toFixed(2)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Margin %</dt>
|
||||
<dd>{product?.avg_margin_percent?.toFixed(2) || '0.00'}%</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Inventory Value</dt>
|
||||
<dd>${product?.inventory_value?.toFixed(2) || '0.00'}</dd>
|
||||
<dd className="text-2xl font-semibold">{product?.metrics.avg_margin_percent.toFixed(2)}%</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</Card>
|
||||
|
||||
<Card className="p-4">
|
||||
<h3 className="font-semibold mb-2">Performance Metrics</h3>
|
||||
<dl className="space-y-2">
|
||||
<h3 className="font-semibold mb-2">Cost Breakdown</h3>
|
||||
<dl className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Turnover Rate</dt>
|
||||
<dd>{product?.turnover_rate?.toFixed(2) || '0.00'}</dd>
|
||||
<dt className="text-sm text-muted-foreground">Cost of Goods Sold</dt>
|
||||
<dd>${formatPrice(product?.metrics.cost_of_goods_sold)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">ABC Classification</dt>
|
||||
<dd>Class {product?.abc_class || 'N/A'}</dd>
|
||||
<dt className="text-sm text-muted-foreground">Landing Cost</dt>
|
||||
<dd>${formatPrice(product?.landing_cost_price)}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</Card>
|
||||
|
||||
<Card className="p-4">
|
||||
<h3 className="font-semibold mb-2">Profit Margin Trend</h3>
|
||||
<div className="h-64">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={combinedData?.monthly_sales || []}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="month" />
|
||||
<YAxis domain={[0, 100]} />
|
||||
<Tooltip />
|
||||
<Line type="monotone" dataKey="profit_margin" stroke="#82ca9d" name="Profit Margin %" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="vendor" className="space-y-4">
|
||||
{isLoading ? (
|
||||
<Skeleton className="h-48 w-full" />
|
||||
) : product?.vendor_performance ? (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-4">
|
||||
<h3 className="font-semibold mb-2">Vendor Performance</h3>
|
||||
<dl className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">On-Time Delivery</dt>
|
||||
<dd className="text-2xl font-semibold">{product.vendor_performance.on_time_delivery_rate.toFixed(1)}%</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Stock Status</dt>
|
||||
<dd>{product?.stock_status || 'N/A'}</dd>
|
||||
<dt className="text-sm text-muted-foreground">Order Fill Rate</dt>
|
||||
<dd className="text-2xl font-semibold">{product.vendor_performance.order_fill_rate.toFixed(1)}%</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Avg Lead Time</dt>
|
||||
<dd className="text-2xl font-semibold">{product.vendor_performance.avg_lead_time_days} days</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</Card>
|
||||
|
||||
<Card className="p-4">
|
||||
<h3 className="font-semibold mb-2">Order History</h3>
|
||||
<dl className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Total Orders</dt>
|
||||
<dd>{product.vendor_performance.total_orders}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Late Orders</dt>
|
||||
<dd>{product.vendor_performance.total_late_orders}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Total Purchase Value</dt>
|
||||
<dd>${formatPrice(product.vendor_performance.total_purchase_value)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Avg Order Value</dt>
|
||||
<dd>${formatPrice(product.vendor_performance.avg_order_value)}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</Card>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center text-muted-foreground">No vendor performance data available</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
Reference in New Issue
Block a user