Simplifies the pricing tiers form by removing help text, card layout, and validation logic. Switches to a compact grid-based UI, adds editable total price field, and streamlines event handling for tier changes. Improves user experience and code maintainability.
133 lines
4.1 KiB
TypeScript
133 lines
4.1 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) => {
|
|
// 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);
|
|
};
|
|
|
|
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
|
|
) => {
|
|
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]
|
|
.sort((a, b) => a.minQuantity - b.minQuantity)
|
|
.map((tier, sortedIndex) => {
|
|
// 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)}
|
|
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)}
|
|
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>
|
|
);
|
|
};
|