Compare commits

...

2 Commits

Author SHA1 Message Date
788b05d6f4 enhance openening times UI styles 2026-01-15 11:51:19 +03:00
0ce2d320a8 intergrate openening times & show extras 2026-01-15 11:43:36 +03:00
8 changed files with 313 additions and 169 deletions

View File

@@ -170,7 +170,15 @@
"justXMorePurchasesToUnlockYourFREEItem": "فقط {{cups}} أكثر للفتح الوجبة المجانية!", "justXMorePurchasesToUnlockYourFREEItem": "فقط {{cups}} أكثر للفتح الوجبة المجانية!",
"youreJustXCupsAwayFromYourNextReward": "🎉 أنت فقط {{cups}} أكثر للحصول على المكافأة التالية!", "youreJustXCupsAwayFromYourNextReward": "🎉 أنت فقط {{cups}} أكثر للحصول على المكافأة التالية!",
"callWaiter": "اتصل بالرادير", "callWaiter": "اتصل بالرادير",
"balance": "الرصيد" "balance": "الرصيد",
"closed": "مغلق",
"sunday": "الأحد",
"monday": "الإثنين",
"tuesday": "الثلاثاء",
"wednesday": "الأربعاء",
"thursday": "الخميس",
"friday": "الجمعة",
"saturday": "السبت"
}, },
"cart": { "cart": {
"addSpecialRequestOptional": "إضافة طلب خاص (اختياري)", "addSpecialRequestOptional": "إضافة طلب خاص (اختياري)",

View File

@@ -182,7 +182,15 @@
"justXMorePurchasesToUnlockYourFREEItem": "Just {{cups}} more purchases to unlock your FREE item!", "justXMorePurchasesToUnlockYourFREEItem": "Just {{cups}} more purchases to unlock your FREE item!",
"youreJustXCupsAwayFromYourNextReward": "🎉 You're just {{cups}} stamps away from your next reward!", "youreJustXCupsAwayFromYourNextReward": "🎉 You're just {{cups}} stamps away from your next reward!",
"callWaiter": "Call Waiter", "callWaiter": "Call Waiter",
"balance": "Balance" "balance": "Balance",
"closed": "Closed",
"sunday": "Sunday",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday"
}, },
"cart": { "cart": {
"remainingToPay": "Remaining to Pay", "remainingToPay": "Remaining to Pay",

View File

@@ -1,22 +1,58 @@
import { Button } from "antd"; import { Card } from "antd";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet"; import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
import ProText from "components/ProText"; import ProText from "components/ProText";
import ProTitle from "components/ProTitle"; import ProTitle from "components/ProTitle";
import { useAppSelector } from "redux/hooks"; import { useAppSelector } from "redux/hooks";
import { useGetOpeningTimesQuery } from "redux/api/others";
import { useMemo } from "react";
import TimeIcon from "components/Icons/order/TimeIcon";
interface OpeningTimesBottomSheetProps { interface OpeningTimesBottomSheetProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
} }
const textStyle: React.CSSProperties = { // Helper function to format time (HH:mm to 12h format)
fontWeight: 400, const formatTime = (time: string | null | undefined): string => {
fontStyle: "Regular", if (!time) return "";
fontSize: 14,
lineHeight: "140%", // If already in 12h format (contains AM/PM), return as is
letterSpacing: "0%", if (time.includes("AM") || time.includes("PM")) {
marginBottom: 4, return time;
}
// Parse 24h format (HH:mm)
const [hours, minutes] = time.split(":");
const hour24 = parseInt(hours, 10);
if (isNaN(hour24)) return time;
const hour12 = hour24 % 12 || 12;
const ampm = hour24 >= 12 ? "PM" : "AM";
return `${hour12}:${minutes} ${ampm}`;
};
// Helper function to get time ranges for a day
const getDayTimes = (
openingTimes: any,
dayIndex: number,
): { shift1: string | null; shift2: string | null } => {
if (!openingTimes) {
return { shift1: null, shift2: null };
}
const from1 = openingTimes[`${dayIndex}_from` as keyof typeof openingTimes];
const to1 = openingTimes[`${dayIndex}_to` as keyof typeof openingTimes];
const from2 = openingTimes[`2_${dayIndex}_from` as keyof typeof openingTimes];
const to2 = openingTimes[`2_${dayIndex}_to` as keyof typeof openingTimes];
const shift1 =
from1 && to1 ? `${formatTime(from1)} - ${formatTime(to1)}` : null;
const shift2 =
from2 && to2 ? `${formatTime(from2)} - ${formatTime(to2)}` : null;
return { shift1, shift2 };
}; };
export function OpeningTimesBottomSheet({ export function OpeningTimesBottomSheet({
@@ -26,6 +62,12 @@ export function OpeningTimesBottomSheet({
const { t } = useTranslation(); const { t } = useTranslation();
const { isRTL } = useAppSelector((state) => state.locale); const { isRTL } = useAppSelector((state) => state.locale);
const { restaurant } = useAppSelector((state) => state.order); const { restaurant } = useAppSelector((state) => state.order);
const { data: openingTimes } = useGetOpeningTimesQuery(
restaurant?.restautantId,
{
skip: !restaurant?.restautantId,
},
);
const days = [ const days = [
"sunday", "sunday",
@@ -37,7 +79,11 @@ export function OpeningTimesBottomSheet({
"saturday", "saturday",
]; ];
const todayIndex = new Date().getDay(); const todayIndex = new Date().getDay();
const todayDay = days[todayIndex];
// Memoize day times to avoid recalculating on every render
const dayTimes = useMemo(() => {
return days.map((_, index) => getDayTimes(openingTimes, index));
}, [openingTimes]);
return ( return (
<ProBottomSheet <ProBottomSheet
@@ -46,170 +92,205 @@ export function OpeningTimesBottomSheet({
title={t("menu.openingTimes")} title={t("menu.openingTimes")}
showCloseButton={false} showCloseButton={false}
initialSnap={1} initialSnap={1}
height={445} height={600}
snapPoints={[445]} snapPoints={[600]}
> >
<div <div
style={{ style={{
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
padding: 20, padding: "20px 0",
gap: 20,
maxHeight: "calc(600px - 120px)",
overflowY: "auto",
}}
>
{/* Address Section */}
<Card
bordered={false}
style={{
backgroundColor: "#f8f9fa",
borderRadius: 12,
boxShadow: "none",
}}
bodyStyle={{ padding: "16px" }}
>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 8,
}}
>
<ProTitle
level={5}
style={{
marginBottom: 0,
fontSize: 16,
fontWeight: 600,
color: "#333",
}}
>
{t("menu.address")}
</ProTitle>
<ProText
type="secondary"
style={{
fontSize: 14,
lineHeight: "20px",
color: "#666",
}} }}
> >
<ProTitle level={5}>{t("menu.address")}</ProTitle>
<ProText type="secondary">
{isRTL ? restaurant?.addressAR : restaurant?.address} {isRTL ? restaurant?.addressAR : restaurant?.address}
</ProText> </ProText>
</div>
</Card>
<ProTitle level={5}>{t("menu.openingTimes")}</ProTitle> {/* Opening Times Section */}
<div style={{ display: "flex", justifyContent: "space-between" }}> <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
<ProText <div
type="secondary"
style={{ style={{
...textStyle, display: "flex",
fontWeight: todayDay === "sunday" ? 700 : 400, alignItems: "center",
gap: 8,
marginBottom: 4,
}} }}
> >
sunday <TimeIcon color="#333" />
</ProText> <ProTitle
<ProText level={5}
type="secondary"
style={{ style={{
...textStyle, marginBottom: 0,
fontWeight: todayDay === "sunday" ? 700 : 400, fontSize: 16,
fontWeight: 600,
color: "#333",
}} }}
> >
10:00 AM to 10:00 PM {t("menu.openingTimes")}
</ProTitle>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 8,
}}
>
{days.map((day, index) => {
const isToday = index === todayIndex;
const { shift1, shift2 } = dayTimes[index];
const hasShifts = shift1 || shift2;
return (
<Card
key={day}
bordered
style={{
borderRadius: 12,
borderColor: isToday ? "#1890ff" : "#e8e8e8",
borderWidth: isToday ? 2 : 1,
backgroundColor: isToday ? "#f0f8ff" : "#ffffff",
boxShadow: isToday
? "0 2px 8px rgba(24, 144, 255, 0.15)"
: "0 1px 2px rgba(0, 0, 0, 0.05)",
transition: "all 0.2s ease",
}}
bodyStyle={{ padding: "14px 16px" }}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
gap: 16,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 12,
flex: 1,
}}
>
{isToday && (
<div
style={{
width: 6,
height: 6,
borderRadius: "50%",
backgroundColor: "#1890ff",
flexShrink: 0,
}}
/>
)}
<ProText
style={{
fontSize: 15,
fontWeight: isToday ? 600 : 500,
color: isToday ? "#1890ff" : "#333",
textTransform: "capitalize",
}}
>
{t(`menu.${day}`)}
</ProText> </ProText>
</div> </div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: isRTL ? "flex-start" : "flex-end",
gap: shift2 ? 4 : 0,
flex: 1,
}}
>
{hasShifts ? (
<>
<ProText
style={{
fontSize: 14,
fontWeight: isToday ? 500 : 400,
color: isToday ? "#1890ff" : "#666",
lineHeight: "20px",
}}
>
{shift1}
</ProText>
{shift2 && (
<ProText
style={{
fontSize: 14,
fontWeight: isToday ? 500 : 400,
color: isToday ? "#1890ff" : "#666",
lineHeight: "20px",
}}
>
{shift2}
</ProText>
)}
</>
) : (
<ProText <ProText
type="secondary" type="secondary"
style={{ style={{
...textStyle, fontSize: 14,
fontWeight: todayDay === "monday" ? 700 : 400, color: "#999",
fontStyle: "italic",
}} }}
> >
monday {t("menu.closed")}
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "monday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between"}}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "tuesday" ? 700 : 400,
}}
>
tuesday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "tuesday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "wednesday" ? 700 : 400,
}}
>
wednesday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "wednesday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "thursday" ? 700 : 400,
}}
>
thursday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "thursday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "friday" ? 700 : 400,
}}
>
friday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "friday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "saturday" ? 700 : 400,
}}
>
saturday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "saturday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText> </ProText>
)}
</div>
</div>
</Card>
);
})}
</div>
</div> </div>
</div> </div>
<Button
type="primary"
style={{ width: "100%", height: 48 }}
onClick={onClose}
>
{t("menu.close")}
</Button>
</ProBottomSheet> </ProBottomSheet>
); );
} }

