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.
This commit is contained in:
NotII
2025-10-09 21:05:13 +01:00
parent 32bf9d790f
commit 864e1e9804
4 changed files with 30 additions and 21 deletions

View File

@@ -15,15 +15,15 @@ interface PricingTiersProps {
} }
export const PricingTiers = ({ export const PricingTiers = ({
pricing, pricing = [],
handleTierChange, handleTierChange,
handleRemoveTier, handleRemoveTier,
handleAddTier, handleAddTier,
}: PricingTiersProps) => { }: PricingTiersProps) => {
const formatNumber = (num: number) => { const formatNumber = (num: number) => {
// Only format to 2 decimal places if the number has decimal places if (isNaN(num) || num === null || num === undefined) return "";
// This prevents cursor jumping when user types whole numbers // Return the number as-is without any formatting to prevent precision issues
return num % 1 === 0 ? num.toString() : num.toFixed(2); return num.toString();
}; };
const formatTotal = (num: number) => { const formatTotal = (num: number) => {
@@ -39,17 +39,24 @@ export const PricingTiers = ({
index: number, index: number,
minQuantity: number minQuantity: number
) => { ) => {
if (!handleTierChange || !e || !e.target) return;
const totalPrice = Number(e.target.value); const totalPrice = Number(e.target.value);
const pricePerUnit = minQuantity > 0 ? totalPrice / minQuantity : 0; const pricePerUnit = minQuantity > 0 ? totalPrice / minQuantity : 0;
// Create a simple synthetic event with the raw number
const syntheticEvent = { const syntheticEvent = {
target: { target: {
name: 'pricePerUnit', name: 'pricePerUnit',
value: formatNumber(pricePerUnit) value: pricePerUnit.toString()
} }
} as React.ChangeEvent<HTMLInputElement>; } as React.ChangeEvent<HTMLInputElement>;
handleTierChange(syntheticEvent, index); try {
handleTierChange(syntheticEvent, index);
} catch (error) {
console.error('Error in handleTotalChange:', error);
}
}; };
return ( return (
@@ -66,11 +73,13 @@ export const PricingTiers = ({
</div> </div>
{[...pricing] {[...pricing]
.sort((a, b) => a.minQuantity - b.minQuantity) .sort((a, b) => (a?.minQuantity || 0) - (b?.minQuantity || 0))
.map((tier, sortedIndex) => { .map((tier, sortedIndex) => {
if (!tier) return null;
// Find the original index for proper event handling // Find the original index for proper event handling
const originalIndex = pricing.findIndex(p => const originalIndex = pricing.findIndex(p =>
p === tier || (p.minQuantity === tier.minQuantity && p.pricePerUnit === tier.pricePerUnit) p === tier || (p?.minQuantity === tier?.minQuantity && p?.pricePerUnit === tier?.pricePerUnit)
); );
return ( return (
<div <div
@@ -81,8 +90,8 @@ export const PricingTiers = ({
name="minQuantity" name="minQuantity"
type="number" type="number"
placeholder="Min Quantity" placeholder="Min Quantity"
value={tier.minQuantity === 0 ? "" : tier.minQuantity} value={tier?.minQuantity === 0 ? "" : (tier?.minQuantity || "")}
onChange={(e) => handleTierChange(e, originalIndex)} onChange={(e) => handleTierChange?.(e, originalIndex)}
className="h-8 text-sm px-2" className="h-8 text-sm px-2"
/> />
@@ -90,8 +99,8 @@ export const PricingTiers = ({
name="pricePerUnit" name="pricePerUnit"
type="number" type="number"
placeholder="Price per unit" placeholder="Price per unit"
value={tier.pricePerUnit === 0 ? "" : formatNumber(tier.pricePerUnit)} value={tier?.pricePerUnit === 0 ? "" : formatNumber(tier?.pricePerUnit || 0)}
onChange={(e) => handleTierChange(e, originalIndex)} onChange={(e) => handleTierChange?.(e, originalIndex)}
className="h-8 text-sm px-2" className="h-8 text-sm px-2"
/> />
@@ -99,11 +108,11 @@ export const PricingTiers = ({
type="number" type="number"
placeholder="Total price" placeholder="Total price"
value={ value={
tier.minQuantity && tier.pricePerUnit tier?.minQuantity && tier?.pricePerUnit
? calculateTotal(tier.minQuantity, tier.pricePerUnit) ? calculateTotal(tier.minQuantity, tier.pricePerUnit)
: "" : ""
} }
onChange={(e) => handleTotalChange(e, originalIndex, tier.minQuantity)} onChange={(e) => handleTotalChange(e, originalIndex, tier?.minQuantity || 0)}
className="h-8 text-sm px-2" className="h-8 text-sm px-2"
/> />
@@ -111,7 +120,7 @@ export const PricingTiers = ({
variant="ghost" variant="ghost"
size="icon" size="icon"
className="text-red-500 hover:bg-red-100" className="text-red-500 hover:bg-red-100"
onClick={() => handleRemoveTier(originalIndex)} onClick={() => handleRemoveTier?.(originalIndex)}
> >
<Trash className="h-5 w-5" /> <Trash className="h-5 w-5" />
</Button> </Button>
@@ -123,7 +132,7 @@ export const PricingTiers = ({
<p className="text-sm text-gray-500 mt-2">No pricing tiers added.</p> <p className="text-sm text-gray-500 mt-2">No pricing tiers added.</p>
)} )}
<Button variant="outline" size="sm" className="mt-2" onClick={handleAddTier}> <Button variant="outline" size="sm" className="mt-2" onClick={() => handleAddTier?.()}>
<PlusCircle className="w-4 h-4 mr-1" /> <PlusCircle className="w-4 h-4 mr-1" />
Add Tier Add Tier
</Button> </Button>

View File

@@ -142,7 +142,7 @@ export const ProductModal: React.FC<ProductModalProps> = ({
...prev, ...prev,
pricing: prev.pricing.map((tier, i) => pricing: prev.pricing.map((tier, i) =>
i === index i === index
? { ...tier, [name]: value === "" ? 0 : Number(value) } ? { ...tier, [name]: value === "" ? 0 : parseFloat(value) || 0 }
: tier : tier
), ),
})); }));

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "my-v0-project", "name": "my-v0-project",
"version": "2.1.0", "version": "2.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "my-v0-project", "name": "my-v0-project",
"version": "2.1.0", "version": "2.2.0",
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.9.1", "@hookform/resolvers": "^3.9.1",
"@radix-ui/react-accordion": "^1.2.2", "@radix-ui/react-accordion": "^1.2.2",

View File

@@ -1,4 +1,4 @@
{ {
"commitHash": "74b7aa4", "commitHash": "32bf9d7",
"buildTime": "2025-09-23T12:09:08.230Z" "buildTime": "2025-10-09T19:56:57.229Z"
} }