Add some additional existing data points to products table (partly broken)

This commit is contained in:
2025-03-29 10:44:13 -04:00
parent 7aa494aaad
commit 0796518e26
6 changed files with 455 additions and 2 deletions

View File

@@ -125,6 +125,11 @@ interface Product {
}>;
category_paths?: Record<string, string>;
description?: string;
preorder_count: number;
notions_inv_count: number;
}
interface ProductDetailProps {
@@ -225,6 +230,7 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
<TabsTrigger value="purchase">Purchase History</TabsTrigger>
<TabsTrigger value="financial">Financial</TabsTrigger>
<TabsTrigger value="vendor">Vendor</TabsTrigger>
<TabsTrigger value="details">Additional Info</TabsTrigger>
</TabsList>
</div>
@@ -255,6 +261,12 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
<dt className="text-sm text-muted-foreground">UPC</dt>
<dd>{product?.barcode || "N/A"}</dd>
</div>
{product?.description && (
<div>
<dt className="text-sm text-muted-foreground">Description</dt>
<dd>{product.description}</dd>
</div>
)}
<div>
<dt className="text-sm text-muted-foreground">Categories</dt>
<dd className="flex flex-col gap-2">
@@ -359,6 +371,51 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
</div>
</Card>
<Card className="p-4">
<h3 className="font-semibold mb-2">Customer Engagement</h3>
<dl className="grid grid-cols-3 gap-4">
{product?.total_sold > 0 && (
<div>
<dt className="text-sm text-muted-foreground">Total Sold</dt>
<dd className="text-2xl font-semibold">{product.total_sold}</dd>
</div>
)}
{product?.rating > 0 && (
<div>
<dt className="text-sm text-muted-foreground">Rating</dt>
<dd className="text-2xl font-semibold flex items-center">
{product.rating.toFixed(1)}
<span className="ml-1 text-yellow-500"></span>
</dd>
</div>
)}
{product?.reviews > 0 && (
<div>
<dt className="text-sm text-muted-foreground">Reviews</dt>
<dd className="text-2xl font-semibold">{product.reviews}</dd>
</div>
)}
{product?.baskets > 0 && (
<div>
<dt className="text-sm text-muted-foreground">In Baskets</dt>
<dd className="text-2xl font-semibold">{product.baskets}</dd>
</div>
)}
{product?.notifies > 0 && (
<div>
<dt className="text-sm text-muted-foreground">Notify Requests</dt>
<dd className="text-2xl font-semibold">{product.notifies}</dd>
</div>
)}
{product?.date_last_sold && (
<div>
<dt className="text-sm text-muted-foreground">Last Sold</dt>
<dd className="text-xl font-semibold">{formatDate(product.date_last_sold)}</dd>
</div>
)}
</dl>
</Card>
<Card className="p-4">
<h3 className="font-semibold mb-2">Financial Metrics</h3>
<dl className="space-y-2">
@@ -426,6 +483,18 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
<dt className="text-sm text-muted-foreground">Days of Inventory</dt>
<dd className="text-2xl font-semibold">{product?.metrics?.days_of_inventory || 0}</dd>
</div>
{product?.preorder_count > 0 && (
<div>
<dt className="text-sm text-muted-foreground">Preorders</dt>
<dd className="text-2xl font-semibold">{product?.preorder_count}</dd>
</div>
)}
{product?.notions_inv_count > 0 && (
<div>
<dt className="text-sm text-muted-foreground">Notions Inventory</dt>
<dd className="text-2xl font-semibold">{product?.notions_inv_count}</dd>
</div>
)}
</dl>
</Card>
@@ -506,6 +575,51 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
</ResponsiveContainer>
</div>
</Card>
<Card className="p-4">
<h3 className="font-semibold mb-2">Customer Engagement</h3>
<dl className="grid grid-cols-3 gap-4">
{product?.total_sold > 0 && (
<div>
<dt className="text-sm text-muted-foreground">Total Sold</dt>
<dd className="text-2xl font-semibold">{product.total_sold}</dd>
</div>
)}
{product?.rating > 0 && (
<div>
<dt className="text-sm text-muted-foreground">Rating</dt>
<dd className="text-2xl font-semibold flex items-center">
{product.rating.toFixed(1)}
<span className="ml-1 text-yellow-500"></span>
</dd>
</div>
)}
{product?.reviews > 0 && (
<div>
<dt className="text-sm text-muted-foreground">Reviews</dt>
<dd className="text-2xl font-semibold">{product.reviews}</dd>
</div>
)}
{product?.baskets > 0 && (
<div>
<dt className="text-sm text-muted-foreground">In Baskets</dt>
<dd className="text-2xl font-semibold">{product.baskets}</dd>
</div>
)}
{product?.notifies > 0 && (
<div>
<dt className="text-sm text-muted-foreground">Notify Requests</dt>
<dd className="text-2xl font-semibold">{product.notifies}</dd>
</div>
)}
{product?.date_last_sold && (
<div>
<dt className="text-sm text-muted-foreground">Last Sold</dt>
<dd className="text-xl font-semibold">{formatDate(product.date_last_sold)}</dd>
</div>
)}
</dl>
</Card>
</div>
)}
</TabsContent>
@@ -661,6 +775,123 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
<div className="text-center text-muted-foreground">No vendor performance data available</div>
)}
</TabsContent>
<TabsContent value="details" className="p-4">
{isLoading ? (
<Skeleton className="h-48 w-full" />
) : (
<div className="space-y-4">
<Card className="p-4">
<h3 className="font-semibold mb-2">Product Details</h3>
<dl className="grid grid-cols-2 gap-4">
{product?.description && (
<div className="col-span-2">
<dt className="text-sm text-muted-foreground">Description</dt>
<dd>{product.description}</dd>
</div>
)}
<div>
<dt className="text-sm text-muted-foreground">Created Date</dt>
<dd>{formatDate(product?.created_at)}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">Last Updated</dt>
<dd>{formatDate(product?.updated_at)}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">Product ID</dt>
<dd>{product?.pid}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">Line</dt>
<dd>{product?.line || 'N/A'}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">Subline</dt>
<dd>{product?.subline || 'N/A'}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">Artist</dt>
<dd>{product?.artist || 'N/A'}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">Country of Origin</dt>
<dd>{product?.country_of_origin || 'N/A'}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">Location</dt>
<dd>{product?.location || 'N/A'}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">HTS Code</dt>
<dd>{product?.harmonized_tariff_code || 'N/A'}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">Notions Reference</dt>
<dd>{product?.notions_reference || 'N/A'}</dd>
</div>
</dl>
</Card>
<Card className="p-4">
<h3 className="font-semibold mb-2">Physical Attributes</h3>
<dl className="grid grid-cols-2 gap-4">
<div>
<dt className="text-sm text-muted-foreground">Weight</dt>
<dd>{product?.weight ? `${product.weight} kg` : 'N/A'}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">Dimensions</dt>
<dd>
{product?.dimensions
? `${product.dimensions.length} × ${product.dimensions.width} × ${product.dimensions.height} cm`
: 'N/A'
}
</dd>
</div>
</dl>
</Card>
<Card className="p-4">
<h3 className="font-semibold mb-2">Customer Metrics</h3>
<dl className="grid grid-cols-2 gap-4">
<div>
<dt className="text-sm text-muted-foreground">Rating</dt>
<dd className="flex items-center">
{product?.rating
? <>
{product.rating.toFixed(1)}
<span className="ml-1 text-yellow-500"></span>
</>
: 'N/A'
}
</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">Review Count</dt>
<dd>{product?.reviews || 'N/A'}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">Total Sold</dt>
<dd>{product?.total_sold || 'N/A'}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">Currently in Baskets</dt>
<dd>{product?.baskets || 'N/A'}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">Notify Requests</dt>
<dd>{product?.notifies || 'N/A'}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">Date Last Sold</dt>
<dd>{formatDate(product?.date_last_sold) || 'N/A'}</dd>
</div>
</dl>
</Card>
</div>
)}
</TabsContent>
</Tabs>
</VaulDrawer.Content>
</VaulDrawer.Portal>

