fix bug save and set data just with one key, remove page login from appo page, disable not avalaible data in calendar, change steps for pages in appo, get data user, edit and set in page user-info

This commit is contained in:
Ehsan
2025-08-31 01:21:59 +03:30
parent 09867bf84a
commit 1e70d88a84
20 changed files with 332 additions and 146 deletions
+12 -12
View File
@@ -20,7 +20,7 @@ function ItemDoctor({ doctor }) {
<Image
width={72}
height={72}
src={doctor.img}
src={doctor?.img}
alt="image-doctor"
className="w-[64px] md:w-[68px] lg:w-[72px] h-[64px] md:h-[68px] lg:h-[72px]"
/>
@@ -28,12 +28,12 @@ function ItemDoctor({ doctor }) {
<div className="flex flex-col items-start justify-center gap-2">
<TextLoading width={90} height={15}>
<p className="text-[#3B3B3B] text-[14px] font-bold">
{doctor.name}
{doctor?.name}
</p>
</TextLoading>
<TextLoading width={120} height={15}>
<p className="text-[#616161] text-[14px] font-normal">
تخصص: {doctor.expertise}
تخصص: {doctor?.expertise}
</p>
</TextLoading>
<CustomLoading width={100} height={25}>
@@ -41,13 +41,13 @@ function ItemDoctor({ doctor }) {
<div className="p-1 bg-[#F8F8FF] flex items-center rounded-[4px] gap-0.5">
<LikeD />
<p className="text-[#616161] text-[12px] font-normal">
{doctor.satisfaction}%
{doctor?.satisfaction}%
</p>
</div>
<div className="p-1 bg-[#F8F8FF] flex items-center rounded-[4px] gap-0.5">
<PointD />
<p className="text-[#616161] text-[12px] font-normal">
{doctor.point}
{doctor?.point}
</p>
</div>
</div>
@@ -60,7 +60,7 @@ function ItemDoctor({ doctor }) {
<ClockD />
<TextLoading width={150} height={15}>
<p className="text-[#7E7E7E] text-[12px] font-normal">
ساعت کاری: {doctor.hours_of_work}
ساعت کاری: {doctor?.hours_of_work}
</p>
</TextLoading>
</div>
@@ -68,24 +68,24 @@ function ItemDoctor({ doctor }) {
<p
className={`text-[12px] font-medium rounded w-fit p-1.5 ml-[52px]
${
Number(doctor.active)
Number(doctor?.active)
? "bg-[rgba(5,_186,_88,_0.04)] text-[#05BA58]"
: "bg-[rgba(211,_47,_47,_0.04)] text-[#D32F2F]"
}`}
>
{Number(doctor.active)
? `اولین نوبت آزاد: ${Number(doctor.active)}`
{Number(doctor?.active)
? `اولین نوبت آزاد: ${Number(doctor?.active)}`
: "نوبت ندارد"}
</p>
</CustomLoading>
{/* Arrow */}
<Link href={`/doctor/${doctor.uuid}`}>
<Link href={`/doctor/${doctor?.uuid}`}>
<Button
variant="contained"
className={`!p-[14px] !absolute !left-4 !bottom-4 !rounded-full !w-fit
${!Number(doctor.active)}
${!Number(doctor?.active)}
`}
disabled={!Number(doctor.active)}
disabled={!Number(doctor?.active)}
>
<ArrowLeftD />
</Button>
@@ -5,8 +5,9 @@ import LoadingDate from "../LoadingDate";
import Image from "next/image";
function FirstDatePicker({
startDate,
loading,
startDate,
disableDates,
handleDisableDate,
nextIconDatePickerF,
prevIconDatePickerF,
+27 -4
View File
@@ -4,6 +4,8 @@ import { useEffect, useRef, useState } from "react";
import FirstDatePicker from "./date/FirstDatePicker";
import SecondDatePicker from "./date/SecondDatePicker";
import { dateToTimestamp } from "@/helper";
import { request } from "@/services/response";
import moment from "moment-jalaali";
function DatePicker({ setDate }) {
const nextIconDatePickerF = useRef();
@@ -14,6 +16,7 @@ function DatePicker({ setDate }) {
const [startDate, setStartDate] = useState(null);
const [endDate, setEndDate] = useState();
const [loading, setLoading] = useState(true);
const [disableDates, setDisableDates] = useState([]);
const handleStartDateChange = (date, e) => {
if (date) {
@@ -31,16 +34,35 @@ function DatePicker({ setDate }) {
}
};
const handleDisableDate = (e) => {
const today = new Date();
return today >= e._d;
const date = moment(e).startOf("day");
const today = moment().startOf("day");
if (date.isBefore(today)) return true;
if (
disableDates.some((ts) => date.isSame(moment.unix(ts).startOf("day")))
) {
return true;
}
return false;
};
useEffect(() => {
request
.getAppointmentNotAvailable(47)
.then((res) => {
setTimeout(() => {
setLoading(false);
}, 1000);
setDisableDates(res.data);
})
.catch((err) => {
});
if (nextIconDatePickerS.current) {
nextIconDatePickerS.current.click();
setLoading(false);
}
}, [nextIconDatePickerS]);
@@ -49,6 +71,7 @@ function DatePicker({ setDate }) {
<FirstDatePicker
loading={loading}
startDate={startDate}
disableDates={disableDates}
handleDisableDate={handleDisableDate}
nextIconDatePickerF={nextIconDatePickerF}
prevIconDatePickerF={prevIconDatePickerF}
+18 -14
View File
@@ -1,29 +1,33 @@
import ArrowLeftB from "@/components/icons/ArrowLeftB";
import { request } from "@/services/response";
import { Button } from "@mui/material";
import Cookies from "js-cookie";
import { useParams } from "next/navigation";
import { useState } from "react";
function SendAppo({ hour }) {
function SendAppo({ hour, setStep }) {
const params = useParams();
const doctorId = params.doctorId;
const [loading, setLoading] = useState(false);
const sendReq = () => {
setLoading(true);
request
.postAppointment({
doctor_id: doctorId,
slot: hour,
})
.then(() => {
setLoading(false);
})
.catch(() => {
//
});
const isLogged = Cookies.get("access_token");
if (isLogged) {
setStep((step) => step + 1);
}
// setLoading(true);
// request
// .postAppointment({
// doctor_id: doctorId,
// slot: hour,
// })
// .then(() => {
// setLoading(false);
// })
// .catch(() => {
// //
// });
};
console.log(hour);
return (
<div className="w-full flex justify-end">
+2 -2
View File
@@ -10,7 +10,7 @@ import SendAppo from "./SendAppo";
const listTab = ["صبح", "بعد از ظهر"];
const nameHours = ["hours_morning", "hours_afternoon"];
function DateTime({ date, doctor }) {
function DateTime({ date, doctor, setStep }) {
const params = useParams();
const doctorId = params.doctorId;
const [value, setValue] = useState(0);
@@ -73,7 +73,7 @@ function DateTime({ date, doctor }) {
setHour={setHour}
nameHours={nameHours}
/>
<SendAppo hour={hour} />
<SendAppo hour={hour} setStep={setStep} />
</div>
);
}
+86
View File
@@ -0,0 +1,86 @@
import BottomSelect from "@/components/icons/BottomSelect";
import { Autocomplete, Button, TextField } from "@mui/material";
import { styleTextSelectRight } from "@/mui";
function DefaultSelect({ list, value, updateData, label, error, name }) {
return (
<Autocomplete
disablePortal
options={list}
value={value || ""}
getOptionLabel={(option) => {
if (typeof option === "string") return option;
return option?.name || option?.label || "";
}}
className="!w-full !rounded-lg auto-complete-selector"
onChange={(_, newValue) => {
updateData(name, newValue?.label || newValue || "");
}}
sx={{
...styleTextSelectRight,
// Hide Icon Number
"& input::-webkit-outer-spin-button, & input::-webkit-inner-spin-button":
{
display: "none",
},
"& input[type=number]": {
MozAppearance: "textfield",
},
"& .MuiInputBase-input": { padding: "12.5px 14px !important" },
"& .MuiInputBase-root": {
borderRadius: 2,
padding: "0 !important",
height: {
xs: "43px !important",
sm: "43px !important",
md: "46px !important",
lg: "48px !important",
},
},
"& .MuiOutlinedInput-notchedOutline": {
border: error
? "1px solid red !important"
: "1px solid #D7D7D7 !important",
},
"& .MuiInput-underline:hover:not(.Mui-disabled):before, .MuiOutlinedInput-notchedOutline":
{
borderColor: error ? "red !important" : "#D7D7D7 !important",
},
"& .muirtl-1kiro5a-MuiInputBase-root-MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline":
{
border: "1px solid #5559CE !important",
},
}}
renderOption={(props, option) => (
<Button
fullWidth
{...props}
key={option.id || props.id}
className="!flex !justify-start !p-[8px] !text-start !text-[#A1A1A1] hover:dark:!bg-[#35343D] !bg-[#FFF] dark:!bg-[#1F1D2B]"
>
{option.name || option.label}
</Button>
)}
renderInput={(params) => (
<TextField
fullWidth
{...params}
InputProps={{
...params.InputProps,
endAdornment: (
<div className="absolute left-4">
<BottomSelect />
</div>
),
}}
placeholder={label}
sx={{
borderRadius: "8px",
}}
/>
)}
/>
);
}
export default DefaultSelect;
+64
View File
@@ -0,0 +1,64 @@
import Date from "./date";
// Components
import Content from "./Content";
import Layout from "@/components/layout";
import Paying from "@/components/appointment/paying";
import Detail from "@/components/appointment/detail";
import FailedPay from "@/components/appointment/failedPay";
import SuccessPay from "@/components/appointment/successPay";
function Container({
step,
data,
doctor,
setData,
setStep,
matchedCity,
isForAnother,
setIsForAnother,
}) {
const elements = [
<Date doctor={doctor} setStep={setStep} />,
// <LogInPage setIsSendMsg={false} setStep={setStep} />,
// <VerificationPage setIsSendMsg={false} link={false} setStep={setStep} />,
<Detail
data={data}
setStep={setStep}
setData={setData}
isForAnother={isForAnother}
setIsForAnother={setIsForAnother}
/>,
<Paying setStep={setStep} />,
<SuccessPay setStep={setStep} />,
<FailedPay setStep={setStep} />,
];
return (
<>
{/* {step === 1 || step === 2 ? (
<RegisterPage>{elements[step]}</RegisterPage>
) : ( */}
<Layout
title="رزرو"
name="booking"
disableFooter={step > 2}
matchedCity={matchedCity}
paddingBottom="pb-[80px]"
>
<Content
disableSide={step === 5 || step === 6}
isFirst={step === 0}
setStep={setStep}
doctor={doctor}
step={step}
>
{elements[step]}
</Content>
</Layout>
{/* )} */}
</>
);
}
export default Container;
-1
View File
@@ -4,7 +4,6 @@ import Time from "./Time";
function Date({ doctor, setStep }) {
const [locateVisit, setLocateVisit] = useState(true);
const [isError, setIsError] = useState(false);
const [date, setDate] = useState();
return (
+17 -14
View File
@@ -1,14 +1,16 @@
import AutoCompleteSelect from "@/app/component/element/AutoCompleteSelect";
import EditField from "../../../app/component/fields/EditField";
import Content from "./Content";
import EditField from "../../../app/component/fields/EditField";
import DefaultSelect from "@/app/component/selectors/DefaultSelect";
const bime = [
{ label: "بیمه 1", id: 10 },
{ label: "بیمه 2", id: 20 },
{ label: "بیمه 3", id: 30 },
];
function Form({ changeData, insurance, data, updateSelect, isForAnother }) {
const findVal = () => {
return insurance?.find(
(g) =>
g.id ===
(data?.basic_insurance?.value ? data?.basic_insurance.value.id : false)
);
};
function Form({ changeData, data, updateSelect, isForAnother }) {
return (
<div
className="grid mt-[12px] sm:mt-[14px] md:mt-[17px] lg:mt-[20px] items-start
@@ -20,19 +22,20 @@ function Form({ changeData, data, updateSelect, isForAnother }) {
<Content title="کد ملی">
<EditField
changeData={changeData}
data={data?.codemeli}
name="codemeli"
data={data?.national_code}
name="national_code"
/>
</Content>
<Content title="نام و نام خانوادگی">
<EditField changeData={changeData} data={data?.name} name="name" />
</Content>
<Content title="نوع بیمه">
<AutoCompleteSelect
updateData={updateSelect}
<DefaultSelect
updateData={(name, val) => updateSelect(val, "value", name)}
label="نوع بیمه"
name="first"
list={bime}
name="basic_insurance"
list={insurance}
value={findVal()}
/>
</Content>
</div>
+21 -5
View File
@@ -1,9 +1,10 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { Button } from "@mui/material";
import AddCircleBlueA from "@/components/icons/AddCircleBlueA";
import ArrowLeftB from "@/components/icons/ArrowLeftB";
import ButtonFixed from "../paying/ButtonFixed";
import Form from "./Form";
import { request } from "@/services/response";
function Detail({
isForAnother,
@@ -14,6 +15,7 @@ function Detail({
fadeElement,
setIsPay,
}) {
const [insurance, setInsurance] = useState();
const [selected, setSelected] = useState({
first: "",
second: "",
@@ -31,6 +33,19 @@ function Detail({
const updateSelect = (event, name) =>
setSelected({ ...selected, [name]: event });
const newStep = () => {
// if(data)
setStep((prev) => prev + 1);
};
useEffect(() => {
request.getInsuranceType().then((res) => {
if (res && res.data) {
setInsurance(res.data);
}
});
}, []);
return (
// ${typePay === 'success' || typePay === 'failed' ? 'bg-transparent border-transparent md:bg-[#FFF] md:border-[#EFEFEF]' : ''}
<div className="w-full lg:w-[59%]">
@@ -43,10 +58,11 @@ function Detail({
{isForAnother ? "اطلاعات کاربر جدید" : "اطلاعات حساب کاربری"}
</p>
<Form
isForAnother={isForAnother}
updateSelect={updateSelect}
changeData={changeData}
data={data}
insurance={insurance}
changeData={changeData}
updateSelect={updateSelect}
isForAnother={isForAnother}
/>
{!isForAnother && (
<Button
@@ -77,7 +93,7 @@ function Detail({
: "!mt-0 md:!mt-[32px]"
}`}
variant="contained"
onClick={() => setStep((prev) => prev + 1)}
onClick={newStep}
>
<p className="text-[#EFEFEF] text-[16px] font-medium">
ثبت اطلاعات
+63 -56
View File
@@ -1,71 +1,78 @@
"use client";
import { useState } from "react";
import Content from "./Content";
import Layout from "@/components/layout";
import { useEffect, useState } from "react";
import doctors from "@/data/doctors.json";
import RegisterPage from "@/components/register";
import Paying from "@/components/appointment/paying";
import Detail from "@/components/appointment/detail";
import FailedPay from "@/components/appointment/failedPay";
import LogInPage from "@/components/register/LogInPage";
import SuccessPay from "@/components/appointment/successPay";
import VerificationPage from "@/components/register/verificationPage";
import Date from "./date";
import { request } from "@/services/response";
import Cookies from "js-cookie";
import Container from "./Container";
const defaultData = {
phone: { value: "", isEdit: false },
national_code: { value: "", isEdit: false },
name: { value: "", isEdit: false },
basic_insurance: { value: "", isEdit: false },
};
function AppointmentPage({ slug, matchedCity }) {
const doctor = doctors.find((item) => item.id === +slug);
const [step, setStep] = useState(0);
const [step, setStep] = useState(1);
const [isForAnother, setIsForAnother] = useState(false);
const [data, setData] = useState({
phone: { value: "09121056987", isEdit: true },
codemeli: { value: "1741025645", isEdit: true },
name: { value: "ساغر صابری نژاد", isEdit: true },
});
const [prevData, setPrevData] = useState(defaultData);
const [data, setData] = useState(defaultData);
const elements = [
<Date doctor={doctor} setStep={setStep} />,
<LogInPage setIsSendMsg={false} setStep={setStep} />,
<VerificationPage setIsSendMsg={false} link={false} setStep={setStep} />,
<Detail
data={data}
setStep={setStep}
setData={setData}
isForAnother={isForAnother}
setIsForAnother={setIsForAnother}
/>,
<Paying setStep={setStep} />,
<SuccessPay setStep={setStep} />,
<FailedPay setStep={setStep} />,
];
useEffect(() => {
const userInfo = Cookies.get("userInfo");
const parsedData = JSON.parse(userInfo);
if (userInfo && parsedData) {
request
.getUserProfile(parsedData.uuid)
.then((res) => {
if (res) {
const newData = {
phone: { value: parsedData.username || "", isEdit: false },
national_code: { value: res.national_code || "", isEdit: true },
name: { value: `${res.name} ${res.family}`, isEdit: true },
basic_insurance: {
value: res.basic_insurance,
isEdit: true,
},
};
setData(newData);
setPrevData(newData);
} else {
const emptyData = {
phone: { value: parsedData.username || "", isEdit: false },
national_code: { value: "", isEdit: true },
name: { value: "", isEdit: true },
basic_insurance: {
value: res.basic_insurance,
isEdit: true,
},
};
setData(emptyData);
setPrevData(emptyData);
}
})
.catch((err) => {
console.log(err);
});
}
}, []);
return (
<>
{step === 1 || step === 2 ? (
<RegisterPage>{elements[step]}</RegisterPage>
) : (
<Layout
title="رزرو"
name="booking"
disableFooter={step > 2}
matchedCity={matchedCity}
paddingBottom="pb-[80px]"
>
<Content
disableSide={step === 5 || step === 6}
isFirst={step === 0}
setStep={setStep}
doctor={doctor}
step={step}
>
{elements[step]}
</Content>
</Layout>
)}
</>
<Container
step={step}
data={data}
doctor={doctor}
setData={setData}
setStep={setStep}
matchedCity={matchedCity}
isForAnother={isForAnother}
setIsForAnother={setIsForAnother}
/>
);
}
+1 -1
View File
@@ -14,7 +14,7 @@ function Detail({ doctor, step, setStep }) {
<LocationA />
</div>
<p className="text-[#616161] text-[14px] md:text-[16px] font-normal">
آدرس: {doctor.location}
آدرس: {doctor?.location}
</p>
</li>
<li className="flex items-center justify-between w-full">
+3 -3
View File
@@ -16,16 +16,16 @@ function Head({ doctor }) {
h-[40px] sm:h-[58px] md:h-[77px] lg:h-[95px]
"
alt="profile doctor"
src={doctor.img}
src={doctor?.img}
height={95}
width={95}
/>
<div className="flex flex-col items-start justify-center gap-[8px] sm:gap-[12px] md:gap-[16px] lg:gap-[20px]">
<p className="text-[#3B3B3B] text-[14px] sm:text-[16px] md:text-[18px] lg:text-[20px] font-bold">
{doctor.name}
{doctor?.name}
</p>
<p className="text-[#616161] text-[14px] md:text-[16px] font-medium">
تخصص: {doctor.expertise}
تخصص: {doctor?.expertise}
</p>
</div>
</div>
@@ -2,30 +2,18 @@ import BottomSelect from "@/components/icons/BottomSelect";
import { Autocomplete, Button, TextField } from "@mui/material";
import { styleTextSelectRight } from "@/mui";
function AutoSelector({
list,
value,
isError,
updateData,
label,
error,
disabled = false,
listboxProps,
name,
}) {
function AutoSelector({ list, value, updateData, label, error, name }) {
return (
<Autocomplete
disablePortal
options={list}
disabled={disabled}
value={value || ""}
ListboxProps={listboxProps}
getOptionLabel={(option) => {
if (typeof option === "string") return option;
return option?.name || option?.label || "";
}}
className="!w-full !rounded-lg auto-complete-selector"
onChange={(e, newValue) => {
onChange={(_, newValue) => {
updateData(name, newValue?.label || newValue || "");
}}
sx={{
@@ -62,9 +50,6 @@ function AutoSelector({
{
border: "1px solid #5559CE !important",
},
"& .MuiOutlinedInput-notchedOutline": {
border: isError ? "1px solid red !important" : "",
},
}}
renderOption={(props, option) => (
<Button
@@ -1,10 +1,10 @@
import { getParsedUserInfo } from "@/helper";
import Content from "./Content";
import DateBirthday from "./DateBirthday";
import AutoSelector from "../component/AutoSelector";
import Field from "../component/Field";
import { gender, blood_group, marital_status, job } from "./listSelector";
import education from "@/data/education.json";
import DefaultSelect from "@/app/component/selectors/DefaultSelect";
function Form({ insurance, errors, changeData, information }) {
const findVal = (list, name) =>
@@ -72,7 +72,7 @@ function Form({ insurance, errors, changeData, information }) {
/>
</Content>
<Content req={true} error={errors?.gender} title="جنسیت">
<AutoSelector
<DefaultSelect
error={errors?.gender}
updateData={(name, data) =>
changeData(data === "زن" ? "female" : "male", name)
@@ -107,7 +107,7 @@ function Form({ insurance, errors, changeData, information }) {
/>
</Content>
<Content req={true} error={errors?.basic_insurance} title="نوع بیمه">
<AutoSelector
<DefaultSelect
error={errors?.basic_insurance}
updateData={(name, val) => {
changeData([Number(val.id)], name);
@@ -141,7 +141,7 @@ function Form({ insurance, errors, changeData, information }) {
/>
</Content>
<Content req={true} error={errors?.blood_type} title="گروه خونی">
<AutoSelector
<DefaultSelect
error={errors?.blood_type}
updateData={(name, value) =>
changeData(blood_group.find((g) => g.label === value).id, name)
@@ -153,7 +153,7 @@ function Form({ insurance, errors, changeData, information }) {
/>
</Content>
<Content req={true} error={errors?.marital_status} title="وضعیت تاهل">
<AutoSelector
<DefaultSelect
error={errors?.marital_status}
updateData={(name, value) =>
changeData(value === "متاهل" ? "married" : "single", name)
@@ -175,7 +175,7 @@ function Form({ insurance, errors, changeData, information }) {
/>
</Content>
<Content req={true} error={errors?.education} title="تحصیلات">
<AutoSelector
<DefaultSelect
error={errors?.education}
updateData={(name, value) =>
changeData(education.find((g) => g.label === value).id, name)
@@ -187,7 +187,7 @@ function Form({ insurance, errors, changeData, information }) {
/>
</Content>
<Content req={true} error={errors?.job} title="شغل">
<AutoSelector
<DefaultSelect
error={errors?.job}
updateData={(name, value) =>
changeData(value === "آزاد" ? "azad" : "dolati", name)
-3
View File
@@ -19,12 +19,9 @@ function DoctorsPage({ params, matchedCity, matchedState, list }) {
province: matchedState?.name || params?.province,
city: matchedCity?.name || params?.city,
});
console.log(specialties);
// console.log(fieldId);
const findItem = (id) => specialties.find((item) => item.id === id);
const isParent = findItem(fieldId) && findItem(fieldId).parent;
console.log(isParent);
const findSpecial = (id) => specialties.find((item) => item.id === id);
-1
View File
@@ -21,7 +21,6 @@ function Content({ data, setData }) {
(item) => item.parent === value.id
);
const firstChildren = filteredChildrenList[0];
console.log(firstChildren);
newData.specialties = firstChildren || "";
changeUrl(firstChildren || value, key);
@@ -1,15 +1,15 @@
import { FormControlLabel, Switch } from "@mui/material";
function SwitchContent({ data }) {
function SwitchContent({ data, changeData }) {
return (
<div className="flex items-center justify-start gap-[12px]">
<FormControlLabel
control={
<Switch
checked={data.turn}
onChange={(event) => {
changeData(event.target.checked ? 1 : 0, "turn");
}}
onChange={(event) =>
changeData(event.target.checked ? 1 : 0, "turn", false, "turn")
}
/>
}
sx={{
+1 -1
View File
@@ -47,7 +47,7 @@ function Form({ data, changeData }) {
name="degree"
/>
<span className="block mt-[16px] mb-[24px] w-full h-px bg-[#EFEFEF]"></span>
<SwitchContent data={data} />
<SwitchContent data={data} changeData={changeData} />
</div>
</>
);
+2
View File
@@ -45,6 +45,8 @@ export const request = {
getUserInfo: (headers) => api.get("oauth/userinfo", headers),
getInsuranceType: () =>
api.get("api/v1/categorys/insurance_type", removeTokenHead),
getAppointmentNotAvailable: (doctor_id) =>
api.get(`api/v1/appointment/not-available/${doctor_id}`, removeTokenHead),
getAppointmentWeeklySchedule: (uuid) =>
api.get(`api/v1/appointment-settings/weekly-schedule/${uuid}`),
postAppointmentWeeklySchedule: () =>