Files
ember-market-frontend/components/forms/pricing-tiers.tsx
NotII 864e1e9804 Improve pricing tier input handling and precision
Refactored PricingTiers component to better handle empty, null, and undefined values, prevent formatting issues, and add error handling for tier changes. Updated ProductModal to use parseFloat for price values to improve precision. Minor robustness improvements to event handling and sorting.
2025-10-09 21:05:13 +01:00

142 lines
4.4 KiB
TypeScript

"use client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Trash, PlusCircle } from "lucide-react";
interface PricingTiersProps {
pricing: any[];
handleTierChange: (
e: React.ChangeEvent<HTMLInputElement>,
index: number
) => void;
handleRemoveTier: (index: number) => void;
handleAddTier: () => void;
}
export const PricingTiers = ({
pricing = [],
handleTierChange,
handleRemoveTier,
handleAddTier,
}: PricingTiersProps) => {
const formatNumber = (num: number) => {
if (isNaN(num) || num === null || num === undefined) return "";
// Return the number as-is without any formatting to prevent precision issues
return num.toString();
};
const formatTotal = (num: number) => {
return num.toFixed(2);
};
const calculateTotal = (quantity: number, pricePerUnit: number) => {
return formatTotal(quantity * pricePerUnit);
};
const handleTotalChange = (
e: React.ChangeEvent<HTMLInputElement>,
index: number,
minQuantity: number
) => {
if (!handleTierChange || !e || !e.target) return;
const totalPrice = Number(e.target.value);
const pricePerUnit = minQuantity > 0 ? totalPrice / minQuantity : 0;
// Create a simple synthetic event with the raw number
const syntheticEvent = {
target: {
name: 'pricePerUnit',
value: pricePerUnit.toString()
}
} as React.ChangeEvent<HTMLInputElement>;
try {
handleTierChange(syntheticEvent, index);
} catch (error) {
console.error('Error in handleTotalChange:', error);
}
};
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]
.sort((a, b) => (a?.minQuantity || 0) - (b?.minQuantity || 0))
.map((tier, sortedIndex) => {
if (!tier) return null;
// Find the original index for proper event handling
const originalIndex = pricing.findIndex(p =>
p === tier || (p?.minQuantity === tier?.minQuantity && p?.pricePerUnit === tier?.pricePerUnit)
);
return (
<div
key={tier._id || originalIndex}
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, originalIndex)}
className="h-8 text-sm px-2"
/>
<Input
name="pricePerUnit"
type="number"
placeholder="Price per unit"
value={tier?.pricePerUnit === 0 ? "" : formatNumber(tier?.pricePerUnit || 0)}
onChange={(e) => handleTierChange?.(e, originalIndex)}
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, originalIndex, tier?.minQuantity || 0)}
className="h-8 text-sm px-2"
/>
<Button
variant="ghost"
size="icon"
className="text-red-500 hover:bg-red-100"
onClick={() => handleRemoveTier?.(originalIndex)}
>
<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>
);
};