View File

@@ -56,6 +56,23 @@ const FILTER_OPTIONS: FilterOption[] = [
{ id: "vendor_reference", label: "Supplier #", type: "text", group: "Basic Info" },
{ id: "brand", label: "Brand", type: "select", group: "Basic Info" },
{ id: "category", label: "Category", type: "select", group: "Basic Info" },
{ id: "description", label: "Description", type: "text", group: "Basic Info" },
{ id: "harmonized_tariff_code", label: "HTS Code", type: "text", group: "Basic Info" },
{ id: "notions_reference", label: "Notions Ref", type: "text", group: "Basic Info" },
{ id: "line", label: "Line", type: "text", group: "Basic Info" },
{ id: "subline", label: "Subline", type: "text", group: "Basic Info" },
{ id: "artist", label: "Artist", type: "text", group: "Basic Info" },
{ id: "country_of_origin", label: "Origin", type: "text", group: "Basic Info" },
{ id: "location", label: "Location", type: "text", group: "Basic Info" },
// Physical Properties
{
id: "weight",
label: "Weight",
type: "number",
group: "Physical Properties",
operators: ["=", ">", ">=", "<", "<=", "between"],
},
// Inventory Group
{
@@ -79,6 +96,20 @@ const FILTER_OPTIONS: FilterOption[] = [
group: "Inventory",
operators: ["=", ">", ">=", "<", "<=", "between"],
},
{
id: "preorderCount",
label: "Preorder Count",
type: "number",
group: "Inventory",
operators: ["=", ">", ">=", "<", "<=", "between"],
},
{
id: "notionsInvCount",
label: "Notions Inventory",
type: "number",
group: "Inventory",
operators: ["=", ">", ">=", "<", "<=", "between"],
},
{
id: "daysOfStock",
label: "Days of Stock",
@@ -200,6 +231,47 @@ const FILTER_OPTIONS: FilterOption[] = [
type: "text",
group: "Sales Metrics",
},
{
id: "date_last_sold",
label: "Date Last Sold",
type: "text",
group: "Sales Metrics",
},
{
id: "total_sold",
label: "Total Sold",
type: "number",
group: "Sales Metrics",
operators: ["=", ">", ">=", "<", "<=", "between"],
},
{
id: "baskets",
label: "In Baskets",
type: "number",
group: "Sales Metrics",
operators: ["=", ">", ">=", "<", "<=", "between"],
},
{
id: "notifies",
label: "Notifies",
type: "number",
group: "Sales Metrics",
operators: ["=", ">", ">=", "<", "<=", "between"],
},
{
id: "rating",
label: "Rating",
type: "number",
group: "Sales Metrics",
operators: ["=", ">", ">=", "<", "<=", "between"],
},
{
id: "reviews",
label: "Reviews",
type: "number",
group: "Sales Metrics",
operators: ["=", ">", ">=", "<", "<=", "between"],
},
// Financial Metrics Group
{

View File

@@ -234,6 +234,11 @@ export function ProductTable({
)) || '-'}
</div>
);
case 'dimensions':
if (value) {
return `${value.length}×${value.width}×${value.height}`;
}
return '-';
case 'stock_status':
return getStockStatus(product.stock_status);
case 'abc_class':
@@ -252,6 +257,14 @@ export function ProductTable({
) : (
<Badge variant="outline">Non-Replenishable</Badge>
);
case 'rating':
if (value === undefined || value === null) return '-';
return (
<div className="flex items-center">
{value.toFixed(1)}
<span className="ml-1 text-yellow-500"></span>
</div>
);
default:
if (columnDef?.format && value !== undefined && value !== null) {
// For numeric formats (those using toFixed), ensure the value is a number

View File

@@ -52,8 +52,25 @@ const AVAILABLE_COLUMNS: ColumnDef[] = [
{ key: 'vendor', label: 'Supplier', group: 'Basic Info' },
{ key: 'vendor_reference', label: 'Supplier #', group: 'Basic Info' },
{ key: 'barcode', label: 'UPC', group: 'Basic Info' },
{ key: 'description', label: 'Description', group: 'Basic Info' },
{ key: 'created_at', label: 'Created', group: 'Basic Info' },
{ key: 'harmonized_tariff_code', label: 'HTS Code', group: 'Basic Info' },
{ key: 'notions_reference', label: 'Notions Ref', group: 'Basic Info' },
{ key: 'line', label: 'Line', group: 'Basic Info' },
{ key: 'subline', label: 'Subline', group: 'Basic Info' },
{ key: 'artist', label: 'Artist', group: 'Basic Info' },
{ key: 'country_of_origin', label: 'Origin', group: 'Basic Info' },
{ key: 'location', label: 'Location', group: 'Basic Info' },
// Physical properties
{ key: 'weight', label: 'Weight', group: 'Physical', format: (v) => v?.toString() ?? '-' },
{ key: 'dimensions', label: 'Dimensions', group: 'Physical', format: (v) => v ? `${v.length}x${v.width}x${v.height}` : '-' },
// Stock columns
{ key: 'stock_quantity', label: 'Shelf Count', group: 'Stock', format: (v) => v?.toString() ?? '-' },
{ key: 'stock_status', label: 'Stock Status', group: 'Stock' },
{ key: 'preorder_count', label: 'Preorders', group: 'Stock', format: (v) => v?.toString() ?? '-' },
{ key: 'notions_inv_count', label: 'Notions Inv', group: 'Stock', format: (v) => v?.toString() ?? '-' },
{ key: 'days_of_inventory', label: 'Days of Stock', group: 'Stock', format: (v) => v?.toFixed(1) ?? '-' },
{ key: 'weeks_of_inventory', label: 'Weeks of Stock', group: 'Stock', format: (v) => v?.toFixed(1) ?? '-' },
{ key: 'abc_class', label: 'ABC Class', group: 'Stock' },
@@ -63,10 +80,14 @@ const AVAILABLE_COLUMNS: ColumnDef[] = [
{ key: 'reorder_point', label: 'Reorder Point', group: 'Stock', format: (v) => v?.toString() ?? '-' },
{ key: 'safety_stock', label: 'Safety Stock', group: 'Stock', format: (v) => v?.toString() ?? '-' },
{ key: 'overstocked_amt', label: 'Overstock Amt', group: 'Stock', format: (v) => v?.toString() ?? '-' },
// Pricing columns
{ key: 'price', label: 'Price', group: 'Pricing', format: (v) => v?.toFixed(2) ?? '-' },
{ key: 'regular_price', label: 'Default Price', group: 'Pricing', format: (v) => v?.toFixed(2) ?? '-' },
{ key: 'cost_price', label: 'Cost', group: 'Pricing', format: (v) => v?.toFixed(2) ?? '-' },
{ key: 'landing_cost_price', label: 'Landing Cost', group: 'Pricing', format: (v) => v?.toFixed(2) ?? '-' },
// Sales columns
{ key: 'daily_sales_avg', label: 'Daily Sales', group: 'Sales', format: (v) => v?.toFixed(1) ?? '-' },
{ key: 'weekly_sales_avg', label: 'Weekly Sales', group: 'Sales', format: (v) => v?.toFixed(1) ?? '-' },
{ key: 'monthly_sales_avg', label: 'Monthly Sales', group: 'Sales', format: (v) => v?.toFixed(1) ?? '-' },
@@ -74,12 +95,22 @@ const AVAILABLE_COLUMNS: ColumnDef[] = [
{ key: 'number_of_orders', label: 'Order Count', group: 'Sales', format: (v) => v?.toString() ?? '-' },
{ key: 'first_sale_date', label: 'First Sale', group: 'Sales' },
{ key: 'last_sale_date', label: 'Last Sale', group: 'Sales' },
{ key: 'date_last_sold', label: 'Date Last Sold', group: 'Sales' },
{ key: 'total_sold', label: 'Total Sold', group: 'Sales', format: (v) => v?.toString() ?? '-' },
{ key: 'baskets', label: 'In Baskets', group: 'Sales', format: (v) => v?.toString() ?? '-' },
{ key: 'notifies', label: 'Notifies', group: 'Sales', format: (v) => v?.toString() ?? '-' },
{ key: 'rating', label: 'Rating', group: 'Sales', format: (v) => v ? v.toFixed(1) : '-' },
{ key: 'reviews', label: 'Reviews', group: 'Sales', format: (v) => v?.toString() ?? '-' },
// Financial columns
{ key: 'gmroi', label: 'GMROI', group: 'Financial', format: (v) => v?.toFixed(2) ?? '-' },
{ key: 'turnover_rate', label: 'Turnover Rate', group: 'Financial', format: (v) => v?.toFixed(2) ?? '-' },
{ key: 'avg_margin_percent', label: 'Margin %', group: 'Financial', format: (v) => v ? `${v.toFixed(1)}%` : '-' },
{ key: 'inventory_value', label: 'Inventory Value', group: 'Financial', format: (v) => v?.toFixed(2) ?? '-' },
{ key: 'cost_of_goods_sold', label: 'COGS', group: 'Financial', format: (v) => v?.toFixed(2) ?? '-' },
{ key: 'gross_profit', label: 'Gross Profit', group: 'Financial', format: (v) => v?.toFixed(2) ?? '-' },
// Lead Time columns
{ key: 'current_lead_time', label: 'Current Lead Time', group: 'Lead Time', format: (v) => v?.toFixed(1) ?? '-' },
{ key: 'target_lead_time', label: 'Target Lead Time', group: 'Lead Time', format: (v) => v?.toFixed(1) ?? '-' },
{ key: 'lead_time_status', label: 'Lead Time Status', group: 'Lead Time' },

View File

@@ -23,6 +23,30 @@ export interface Product {
created_at: string;
updated_at: string;
// New fields
description?: string;
preorder_count?: number;
notions_inv_count?: number;
harmonized_tariff_code?: string;
notions_reference?: string;
line?: string;
subline?: string;
artist?: string;
rating?: number;
reviews?: number;
weight?: number;
dimensions?: {
length: number;
width: number;
height: number;
};
country_of_origin?: string;
location?: string;
total_sold?: number;
baskets?: number;
notifies?: number;
date_last_sold?: string;
// Metrics
daily_sales_avg?: string; // numeric(15,3)
weekly_sales_avg?: string; // numeric(15,3)