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>
|
<h3 className="font-semibold mb-2">Basic Information</h3>
|
||||||
<dl className="space-y-2">
|
<dl className="space-y-2">
|
||||||
<div>
|
<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>
|
<dd>{product?.brand || "N/A"}</dd>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
<dd>{product?.vendor || "N/A"}</dd>
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<dt className="text-sm text-muted-foreground">Categories</dt>
|
<dt className="text-sm text-muted-foreground">Categories</dt>
|
||||||
<dd className="flex flex-wrap gap-2">
|
<dd className="flex flex-wrap gap-2">
|
||||||
@@ -276,7 +284,7 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
|||||||
<dd>${formatPrice(product?.price)}</dd>
|
<dd>${formatPrice(product?.price)}</dd>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
<dd>${formatPrice(product?.regular_price)}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -294,7 +302,7 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
|||||||
<h3 className="font-semibold mb-2">Stock Status</h3>
|
<h3 className="font-semibold mb-2">Stock Status</h3>
|
||||||
<dl className="space-y-2">
|
<dl className="space-y-2">
|
||||||
<div>
|
<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>
|
<dd className="text-2xl font-semibold">{product?.stock_quantity}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -395,7 +403,7 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
|||||||
<h3 className="font-semibold mb-2">Current Stock</h3>
|
<h3 className="font-semibold mb-2">Current Stock</h3>
|
||||||
<dl className="grid grid-cols-3 gap-4">
|
<dl className="grid grid-cols-3 gap-4">
|
||||||
<div>
|
<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>
|
<dd className="text-2xl font-semibold">{product?.stock_quantity}</dd>
|
||||||
</div>
|
</div>
|
||||||
<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;
|
lead_time_status?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Column definition interface
|
// Column definition type
|
||||||
interface ColumnDef {
|
interface ColumnDef {
|
||||||
key: keyof Product | 'image';
|
key: keyof Product | 'image';
|
||||||
label: string;
|
label: string;
|
||||||
group: string;
|
group: string;
|
||||||
format?: (value: any) => string | number;
|
|
||||||
width?: string;
|
|
||||||
noLabel?: boolean;
|
noLabel?: boolean;
|
||||||
|
width?: string;
|
||||||
|
format?: (value: any) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define available columns with grouping
|
// Define available columns with their groups
|
||||||
const AVAILABLE_COLUMNS: ColumnDef[] = [
|
const AVAILABLE_COLUMNS: ColumnDef[] = [
|
||||||
// Image (special column)
|
|
||||||
{ key: 'image', label: 'Image', group: 'Basic Info', noLabel: true, width: 'w-[60px]' },
|
{ key: 'image', label: 'Image', group: 'Basic Info', noLabel: true, width: 'w-[60px]' },
|
||||||
|
{ key: 'title', label: 'Name', group: 'Basic Info' },
|
||||||
// Basic Info Group
|
{ key: 'SKU', label: 'SKU', group: 'Basic Info' },
|
||||||
{ key: 'title', label: 'Title', group: 'Basic Info' },
|
{ key: 'brand', label: 'Company', group: 'Basic Info' },
|
||||||
{ key: 'brand', label: 'Brand', group: 'Basic Info' },
|
{ key: 'vendor', label: 'Supplier', group: 'Basic Info' },
|
||||||
{ key: 'categories', label: 'Categories', group: 'Basic Info' },
|
{ key: 'vendor_reference', label: 'Supplier #', group: 'Basic Info' },
|
||||||
{ key: 'vendor', label: 'Vendor', group: 'Basic Info' },
|
{ key: 'barcode', label: 'UPC', group: 'Basic Info' },
|
||||||
{ key: 'vendor_reference', label: 'Vendor Reference', group: 'Basic Info' },
|
{ key: 'stock_quantity', label: 'Shelf Count', group: 'Stock', format: (v) => v?.toString() ?? '-' },
|
||||||
{ key: 'barcode', label: 'Barcode', group: 'Basic Info' },
|
{ key: 'stock_status', label: 'Stock Status', group: 'Stock' },
|
||||||
|
{ key: 'days_of_inventory', label: 'Days of Stock', group: 'Stock', format: (v) => v?.toFixed(1) ?? '-' },
|
||||||
// Inventory Group
|
{ key: 'abc_class', label: 'ABC Class', group: 'Stock' },
|
||||||
{ 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: 'price', label: 'Price', group: 'Pricing', format: (v) => v?.toFixed(2) ?? '-' },
|
{ 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: 'regular_price', label: 'Default Price', group: 'Pricing', format: (v) => v?.toFixed(2) ?? '-' },
|
||||||
{ key: 'cost_price', label: 'Cost 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) ?? '-' },
|
{ key: 'landing_cost_price', label: 'Landing Cost', group: 'Pricing', format: (v) => v?.toFixed(2) ?? '-' },
|
||||||
|
{ key: 'daily_sales_avg', label: 'Daily Sales', group: 'Sales', format: (v) => v?.toFixed(1) ?? '-' },
|
||||||
// Sales Metrics Group
|
{ key: 'weekly_sales_avg', label: 'Weekly Sales', group: 'Sales', format: (v) => v?.toFixed(1) ?? '-' },
|
||||||
{ key: 'daily_sales_avg', label: 'Daily Sales Avg', group: 'Sales Metrics', format: (v) => v?.toFixed(2) ?? '-' },
|
{ key: 'monthly_sales_avg', label: 'Monthly Sales', group: 'Sales', format: (v) => v?.toFixed(1) ?? '-' },
|
||||||
{ key: 'weekly_sales_avg', label: 'Weekly Sales Avg', group: 'Sales Metrics', format: (v) => v?.toFixed(2) ?? '-' },
|
{ key: 'first_sale_date', label: 'First Sale', group: 'Sales' },
|
||||||
{ key: 'monthly_sales_avg', label: 'Monthly Sales Avg', group: 'Sales Metrics', format: (v) => v?.toFixed(2) ?? '-' },
|
{ key: 'last_sale_date', label: 'Last Sale', group: 'Sales' },
|
||||||
{ key: 'avg_quantity_per_order', label: 'Avg Qty per Order', group: 'Sales Metrics', format: (v) => v?.toFixed(1) ?? '-' },
|
{ key: 'gmroi', label: 'GMROI', group: 'Financial', format: (v) => v?.toFixed(2) ?? '-' },
|
||||||
{ key: 'number_of_orders', label: 'Number of Orders', group: 'Sales Metrics', format: (v) => v?.toString() ?? '-' },
|
{ key: 'turnover_rate', label: 'Turnover Rate', group: 'Financial', format: (v) => v?.toFixed(2) ?? '-' },
|
||||||
{ key: 'first_sale_date', label: 'First Sale', group: 'Sales Metrics' },
|
{ key: 'avg_margin_percent', label: 'Margin %', group: 'Financial', format: (v) => v ? `${v.toFixed(1)}%` : '-' },
|
||||||
{ key: 'last_sale_date', label: 'Last Sale', group: 'Sales Metrics' },
|
{ 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) ?? '-' },
|
||||||
// Financial Metrics Group
|
{ key: 'lead_time_status', label: 'Lead Time Status', group: 'Lead Time' },
|
||||||
{ key: 'avg_margin_percent', label: 'Avg Margin %', group: 'Financial Metrics', format: (v) => v ? `${v.toFixed(1)}%` : '-' },
|
{ key: 'last_purchase_date', label: 'Last Purchase', group: 'Lead Time' },
|
||||||
{ 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' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Default visible columns
|
// Default visible columns
|
||||||
const DEFAULT_VISIBLE_COLUMNS: (keyof Product | 'image')[] = [
|
const DEFAULT_VISIBLE_COLUMNS: (keyof Product | 'image')[] = [
|
||||||
'image',
|
'image',
|
||||||
'title',
|
'title',
|
||||||
|
'SKU',
|
||||||
|
'brand',
|
||||||
|
'vendor',
|
||||||
'stock_quantity',
|
'stock_quantity',
|
||||||
'stock_status',
|
'stock_status',
|
||||||
'price',
|
'price',
|
||||||
'vendor',
|
'regular_price',
|
||||||
'brand',
|
'daily_sales_avg',
|
||||||
'categories',
|
'weekly_sales_avg',
|
||||||
|
'monthly_sales_avg',
|
||||||
];
|
];
|
||||||
|
|
||||||
export function Products() {
|
export function Products() {
|
||||||
|
|||||||
Reference in New Issue
Block a user