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 = ({
pricing,
pricing = [],
handleTierChange,
handleRemoveTier,
handleAddTier,
}: PricingTiersProps) => {
const formatNumber = (num: number) => {
// Only format to 2 decimal places if the number has decimal places
// This prevents cursor jumping when user types whole numbers
return num % 1 === 0 ? num.toString() : num.toFixed(2);
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) => {
@@ -39,17 +39,24 @@ export const PricingTiers = ({
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: formatNumber(pricePerUnit)
value: pricePerUnit.toString()
}
} as React.ChangeEvent<HTMLInputElement>;
handleTierChange(syntheticEvent, index);
try {
handleTierChange(syntheticEvent, index);
} catch (error) {
console.error('Error in handleTotalChange:', error);
}
};
return (
@@ -66,11 +73,13 @@ export const PricingTiers = ({
</div>
{[...pricing]
.sort((a, b) => a.minQuantity - b.minQuantity)
.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)
p === tier || (p?.minQuantity === tier?.minQuantity && p?.pricePerUnit === tier?.pricePerUnit)
);
return (
<div
@@ -81,8 +90,8 @@ export const PricingTiers = ({
name="minQuantity"
type="number"
placeholder="Min Quantity"
value={tier.minQuantity === 0 ? "" : tier.minQuantity}
onChange={(e) => handleTierChange(e, originalIndex)}
value={tier?.minQuantity === 0 ? "" : (tier?.minQuantity || "")}
onChange={(e) => handleTierChange?.(e, originalIndex)}
className="h-8 text-sm px-2"
/>
@@ -90,8 +99,8 @@ export const PricingTiers = ({
name="pricePerUnit"
type="number"
placeholder="Price per unit"
value={tier.pricePerUnit === 0 ? "" : formatNumber(tier.pricePerUnit)}
onChange={(e) => handleTierChange(e, originalIndex)}
value={tier?.pricePerUnit === 0 ? "" : formatNumber(tier?.pricePerUnit || 0)}
onChange={(e) => handleTierChange?.(e, originalIndex)}
className="h-8 text-sm px-2"
/>
@@ -99,11 +108,11 @@ export const PricingTiers = ({
type="number"
placeholder="Total price"
value={
tier.minQuantity && tier.pricePerUnit
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"
/>
@@ -111,7 +120,7 @@ export const PricingTiers = ({
variant="ghost"
size="icon"
className="text-red-500 hover:bg-red-100"
onClick={() => handleRemoveTier(originalIndex)}
onClick={() => handleRemoveTier?.(originalIndex)}
>
<Trash className="h-5 w-5" />
</Button>
@@ -123,7 +132,7 @@ export const PricingTiers = ({
<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" />
Add Tier
</Button>

View File

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

4
package-lock.json generated
View File

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

View File

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