This commit is contained in:
NotII
2025-02-16 16:30:57 +00:00
parent 236fb223e4
commit 62e2602d28
5 changed files with 116 additions and 57 deletions

View File

@@ -105,7 +105,6 @@ export default function ProductsPage() {
};
// Save product data after modal form submission
const handleSaveProduct = async (data: Product, file?: File | null) => {
console.log("handleSaveProduct:", data, file);

View File

@@ -19,52 +19,106 @@ export const PricingTiers = ({
handleTierChange,
handleRemoveTier,
handleAddTier,
}: PricingTiersProps) => (
<div>
<h3 className="text-sm font-medium">Tiered Pricing</h3>
}: PricingTiersProps) => {
const formatNumber = (num: number) => {
// For price per unit, show up to 6 decimal places if needed
return Number(num.toFixed(6)).toString();
};
{pricing?.length > 0 ? (
pricing.map((tier, index) => (
<div key={tier._id || index} className="flex items-center gap-2 mt-2">
<Input
name="minQuantity"
type="number"
placeholder="Min Quantity"
value={tier.minQuantity === 0 ? "" : tier.minQuantity} // ✅ Show empty string when 0
onChange={(e) => handleTierChange(e, index)}
className="h-8 text-sm px-2 flex-1"
/>
const formatTotal = (num: number) => {
// For total price, always show 2 decimal places
return num.toFixed(2);
};
<Input
name="pricePerUnit"
type="number"
placeholder="Price per unit"
value={tier.pricePerUnit === 0 ? "" : tier.pricePerUnit} // ✅ Show empty string when 0
onChange={(e) => handleTierChange(e, index)}
className="h-8 text-sm px-2 flex-1"
/>
<Button
variant="ghost"
size="icon"
className="text-red-500 hover:bg-red-100"
onClick={() => handleRemoveTier(index)}
>
<Trash className="h-5 w-5" />
</Button>
</div>
))
) : (
<p className="text-sm text-gray-500 mt-2">No pricing tiers added.</p>
)}
const calculateTotal = (quantity: number, pricePerUnit: number) => {
return formatTotal(quantity * pricePerUnit);
};
<Button
variant="outline"
size="sm"
className="mt-2"
onClick={handleAddTier}
>
<PlusCircle className="w-4 h-4 mr-1" />
Add Tier
</Button>
</div>
);
const handleTotalChange = (
e: React.ChangeEvent<HTMLInputElement>,
index: number,
minQuantity: number
) => {
const totalPrice = Number(e.target.value);
const pricePerUnit = minQuantity > 0 ? totalPrice / minQuantity : 0;
const syntheticEvent = {
target: {
name: 'pricePerUnit',
value: formatNumber(pricePerUnit)
}
} as React.ChangeEvent<HTMLInputElement>;
handleTierChange(syntheticEvent, index);
};
return (
<div>
<h3 className="text-sm font-medium">Tiered Pricing</h3>
{pricing?.length > 0 ? (
<>
<div className="grid grid-cols-[1fr_1fr_1fr_auto] gap-2 mt-2 mb-1">
<div className="text-xs text-muted-foreground">Quantity</div>
<div className="text-xs text-muted-foreground">Price Per Unit</div>
<div className="text-xs text-muted-foreground">Total Price</div>
<div className="w-8" />
</div>
{pricing.map((tier, index) => (
<div
key={tier._id || index}
className="grid grid-cols-[1fr_1fr_1fr_auto] gap-2 mt-2"
>
<Input
name="minQuantity"
type="number"
placeholder="Min Quantity"
value={tier.minQuantity === 0 ? "" : tier.minQuantity}
onChange={(e) => handleTierChange(e, index)}
className="h-8 text-sm px-2"
/>
<Input
name="pricePerUnit"
type="number"
placeholder="Price per unit"
value={tier.pricePerUnit === 0 ? "" : formatNumber(tier.pricePerUnit)}
onChange={(e) => handleTierChange(e, index)}
className="h-8 text-sm px-2"
/>
<Input
type="number"
placeholder="Total price"
value={
tier.minQuantity && tier.pricePerUnit
? calculateTotal(tier.minQuantity, tier.pricePerUnit)
: ""
}
onChange={(e) => handleTotalChange(e, index, tier.minQuantity)}
className="h-8 text-sm px-2"
/>
<Button
variant="ghost"
size="icon"
className="text-red-500 hover:bg-red-100"
onClick={() => handleRemoveTier(index)}
>
<Trash className="h-5 w-5" />
</Button>
</div>
))}
</>
) : (
<p className="text-sm text-gray-500 mt-2">No pricing tiers added.</p>
)}
<Button variant="outline" size="sm" className="mt-2" onClick={handleAddTier}>
<PlusCircle className="w-4 h-4 mr-1" />
Add Tier
</Button>
</div>
);
};

View File

@@ -122,7 +122,7 @@ export const ProductModal: React.FC<ProductModalProps> = ({
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-6xl">
<DialogContent className="max-w-[95vw] lg:max-w-6xl w-full overflow-y-auto max-h-[90vh] z-[80]">
<DialogHeader>
<DialogTitle className="text-base">
{editing ? "Edit Product" : "Add Product"}

View File

@@ -18,6 +18,12 @@ const ProductTable = ({
onDelete,
getCategoryNameById
}: ProductTableProps) => {
const sortedProducts = [...products].sort((a, b) => {
const categoryNameA = getCategoryNameById(a.category);
const categoryNameB = getCategoryNameById(b.category);
return categoryNameA.localeCompare(categoryNameB);
});
return (
<div className="rounded-lg border dark:border-zinc-700 shadow-sm overflow-hidden">
<Table className="relative">
@@ -39,8 +45,8 @@ const ProductTable = ({
<TableCell>Loading...</TableCell>
</TableRow>
))
) : products.length > 0 ? (
products.map((product) => (
) : sortedProducts.length > 0 ? (
sortedProducts.map((product) => (
<TableRow key={product._id} className="transition-colors hover:bg-gray-50 dark:hover:bg-zinc-800/70">
<TableCell className="font-medium">{product.name}</TableCell>
<TableCell className="text-center">

View File

@@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"fixed inset-0 z-[75] bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
@@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
"fixed left-[50%] top-[50%] z-[76] grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}