191 lines
5.6 KiB
TypeScript
191 lines
5.6 KiB
TypeScript
import { useState } from "react";
|
||
import { Plus, Minus, Trash2 } from "lucide-react";
|
||
import { Dialog, DialogContent, DialogOverlay } from "../ui/dialog";
|
||
import { Button } from "../ui/button";
|
||
|
||
interface QuantityControlProps {
|
||
quantity: number;
|
||
maxQuantity: number;
|
||
onIncrease: () => void;
|
||
onDecrease: () => void;
|
||
onRemove?: () => void;
|
||
isPending: boolean;
|
||
allowManualEdit?: boolean;
|
||
onQuantityChange?: (quantity: number) => void;
|
||
}
|
||
|
||
export const QuantityControl = ({
|
||
quantity,
|
||
maxQuantity,
|
||
onIncrease,
|
||
onDecrease,
|
||
onRemove,
|
||
isPending,
|
||
allowManualEdit = false,
|
||
onQuantityChange,
|
||
}: QuantityControlProps) => {
|
||
const [openDeleteModal, setOpenDeleteModal] = useState(false);
|
||
const [isEditing, setIsEditing] = useState(false);
|
||
const [editValue, setEditValue] = useState(quantity.toString());
|
||
|
||
const handleEditStart = () => {
|
||
if (allowManualEdit) {
|
||
setIsEditing(true);
|
||
setEditValue(quantity.toString());
|
||
}
|
||
};
|
||
|
||
const handleEditConfirm = () => {
|
||
const newQuantity = parseInt(editValue);
|
||
if (!isNaN(newQuantity) && newQuantity > 0 && newQuantity <= maxQuantity) {
|
||
onQuantityChange?.(newQuantity);
|
||
}
|
||
setIsEditing(false);
|
||
};
|
||
|
||
const handleEditCancel = () => {
|
||
setIsEditing(false);
|
||
setEditValue(quantity.toString());
|
||
};
|
||
|
||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||
if (e.key === "Enter") {
|
||
handleEditConfirm();
|
||
} else if (e.key === "Escape") {
|
||
handleEditCancel();
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="flex gap-6 items-center rounded-[100px] bg-WHITE3 py-2 px-4">
|
||
<Plus
|
||
onClick={() => {
|
||
if (quantity < maxQuantity) {
|
||
onIncrease();
|
||
}
|
||
}}
|
||
className={`${quantity >= maxQuantity ? "text-GRAY" : "text-BLACK"} cursor-pointer`}
|
||
/>
|
||
|
||
{isEditing ? (
|
||
<input
|
||
type="number"
|
||
value={editValue}
|
||
onChange={(e) => setEditValue(e.target.value)}
|
||
onBlur={handleEditConfirm}
|
||
onKeyDown={handleKeyDown}
|
||
className="px-2 text-B14 font-bold w-12 text-center bg-transparent border-none outline-none"
|
||
min="1"
|
||
max={maxQuantity}
|
||
/>
|
||
) : (
|
||
<span
|
||
className={`px-2 text-B14 font-bold ${allowManualEdit ? "cursor-pointer hover:bg-WHITE3 rounded" : ""}`}
|
||
onClick={handleEditStart}
|
||
onKeyDown={(e) => {
|
||
if (e.key === "Enter" || e.key === " ") {
|
||
handleEditStart();
|
||
}
|
||
}}
|
||
role={allowManualEdit ? "button" : undefined}
|
||
tabIndex={allowManualEdit ? 0 : undefined}
|
||
>
|
||
{quantity}
|
||
</span>
|
||
)}
|
||
|
||
{quantity > 1 || !onRemove ? (
|
||
<Minus
|
||
size={24}
|
||
onClick={() => {
|
||
if (quantity > 1 || !onRemove) {
|
||
onDecrease();
|
||
}
|
||
}}
|
||
className={`${(quantity <= 1 && onRemove) || (quantity <= 0 && !onRemove) ? "text-GRAY" : "text-BLACK"} cursor-pointer`}
|
||
/>
|
||
) : (
|
||
<>
|
||
<Trash2
|
||
size={24}
|
||
className="text-RED cursor-pointer"
|
||
onClick={() => {
|
||
setOpenDeleteModal(true);
|
||
}}
|
||
/>
|
||
<RemoveCartModal
|
||
isPending={isPending}
|
||
onRemove={onRemove || (() => {})}
|
||
onOpenChange={setOpenDeleteModal}
|
||
open={openDeleteModal}
|
||
/>
|
||
</>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
interface RemoveCartModalProps {
|
||
isPending: boolean;
|
||
onRemove: () => void;
|
||
open: boolean;
|
||
onOpenChange: (open: boolean) => void;
|
||
}
|
||
|
||
const RemoveCartModal = ({
|
||
isPending,
|
||
onRemove,
|
||
open,
|
||
onOpenChange,
|
||
}: RemoveCartModalProps) => {
|
||
return (
|
||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||
<DialogOverlay />
|
||
<DialogContent className="p-4 bg-WHITE rounded-[15px] w-[calc(100%-40px)]">
|
||
<div className="flex flex-col gap-10">
|
||
<div className={"flex flex-col gap-3 items-center w-full"}>
|
||
<Trash2 className="text-RED text-6" />
|
||
<p className="text-B12 font-bold mt-1">حذف محصول از سبد خرید</p>
|
||
<p className="text-R12">این محصول از سبد شما حذف شود؟</p>
|
||
</div>
|
||
<div className="flex flex-row gap-6 w-full">
|
||
<Button
|
||
className="w-[100%]"
|
||
size={"sm"}
|
||
variant="dark"
|
||
onClick={() => onOpenChange(false)}
|
||
>
|
||
انصراف
|
||
</Button>
|
||
<Button
|
||
disabled={isPending}
|
||
className="w-[100%]"
|
||
size={"sm"}
|
||
variant="primary"
|
||
onClick={() => onRemove()}
|
||
>
|
||
{isPending ? (
|
||
<>
|
||
<div className="relative w-5 h-5 mr-2">
|
||
<div className="absolute top-0 left-0 w-full h-full border-2 border-t-RED border-r-transparent border-b-transparent border-l-transparent rounded-full animate-spin"></div>
|
||
<div
|
||
className="absolute top-0.5 left-0.5 w-4 h-4 border-2 border-t-transparent border-r-RED border-b-transparent border-l-transparent rounded-full animate-spin"
|
||
style={{
|
||
animationDirection: "reverse",
|
||
animationDuration: "0.7s",
|
||
}}
|
||
></div>
|
||
</div>
|
||
درحال حذف...
|
||
</>
|
||
) : (
|
||
"حذف از سبد خرید"
|
||
)}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
};
|