feat(appointment): real for-another-patient form and booking payload
Replace the mock for-another button with a real toggle: switching keeps the user's own data, clears the form to editable patient fields (phone becomes an input, adds علت مراجعه), and can switch back. SubmitData now sends for_self plus patient_* only when booking for someone else, skips the self-profile PATCH/POST in that case, stores the booking expires_at, and surfaces a clearer 409 message. Thread appointmentExpiresAt through the wizard to the payment step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,160 @@
|
|||||||
|
# فلوی نوبتگیری: قفل واقعی ۱۵دقیقهای، فرم «نوبت برای شخص دیگر»، و UX وضعیتمحور
|
||||||
|
|
||||||
|
## پروژه
|
||||||
|
|
||||||
|
`nobat724_front` — سایت عمومی. **بعد از پرامپت backend اجرا شود.**
|
||||||
|
|
||||||
|
> **Cross-repo:** وابسته به اصلاحات backend در
|
||||||
|
> `clinicpro/.claude/prompt/appointment-lock-patient-confirm.md`
|
||||||
|
> (فیلدهای `patient_*` و `for_self` در `POST /api/v1/appointment`، `expires_at` در پاسخ، تأیید پس از پرداخت). اگر آنها هنوز نیستند، **اول backend را اجرا کن.**
|
||||||
|
|
||||||
|
## زمینه
|
||||||
|
|
||||||
|
صفحهی `/appointment/[doctorId]` یک wizard چندمرحلهای است (`components/appointment/`): انتخاب تاریخ/ساعت → لاگین OTP → تکمیل اطلاعات (Detail) → پرداخت (Paying). در اصلاحات قبلی فلو به API واقعی وصل شد (ساخت نوبت در `SubmitData.js`، پرداخت در `paying/index.js`). اما:
|
||||||
|
|
||||||
|
- **تایمر پرداخت قلابی است:** `paying/index.js` یک `timeLeft = 600` (۱۰ دقیقه) شمارش معکوس محلی دارد که به قفل واقعی backend وصل نیست. باید ۱۵ دقیقه و بر مبنای `expires_at` واقعی نوبت باشد.
|
||||||
|
- **«نوبت برای شخص دیگر» فقط mock بود:** در `detail/index.js` دکمهی «دریافت نوبت برای فرد دیگر» دادهی ثابت ست میکند و فیلدهای واقعی بیمار (نام، موبایل، جنسیت، کد ملی، علت) را نمیگیرد و به backend نمیفرستد.
|
||||||
|
- **بدنهی ساخت نوبت فیلد بیمار/`for_self` ندارد.**
|
||||||
|
|
||||||
|
## مشکل / هدف
|
||||||
|
|
||||||
|
۱. فرم «نوبت برای شخص دیگر» با فیلدهای واقعی بیمار، و ارسال `patient_*` + `for_self` در ساخت نوبت.
|
||||||
|
۲. تایمر پرداخت بر مبنای `expires_at` واقعی نوبت (۱۵ دقیقه)؛ اتمام تایمر → نوبت منقضی، پیام مناسب، بازگشت به انتخاب ساعت.
|
||||||
|
۳. UX وضعیتمحور: پیامهای واضح برای رزرو همزمان (۴۰۹)، انقضا، و موفقیت.
|
||||||
|
|
||||||
|
## فایلهای مرتبط
|
||||||
|
|
||||||
|
| فایل | نقش |
|
||||||
|
|------|-----|
|
||||||
|
| `components/appointment/index.js` | state کل wizard (`data`, `isForAnother`, `selectedSlot`, `appointmentId`) |
|
||||||
|
| `components/appointment/detail/index.js` | فرم Detail + دکمهی «برای فرد دیگر» (الان mock) |
|
||||||
|
| `components/appointment/detail/Form.js` | رندر فیلدها |
|
||||||
|
| `components/appointment/detail/SubmitData.js` | `request.postAppointment` — بدنه باید `patient_*`/`for_self` بگیرد و `expires_at` را نگه دارد |
|
||||||
|
| `components/appointment/paying/index.js` | تایمر و پرداخت — تایمر باید از `expires_at` بیاید |
|
||||||
|
| `services/response.js` | `postAppointment` (بدنه از caller) |
|
||||||
|
|
||||||
|
## وضعیت فعلی (کد واقعی)
|
||||||
|
|
||||||
|
### `detail/index.js` — دکمهی mock «برای فرد دیگر»
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
{!isForAnother && (
|
||||||
|
<Button onClick={() => {
|
||||||
|
setIsForAnother(true);
|
||||||
|
setData({
|
||||||
|
phone: { value: "09121056987", isEdit: true },
|
||||||
|
codemeli: { value: "1741025645", isEdit: true },
|
||||||
|
name: { value: "ساغر صابری نژاد", isEdit: true },
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
<AddCircleBlueA />
|
||||||
|
<p>دریافت نوبت برای فرد دیگر</p>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `SubmitData.js` — بدنهی نوبت بدون بیمار
|
||||||
|
|
||||||
|
```js
|
||||||
|
const appointmentPayload = {
|
||||||
|
doctor_uuid: doctor.uuid,
|
||||||
|
slot_start: selectedSlot.start,
|
||||||
|
slot_end: selectedSlot.end,
|
||||||
|
note: "",
|
||||||
|
};
|
||||||
|
const res = await request.postAppointment(appointmentPayload);
|
||||||
|
const appointmentUuid = res?.data?.uuid;
|
||||||
|
if (appointmentUuid) setAppointmentId(appointmentUuid);
|
||||||
|
```
|
||||||
|
|
||||||
|
### `paying/index.js` — تایمر محلی ۶۰۰ ثانیه (قلابی)
|
||||||
|
|
||||||
|
```js
|
||||||
|
const [timeLeft, setTimeLeft] = useState(600); // 10 minutes
|
||||||
|
useEffect(() => {
|
||||||
|
if (timeLeft === 0) return;
|
||||||
|
const timer = setInterval(() => setTimeLeft((p) => p - 1), 1000);
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, [timeLeft]);
|
||||||
|
```
|
||||||
|
|
||||||
|
## قرارداد backend (بعد از اجرای پرامپت همتا)
|
||||||
|
|
||||||
|
`POST /api/v1/appointment` بدنه:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"doctor_uuid": "...", "slot_start": 1781933400, "slot_end": 1781934600,
|
||||||
|
"for_self": true,
|
||||||
|
"patient_name": "...", "patient_mobile": "09...", "patient_national_code": "...",
|
||||||
|
"patient_gender": "male", "patient_reason": "...", "note": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
پاسخ `201`: `{ success, data: { uuid, status:"pending", expires_at: 1781934300, patient_name, ... } }`
|
||||||
|
خطاها: `409` (اسلات همزمان رزرو شد)، `422` (فیلد بیمار ناقص وقتی for_self=false).
|
||||||
|
|
||||||
|
> interceptor یکبار باز میکند → داده در `res.data`. `expires_at` Unix ثانیه است.
|
||||||
|
|
||||||
|
## وظایف
|
||||||
|
|
||||||
|
### ۱. فرم «نوبت برای شخص دیگر» (`detail/index.js` + `Form.js`)
|
||||||
|
|
||||||
|
- دکمه را به یک toggle واقعی تبدیل کن: با `isForAnother=true` فیلدهای بیمار خالی و قابلویرایش شوند (نه دادهی mock).
|
||||||
|
- فیلدهای بیمار: نام، نام خانوادگی، شماره موبایل (هر دو اجباری وقتی برای دیگری)، جنسیت (اختیاری)، کد ملی (اختیاری)، علت مراجعه (اختیاری، textarea).
|
||||||
|
- وقتی «برای خودم»: همان رفتار فعلی (اطلاعات پروفایل، قابلویرایش).
|
||||||
|
- اعتبارسنجی حداقلی سمت کلاینت (نام و موبایل بیمار اجباری در حالت «برای دیگری»)؛ خطای فیلد را با همان الگوی `errors` موجود نشان بده.
|
||||||
|
|
||||||
|
### ۲. ارسال `patient_*` و `for_self` در `SubmitData.js`
|
||||||
|
|
||||||
|
- بدنه را گسترش بده:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const appointmentPayload = {
|
||||||
|
doctor_uuid: doctor.uuid,
|
||||||
|
slot_start: selectedSlot.start,
|
||||||
|
slot_end: selectedSlot.end,
|
||||||
|
for_self: !isForAnother,
|
||||||
|
note: data?.patient_reason?.value || "",
|
||||||
|
...(isForAnother ? {
|
||||||
|
patient_name: data?.name?.value,
|
||||||
|
patient_mobile: data?.phone?.value,
|
||||||
|
patient_national_code: data?.national_code?.value || "",
|
||||||
|
patient_gender: data?.gender?.value?.id || data?.gender?.value || "",
|
||||||
|
patient_reason: data?.patient_reason?.value || "",
|
||||||
|
} : {}),
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- `isForAnother` را به `SubmitData` پاس بده (الان در `Container`/`Detail` هست).
|
||||||
|
- `expires_at` پاسخ را نگهدار و به مرحلهی پرداخت پاس بده (state جدید `appointmentExpiresAt` در `components/appointment/index.js`).
|
||||||
|
- خطای `409`: پیام «این زمان همین لحظه توسط کاربر دیگری رزرو شد» + بازگشت به مرحلهی انتخاب ساعت (`setStep(0)`) و تازهسازی اسلاتها.
|
||||||
|
- خطای `422`: پیامهای فیلد بیمار.
|
||||||
|
|
||||||
|
### ۳. تایمر پرداخت بر مبنای `expires_at` (`paying/index.js`)
|
||||||
|
|
||||||
|
- بهجای `useState(600)`، باقیمانده را از `expiresAt` (prop) حساب کن:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const computeLeft = () => Math.max(0, Math.floor((expiresAt * 1000 - Date.now()) / 1000));
|
||||||
|
const [timeLeft, setTimeLeft] = useState(computeLeft);
|
||||||
|
useEffect(() => {
|
||||||
|
const t = setInterval(() => setTimeLeft(computeLeft()), 1000);
|
||||||
|
return () => clearInterval(t);
|
||||||
|
}, [expiresAt]);
|
||||||
|
```
|
||||||
|
|
||||||
|
- وقتی `timeLeft === 0`: دکمهی پرداخت غیرفعال، پیام «مهلت پرداخت تمام شد؛ لطفاً دوباره زمان نوبت را انتخاب کنید»، و دکمهای برای بازگشت به مرحلهی ۰.
|
||||||
|
- اگر `expiresAt` نبود (سازگاری)، fallback به ۹۰۰ ثانیه از زمان mount.
|
||||||
|
|
||||||
|
### ۴. UX نهایی
|
||||||
|
|
||||||
|
- در مرحلهی پرداخت، نام بیمار و زمان نوبت را نمایش بده (از همان دادهی نوبت).
|
||||||
|
- بعد از پرداخت موفق (بازگشت از درگاه به `/payment/result` یا step success)، پیام تأیید + اشاره به ارسال SMS.
|
||||||
|
|
||||||
|
## نکات مهم
|
||||||
|
|
||||||
|
- **وابستگی cross-repo:** بدون فیلدهای `patient_*`/`for_self` و `expires_at` در backend این کار کامل نیست. اگر نبود متوقف شو.
|
||||||
|
- **پرداختکننده ≠ بیمار:** کاربر لاگینشده پرداختکننده است (از کوکی/توکن، خودکار)؛ فیلدهای بیمار جدا ارسال میشوند. این تفکیک را در UI هم روشن نشان بده.
|
||||||
|
- **زمانها Unix ثانیه:** `expires_at * 1000` برای مقایسه با `Date.now()`.
|
||||||
|
- **عدم رگرسیون:** فلوی OTP (step 1/2)، تقویم دوماهه، و گرید ساعت دستنخورده بمانند. منطق فعلی `SubmitData` برای PATCH/POST پروفایلِ کاربرِ «برای خودم» را حفظ کن (اطلاعات خودِ کاربر هنوز در پروفایلش ذخیره میشود؛ ولی فیلدهای بیمارِ نوبت جداگانه به نوبت میروند).
|
||||||
|
- **RTL/Jalali/multi-domain** حفظ شوند؛ فونت/کتابخانهی جدید اضافه نکن.
|
||||||
|
- **تست:** `npm run build`؛ سپس دستی: «برای دیگری» با نام/موبایل بیمار → نوبت ساخته شود؛ تایمر از ~۱۵:۰۰ بشمارد؛ رزرو همزمان روی یک اسلات → پیام ۴۰۹. سپس commit با پیام توصیفی برای هر قابلیت.
|
||||||
@@ -36,6 +36,8 @@ function Container({
|
|||||||
setSelectedDate,
|
setSelectedDate,
|
||||||
appointmentId,
|
appointmentId,
|
||||||
setAppointmentId,
|
setAppointmentId,
|
||||||
|
appointmentExpiresAt,
|
||||||
|
setAppointmentExpiresAt,
|
||||||
}) {
|
}) {
|
||||||
const elements = [
|
const elements = [
|
||||||
<Date
|
<Date
|
||||||
@@ -73,8 +75,16 @@ function Container({
|
|||||||
selectedSlot={selectedSlot}
|
selectedSlot={selectedSlot}
|
||||||
selectedDate={selectedDate}
|
selectedDate={selectedDate}
|
||||||
setAppointmentId={setAppointmentId}
|
setAppointmentId={setAppointmentId}
|
||||||
|
setAppointmentExpiresAt={setAppointmentExpiresAt}
|
||||||
|
/>,
|
||||||
|
<Paying
|
||||||
|
setStep={setStep}
|
||||||
|
appointmentId={appointmentId}
|
||||||
|
expiresAt={appointmentExpiresAt}
|
||||||
|
doctor={doctor}
|
||||||
|
selectedSlot={selectedSlot}
|
||||||
|
selectedDate={selectedDate}
|
||||||
/>,
|
/>,
|
||||||
<Paying setStep={setStep} appointmentId={appointmentId} />,
|
|
||||||
<SuccessPay setStep={setStep} />,
|
<SuccessPay setStep={setStep} />,
|
||||||
<FailedPay setStep={setStep} />,
|
<FailedPay setStep={setStep} />,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -24,11 +24,15 @@ function Form({ changeData, insurance, supplementaryInsurance, data, isForAnothe
|
|||||||
className="grid mt-[12px] sm:mt-[14px] md:mt-[17px] lg:mt-[20px] items-start
|
className="grid mt-[12px] sm:mt-[14px] md:mt-[17px] lg:mt-[20px] items-start
|
||||||
justify-center gap-x-[24px] gap-y-[12px] md:gap-y-[14px] lg:gap-y-[16px] grid-cols-1 sm:grid-cols-2"
|
justify-center gap-x-[24px] gap-y-[12px] md:gap-y-[14px] lg:gap-y-[16px] grid-cols-1 sm:grid-cols-2"
|
||||||
>
|
>
|
||||||
<Content title="شماره موبایل" isConfirmed={true}>
|
<Content title="شماره موبایل">
|
||||||
<div className="flex items-center justify-between px-[14px] py-[12.5px] border border-[#D7D7D7] rounded-lg bg-[#F5F5F5]">
|
{isForAnother ? (
|
||||||
<span className="text-[#3B3B3B]">{data?.phone?.value}</span>
|
<EditField changeData={changeData} data={data?.phone} name="phone" error={errors?.phone} />
|
||||||
<span className="text-[#4CAF50] text-[14px]">تایید شده</span>
|
) : (
|
||||||
</div>
|
<div className="flex items-center justify-between px-[14px] py-[12.5px] border border-[#D7D7D7] rounded-lg bg-[#F5F5F5]">
|
||||||
|
<span className="text-[#3B3B3B]">{data?.phone?.value}</span>
|
||||||
|
<span className="text-[#4CAF50] text-[14px]">تایید شده</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Content>
|
</Content>
|
||||||
<Content title="کد ملی">
|
<Content title="کد ملی">
|
||||||
<EditField
|
<EditField
|
||||||
@@ -61,6 +65,11 @@ function Form({ changeData, insurance, supplementaryInsurance, data, isForAnothe
|
|||||||
error={errors?.gender}
|
error={errors?.gender}
|
||||||
/>
|
/>
|
||||||
</Content>
|
</Content>
|
||||||
|
{isForAnother && (
|
||||||
|
<Content title="علت مراجعه (اختیاری)">
|
||||||
|
<EditField changeData={changeData} data={data?.patient_reason} name="patient_reason" error={errors?.patient_reason} />
|
||||||
|
</Content>
|
||||||
|
)}
|
||||||
<Content title="شماره بیمه (اختیاری)">
|
<Content title="شماره بیمه (اختیاری)">
|
||||||
<EditField changeData={changeData} data={data?.insurance_id} name="insurance_id" error={errors?.insurance_id} />
|
<EditField changeData={changeData} data={data?.insurance_id} name="insurance_id" error={errors?.insurance_id} />
|
||||||
</Content>
|
</Content>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import ArrowLeftB from "@/components/icons/ArrowLeftB";
|
|||||||
import { request } from "@/services/response";
|
import { request } from "@/services/response";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
function SubmitData({ setStep, data, prevData, setErrors, doctor, selectedSlot, selectedDate, setAppointmentId }) {
|
function SubmitData({ setStep, data, prevData, setErrors, doctor, isForAnother, selectedSlot, selectedDate, setAppointmentId, setAppointmentExpiresAt }) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const newStep = () => setStep((prev) => prev + 1);
|
const newStep = () => setStep((prev) => prev + 1);
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ function SubmitData({ setStep, data, prevData, setErrors, doctor, selectedSlot,
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isChanged) {
|
if (!isForAnother && isChanged) {
|
||||||
const { basic_insurance, supplementary_insurance, name, family, national_code, gender, insurance_id, uuid } = data;
|
const { basic_insurance, supplementary_insurance, name, family, national_code, gender, insurance_id, uuid } = data;
|
||||||
|
|
||||||
let payload = {};
|
let payload = {};
|
||||||
@@ -101,13 +101,24 @@ function SubmitData({ setStep, data, prevData, setErrors, doctor, selectedSlot,
|
|||||||
doctor_uuid: doctor.uuid,
|
doctor_uuid: doctor.uuid,
|
||||||
slot_start: selectedSlot.start,
|
slot_start: selectedSlot.start,
|
||||||
slot_end: selectedSlot.end,
|
slot_end: selectedSlot.end,
|
||||||
note: "",
|
for_self: !isForAnother,
|
||||||
|
note: data?.patient_reason?.value || "",
|
||||||
|
...(isForAnother
|
||||||
|
? {
|
||||||
|
patient_name: [data?.name?.value, data?.family?.value].filter(Boolean).join(" ").trim(),
|
||||||
|
patient_mobile: data?.phone?.value,
|
||||||
|
patient_national_code: data?.national_code?.value || "",
|
||||||
|
patient_gender: data?.gender?.value?.id || data?.gender?.value || "",
|
||||||
|
patient_reason: data?.patient_reason?.value || "",
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await request.postAppointment(appointmentPayload);
|
const res = await request.postAppointment(appointmentPayload);
|
||||||
const appointmentUuid = res?.data?.uuid;
|
const appointmentUuid = res?.data?.uuid;
|
||||||
if (appointmentUuid) {
|
if (appointmentUuid) {
|
||||||
setAppointmentId(appointmentUuid);
|
setAppointmentId(appointmentUuid);
|
||||||
|
setAppointmentExpiresAt?.(res?.data?.expires_at ?? null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +127,7 @@ function SubmitData({ setStep, data, prevData, setErrors, doctor, selectedSlot,
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (error?.response?.status === 409) {
|
if (error?.response?.status === 409) {
|
||||||
alert("این نوبت پیشتر رزرو شده است. لطفاً ساعت دیگری انتخاب کنید.");
|
alert("این زمان همین لحظه توسط کاربر دیگری رزرو شد. لطفاً ساعت دیگری انتخاب کنید.");
|
||||||
setStep(0);
|
setStep(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ function Detail({
|
|||||||
selectedSlot,
|
selectedSlot,
|
||||||
selectedDate,
|
selectedDate,
|
||||||
setAppointmentId,
|
setAppointmentId,
|
||||||
|
setAppointmentExpiresAt,
|
||||||
}) {
|
}) {
|
||||||
const [insurance, setInsurance] = useState();
|
const [insurance, setInsurance] = useState();
|
||||||
const [supplementaryInsurance, setSupplementaryInsurance] = useState();
|
const [supplementaryInsurance, setSupplementaryInsurance] = useState();
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
|
const [selfData, setSelfData] = useState(null);
|
||||||
|
|
||||||
const changeData = (value, type, name) =>
|
const changeData = (value, type, name) =>
|
||||||
setData({
|
setData({
|
||||||
@@ -33,6 +35,28 @@ function Detail({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emptyPatientData = () => ({
|
||||||
|
phone: { value: "", isEdit: true },
|
||||||
|
national_code: { value: "", isEdit: true },
|
||||||
|
name: { value: "", isEdit: true },
|
||||||
|
family: { value: "", isEdit: true },
|
||||||
|
gender: { value: "", isEdit: true },
|
||||||
|
patient_reason: { value: "", isEdit: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const switchToAnother = () => {
|
||||||
|
setSelfData(data);
|
||||||
|
setIsForAnother(true);
|
||||||
|
setErrors({});
|
||||||
|
setData(emptyPatientData());
|
||||||
|
};
|
||||||
|
|
||||||
|
const switchToSelf = () => {
|
||||||
|
setIsForAnother(false);
|
||||||
|
setErrors({});
|
||||||
|
if (selfData) setData(selfData);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// دریافت لیست بیمه پایه
|
// دریافت لیست بیمه پایه
|
||||||
request.getInsuranceType().then((res) => {
|
request.getInsuranceType().then((res) => {
|
||||||
@@ -78,33 +102,26 @@ function Detail({
|
|||||||
isForAnother={isForAnother}
|
isForAnother={isForAnother}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
{!isForAnother && (
|
<Button
|
||||||
<Button
|
className="!flex !items-center !justify-start !gap-[8px] !mt-[12px] sm:!mt-[16px] md:!mt-[20px] lg:!mt-[24px] !p-1"
|
||||||
className="!flex !items-center !justify-start !gap-[8px] !mt-[12px] sm:!mt-[16px] md:!mt-[20px] lg:!mt-[24px] !p-1"
|
onClick={() => (isForAnother ? switchToSelf() : switchToAnother())}
|
||||||
onClick={() => {
|
>
|
||||||
setIsForAnother(true);
|
<AddCircleBlueA />
|
||||||
setData({
|
<p className="text-[#5559CE] text-[16px] font-medium">
|
||||||
phone: { value: "09121056987", isEdit: true },
|
{isForAnother ? "نوبت برای خودم است" : "نوبت برای شخص دیگری است"}
|
||||||
codemeli: { value: "1741025645", isEdit: true },
|
</p>
|
||||||
name: { value: "ساغر صابری نژاد", isEdit: true },
|
</Button>
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AddCircleBlueA />
|
|
||||||
<p className="text-[#5559CE] text-[16px] font-medium">
|
|
||||||
دریافت نوبت برای فرد دیگر{" "}
|
|
||||||
</p>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<SubmitData
|
<SubmitData
|
||||||
setStep={setStep}
|
setStep={setStep}
|
||||||
data={data}
|
data={data}
|
||||||
prevData={prevData}
|
prevData={prevData}
|
||||||
setErrors={setErrors}
|
setErrors={setErrors}
|
||||||
doctor={doctor}
|
doctor={doctor}
|
||||||
|
isForAnother={isForAnother}
|
||||||
selectedSlot={selectedSlot}
|
selectedSlot={selectedSlot}
|
||||||
selectedDate={selectedDate}
|
selectedDate={selectedDate}
|
||||||
setAppointmentId={setAppointmentId}
|
setAppointmentId={setAppointmentId}
|
||||||
|
setAppointmentExpiresAt={setAppointmentExpiresAt}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ function AppointmentPage({ doctor, disabledDates, matchedCity }) {
|
|||||||
const [selectedSlot, setSelectedSlot] = useState(null);
|
const [selectedSlot, setSelectedSlot] = useState(null);
|
||||||
const [selectedDate, setSelectedDate] = useState(null);
|
const [selectedDate, setSelectedDate] = useState(null);
|
||||||
const [appointmentId, setAppointmentId] = useState(null);
|
const [appointmentId, setAppointmentId] = useState(null);
|
||||||
|
const [appointmentExpiresAt, setAppointmentExpiresAt] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchUserData = async () => {
|
const fetchUserData = async () => {
|
||||||
@@ -183,6 +184,8 @@ function AppointmentPage({ doctor, disabledDates, matchedCity }) {
|
|||||||
setSelectedDate={setSelectedDate}
|
setSelectedDate={setSelectedDate}
|
||||||
appointmentId={appointmentId}
|
appointmentId={appointmentId}
|
||||||
setAppointmentId={setAppointmentId}
|
setAppointmentId={setAppointmentId}
|
||||||
|
appointmentExpiresAt={appointmentExpiresAt}
|
||||||
|
setAppointmentExpiresAt={setAppointmentExpiresAt}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user