fix(appointment): update display logic for inactive doctors and free turns
This commit is contained in:
@@ -0,0 +1,172 @@
|
|||||||
|
# رفع نمایش نوبت آزاد و دکمه نوبت برای پزشک با نوبتدهی غیرفعال
|
||||||
|
|
||||||
|
## پروژه
|
||||||
|
|
||||||
|
`nobat724_front` (سایت عمومی — فقط نمایش؛ backend از قبل `free_turn` و `active` صحیح برمیگرداند)
|
||||||
|
|
||||||
|
## زمینه
|
||||||
|
|
||||||
|
Backend (`clinicpro`) برای هر پزشک دو فیلد میفرستد:
|
||||||
|
- `active` (boolean) = `activeDoctorAppointment && has_schedule`
|
||||||
|
- `free_turn` (string) = نزدیکترین روز/ساعت کاری (مثل `دوشنبه 09:00–13:00`)، یا اگر نوبتدهی آنلاین خاموش باشد `"نوبتدهی آنلاین غیرفعال است"`.
|
||||||
|
|
||||||
|
سایت عمومی این دو فیلد را در سه جا نمایش میدهد و الان رفتارش ناقص است.
|
||||||
|
|
||||||
|
## مشکل / هدف
|
||||||
|
|
||||||
|
۱. در کارت لیست `/doctors` و در باکس نوبتدهی صفحهی پزشک، متن نوبت آزاد باید با پیشوند **«اولین نوبت آزاد:»** نمایش داده شود (مثلاً `اولین نوبت آزاد: دوشنبه 09:00–13:00`).
|
||||||
|
۲. اگر پزشک نوبتدهی آنلاینش خاموش است (`active === false`)، بهجای `free_turn` باید **«نوبتدهی غیرفعال است»** نوشته شود.
|
||||||
|
۳. در صفحهی پزشک (`/doctor/{uuid}`) بخش «نوبت دهی»، اگر `active === false` دکمهی **«دریافت نوبت»** باید **disabled** شود (نه لینک فعال به `/appointment`).
|
||||||
|
|
||||||
|
## فایلهای مرتبط
|
||||||
|
|
||||||
|
| فایل | نقش |
|
||||||
|
|------|-----|
|
||||||
|
| `app/component/ItemDoctor.js` | کارت پزشک در لیست `/doctors` — متن `free_turn` |
|
||||||
|
| `components/doctor/appointmentList/ItemAppointment.js` | باکس نوبتدهی دسکتاپ صفحه پزشک — متن + دکمه |
|
||||||
|
| `components/doctor/appointmentList/index.js` | نوار نوبتدهی موبایل (`fixed bottom`) — متن + دکمه |
|
||||||
|
|
||||||
|
## وضعیت فعلی
|
||||||
|
|
||||||
|
### `app/component/ItemDoctor.js` (حوالی خط ۸۴)
|
||||||
|
```jsx
|
||||||
|
<CustomLoading loading={loading} width={150} height={20}>
|
||||||
|
<p
|
||||||
|
className={`text-[12px] font-medium rounded w-fit p-1.5 ml-[52px]
|
||||||
|
${Number(doctor?.active)
|
||||||
|
? "bg-[rgba(5,_186,_88,_0.04)] text-[#05BA58]"
|
||||||
|
: "bg-[rgba(211,_47,_47,_0.04)] text-[#D32F2F]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{doctor?.free_turn}
|
||||||
|
</p>
|
||||||
|
</CustomLoading>
|
||||||
|
```
|
||||||
|
مشکل: متن خام `free_turn` بدون پیشوند «اولین نوبت آزاد».
|
||||||
|
|
||||||
|
### `components/doctor/appointmentList/ItemAppointment.js` (خط ۳۶–۵۸)
|
||||||
|
```jsx
|
||||||
|
<div className="flex mt-5 gap-2 items-center justify-between">
|
||||||
|
<div className="flex items-start justify-start gap-1">
|
||||||
|
<GreenCalendarTurn />
|
||||||
|
<TextLoading width={100} height={15} loading={loading}>
|
||||||
|
<p className="text-[#05BA58] text-[12px] font-medium">
|
||||||
|
{turn?.free_turn
|
||||||
|
? ` ${turn?.free_turn}`
|
||||||
|
: "نوبت ندارد"}
|
||||||
|
</p>
|
||||||
|
</TextLoading>
|
||||||
|
</div>
|
||||||
|
<Link href={loading ? "#" : `/appointment/${doctorSlug}`}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
className="!py-2 !px-3 xl:!px-6 gap-1"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
دریافت نوبت
|
||||||
|
<div className="w-[20px] h-[20px]">
|
||||||
|
<ArrowLeftD />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
مشکل: پیشوند ندارد؛ دکمه فقط با `loading` غیرفعال میشود، نه با `active === false`.
|
||||||
|
|
||||||
|
### `components/doctor/appointmentList/index.js` (نوار موبایل، خط ۱۶–۳۴)
|
||||||
|
```jsx
|
||||||
|
<div className="fixed lg:hidden ... flex items-center justify-between">
|
||||||
|
<p className="text-[#05BA58] text-[11px] font-normal">
|
||||||
|
{doctor && doctor?.free_turn}
|
||||||
|
</p>
|
||||||
|
<Link href={`/appointment/${doctorSlug}`}>
|
||||||
|
<Button variant="contained" className="!py-2 !px-4 gap-1 !text-[14px] !font-medium">
|
||||||
|
دریافت نوبت
|
||||||
|
<div className="w-[20px] h-[20px]"><ArrowLeftD /></div>
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
مشکل: پیشوند ندارد؛ دکمه هیچوقت با `active === false` غیرفعال نمیشود.
|
||||||
|
|
||||||
|
## وظایف
|
||||||
|
|
||||||
|
### ۱. کارت لیست — `app/component/ItemDoctor.js`
|
||||||
|
|
||||||
|
متن `free_turn` را با پیشوند نشان بده؛ اگر `active` خاموش بود متن «نوبتدهی غیرفعال است».
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
{Number(doctor?.active) && doctor?.free_turn
|
||||||
|
? `اولین نوبت آزاد: ${doctor.free_turn}`
|
||||||
|
: "نوبتدهی غیرفعال است"}
|
||||||
|
```
|
||||||
|
رنگ سبز/قرمز موجود حفظ شود (بر اساس `Number(doctor?.active)`).
|
||||||
|
|
||||||
|
### ۲. باکس دسکتاپ — `components/doctor/appointmentList/ItemAppointment.js`
|
||||||
|
|
||||||
|
اول یک متغیر در ابتدای کامپوننت:
|
||||||
|
```jsx
|
||||||
|
function ItemAppointment({ turn, loading, doctorSlug }) {
|
||||||
|
const bookingDisabled = !loading && turn?.active === false;
|
||||||
|
return (
|
||||||
|
```
|
||||||
|
|
||||||
|
متن:
|
||||||
|
```jsx
|
||||||
|
<p className="text-[#05BA58] text-[12px] font-medium">
|
||||||
|
{bookingDisabled || !turn?.free_turn
|
||||||
|
? "نوبتدهی غیرفعال است"
|
||||||
|
: `اولین نوبت آزاد: ${turn.free_turn}`}
|
||||||
|
</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
دکمه — وقتی `bookingDisabled` بود disabled و بدون `<Link>`:
|
||||||
|
```jsx
|
||||||
|
{bookingDisabled ? (
|
||||||
|
<Button variant="contained" className="!py-2 !px-3 xl:!px-6 gap-1" disabled>
|
||||||
|
نوبتدهی غیرفعال
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Link href={loading ? "#" : `/appointment/${doctorSlug}`}>
|
||||||
|
<Button variant="contained" className="!py-2 !px-3 xl:!px-6 gap-1" disabled={loading}>
|
||||||
|
دریافت نوبت
|
||||||
|
<div className="w-[20px] h-[20px]"><ArrowLeftD /></div>
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۳. نوار موبایل — `components/doctor/appointmentList/index.js`
|
||||||
|
|
||||||
|
متن:
|
||||||
|
```jsx
|
||||||
|
<p className="text-[#05BA58] text-[11px] font-normal">
|
||||||
|
{doctor?.active === false || !doctor?.free_turn
|
||||||
|
? "نوبتدهی غیرفعال است"
|
||||||
|
: `اولین نوبت آزاد: ${doctor.free_turn}`}
|
||||||
|
</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
دکمه:
|
||||||
|
```jsx
|
||||||
|
{doctor?.active === false ? (
|
||||||
|
<Button variant="contained" className="!py-2 !px-4 gap-1 !text-[14px] !font-medium" disabled>
|
||||||
|
نوبتدهی غیرفعال
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Link href={`/appointment/${doctorSlug}`}>
|
||||||
|
<Button variant="contained" className="!py-2 !px-4 gap-1 !text-[14px] !font-medium">
|
||||||
|
دریافت نوبت
|
||||||
|
<div className="w-[20px] h-[20px]"><ArrowLeftD /></div>
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
|
||||||
|
## نکات مهم
|
||||||
|
|
||||||
|
- `active` از API نوع boolean است؛ در `ItemDoctor.js` با `Number(doctor?.active)` چک میشود (الگوی موجود را نگهدار)، در باقی جاها مستقیم `=== false`.
|
||||||
|
- در حالت `loading` (اسکلتون) نباید «نوبتدهی غیرفعال» نشان داده شود؛ برای همین `bookingDisabled = !loading && turn?.active === false`.
|
||||||
|
- backend از قبل پیام `"نوبتدهی آنلاین غیرفعال است"` در `free_turn` میفرستد — ولی frontend نباید به متن backend وابسته باشد؛ بر اساس فیلد boolean `active` تصمیم بگیر (پایدارتر).
|
||||||
|
- تست با پزشک `7db9be49-db71-4ec5-add6-24f890b4ad29` (در DB محلی باید `active_doctor_appointment=0` شود تا غیرفعال شود). پزشک غیرفعال در لیست `/doctors` اصلاً نمیآید (فیلتر backend)، پس باگ کارت لیست فقط برای پزشکانی که `active:false` ولی همچنان در نتیجه هستند مهم است.
|
||||||
|
- بعد از تغییر: `npm run build` بدون خطا.
|
||||||
@@ -88,7 +88,9 @@ function ItemDoctor({ doctor, loading, setDoctors, priority = false }) {
|
|||||||
: "bg-[rgba(211,_47,_47,_0.04)] text-[#D32F2F]"
|
: "bg-[rgba(211,_47,_47,_0.04)] text-[#D32F2F]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{doctor?.free_turn}
|
{Number(doctor?.active) && doctor?.free_turn
|
||||||
|
? `اولین نوبت آزاد: ${doctor.free_turn}`
|
||||||
|
: "نوبتدهی غیرفعال است"}
|
||||||
</p>
|
</p>
|
||||||
</CustomLoading>
|
</CustomLoading>
|
||||||
{/* Arrow */}
|
{/* Arrow */}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Link from "next/link";
|
|||||||
import DoctorAvatar from "@/app/component/DoctorAvatar";
|
import DoctorAvatar from "@/app/component/DoctorAvatar";
|
||||||
|
|
||||||
function ItemAppointment({ turn, loading, doctorSlug }) {
|
function ItemAppointment({ turn, loading, doctorSlug }) {
|
||||||
|
const bookingDisabled = !loading && turn?.active === false;
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={turn.id}
|
key={turn.id}
|
||||||
@@ -38,24 +39,34 @@ function ItemAppointment({ turn, loading, doctorSlug }) {
|
|||||||
<GreenCalendarTurn />
|
<GreenCalendarTurn />
|
||||||
<TextLoading width={100} height={15} loading={loading}>
|
<TextLoading width={100} height={15} loading={loading}>
|
||||||
<p className="text-[#05BA58] text-[12px] font-medium">
|
<p className="text-[#05BA58] text-[12px] font-medium">
|
||||||
{turn?.free_turn
|
{bookingDisabled || !turn?.free_turn
|
||||||
? ` ${turn?.free_turn}`
|
? "نوبتدهی غیرفعال است"
|
||||||
: "نوبت ندارد"}
|
: `اولین نوبت آزاد: ${turn.free_turn}`}
|
||||||
</p>
|
</p>
|
||||||
</TextLoading>
|
</TextLoading>
|
||||||
</div>
|
</div>
|
||||||
<Link href={loading ? "#" : `/appointment/${doctorSlug}`}>
|
{bookingDisabled ? (
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
className="!py-2 !px-3 xl:!px-6 gap-1"
|
className="!py-2 !px-3 xl:!px-6 gap-1"
|
||||||
disabled={loading}
|
disabled
|
||||||
>
|
>
|
||||||
دریافت نوبت
|
نوبتدهی غیرفعال
|
||||||
<div className="w-[20px] h-[20px]">
|
|
||||||
<ArrowLeftD />
|
|
||||||
</div>
|
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
) : (
|
||||||
|
<Link href={loading ? "#" : `/appointment/${doctorSlug}`}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
className="!py-2 !px-3 xl:!px-6 gap-1"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
دریافت نوبت
|
||||||
|
<div className="w-[20px] h-[20px]">
|
||||||
|
<ArrowLeftD />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,19 +18,31 @@ function AppointmentList({ doctor, loading, doctorSlug }) {
|
|||||||
border-solid bottom-0 right-0 w-full p-4 flex items-center justify-between"
|
border-solid bottom-0 right-0 w-full p-4 flex items-center justify-between"
|
||||||
>
|
>
|
||||||
<p className="text-[#05BA58] text-[11px] font-normal">
|
<p className="text-[#05BA58] text-[11px] font-normal">
|
||||||
{doctor && doctor?.free_turn}
|
{doctor?.active === false || !doctor?.free_turn
|
||||||
|
? "نوبتدهی غیرفعال است"
|
||||||
|
: `اولین نوبت آزاد: ${doctor.free_turn}`}
|
||||||
</p>
|
</p>
|
||||||
<Link href={`/appointment/${doctorSlug}`}>
|
{doctor?.active === false ? (
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
className="!py-2 !px-4 gap-1 !text-[14px] !font-medium"
|
className="!py-2 !px-4 gap-1 !text-[14px] !font-medium"
|
||||||
|
disabled
|
||||||
>
|
>
|
||||||
دریافت نوبت
|
نوبتدهی غیرفعال
|
||||||
<div className="w-[20px] h-[20px]">
|
|
||||||
<ArrowLeftD />
|
|
||||||
</div>
|
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
) : (
|
||||||
|
<Link href={`/appointment/${doctorSlug}`}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
className="!py-2 !px-4 gap-1 !text-[14px] !font-medium"
|
||||||
|
>
|
||||||
|
دریافت نوبت
|
||||||
|
<div className="w-[20px] h-[20px]">
|
||||||
|
<ArrowLeftD />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user