cart refactor code
This commit is contained in:
@@ -220,7 +220,8 @@
|
|||||||
"plateNumberPlaceholder": "أدخل رقم السيارة",
|
"plateNumberPlaceholder": "أدخل رقم السيارة",
|
||||||
"inYourCart": "في سلة المشتريات",
|
"inYourCart": "في سلة المشتريات",
|
||||||
"updatedSuccessfully": "تم التحديث بنجاح",
|
"updatedSuccessfully": "تم التحديث بنجاح",
|
||||||
"editNote": "تعديل الملاحظة"
|
"editNote": "تعديل الملاحظة",
|
||||||
|
"selectTimeEstimate": "أدخل وقت التقديم"
|
||||||
},
|
},
|
||||||
"checkout": {
|
"checkout": {
|
||||||
"title": "الدفع",
|
"title": "الدفع",
|
||||||
|
|||||||
@@ -229,7 +229,8 @@
|
|||||||
"Cash": "Cash",
|
"Cash": "Cash",
|
||||||
"e-payment": "e-payment",
|
"e-payment": "e-payment",
|
||||||
"plateNumber": "Plate Number",
|
"plateNumber": "Plate Number",
|
||||||
"plateNumberPlaceholder": "Enter plate number"
|
"plateNumberPlaceholder": "Enter plate number",
|
||||||
|
"selectTimeEstimate": "Select Estimate Time"
|
||||||
},
|
},
|
||||||
"checkout": {
|
"checkout": {
|
||||||
"title": "Checkout",
|
"title": "Checkout",
|
||||||
|
|||||||
@@ -1,265 +0,0 @@
|
|||||||
// import { useGlobals } from "../../hooks/useGlobals";
|
|
||||||
import { Button, Divider } from "antd";
|
|
||||||
import BackIcon from "components/Icons/BackIcon";
|
|
||||||
import NextIcon from "components/Icons/NextIcon";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
|
||||||
import styles from "./CustomBottomSheet.module.css";
|
|
||||||
|
|
||||||
interface EstimateTimeBottomSheetProps {
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
onSave: (date: Date, time: string) => void;
|
|
||||||
initialDate?: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function EstimateTimeBottomSheet({
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
onSave,
|
|
||||||
initialDate = new Date(),
|
|
||||||
}: EstimateTimeBottomSheetProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const isRTL = false; // Default to LTR
|
|
||||||
const [selectedDate, setSelectedDate] = useState(initialDate);
|
|
||||||
const [selectedTime, setSelectedTime] = useState("12:00");
|
|
||||||
const [isAM, setIsAM] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSelectedDate(initialDate);
|
|
||||||
}, [initialDate]);
|
|
||||||
|
|
||||||
const formatDate = (date: Date) => {
|
|
||||||
return date.toLocaleDateString("en-US", {
|
|
||||||
weekday: "long",
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const goToPreviousDay = () => {
|
|
||||||
const newDate = new Date(selectedDate);
|
|
||||||
newDate.setDate(newDate.getDate() - 1);
|
|
||||||
setSelectedDate(newDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
const goToNextDay = () => {
|
|
||||||
const newDate = new Date(selectedDate);
|
|
||||||
newDate.setDate(newDate.getDate() + 1);
|
|
||||||
setSelectedDate(newDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTimeSelect = (time: string) => {
|
|
||||||
setSelectedTime(time);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSave = () => {
|
|
||||||
const [hours, minutes] = selectedTime.split(":");
|
|
||||||
const date = new Date(selectedDate);
|
|
||||||
date.setHours(
|
|
||||||
isAM ? parseInt(hours) : parseInt(hours) + 12,
|
|
||||||
parseInt(minutes),
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
onSave(date, `${selectedTime} ${isAM ? "AM" : "PM"}`);
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
const timeSlots = [
|
|
||||||
"12:00",
|
|
||||||
"1:00",
|
|
||||||
"2:00",
|
|
||||||
"3:00",
|
|
||||||
"4:00",
|
|
||||||
"5:00",
|
|
||||||
"6:00",
|
|
||||||
"7:00",
|
|
||||||
"8:00",
|
|
||||||
"9:00",
|
|
||||||
"10:00",
|
|
||||||
"11:00",
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ProBottomSheet
|
|
||||||
isOpen={isOpen}
|
|
||||||
onClose={onClose}
|
|
||||||
title="Select Estimate Time"
|
|
||||||
showCloseButton={true}
|
|
||||||
initialSnap={1}
|
|
||||||
height={"50vh"}
|
|
||||||
snapPoints={["50vh", "60vh"]}
|
|
||||||
>
|
|
||||||
<div style={{ padding: "0 20px" }}>
|
|
||||||
{/* Day Selection */}
|
|
||||||
<div style={{ marginBottom: 30 }}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
marginBottom: 20,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
icon={
|
|
||||||
isRTL ? (
|
|
||||||
<NextIcon className={styles.nextIcon} />
|
|
||||||
) : (
|
|
||||||
<BackIcon className={styles.backIcon} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={goToPreviousDay}
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
textAlign: "center",
|
|
||||||
padding: "0 20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: 600,
|
|
||||||
color: "rgba(95, 108, 123, 1)",
|
|
||||||
marginBottom: 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{formatDate(selectedDate)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
icon={
|
|
||||||
isRTL ? (
|
|
||||||
<BackIcon className={styles.backIcon} />
|
|
||||||
) : (
|
|
||||||
<NextIcon className={styles.nextIcon} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={goToNextDay}
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Time Selection */}
|
|
||||||
<div>
|
|
||||||
{/* AM/PM Toggle */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "end",
|
|
||||||
marginBottom: 20,
|
|
||||||
gap: 10,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type={isAM ? "primary" : "default"}
|
|
||||||
onClick={() => setIsAM(true)}
|
|
||||||
style={{
|
|
||||||
height: 32,
|
|
||||||
borderRadius: 888,
|
|
||||||
fontWeight: 600,
|
|
||||||
backgroundColor: isAM
|
|
||||||
? "rgba(255, 183, 0, 0.12)"
|
|
||||||
: "transparent",
|
|
||||||
color: isAM ? "rgba(204, 147, 0, 1)" : "rgba(95, 108, 123, 1)",
|
|
||||||
border: "none",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
AM
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type={!isAM ? "primary" : "default"}
|
|
||||||
onClick={() => setIsAM(false)}
|
|
||||||
style={{
|
|
||||||
height: 32,
|
|
||||||
borderRadius: 888,
|
|
||||||
fontWeight: 600,
|
|
||||||
backgroundColor: !isAM
|
|
||||||
? "rgba(255, 183, 0, 0.12)"
|
|
||||||
: "rgba(95, 108, 123, 0.05)",
|
|
||||||
color: !isAM ? "rgba(204, 147, 0, 1)" : "rgba(95, 108, 123, 1)",
|
|
||||||
border: "none",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
PM
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Time Slots Grid */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(4, 1fr)",
|
|
||||||
gap: 12,
|
|
||||||
marginBottom: 30,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{timeSlots.map((time) => (
|
|
||||||
<Button
|
|
||||||
key={time}
|
|
||||||
type={selectedTime === time ? "primary" : "default"}
|
|
||||||
onClick={() => handleTimeSelect(time)}
|
|
||||||
style={{
|
|
||||||
height: 32,
|
|
||||||
width: 70,
|
|
||||||
borderRadius: 888,
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: 16,
|
|
||||||
border: "none",
|
|
||||||
backgroundColor:
|
|
||||||
selectedTime === time
|
|
||||||
? "rgba(255, 183, 0, 0.12)"
|
|
||||||
: "rgba(95, 108, 123, 0.05)",
|
|
||||||
color:
|
|
||||||
selectedTime === time
|
|
||||||
? "rgba(204, 147, 0, 1)"
|
|
||||||
: "rgba(95, 108, 123, 1)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{time}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
{/* Save Button */}
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={handleSave}
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: 50,
|
|
||||||
borderRadius: 12,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: 600,
|
|
||||||
backgroundColor: "var(--primary)",
|
|
||||||
border: "none",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("cart.confirmTime")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ProBottomSheet>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,8 @@ import CouponCard from "pages/cart/components/CouponCard.tsx";
|
|||||||
import RewardWaiterCard from "pages/cart/components/RewardWaiterCard.tsx";
|
import RewardWaiterCard from "pages/cart/components/RewardWaiterCard.tsx";
|
||||||
import TableNumberCard from "pages/cart/components/TableNumberCard.tsx";
|
import TableNumberCard from "pages/cart/components/TableNumberCard.tsx";
|
||||||
import CarPlateCard from "pages/cart/components/CarPlateCard.tsx";
|
import CarPlateCard from "pages/cart/components/CarPlateCard.tsx";
|
||||||
|
import YouMightAlsoLike from "pages/cart/components/YouMightAlsoLike.tsx";
|
||||||
|
import TimeEstimateCard from "pages/cart/components/timeEstimate/TimeEstimateCard.tsx";
|
||||||
|
|
||||||
export default function CartDesktopLayout() {
|
export default function CartDesktopLayout() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -170,6 +172,8 @@ export default function CartDesktopLayout() {
|
|||||||
{/* Sidebar Column */}
|
{/* Sidebar Column */}
|
||||||
<Col xs={24} lg={8} xl={7}>
|
<Col xs={24} lg={8} xl={7}>
|
||||||
<div className={styles.desktopSidebar}>
|
<div className={styles.desktopSidebar}>
|
||||||
|
<YouMightAlsoLike />
|
||||||
|
|
||||||
<div className={styles.desktopSidebarCard}>
|
<div className={styles.desktopSidebarCard}>
|
||||||
<SpecialRequestCard />
|
<SpecialRequestCard />
|
||||||
</div>
|
</div>
|
||||||
@@ -183,6 +187,10 @@ export default function CartDesktopLayout() {
|
|||||||
|
|
||||||
{orderType === "pickup" && <CarPlateCard />}
|
{orderType === "pickup" && <CarPlateCard />}
|
||||||
|
|
||||||
|
{(orderType === "delivery" || orderType === "pickup") && (
|
||||||
|
<TimeEstimateCard />
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={styles.desktopSidebarCard}
|
className={styles.desktopSidebarCard}
|
||||||
style={{ "--animation-order": 1 } as React.CSSProperties}
|
style={{ "--animation-order": 1 } as React.CSSProperties}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import ProHeader from "components/ProHeader/ProHeader.tsx";
|
import ProHeader from "components/ProHeader/ProHeader.tsx";
|
||||||
import styles from "pages/cart/cart.module.css";
|
import styles from "pages/cart/cart.module.css";
|
||||||
import { Space, Card, Divider, Button, message } from "antd";
|
import { Space, Card, Divider, Button } from "antd";
|
||||||
import ProTitle from "components/ProTitle.tsx";
|
import ProTitle from "components/ProTitle.tsx";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import { colors, ProBlack2 } from "ThemeConstants.ts";
|
import { colors, ProBlack2 } from "ThemeConstants.ts";
|
||||||
@@ -14,14 +14,12 @@ import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
|
|||||||
import ProRatioGroups from "components/ProRatioGroups/ProRatioGroups.tsx";
|
import ProRatioGroups from "components/ProRatioGroups/ProRatioGroups.tsx";
|
||||||
import {
|
import {
|
||||||
updateCollectionMethod,
|
updateCollectionMethod,
|
||||||
updateEstimateTime,
|
|
||||||
selectCart,
|
selectCart,
|
||||||
} from "features/order/orderSlice.ts";
|
} from "features/order/orderSlice.ts";
|
||||||
|
|
||||||
import OrderSummary from "components/OrderSummary/OrderSummary.tsx";
|
import OrderSummary from "components/OrderSummary/OrderSummary.tsx";
|
||||||
import { EstimateTimeBottomSheet } from "components/CustomBottomSheet/EstimateTimeBottomSheet.tsx";
|
import { useAppSelector } from "redux/hooks.ts";
|
||||||
import { useAppSelector, useAppDispatch } from "redux/hooks.ts";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import SpecialRequestCard from "pages/cart/components/SpecialRequestCard.tsx";
|
import SpecialRequestCard from "pages/cart/components/SpecialRequestCard.tsx";
|
||||||
import useBreakPoint from "hooks/useBreakPoint.ts";
|
import useBreakPoint from "hooks/useBreakPoint.ts";
|
||||||
@@ -29,12 +27,11 @@ import CouponCard from "pages/cart/components/CouponCard.tsx";
|
|||||||
import RewardWaiterCard from "pages/cart/components/RewardWaiterCard.tsx";
|
import RewardWaiterCard from "pages/cart/components/RewardWaiterCard.tsx";
|
||||||
import CarPlateCard from "pages/cart/components/CarPlateCard.tsx";
|
import CarPlateCard from "pages/cart/components/CarPlateCard.tsx";
|
||||||
import TableNumberCard from "pages/cart/components/TableNumberCard.tsx";
|
import TableNumberCard from "pages/cart/components/TableNumberCard.tsx";
|
||||||
|
import TimeEstimateCard from "pages/cart/components/timeEstimate/TimeEstimateCard.tsx";
|
||||||
|
|
||||||
export default function CartMobileTabletLayout() {
|
export default function CartMobileTabletLayout() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const { items, collectionMethod } = useAppSelector(selectCart);
|
||||||
const { items, estimateTimeDate, collectionMethod } =
|
|
||||||
useAppSelector(selectCart);
|
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
const { isMobile, isTablet } = useBreakPoint();
|
const { isMobile, isTablet } = useBreakPoint();
|
||||||
@@ -44,18 +41,6 @@ export default function CartMobileTabletLayout() {
|
|||||||
const { themeName } = useAppSelector((state) => state.theme);
|
const { themeName } = useAppSelector((state) => state.theme);
|
||||||
const orderType = localStorage.getItem("orderType");
|
const orderType = localStorage.getItem("orderType");
|
||||||
|
|
||||||
const [estimateWay, setEstimateWay] = useState("now");
|
|
||||||
const [isEstimateTimeOpen, setIsEstimateTimeOpen] = useState(false);
|
|
||||||
|
|
||||||
const handleEstimateTimeSave = (date: Date, time: string) => {
|
|
||||||
dispatch(updateEstimateTime({ date, time }));
|
|
||||||
message.success(t("cart.estimateTime") + " " + t("updatedSuccessfully"));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEstimateTimeClose = () => {
|
|
||||||
setIsEstimateTimeOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getMenuItemImageStyle = () => {
|
const getMenuItemImageStyle = () => {
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return {
|
return {
|
||||||
@@ -239,24 +224,7 @@ export default function CartMobileTabletLayout() {
|
|||||||
|
|
||||||
{/* Estimate Time */}
|
{/* Estimate Time */}
|
||||||
{(orderType === "delivery" || orderType === "pickup") && (
|
{(orderType === "delivery" || orderType === "pickup") && (
|
||||||
<ProInputCard title={t("cart.estimateTime")}>
|
<TimeEstimateCard />
|
||||||
<ProRatioGroups
|
|
||||||
options={[
|
|
||||||
{ label: t("cart.now"), value: "now", price: "" },
|
|
||||||
{ label: t("cart.later"), value: "later", price: "" },
|
|
||||||
]}
|
|
||||||
value={estimateWay}
|
|
||||||
onRatioClick={(value) => {
|
|
||||||
if (value === "now") {
|
|
||||||
setEstimateWay(value);
|
|
||||||
handleEstimateTimeSave(new Date(), "now");
|
|
||||||
} else {
|
|
||||||
setEstimateWay(value);
|
|
||||||
setIsEstimateTimeOpen(true);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ProInputCard>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Collection Method */}
|
{/* Collection Method */}
|
||||||
@@ -353,13 +321,6 @@ export default function CartMobileTabletLayout() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile/Tablet Bottom Sheets */}
|
{/* Mobile/Tablet Bottom Sheets */}
|
||||||
|
|
||||||
<EstimateTimeBottomSheet
|
|
||||||
isOpen={isEstimateTimeOpen}
|
|
||||||
onClose={handleEstimateTimeClose}
|
|
||||||
initialDate={estimateTimeDate}
|
|
||||||
onSave={handleEstimateTimeSave}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/pages/cart/components/timeEstimate/BottomSheet.tsx
Normal file
33
src/pages/cart/components/timeEstimate/BottomSheet.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ProBottomSheet } from "components/ProBottomSheet/ProBottomSheet.tsx";
|
||||||
|
import Content from "pages/cart/components/timeEstimate/Content.tsx";
|
||||||
|
|
||||||
|
interface EstimateTimeBottomSheetProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSave: (date: Date, time: string) => void;
|
||||||
|
initialDate?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BottomSheet({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
onSave,
|
||||||
|
initialDate = new Date(),
|
||||||
|
}: EstimateTimeBottomSheetProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProBottomSheet
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
title={t("cart.selectTimeEstimate")}
|
||||||
|
showCloseButton={true}
|
||||||
|
initialSnap={1}
|
||||||
|
height={"50vh"}
|
||||||
|
snapPoints={["50vh", "60vh"]}
|
||||||
|
>
|
||||||
|
<Content onSave={onSave} initialDate={initialDate} onClose={onClose} />
|
||||||
|
</ProBottomSheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
248
src/pages/cart/components/timeEstimate/Content.tsx
Normal file
248
src/pages/cart/components/timeEstimate/Content.tsx
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
import { Button, Divider } from "antd";
|
||||||
|
import NextIcon from "components/Icons/NextIcon.tsx";
|
||||||
|
import styles from "components/CustomBottomSheet/CustomBottomSheet.module.css";
|
||||||
|
import BackIcon from "components/Icons/BackIcon.tsx";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const timeSlots = [
|
||||||
|
"12:00",
|
||||||
|
"1:00",
|
||||||
|
"2:00",
|
||||||
|
"3:00",
|
||||||
|
"4:00",
|
||||||
|
"5:00",
|
||||||
|
"6:00",
|
||||||
|
"7:00",
|
||||||
|
"8:00",
|
||||||
|
"9:00",
|
||||||
|
"10:00",
|
||||||
|
"11:00",
|
||||||
|
];
|
||||||
|
interface EstimateTimeContentProps {
|
||||||
|
onSave: (date: Date, time: string) => void;
|
||||||
|
initialDate: Date;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Content({
|
||||||
|
onSave,
|
||||||
|
initialDate,
|
||||||
|
onClose,
|
||||||
|
}: EstimateTimeContentProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const isRTL = false; // Default to LTR
|
||||||
|
const [selectedDate, setSelectedDate] = useState(initialDate);
|
||||||
|
const [selectedTime, setSelectedTime] = useState("12:00");
|
||||||
|
const [isAM, setIsAM] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedDate(initialDate);
|
||||||
|
}, [initialDate]);
|
||||||
|
|
||||||
|
const formatDate = (date: Date) => {
|
||||||
|
return date.toLocaleDateString("en-US", {
|
||||||
|
weekday: "long",
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToPreviousDay = () => {
|
||||||
|
const newDate = new Date(selectedDate);
|
||||||
|
newDate.setDate(newDate.getDate() - 1);
|
||||||
|
setSelectedDate(newDate);
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToNextDay = () => {
|
||||||
|
const newDate = new Date(selectedDate);
|
||||||
|
newDate.setDate(newDate.getDate() + 1);
|
||||||
|
setSelectedDate(newDate);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTimeSelect = (time: string) => {
|
||||||
|
setSelectedTime(time);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
const [hours, minutes] = selectedTime.split(":");
|
||||||
|
const date = new Date(selectedDate);
|
||||||
|
date.setHours(
|
||||||
|
isAM ? parseInt(hours) : parseInt(hours) + 12,
|
||||||
|
parseInt(minutes),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
onSave(date, `${selectedTime} ${isAM ? "AM" : "PM"}`);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "0 20px" }}>
|
||||||
|
{/* Day Selection */}
|
||||||
|
<div style={{ marginBottom: 30 }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginBottom: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={
|
||||||
|
isRTL ? (
|
||||||
|
<NextIcon className={styles.nextIcon} />
|
||||||
|
) : (
|
||||||
|
<BackIcon className={styles.backIcon} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={goToPreviousDay}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
textAlign: "center",
|
||||||
|
padding: "0 20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: "rgba(95, 108, 123, 1)",
|
||||||
|
marginBottom: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatDate(selectedDate)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={
|
||||||
|
isRTL ? (
|
||||||
|
<BackIcon className={styles.backIcon} />
|
||||||
|
) : (
|
||||||
|
<NextIcon className={styles.nextIcon} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={goToNextDay}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Time Selection */}
|
||||||
|
<div>
|
||||||
|
{/* AM/PM Toggle */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "end",
|
||||||
|
marginBottom: 20,
|
||||||
|
gap: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type={isAM ? "primary" : "default"}
|
||||||
|
onClick={() => setIsAM(true)}
|
||||||
|
style={{
|
||||||
|
height: 32,
|
||||||
|
borderRadius: 888,
|
||||||
|
fontWeight: 600,
|
||||||
|
backgroundColor: isAM ? "rgba(255, 183, 0, 0.12)" : "transparent",
|
||||||
|
color: isAM ? "rgba(204, 147, 0, 1)" : "rgba(95, 108, 123, 1)",
|
||||||
|
border: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
AM
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type={!isAM ? "primary" : "default"}
|
||||||
|
onClick={() => setIsAM(false)}
|
||||||
|
style={{
|
||||||
|
height: 32,
|
||||||
|
borderRadius: 888,
|
||||||
|
fontWeight: 600,
|
||||||
|
backgroundColor: !isAM
|
||||||
|
? "rgba(255, 183, 0, 0.12)"
|
||||||
|
: "rgba(95, 108, 123, 0.05)",
|
||||||
|
color: !isAM ? "rgba(204, 147, 0, 1)" : "rgba(95, 108, 123, 1)",
|
||||||
|
border: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
PM
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Time Slots Grid */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(4, 1fr)",
|
||||||
|
gap: 12,
|
||||||
|
marginBottom: 30,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{timeSlots.map((time) => (
|
||||||
|
<Button
|
||||||
|
key={time}
|
||||||
|
type={selectedTime === time ? "primary" : "default"}
|
||||||
|
onClick={() => handleTimeSelect(time)}
|
||||||
|
style={{
|
||||||
|
height: 32,
|
||||||
|
width: 70,
|
||||||
|
borderRadius: 888,
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 16,
|
||||||
|
border: "none",
|
||||||
|
backgroundColor:
|
||||||
|
selectedTime === time
|
||||||
|
? "rgba(255, 183, 0, 0.12)"
|
||||||
|
: "rgba(95, 108, 123, 0.05)",
|
||||||
|
color:
|
||||||
|
selectedTime === time
|
||||||
|
? "rgba(204, 147, 0, 1)"
|
||||||
|
: "rgba(95, 108, 123, 1)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{time}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
{/* Save Button */}
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleSave}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: 50,
|
||||||
|
borderRadius: 12,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 600,
|
||||||
|
backgroundColor: "var(--primary)",
|
||||||
|
border: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("cart.confirmTime")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
37
src/pages/cart/components/timeEstimate/Dialog.tsx
Normal file
37
src/pages/cart/components/timeEstimate/Dialog.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { Button, Modal } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import Content from "pages/cart/components/timeEstimate/Content.tsx";
|
||||||
|
|
||||||
|
interface EstimateTimeDialogProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSave: (date: Date, time: string) => void;
|
||||||
|
initialDate?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Dialog({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
onSave,
|
||||||
|
initialDate = new Date(),
|
||||||
|
}: EstimateTimeDialogProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={t("cart.selectTimeEstimate")}
|
||||||
|
open={isOpen}
|
||||||
|
onCancel={onClose}
|
||||||
|
footer={[
|
||||||
|
<Button key="cancel" onClick={onClose}>
|
||||||
|
{t("cart.cancel")}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
width={500}
|
||||||
|
destroyOnHidden
|
||||||
|
>
|
||||||
|
<Content onSave={onSave} initialDate={initialDate} onClose={onClose} />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
66
src/pages/cart/components/timeEstimate/TimeEstimateCard.tsx
Normal file
66
src/pages/cart/components/timeEstimate/TimeEstimateCard.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import ProRatioGroups from "components/ProRatioGroups/ProRatioGroups.tsx";
|
||||||
|
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { updateEstimateTime, selectCart } from "features/order/orderSlice.ts";
|
||||||
|
import { message } from "antd";
|
||||||
|
import { BottomSheet } from "pages/cart/components/timeEstimate/BottomSheet.tsx";
|
||||||
|
import { useAppDispatch, useAppSelector } from "redux/hooks.ts";
|
||||||
|
import useBreakPoint from "hooks/useBreakPoint.ts";
|
||||||
|
import { Dialog } from "pages/cart/components/timeEstimate/Dialog.tsx";
|
||||||
|
|
||||||
|
export default function TimeEstimateCard() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { estimateTimeDate } = useAppSelector(selectCart);
|
||||||
|
const { isDesktop } = useBreakPoint();
|
||||||
|
const [estimateWay, setEstimateWay] = useState("now");
|
||||||
|
const [isEstimateTimeOpen, setIsEstimateTimeOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleEstimateTimeSave = (date: Date, time: string) => {
|
||||||
|
dispatch(updateEstimateTime({ date, time }));
|
||||||
|
message.success(t("cart.estimateTime") + " " + t("updatedSuccessfully"));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEstimateTimeClose = () => {
|
||||||
|
setIsEstimateTimeOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProInputCard title={t("cart.estimateTime")}>
|
||||||
|
<ProRatioGroups
|
||||||
|
options={[
|
||||||
|
{ label: t("cart.now"), value: "now", price: "" },
|
||||||
|
{ label: t("cart.later"), value: "later", price: "" },
|
||||||
|
]}
|
||||||
|
value={estimateWay}
|
||||||
|
onRatioClick={(value) => {
|
||||||
|
if (value === "now") {
|
||||||
|
setEstimateWay(value);
|
||||||
|
handleEstimateTimeSave(new Date(), "now");
|
||||||
|
} else {
|
||||||
|
setEstimateWay(value);
|
||||||
|
setIsEstimateTimeOpen(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ProInputCard>
|
||||||
|
{isDesktop ? (
|
||||||
|
<Dialog
|
||||||
|
isOpen={isEstimateTimeOpen}
|
||||||
|
onClose={handleEstimateTimeClose}
|
||||||
|
initialDate={estimateTimeDate}
|
||||||
|
onSave={handleEstimateTimeSave}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<BottomSheet
|
||||||
|
isOpen={isEstimateTimeOpen}
|
||||||
|
onClose={handleEstimateTimeClose}
|
||||||
|
initialDate={estimateTimeDate}
|
||||||
|
onSave={handleEstimateTimeSave}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user