Vitron-Front/app/components/cart/ShippingInformation.tsx
2026-04-29 01:44:16 +03:30

441 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* eslint-disable react-hooks/exhaustive-deps */
import {
Plus,
Menu,
Truck,
X,
Ellipsis,
CalendarDays,
MapPin,
} from "lucide-react";
import { Link } from "@remix-run/react";
import { Button } from "../ui/button";
import { useServiceGetAddresses } from "../../requestHandler/use-address-hooks";
import { useServiceGetCouriers } from "../../requestHandler/use-courier-hooks";
import { useEffect, useState } from "react";
import { DrawerContent, Drawer, DrawerHeader } from "../ui/drawer";
import { RadioGroup, RadioGroupItem } from "../ui/radio-group";
import { formatPrice } from "../../utils/helpers";
import { useToast } from "../../hooks/use-toast";
import { AdderssSettingDrawer } from "../../routes/profile.location._index";
import jMoment from "moment-jalaali";
import { Address, SellerCourierList } from "src/api/types";
export const ShippingInformation = ({
sellerId,
total,
setSelectedAddressInfo,
setSelectedCourierInfo,
setShippingInfoIsLoading,
selectedAddressInfo,
cartId,
}: {
sellerId: string;
total: string;
setSelectedAddressInfo: (address: Address) => void;
setSelectedCourierInfo: (courierId: SellerCourierList) => void;
setShippingInfoIsLoading: (isLoading: boolean) => void;
selectedAddressInfo?: Address | null;
cartId?: string;
}) => {
const [selectedCourier, setSelectedCourier] =
useState<SellerCourierList | null>(null);
const handleCourierSelection = (courier: SellerCourierList) => {
console.log("sss");
setSelectedCourier(courier);
setSelectedCourierInfo(courier);
};
return (
<>
<div className="flex flex-col w-full">
<AddressSelection
setSelectedAddressInfo={setSelectedAddressInfo}
total={total}
setShippingInfoIsLoading={setShippingInfoIsLoading}
selectedAddressInfo={selectedAddressInfo}
selectedCourier={selectedCourier}
cartId={cartId}
/>
<ShippingInformationSelection
setSelectedCourierInfo={handleCourierSelection}
sellerId={sellerId}
setShippingInfoIsLoading={setShippingInfoIsLoading}
/>
</div>
</>
);
};
const ShippingInformationSelection = ({
sellerId,
setSelectedCourierInfo,
setShippingInfoIsLoading,
}: {
sellerId: string;
setSelectedCourierInfo: (courierId: SellerCourierList) => void;
setShippingInfoIsLoading: (isLoading: boolean) => void;
}) => {
const { data: couriersData, isLoading } = useServiceGetCouriers(sellerId);
useEffect(() => {
setShippingInfoIsLoading(isLoading);
}, [isLoading]);
const [selectedCourierIndex, setSelectedCourierIndex] = useState<string>("");
const selectedCourier = couriersData?.filter(
(courier: SellerCourierList) => courier.id === selectedCourierIndex
)?.[0];
const isFaceToFace = selectedCourier?.courierIsFaceToFace;
useEffect(() => {
if (couriersData && couriersData.length > 0) {
setSelectedCourierIndex(couriersData[0].id || "");
}
}, [couriersData]);
useEffect(() => {
setSelectedCourierInfo(
couriersData?.find(
(courier: SellerCourierList) => courier.id === selectedCourierIndex
) as SellerCourierList
);
}, [selectedCourierIndex, couriersData]);
return (
<>
<div className="py-4 flex flex-col gap-4">
<div className="gap-1 px-4 flex items-center">
<div>
<Truck size={24} />
</div>
<p className={"text-B16 font-bold"}>انتخاب شیوه ارسال:</p>
</div>
{isLoading ? (
<p className="px-4">در حال بارگذاری...</p>
) : couriersData ? (
<>
<RadioGroup
onValueChange={(value) => setSelectedCourierIndex(value)}
value={selectedCourierIndex}
className="flex gap-4 px-4"
dir="rtl"
>
{couriersData.map((courier: SellerCourierList) => (
<div key={courier.id} className="flex items-center gap-1">
<RadioGroupItem
value={courier.id || ""}
id={`courier-${courier.id}`}
className="size-5"
/>
<label
htmlFor={`courier-${courier.id}`}
className="flex flex-col gap-1"
>
<p className="text-R14">{courier.courierName}</p>
</label>
</div>
))}
</RadioGroup>
<div className="py-6 px-4 bg-WHITE3 gap-4 flex flex-col">
{isFaceToFace ? (
<p className={"text-B12 line-clamp-1 w-full font-bold"}>
روش ارسال حضوری است و در همان محل تحویل خواهد شد
</p>
) : (
<>
<div className="gap-1 flex items-center">
<div>
<CalendarDays size={24} />
</div>
<p className={"text-B12 line-clamp-1 w-full font-bold"}>
زمان تحویل سفارش:
</p>
<p className="text-R12 w-full">
{formatDateRange(selectedCourier?.estimatedDays || 0)}
</p>
</div>
<div className="gap-1 flex items-center">
<div>
<Truck size={24} />
</div>
<p className={"text-B12 line-clamp-1 w-full font-bold"}>
هزینه ارسال:
</p>
<p className="text-R12 w-full">
{selectedCourier?.courierIsFreightCollect
? "پس پرداخت"
: `${formatPrice(
JSON.stringify(selectedCourier?.deliveryPrice)
)} تومان`}
</p>
</div>
</>
)}
</div>
</>
) : (
<p>هیچ روش ارسالی یافت نشد</p>
)}
</div>
</>
);
};
const AddressSelection = ({
total,
setSelectedAddressInfo,
setShippingInfoIsLoading,
selectedAddressInfo,
selectedCourier,
cartId,
}: {
total: string;
setSelectedAddressInfo: (addressInfo: Address) => void;
setShippingInfoIsLoading: (isLoading: boolean) => void;
selectedAddressInfo?: Address | null;
selectedCourier?: SellerCourierList | null;
cartId?: string;
}) => {
const { data, isLoading } = useServiceGetAddresses();
const [selectedAddress, setSelectedAddress] = useState<Address | null>(
selectedAddressInfo || null
);
useEffect(() => {
setShippingInfoIsLoading(isLoading);
}, [isLoading]);
const defaultAddress = data?.find((address: Address) => address.isDefault);
useEffect(() => {
if (!selectedAddress && defaultAddress) {
setSelectedAddress(defaultAddress);
setSelectedAddressInfo(defaultAddress);
}
}, [defaultAddress]);
const handleAddressChange = (address: Address) => {
setSelectedAddress(address);
setSelectedAddressInfo(address);
};
const [settingsDrawerOpen, setSettingsDrawerOpen] = useState(false);
const [selectedAddressForSettings, setSelectedAddressForSettings] =
useState<Address | null>(null);
const openSettingsDrawer = (address: Address) => {
setSelectedAddressForSettings(address);
setSettingsDrawerOpen(true);
};
const isFaceToFace = selectedCourier?.courierIsFaceToFace;
return (
<>
<div className="py-4 flex flex-col gap-4 border-b px-4">
<div className="gap-1 flex items-center">
<div>
<MapPin size={24} />
</div>
<p className={"text-B16 font-bold"}>آدرس دریافت سفارش</p>
</div>
<div
onKeyDown={() => {}}
role="button"
tabIndex={0}
className={`w-full flex gap-2 items-center py-4`}
>
<div className={`flex flex-col w-full`}>
{selectedAddress || defaultAddress ? (
<>
<p className="text-B14 font-bold text-GRAY">
{selectedAddress?.title || defaultAddress?.title}
</p>
<p className="text-R12 line-clamp-1 text-GRAY">
{selectedAddress?.mainAddress || defaultAddress?.mainAddress}
</p>
</>
) : (
<>
{isFaceToFace ? (
<>
<p className="text-R12 text-GRAY">
برای دریافت حضوری نیازی به آدرس نیست
</p>
</>
) : (
<>
<p className="text-B14 font-bold text-RED">
آدرسی اضافه نشده است
</p>
<p className="text-R12 text-GRAY">
برای ادامه خرید، لطفا یک آدرس جدید اضافه کنید
</p>
</>
)}
</>
)}
</div>
{(selectedAddress || defaultAddress) && (
<ChangeDefaulAddressDrawer
total={total}
onOpenSettings={openSettingsDrawer}
selectedAddress={selectedAddress}
onAddressChange={handleAddressChange}
/>
)}
</div>
{!isFaceToFace && (
<Link to={cartId ? `/profile/location/add?returnTo=cart&cartId=${cartId}` : "/profile/location/add"}>
<Button size={"xl"} variant="primary" className="w-full">
<Plus />
افزودن آدرس جدید
</Button>
</Link>
)}
</div>
<AdderssSettingDrawer
isOpen={settingsDrawerOpen}
setIsOpen={setSettingsDrawerOpen}
address={selectedAddressForSettings}
/>
</>
);
};
const ChangeDefaulAddressDrawer = ({
total,
onOpenSettings,
selectedAddress,
onAddressChange,
}: {
total: string;
onOpenSettings: (address: Address) => void;
selectedAddress: Address | null;
onAddressChange: (address: Address) => void;
}) => {
const [isOpen, setIsOpen] = useState(false);
const { data: dataAddress } = useServiceGetAddresses();
const [address, setAddress] = useState(selectedAddress?.id);
const { toast } = useToast();
useEffect(() => {
if (selectedAddress) {
setAddress(selectedAddress.id);
}
}, [selectedAddress]);
const changeShippingAddress = () => {
const newAddress = dataAddress?.find(
(addr: Address) => addr.id === address
);
if (newAddress) {
onAddressChange(newAddress);
setIsOpen(false);
toast({
title: "آدرس ارسال با موفقیت تغییر کرد",
});
}
};
return (
<>
<Menu className="text-6" onClick={() => setIsOpen(true)} />
<Drawer open={isOpen} onOpenChange={setIsOpen}>
<DrawerContent>
<DrawerHeader>
<div className="flex flex-row w-full relative items-center justify-center">
<X
className="absolute size-6 top-2 right-3"
onClick={() => setIsOpen(false)}
/>
<p className={"text-B12 font-bold pt-3"}>انتخاب آدرس ارسال</p>
</div>
</DrawerHeader>
{dataAddress && (
<div className="flex flex-col items-center border-b pt-2 mb-2 px-4 w-full">
<RadioGroup
onValueChange={(value) => setAddress(value)}
value={address}
className="w-full"
>
{dataAddress.map((address: Address, index: number) => (
<AddressRow
address={address}
key={index}
isLastItem={index === dataAddress?.length - 1}
onOpenSettings={() => {
setIsOpen(false);
onOpenSettings(address);
}}
/>
))}
</RadioGroup>
</div>
)}
<div className="flex gap-2 flex-row w-full py-1 px-4 bg-WHITE">
<Button
variant="dark"
size="lg"
className="w-full flex-1 font-bold"
onClick={changeShippingAddress}
>
تغییر آدرس ارسال
</Button>
<div className="flex-1 gap-2 w-full flex flex-col items-end justify-center">
<div className="flex gap-2 flex-col items-center">
<p className="text-B14 font-bold text-GRAY">مبلغ کل فاکتور:</p>
<p className="text-B14 font-bold">{formatPrice(total)} تومان</p>
</div>
</div>
</div>
</DrawerContent>
</Drawer>
</>
);
};
const AddressRow = ({
address,
isLastItem,
onOpenSettings,
}: {
address: Address;
isLastItem: boolean;
onOpenSettings: () => void;
}) => {
return (
<div
className={`flex py-4 items-center gap-2 flex-row-reverse ${!isLastItem ? "border-b" : ""}`}
>
<RadioGroupItem value={address?.id || ""} id={`radio-${address?.id}`} />
<label
className="w-full"
style={{ direction: "rtl" }}
htmlFor={`radio-${address?.id}`}
>
<p className="text-R14">{address?.title}</p>
<p className="text-R12 line-clamp-1">{address?.mainAddress}</p>
</label>
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onOpenSettings();
}}
className="p-1 hover:bg-WHITE3 rounded-full"
aria-label="تنظیمات آدرس"
>
<Ellipsis size={24} />
</button>
</div>
);
};
export const formatDateRange = (
untilDays?: number | null,
created_at?: string
) => {
const startDate = created_at ? jMoment(created_at) : jMoment();
if (!untilDays) return `از ${startDate.format("jYYYY/jMM/jDD")}`;
const deliveryDate = startDate.clone().add(untilDays, "days");
return `بین ${startDate.format("jYYYY/jMM/jDD")} تا ${deliveryDate.format("jYYYY/jMM/jDD")}`;
};