441 lines
14 KiB
TypeScript
441 lines
14 KiB
TypeScript
/* 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")}`;
|
||
};
|