View File

@@ -14,7 +14,6 @@ export default function ExtraGroupsContainer({
selectedExtrasByGroup: Record<number, string[]>; selectedExtrasByGroup: Record<number, string[]>;
setSelectedExtrasByGroup: Dispatch<SetStateAction<Record<number, string[]>>>; setSelectedExtrasByGroup: Dispatch<SetStateAction<Record<number, string[]>>>;
}) { }) {
return ( return (
<> <>
{groupsList.length > 0 && ( {groupsList.length > 0 && (

View File

@@ -214,6 +214,8 @@ export default function ProductDetailPage({
); );
} }
console.log(product.theExtrasGroups);
return ( return (
<div <div
style={{ style={{
@@ -367,8 +369,7 @@ export default function ProductDetailPage({
/> />
)} )}
{product.theExtrasGroups.length === 0 && {getExtras()?.length > 0 && (
getExtras()?.length > 0 && (
<ExtraComponent <ExtraComponent
extrasList={getExtras()} extrasList={getExtras()}
selectedExtras={selectedExtras} selectedExtras={selectedExtras}

View File

@@ -13,6 +13,7 @@ import {
REDEEM_DETAILS_URL, REDEEM_DETAILS_URL,
LOYALTY_HISTORY_URL, LOYALTY_HISTORY_URL,
CREATE_GIFT_AMOUNT_URL, CREATE_GIFT_AMOUNT_URL,
OPENING_TIMES_URL,
} from "utils/constants"; } from "utils/constants";
import { OrderDetails } from "pages/checkout/hooks/types"; import { OrderDetails } from "pages/checkout/hooks/types";
@@ -25,6 +26,7 @@ import {
import { baseApi } from "./apiSlice"; import { baseApi } from "./apiSlice";
import { EGiftCard } from "pages/EGiftCards/type"; import { EGiftCard } from "pages/EGiftCards/type";
import { RedeemResponse } from "pages/redeem/types"; import { RedeemResponse } from "pages/redeem/types";
import { OpeningTimeResponse } from "./types";
export const branchApi = baseApi.injectEndpoints({ export const branchApi = baseApi.injectEndpoints({
endpoints: (builder) => ({ endpoints: (builder) => ({
@@ -210,6 +212,15 @@ export const branchApi = baseApi.injectEndpoints({
body, body,
}), }),
}), }),
getOpeningTimes: builder.query<OpeningTimeResponse, string | void>({
query: (restaurantId: string) => ({
url: OPENING_TIMES_URL +"/"+ restaurantId,
method: "GET",
}),
transformResponse: (response: any) => {
return response.result;
},
}),
}), }),
}); });
export const { export const {
@@ -227,4 +238,5 @@ export const {
useGetRedeemDetailsQuery, useGetRedeemDetailsQuery,
useGetLoyaltyHistoryQuery, useGetLoyaltyHistoryQuery,
useCreateGiftAmountMutation, useCreateGiftAmountMutation,
useGetOpeningTimesQuery,
} = branchApi; } = branchApi;

34
src/redux/api/types.ts Normal file
View File

@@ -0,0 +1,34 @@
export interface OpeningTimeResponse {
id: number;
created_at: string;
updated_at: string;
"0_from": string;
"0_to": string;
"1_from": string;
"1_to": string;
"2_from": string;
"2_to": string;
"3_from": string;
"3_to": string;
"4_from": string;
"4_to": string;
"5_from": string;
"5_to": string;
"6_from": string;
"6_to": string;
restorant_id: number;
"2_0_from": any;
"2_0_to": any;
"2_1_from": any;
"2_1_to": any;
"2_2_from": any;
"2_2_to": any;
"2_3_from": any;
"2_3_to": any;
"2_4_from": any;
"2_4_to": any;
"2_5_from": any;
"2_5_to": any;
"2_6_from": any;
"2_6_to": any;
}

View File

@@ -112,3 +112,4 @@ export const EGIFT_CARDS_URL = `${BASE_URL}gift/cards`;
export const REDEEM_DETAILS_URL = `${BASE_URL}gift/getGiftOrderByVoucherCode`; export const REDEEM_DETAILS_URL = `${BASE_URL}gift/getGiftOrderByVoucherCode`;
export const LOYALTY_HISTORY_URL = `${BASE_URL}loyaltyHistory`; export const LOYALTY_HISTORY_URL = `${BASE_URL}loyaltyHistory`;
export const CREATE_GIFT_AMOUNT_URL = `${BASE_URL}gift/addGiftAmount`; export const CREATE_GIFT_AMOUNT_URL = `${BASE_URL}gift/addGiftAmount`;
export const OPENING_TIMES_URL = `${BASE_URL}restaurant/getWorkingHours`;