Rename some columns to match backend
This commit is contained in:
@@ -238,13 +238,21 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
||||
<h3 className="font-semibold mb-2">Basic Information</h3>
|
||||
<dl className="space-y-2">
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Brand</dt>
|
||||
<dt className="text-sm text-muted-foreground">Company</dt>
|
||||
<dd>{product?.brand || "N/A"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Vendor</dt>
|
||||
<dt className="text-sm text-muted-foreground">Supplier</dt>
|
||||
<dd>{product?.vendor || "N/A"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Supplier #</dt>
|
||||
<dd>{product?.vendor_reference || "N/A"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">UPC</dt>
|
||||
<dd>{product?.barcode || "N/A"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Categories</dt>
|
||||
<dd className="flex flex-wrap gap-2">
|
||||
@@ -276,7 +284,7 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
||||
<dd>${formatPrice(product?.price)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Regular Price</dt>
|
||||
<dt className="text-sm text-muted-foreground">Default Price</dt>
|
||||
<dd>${formatPrice(product?.regular_price)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
@@ -294,7 +302,7 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
||||
<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>
|
||||
<dt className="text-sm text-muted-foreground">Shelf Count</dt>
|
||||
<dd className="text-2xl font-semibold">{product?.stock_quantity}</dd>
|
||||
</div>
|
||||
<div>
|
||||
@@ -395,7 +403,7 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
||||
<h3 className="font-semibold mb-2">Current Stock</h3>
|
||||
<dl className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">Stock Quantity</dt>
|
||||
<dt className="text-sm text-muted-foreground">Shelf Count</dt>
|
||||
<dd className="text-2xl font-semibold">{product?.stock_quantity}</dd>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
DialogDescription,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
|
||||
interface Product {
|
||||
product_id: string;
|
||||
title: string;
|
||||
sku: string;
|
||||
stock_quantity: number;
|
||||
price: number;
|
||||
regular_price: number;
|
||||
cost_price: number;
|
||||
vendor: string;
|
||||
brand: string;
|
||||
categories: string;
|
||||
visible: boolean;
|
||||
managing_stock: boolean;
|
||||
}
|
||||
|
||||
interface ProductEditDialogProps {
|
||||
product: Product | null;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSave: (product: Product) => void;
|
||||
}
|
||||
|
||||
export function ProductEditDialog({
|
||||
product,
|
||||
open,
|
||||
onOpenChange,
|
||||
onSave,
|
||||
}: ProductEditDialogProps) {
|
||||
const [formData, setFormData] = useState<Partial<Product>>(product || {});
|
||||
|
||||
const handleChange = (field: keyof Product, value: any) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (product && formData) {
|
||||
onSave({ ...product, ...formData });
|
||||
}
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
if (!product) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Product - {product.title}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to the product details below.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit} className="space-y-4 py-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">Title</Label>
|
||||
<Input
|
||||
id="title"
|
||||
value={formData.title || ""}
|
||||
onChange={(e) => handleChange("title", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sku">SKU</Label>
|
||||
<Input
|
||||
id="sku"
|
||||
value={formData.sku || ""}
|
||||
onChange={(e) => handleChange("sku", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="price">Price</Label>
|
||||
<Input
|
||||
id="price"
|
||||
type="number"
|
||||
step="0.01"
|
||||
value={formData.price || ""}
|
||||
onChange={(e) => handleChange("price", parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="regular_price">Regular Price</Label>
|
||||
<Input
|
||||
id="regular_price"
|
||||
type="number"
|
||||
step="0.01"
|
||||
value={formData.regular_price || ""}
|
||||
onChange={(e) => handleChange("regular_price", parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cost_price">Cost Price</Label>
|
||||
<Input
|
||||
id="cost_price"
|
||||
type="number"
|
||||
step="0.01"
|
||||
value={formData.cost_price || ""}
|
||||
onChange={(e) => handleChange("cost_price", parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="vendor">Vendor</Label>
|
||||
<Input
|
||||
id="vendor"
|
||||
value={formData.vendor || ""}
|
||||
onChange={(e) => handleChange("vendor", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="brand">Brand</Label>
|
||||
<Input
|
||||
id="brand"
|
||||
value={formData.brand || ""}
|
||||
onChange={(e) => handleChange("brand", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="categories">Categories</Label>
|
||||
<Input
|
||||
id="categories"
|
||||
value={formData.categories || ""}
|
||||
onChange={(e) => handleChange("categories", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stock_quantity">Stock Quantity</Label>
|
||||
<Input
|
||||
id="stock_quantity"
|
||||
type="number"
|
||||
value={formData.stock_quantity || ""}
|
||||
onChange={(e) => handleChange("stock_quantity", parseInt(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 pt-8">
|
||||
<Switch
|
||||
id="managing_stock"
|
||||
checked={formData.managing_stock}
|
||||
onCheckedChange={(checked) => handleChange("managing_stock", checked)}
|
||||
/>
|
||||
<Label htmlFor="managing_stock">Track Stock</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
id="visible"
|
||||
checked={formData.visible}
|
||||
onCheckedChange={(checked) => handleChange("visible", checked)}
|
||||
/>
|
||||
<Label htmlFor="visible">Visible</Label>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="submit">Save changes</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -72,84 +72,61 @@ interface Product {
|
||||
lead_time_status?: string;
|
||||
}
|
||||
|
||||
// Column definition interface
|
||||
// Column definition type
|
||||
interface ColumnDef {
|
||||
key: keyof Product | 'image';
|
||||
label: string;
|
||||
group: string;
|
||||
format?: (value: any) => string | number;
|
||||
width?: string;
|
||||
noLabel?: boolean;
|
||||
width?: string;
|
||||
format?: (value: any) => string;
|
||||
}
|
||||
|
||||
// Define available columns with grouping
|
||||
// Define available columns with their groups
|
||||
const AVAILABLE_COLUMNS: ColumnDef[] = [
|
||||
// Image (special column)
|
||||
{ key: 'image', label: 'Image', group: 'Basic Info', noLabel: true, width: 'w-[60px]' },
|
||||
|
||||
// Basic Info Group
|
||||
{ key: 'title', label: 'Title', group: 'Basic Info' },
|
||||
{ key: 'brand', label: 'Brand', group: 'Basic Info' },
|
||||
{ key: 'categories', label: 'Categories', group: 'Basic Info' },
|
||||
{ key: 'vendor', label: 'Vendor', group: 'Basic Info' },
|
||||
{ key: 'vendor_reference', label: 'Vendor Reference', group: 'Basic Info' },
|
||||
{ key: 'barcode', label: 'Barcode', group: 'Basic Info' },
|
||||
|
||||
// Inventory Group
|
||||
{ key: 'stock_quantity', label: 'Stock Quantity', group: 'Inventory', format: (v) => v?.toString() ?? '-' },
|
||||
{ key: 'stock_status', label: 'Stock Status', group: 'Inventory' },
|
||||
{ key: 'days_of_inventory', label: 'Days of Inventory', group: 'Inventory', format: (v) => v?.toFixed(1) ?? '-' },
|
||||
{ key: 'reorder_point', label: 'Reorder Point', group: 'Inventory', format: (v) => v?.toString() ?? '-' },
|
||||
{ key: 'safety_stock', label: 'Safety Stock', group: 'Inventory', format: (v) => v?.toString() ?? '-' },
|
||||
{ key: 'moq', label: 'MOQ', group: 'Inventory', format: (v) => v?.toString() ?? '-' },
|
||||
{ key: 'uom', label: 'UOM', group: 'Inventory', format: (v) => v?.toString() ?? '-' },
|
||||
|
||||
// Pricing Group
|
||||
{ key: 'title', label: 'Name', group: 'Basic Info' },
|
||||
{ key: 'SKU', label: 'SKU', group: 'Basic Info' },
|
||||
{ key: 'brand', label: 'Company', group: 'Basic Info' },
|
||||
{ key: 'vendor', label: 'Supplier', group: 'Basic Info' },
|
||||
{ key: 'vendor_reference', label: 'Supplier #', group: 'Basic Info' },
|
||||
{ key: 'barcode', label: 'UPC', group: 'Basic Info' },
|
||||
{ key: 'stock_quantity', label: 'Shelf Count', group: 'Stock', format: (v) => v?.toString() ?? '-' },
|
||||
{ key: 'stock_status', label: 'Stock Status', group: 'Stock' },
|
||||
{ key: 'days_of_inventory', label: 'Days of Stock', group: 'Stock', format: (v) => v?.toFixed(1) ?? '-' },
|
||||
{ key: 'abc_class', label: 'ABC Class', group: 'Stock' },
|
||||
{ key: 'price', label: 'Price', group: 'Pricing', format: (v) => v?.toFixed(2) ?? '-' },
|
||||
{ key: 'regular_price', label: 'Regular Price', group: 'Pricing', format: (v) => v?.toFixed(2) ?? '-' },
|
||||
{ key: 'cost_price', label: 'Cost 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 Metrics Group
|
||||
{ key: 'daily_sales_avg', label: 'Daily Sales Avg', group: 'Sales Metrics', format: (v) => v?.toFixed(2) ?? '-' },
|
||||
{ key: 'weekly_sales_avg', label: 'Weekly Sales Avg', group: 'Sales Metrics', format: (v) => v?.toFixed(2) ?? '-' },
|
||||
{ key: 'monthly_sales_avg', label: 'Monthly Sales Avg', group: 'Sales Metrics', format: (v) => v?.toFixed(2) ?? '-' },
|
||||
{ key: 'avg_quantity_per_order', label: 'Avg Qty per Order', group: 'Sales Metrics', format: (v) => v?.toFixed(1) ?? '-' },
|
||||
{ key: 'number_of_orders', label: 'Number of Orders', group: 'Sales Metrics', format: (v) => v?.toString() ?? '-' },
|
||||
{ key: 'first_sale_date', label: 'First Sale', group: 'Sales Metrics' },
|
||||
{ key: 'last_sale_date', label: 'Last Sale', group: 'Sales Metrics' },
|
||||
|
||||
// Financial Metrics Group
|
||||
{ key: 'avg_margin_percent', label: 'Avg Margin %', group: 'Financial Metrics', format: (v) => v ? `${v.toFixed(1)}%` : '-' },
|
||||
{ key: 'total_revenue', label: 'Total Revenue', group: 'Financial Metrics', format: (v) => v?.toFixed(2) ?? '-' },
|
||||
{ key: 'inventory_value', label: 'Inventory Value', group: 'Financial Metrics', format: (v) => v?.toFixed(2) ?? '-' },
|
||||
{ key: 'cost_of_goods_sold', label: 'COGS', group: 'Financial Metrics', format: (v) => v?.toFixed(2) ?? '-' },
|
||||
{ key: 'gross_profit', label: 'Gross Profit', group: 'Financial Metrics', format: (v) => v?.toFixed(2) ?? '-' },
|
||||
{ key: 'gmroi', label: 'GMROI', group: 'Financial Metrics', format: (v) => v?.toFixed(2) ?? '-' },
|
||||
{ key: 'turnover_rate', label: 'Turnover Rate', group: 'Financial Metrics', format: (v) => v?.toFixed(2) ?? '-' },
|
||||
|
||||
// Purchase & Lead Time Group
|
||||
{ key: 'avg_lead_time_days', label: 'Avg Lead Time (Days)', group: 'Purchase & Lead Time', format: (v) => v?.toFixed(1) ?? '-' },
|
||||
{ key: 'current_lead_time', label: 'Current Lead Time', group: 'Purchase & Lead Time', format: (v) => v?.toFixed(1) ?? '-' },
|
||||
{ key: 'target_lead_time', label: 'Target Lead Time', group: 'Purchase & Lead Time', format: (v) => v?.toFixed(1) ?? '-' },
|
||||
{ key: 'lead_time_status', label: 'Lead Time Status', group: 'Purchase & Lead Time' },
|
||||
{ key: 'last_purchase_date', label: 'Last Purchase', group: 'Purchase & Lead Time' },
|
||||
{ key: 'last_received_date', label: 'Last Received', group: 'Purchase & Lead Time' },
|
||||
|
||||
// Classification Group
|
||||
{ key: 'abc_class', label: 'ABC Class', group: 'Classification' },
|
||||
{ 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) ?? '-' },
|
||||
{ key: 'first_sale_date', label: 'First Sale', group: 'Sales' },
|
||||
{ key: 'last_sale_date', label: 'Last Sale', group: 'Sales' },
|
||||
{ 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: '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' },
|
||||
{ key: 'last_purchase_date', label: 'Last Purchase', group: 'Lead Time' },
|
||||
];
|
||||
|
||||
// Default visible columns
|
||||
const DEFAULT_VISIBLE_COLUMNS: (keyof Product | 'image')[] = [
|
||||
'image',
|
||||
'title',
|
||||
'SKU',
|
||||
'brand',
|
||||
'vendor',
|
||||
'stock_quantity',
|
||||
'stock_status',
|
||||
'price',
|
||||
'vendor',
|
||||
'brand',
|
||||
'categories',
|
||||
'regular_price',
|
||||
'daily_sales_avg',
|
||||
'weekly_sales_avg',
|
||||
'monthly_sales_avg',
|
||||
];
|
||||
|
||||
export function Products() {
|
||||
|
||||
Reference in New Issue
Block a user