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:
@@ -36,6 +36,8 @@ function Container({
|
||||
setSelectedDate,
|
||||
appointmentId,
|
||||
setAppointmentId,
|
||||
appointmentExpiresAt,
|
||||
setAppointmentExpiresAt,
|
||||
}) {
|
||||
const elements = [
|
||||
<Date
|
||||
@@ -73,8 +75,16 @@ function Container({
|
||||
selectedSlot={selectedSlot}
|
||||
selectedDate={selectedDate}
|
||||
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} />,
|
||||
<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
|
||||
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}>
|
||||
<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 title="شماره موبایل">
|
||||
{isForAnother ? (
|
||||
<EditField changeData={changeData} data={data?.phone} name="phone" error={errors?.phone} />
|
||||
) : (
|
||||
<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 title="کد ملی">
|
||||
<EditField
|
||||
@@ -61,6 +65,11 @@ function Form({ changeData, insurance, supplementaryInsurance, data, isForAnothe
|
||||
error={errors?.gender}
|
||||
/>
|
||||
</Content>
|
||||
{isForAnother && (
|
||||
<Content title="علت مراجعه (اختیاری)">
|
||||
<EditField changeData={changeData} data={data?.patient_reason} name="patient_reason" error={errors?.patient_reason} />
|
||||
</Content>
|
||||
)}
|
||||
<Content title="شماره بیمه (اختیاری)">
|
||||
<EditField changeData={changeData} data={data?.insurance_id} name="insurance_id" error={errors?.insurance_id} />
|
||||
</Content>
|
||||
|
||||
@@ -5,7 +5,7 @@ import ArrowLeftB from "@/components/icons/ArrowLeftB";
|
||||
import { request } from "@/services/response";
|
||||
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 newStep = () => setStep((prev) => prev + 1);
|
||||
|
||||
@@ -41,7 +41,7 @@ function SubmitData({ setStep, data, prevData, setErrors, doctor, selectedSlot,
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
if (isChanged) {
|
||||
if (!isForAnother && isChanged) {
|
||||
const { basic_insurance, supplementary_insurance, name, family, national_code, gender, insurance_id, uuid } = data;
|
||||
|
||||
let payload = {};
|
||||
@@ -101,13 +101,24 @@ function SubmitData({ setStep, data, prevData, setErrors, doctor, selectedSlot,
|
||||
doctor_uuid: doctor.uuid,
|
||||
slot_start: selectedSlot.start,
|
||||
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 appointmentUuid = res?.data?.uuid;
|
||||
if (appointmentUuid) {
|
||||
setAppointmentId(appointmentUuid);
|
||||
setAppointmentExpiresAt?.(res?.data?.expires_at ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +127,7 @@ function SubmitData({ setStep, data, prevData, setErrors, doctor, selectedSlot,
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
if (error?.response?.status === 409) {
|
||||
alert("این نوبت پیشتر رزرو شده است. لطفاً ساعت دیگری انتخاب کنید.");
|
||||
alert("این زمان همین لحظه توسط کاربر دیگری رزرو شد. لطفاً ساعت دیگری انتخاب کنید.");
|
||||
setStep(0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -19,10 +19,12 @@ function Detail({
|
||||
selectedSlot,
|
||||
selectedDate,
|
||||
setAppointmentId,
|
||||
setAppointmentExpiresAt,
|
||||
}) {
|
||||
const [insurance, setInsurance] = useState();
|
||||
const [supplementaryInsurance, setSupplementaryInsurance] = useState();
|
||||
const [errors, setErrors] = useState({});
|
||||
const [selfData, setSelfData] = useState(null);
|
||||
|
||||
const changeData = (value, type, name) =>
|
||||
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(() => {
|
||||
// دریافت لیست بیمه پایه
|
||||
request.getInsuranceType().then((res) => {
|
||||
@@ -78,33 +102,26 @@ function Detail({
|
||||
isForAnother={isForAnother}
|
||||
errors={errors}
|
||||
/>
|
||||
{!isForAnother && (
|
||||
<Button
|
||||
className="!flex !items-center !justify-start !gap-[8px] !mt-[12px] sm:!mt-[16px] md:!mt-[20px] lg:!mt-[24px] !p-1"
|
||||
onClick={() => {
|
||||
setIsForAnother(true);
|
||||
setData({
|
||||
phone: { value: "09121056987", isEdit: true },
|
||||
codemeli: { value: "1741025645", isEdit: true },
|
||||
name: { value: "ساغر صابری نژاد", isEdit: true },
|
||||
});
|
||||
}}
|
||||
>
|
||||
<AddCircleBlueA />
|
||||
<p className="text-[#5559CE] text-[16px] font-medium">
|
||||
دریافت نوبت برای فرد دیگر{" "}
|
||||
</p>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
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())}
|
||||
>
|
||||
<AddCircleBlueA />
|
||||
<p className="text-[#5559CE] text-[16px] font-medium">
|
||||
{isForAnother ? "نوبت برای خودم است" : "نوبت برای شخص دیگری است"}
|
||||
</p>
|
||||
</Button>
|
||||
<SubmitData
|
||||
setStep={setStep}
|
||||
data={data}
|
||||
prevData={prevData}
|
||||
setErrors={setErrors}
|
||||
doctor={doctor}
|
||||
isForAnother={isForAnother}
|
||||
selectedSlot={selectedSlot}
|
||||
selectedDate={selectedDate}
|
||||
setAppointmentId={setAppointmentId}
|
||||
setAppointmentExpiresAt={setAppointmentExpiresAt}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -56,6 +56,7 @@ function AppointmentPage({ doctor, disabledDates, matchedCity }) {
|
||||
const [selectedSlot, setSelectedSlot] = useState(null);
|
||||
const [selectedDate, setSelectedDate] = useState(null);
|
||||
const [appointmentId, setAppointmentId] = useState(null);
|
||||
const [appointmentExpiresAt, setAppointmentExpiresAt] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUserData = async () => {
|
||||
@@ -183,6 +184,8 @@ function AppointmentPage({ doctor, disabledDates, matchedCity }) {
|
||||
setSelectedDate={setSelectedDate}
|
||||
appointmentId={appointmentId}
|
||||
setAppointmentId={setAppointmentId}
|
||||
appointmentExpiresAt={appointmentExpiresAt}
|
||||
setAppointmentExpiresAt={setAppointmentExpiresAt}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user