diff --git a/package.json b/package.json index 65163321..22d3a9ce 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "app.salona", "private": true, - "version": "2.103.27", + "version": "2.103.28", "type": "module", "scripts": { "dev": "vite --host", diff --git a/src/apps/new-ui/components/AppointmentComponents/CustomerCard.tsx b/src/apps/new-ui/components/AppointmentComponents/CustomerCard.tsx index 141d3670..363f2c2d 100644 --- a/src/apps/new-ui/components/AppointmentComponents/CustomerCard.tsx +++ b/src/apps/new-ui/components/AppointmentComponents/CustomerCard.tsx @@ -2,323 +2,315 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import { useGetAllCustomers } from "@/apps/new-ui/services/Customer"; import { useLocation } from "react-router-dom"; import avatar from "../../assets/appointment/avatar.svg"; -import status from "../../assets/appointment/status.svg"; import tick from "../../assets/appointment/tick.svg"; import emptyTick from "../../assets/appointment/emptyTick.svg"; import AddCustomer from "./AddCustomer"; import { XIcon } from "lucide-react"; import VoiceSearchInput from "../VoiceSearchInput"; +import BronzeMedal from "@new-ui/assets/medal/bronze-medal.svg"; +import SilverMedal from "@new-ui/assets/medal/silver-medal.svg"; +import GoldMedal from "@new-ui/assets/medal/gold-medal.svg"; +import VIPMedal from "@new-ui/assets/medal/crown.svg"; + const LS = { - selectedCustomerId: "selectedCustomerId", - selectedName: "selectedName", - selectedPhoneNumber: "selectedPhoneNumber", - btnTrigger: "btnTrigger", + selectedCustomerId: "selectedCustomerId", + selectedName: "selectedName", + selectedPhoneNumber: "selectedPhoneNumber", + btnTrigger: "btnTrigger", }; const CustomerCard: React.FC = () => { - const { data: customersData, isLoading, isError } = useGetAllCustomers(); - // try to hydrate selectedCustomer id from localStorage (may be null) - const [selectedCustomer, setSelectedCustomer] = useState(() => { - const id = localStorage.getItem(LS.selectedCustomerId); - return id ? Number(id) : null; - }); - const appointmentStatus = localStorage.getItem("appointmentStatus"); - const disableInput = appointmentStatus === "edit" ? true : false; - - const [searchTerm, setSearchTerm] = useState(""); - const [isOpen, setIsOpen] = useState(false); - - const [selectedName, setSelectedName] = useState( - () => localStorage.getItem(LS.selectedName) || "" - ); - const [selectedPhoneNumber, setSelectedPhoneNumber] = useState( - () => localStorage.getItem(LS.selectedPhoneNumber) || "" - ); - - const location = useLocation(); - - // filtered list memoized - const filteredCustomers = useMemo(() => { - const list = customersData?.customers ?? []; - const term = searchTerm.trim().toLowerCase(); - if (!term) return list; - return list.filter((customer) => { - return ( - customer.name?.toLowerCase().includes(term) || - String(customer.phone_number || "").includes(term) - ); + const { data: customersData, isLoading, isError } = useGetAllCustomers(); + // try to hydrate selectedCustomer id from localStorage (may be null) + const [selectedCustomer, setSelectedCustomer] = useState(() => { + const id = localStorage.getItem(LS.selectedCustomerId); + return id ? Number(id) : null; }); - }, [customersData, searchTerm]); + const appointmentStatus = localStorage.getItem("appointmentStatus"); + const disableInput = appointmentStatus === "edit" ? true : false; - // Keep localStorage in sync with selected name/phone (if those states change) - useEffect(() => { - localStorage.setItem(LS.selectedName, selectedName); - localStorage.setItem(LS.selectedPhoneNumber, selectedPhoneNumber); - }, [selectedName, selectedPhoneNumber]); + const [searchTerm, setSearchTerm] = useState(""); + const [isOpen, setIsOpen] = useState(false); + const [medal, setMedal] = useState(BronzeMedal); - // When customersData arrives, if we have selectedCustomer id in LS try to re-select it - useEffect(() => { - if (!customersData?.customers?.length) return; - - // If selectedCustomer is already set and valid, ensure name/phone are populated - if (selectedCustomer != null) { - const found = customersData.customers.find( - (c) => c.id === selectedCustomer - ); - if (found) { - // sync name/phone in state & LS in case they were missing - if (selectedName !== found.name) setSelectedName(found.name); - if (selectedPhoneNumber !== found.phone_number) - setSelectedPhoneNumber(found.phone_number); - localStorage.setItem(LS.selectedCustomerId, String(found.id)); - localStorage.setItem(LS.btnTrigger, "1"); - return; - } else { - // id in LS but customer not found (stale) → clear it - setSelectedCustomer(null); - localStorage.removeItem(LS.selectedCustomerId); - localStorage.setItem(LS.btnTrigger, "0"); - } - } - - // If there's no selectedCustomer but LS contains an id (e.g. hydrated earlier), try to set it - const lsId = localStorage.getItem(LS.selectedCustomerId); - if (lsId) { - const numId = Number(lsId); - const found = customersData.customers.find((c) => c.id === numId); - if (found) { - setSelectedCustomer(found.id); - setSelectedName(found.name); - setSelectedPhoneNumber(found.phone_number); - localStorage.setItem(LS.btnTrigger, "1"); - } else { - // remove stale id - localStorage.removeItem(LS.selectedCustomerId); - localStorage.setItem(LS.btnTrigger, "0"); - } - } - }, [customersData, selectedCustomer, selectedName, selectedPhoneNumber]); - - // Fix: when route changes (or on mount), if we're NOT on `/appointments/advance` clear LS keys - useEffect(() => { - if (!location.pathname.includes("/appointments/advance")) { - localStorage.removeItem(LS.selectedCustomerId); - localStorage.removeItem(LS.selectedName); - localStorage.removeItem(LS.selectedPhoneNumber); - localStorage.removeItem(LS.btnTrigger); - // Also clear local state to keep UI consistent - setSelectedCustomer(null); - setSelectedName(""); - setSelectedPhoneNumber(""); - } - }, [location.pathname]); - - useEffect(() => { - if (!isOpen) return; - - const onKey = (e: KeyboardEvent) => { - if (e.key === "Escape") setIsOpen(false); - }; - document.addEventListener("keydown", onKey); - - const prevOverflow = document.body.style.overflow; - document.body.style.overflow = "hidden"; - - return () => { - document.removeEventListener("keydown", onKey); - document.body.style.overflow = prevOverflow; - }; - }, [isOpen]); - - const handleCustomerClick = useCallback( - (customerId: number | undefined) => { - if (customerId === undefined) return; - // find customer - const customer = customersData?.customers?.find( - (c) => c.id === customerId - ); - if (!customer) return; - - if (selectedCustomer === customerId) { - // deselect - setSelectedCustomer(null); - setSelectedName(""); - setSelectedPhoneNumber(""); - localStorage.removeItem(LS.selectedCustomerId); - localStorage.setItem(LS.btnTrigger, "0"); - } else { - // select - setSelectedCustomer(customerId); - setSelectedName(customer.name); - setSelectedPhoneNumber(customer.phone_number); - localStorage.setItem(LS.selectedCustomerId, String(customerId)); - localStorage.setItem(LS.selectedName, customer.name); - localStorage.setItem(LS.selectedPhoneNumber, customer.phone_number); - localStorage.setItem(LS.btnTrigger, "1"); - } - }, - [customersData?.customers, selectedCustomer] - ); - - useEffect(() => { - const appointmentPhone = localStorage.getItem("editAppointmentPhone"); - if (appointmentPhone) { - setSearchTerm(appointmentPhone); - } - - const found = customersData?.customers.find( - (c) => c.phone_number == appointmentPhone + const [selectedName, setSelectedName] = useState(() => localStorage.getItem(LS.selectedName) || ""); + const [selectedPhoneNumber, setSelectedPhoneNumber] = useState( + () => localStorage.getItem(LS.selectedPhoneNumber) || "" ); - if (found) { - setSelectedCustomer(found.id); - setSelectedName(found.name); - setSelectedPhoneNumber(found.phone_number); + const location = useLocation(); - localStorage.setItem("selectedCustomerId", String(found.id)); - localStorage.setItem("selectedName", found.name); - localStorage.setItem("selectedPhoneNumber", found.phone_number); - localStorage.setItem("btnTrigger", "1"); - } - }, []); + // filtered list memoized + const filteredCustomers = useMemo(() => { + const list = customersData?.customers ?? []; + const term = searchTerm.trim().toLowerCase(); + if (!term) return list; + return list.filter((customer) => { + return customer.name?.toLowerCase().includes(term) || String(customer.phone_number || "").includes(term); + }); + }, [customersData, searchTerm]); - return ( -
- {/* search + add */} -
- + // Keep localStorage in sync with selected name/phone (if those states change) + useEffect(() => { + localStorage.setItem(LS.selectedName, selectedName); + localStorage.setItem(LS.selectedPhoneNumber, selectedPhoneNumber); + }, [selectedName, selectedPhoneNumber]); -
(disableInput ? "" : setIsOpen(true))} - className="w-1/3 flex flex-nowrap whitespace-nowrap px-2 gap-1 items-center justify-center border-2 border-[#A780C3] rounded-full" - role="button" - tabIndex={0} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") setIsOpen(true); - }} - > -

+

-

- افزودن مشتری -

-
-
+ const HandleCustomerMedal = (type: string) => { + if (type === "Bronze") { + setMedal(BronzeMedal); + } else if (type === "Silver") { + setMedal(SilverMedal); + } else if (type === "Gold") { + setMedal(GoldMedal); + } else if (type === "VIP") { + setMedal(VIPMedal); + } + }; - {/* list */} -
-
+ // When customersData arrives, if we have selectedCustomer id in LS try to re-select it + useEffect(() => { + if (!customersData?.customers?.length) return; - {isLoading ? ( -
-

در حال بارگذاری...

-
- ) : isError ? ( -
-

خطا در دریافت اطلاعات مشتریان

-
- ) : ( -
- {(searchTerm - ? filteredCustomers - : customersData?.customers ?? [] - ).map((customer) => ( -
handleCustomerClick(customer.id)} - > -
-
- avatar - status -
+ // If selectedCustomer is already set and valid, ensure name/phone are populated + if (selectedCustomer != null) { + const found = customersData.customers.find((c) => c.id === selectedCustomer); + if (found) { + // sync name/phone in state & LS in case they were missing + if (selectedName !== found.name) setSelectedName(found.name); + if (selectedPhoneNumber !== found.phone_number) setSelectedPhoneNumber(found.phone_number); + localStorage.setItem(LS.selectedCustomerId, String(found.id)); + localStorage.setItem(LS.btnTrigger, "1"); + return; + } else { + // id in LS but customer not found (stale) → clear it + setSelectedCustomer(null); + localStorage.removeItem(LS.selectedCustomerId); + localStorage.setItem(LS.btnTrigger, "0"); + } + } -
- {customer.name.length > 16 ? ( -
-
-

- {customer.name} -

-
-
-
-
- ) : ( -

- {customer.name} -

- )} -

- {customer.phone_number} -

-
+ // If there's no selectedCustomer but LS contains an id (e.g. hydrated earlier), try to set it + const lsId = localStorage.getItem(LS.selectedCustomerId); + if (lsId) { + const numId = Number(lsId); + const found = customersData.customers.find((c) => c.id === numId); + if (found) { + setSelectedCustomer(found.id); + setSelectedName(found.name); + setSelectedPhoneNumber(found.phone_number); + localStorage.setItem(LS.btnTrigger, "1"); + } else { + // remove stale id + localStorage.removeItem(LS.selectedCustomerId); + localStorage.setItem(LS.btnTrigger, "0"); + } + } + }, [customersData, selectedCustomer, selectedName, selectedPhoneNumber]); + + // Fix: when route changes (or on mount), if we're NOT on `/appointments/advance` clear LS keys + useEffect(() => { + if (!location.pathname.includes("/appointments/advance")) { + localStorage.removeItem(LS.selectedCustomerId); + localStorage.removeItem(LS.selectedName); + localStorage.removeItem(LS.selectedPhoneNumber); + localStorage.removeItem(LS.btnTrigger); + // Also clear local state to keep UI consistent + setSelectedCustomer(null); + setSelectedName(""); + setSelectedPhoneNumber(""); + } + }, [location.pathname]); + + useEffect(() => { + if (!isOpen) return; + + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") setIsOpen(false); + }; + document.addEventListener("keydown", onKey); + + const prevOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + + return () => { + document.removeEventListener("keydown", onKey); + document.body.style.overflow = prevOverflow; + }; + }, [isOpen]); + + const handleCustomerClick = useCallback( + (customerId: number | undefined) => { + if (customerId === undefined) return; + // find customer + const customer = customersData?.customers?.find((c) => c.id === customerId); + if (!customer) return; + + if (selectedCustomer === customerId) { + // deselect + setSelectedCustomer(null); + setSelectedName(""); + setSelectedPhoneNumber(""); + localStorage.removeItem(LS.selectedCustomerId); + localStorage.setItem(LS.btnTrigger, "0"); + } else { + // select + setSelectedCustomer(customerId); + setSelectedName(customer.name); + setSelectedPhoneNumber(customer.phone_number); + localStorage.setItem(LS.selectedCustomerId, String(customerId)); + localStorage.setItem(LS.selectedName, customer.name); + localStorage.setItem(LS.selectedPhoneNumber, customer.phone_number); + localStorage.setItem(LS.btnTrigger, "1"); + } + }, + [customersData?.customers, selectedCustomer] + ); + + useEffect(() => { + const appointmentPhone = localStorage.getItem("editAppointmentPhone"); + if (appointmentPhone) { + setSearchTerm(appointmentPhone); + } + + const found = customersData?.customers.find((c) => c.phone_number == appointmentPhone); + + if (found) { + setSelectedCustomer(found.id); + setSelectedName(found.name); + setSelectedPhoneNumber(found.phone_number); + + localStorage.setItem("selectedCustomerId", String(found.id)); + localStorage.setItem("selectedName", found.name); + localStorage.setItem("selectedPhoneNumber", found.phone_number); + localStorage.setItem("btnTrigger", "1"); + } + }, []); + + return ( +
+ {/* search + add */} +
+ + +
(disableInput ? "" : setIsOpen(true))} + className="w-1/3 flex flex-nowrap whitespace-nowrap px-2 gap-1 items-center justify-center border-2 border-[#A780C3] rounded-full" + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") setIsOpen(true); + }} + > +

+

+

افزودن مشتری

- - {selectedCustomer === customer.id ? ( - selected - ) : ( - not selected - )} -
- ))} -
- )} - - {/*
*/} -
- - {/* add customer modal */} - {isOpen && ( -
setIsOpen(false)} - className="fixed z-[200] inset-0 bg-black/20 backdrop-blur-sm flex justify-center items-center" - > -
e.stopPropagation()} - className="relative bg-white w-[95%] max-w-lg max-h-[600px] rounded-2xl p-4 overflow-y-scroll scrollbar-hide" - > -
setIsOpen(false)} - className="absolute cursor-pointer text-black/80 text-2xl rounded-full p-2 top-2 left-2 " - role="button" - tabIndex={0} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") setIsOpen(false); - }} - > -
- setIsOpen(false)} /> -
+ {/* list */} +
+
+ + {isLoading ? ( +
+

در حال بارگذاری...

+
+ ) : isError ? ( +
+

خطا در دریافت اطلاعات مشتریان

+
+ ) : ( +
+ {(searchTerm ? filteredCustomers : customersData?.customers ?? []).map((customer) => { + // HandleCustomerMedal(customer.customer_type); + console.log(customer.customer_type); + return ( +
handleCustomerClick(customer.id)} + > +
+
+ avatar + medal +
+ +
+ {customer.name.length > 16 ? ( +
+
+

+ {customer.name} +

+
+
+
+
+ ) : ( +

{customer.name}

+ )} +

{customer.phone_number}

+
+
+ + {selectedCustomer === customer.id ? ( + selected + ) : ( + not selected + )} +
+ ); + })} +
+ )} + + {/*
*/} +
+ + {/* add customer modal */} + {isOpen && ( +
setIsOpen(false)} + className="fixed z-[200] inset-0 bg-black/20 backdrop-blur-sm flex justify-center items-center" + > +
e.stopPropagation()} + className="relative bg-white w-[95%] max-w-lg max-h-[600px] rounded-2xl p-4 overflow-y-scroll scrollbar-hide" + > +
setIsOpen(false)} + className="absolute cursor-pointer text-black/80 text-2xl rounded-full p-2 top-2 left-2 " + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") setIsOpen(false); + }} + > + +
+ + setIsOpen(false)} /> +
+
+ )}
- )} -
- ); + ); }; export default CustomerCard; diff --git a/src/apps/new-ui/pages/CalendarManagement/+components/WorkHour.tsx b/src/apps/new-ui/pages/CalendarManagement/+components/WorkHour.tsx index 0d9c8ea6..abf89eb4 100644 --- a/src/apps/new-ui/pages/CalendarManagement/+components/WorkHour.tsx +++ b/src/apps/new-ui/pages/CalendarManagement/+components/WorkHour.tsx @@ -149,6 +149,8 @@ const WorkHour = ({ days, isHolidayDefault = false, refetch }: Props) => { // نوتر: دیگر با localStorage کاری نداریم refetch?.(); window.scrollTo(0, 0); + localStorage.removeItem("appointmentDateGregorianMulti"); + localStorage.removeItem("appointmentDateGregorian"); clearAll(); showSuccessAlert(data.message); }, diff --git a/src/apps/new-ui/pages/CheckTimeSlotCustomer/index.tsx b/src/apps/new-ui/pages/CheckTimeSlotCustomer/index.tsx index 7f77f956..6680f858 100644 --- a/src/apps/new-ui/pages/CheckTimeSlotCustomer/index.tsx +++ b/src/apps/new-ui/pages/CheckTimeSlotCustomer/index.tsx @@ -393,28 +393,28 @@ export default function CheckTimeSlotCustomer(): JSX.Element { return (
-
-
+
+
avatar - status + status
-
-

- نام مشتری : {customerName} +

+

+ نام : {customerName}

-

+

لاین آرایشی : {customLink?.service_name}

-

- کد اشتراک : {customLink?.customer_id} +

+ کد : {customLink?.customer_id}

-

+

قیمت : {servicePrice}

{/*

{serviceName}

diff --git a/src/apps/new-ui/pages/ReportAll/AppointmentSelect/index.tsx b/src/apps/new-ui/pages/ReportAll/AppointmentSelect/index.tsx index c9b6c73a..102cc50a 100644 --- a/src/apps/new-ui/pages/ReportAll/AppointmentSelect/index.tsx +++ b/src/apps/new-ui/pages/ReportAll/AppointmentSelect/index.tsx @@ -200,6 +200,7 @@ export default function AppointmentSelect() { ); const Holidays = data?.holidays; const IranHolidays = data?.iran_holidays; + const Days = data?.days; // وقتی ماه تغییر کرد، selectedDateInfo بروزرسانی میشه و سپس refetch میزنیم useEffect(() => { @@ -283,6 +284,7 @@ export default function AppointmentSelect() { }} allowPastSelection={true} onSelect={handleDateSelect} + Days={Days} appointmentsMap={appointmentsMap} jalaliHolidays={Holidays || []} isUserCanClickHolidays={true} diff --git a/src/apps/new-ui/pages/appointments/+components/QuickAppointment.tsx b/src/apps/new-ui/pages/appointments/+components/QuickAppointment.tsx index 42c56458..917ea640 100644 --- a/src/apps/new-ui/pages/appointments/+components/QuickAppointment.tsx +++ b/src/apps/new-ui/pages/appointments/+components/QuickAppointment.tsx @@ -447,7 +447,7 @@ const QuickAppointment: React.FC = () => { const CustomerLink = customerId && phone && service - ? `https://testapp.mysalona.ir/check-time-slot-customer/${customId}` + ? `http://localhost:5173/check-time-slot-customer/${customId}` : `https://testapp.mysalona.ir/check-time-slot`; const { state: copyState, onCopy } = useCopy(); @@ -597,6 +597,7 @@ const QuickAppointment: React.FC = () => { {daySchedule?.is_holiday === false && (
{ setTime(t ?? ""); diff --git a/src/apps/new-ui/pages/appointments/+components/TakeAppointment.tsx b/src/apps/new-ui/pages/appointments/+components/TakeAppointment.tsx index 52dd0905..0d5c3085 100644 --- a/src/apps/new-ui/pages/appointments/+components/TakeAppointment.tsx +++ b/src/apps/new-ui/pages/appointments/+components/TakeAppointment.tsx @@ -40,7 +40,7 @@ const TakeAppointment = () => { className="relative w-full flex flex-col items-center justify-center gap-1 py-1.5 rounded-xl shadow-[0_2px_8px_0_#2D0A4829] border-2 border-gray-200 bg-white" > {reservedCount && reservedCount > 0 && ( -
+