feat: Implement resource-based appointment booking flow
- Updated DoctorPage component to accept bookingResources prop for appointment list. - Added serviceQuery function to serialize service item UUIDs for API requests. - Introduced new API endpoints for fetching booking resources and resource slots. - Enhanced tests for new resource-based booking functionality, including resource selection and service availability. - Created ResourceSelect component for selecting appointment types, including doctor and resource options. - Updated appointment submission logic to include resource_uuid in payload when applicable. - Ensured UI reflects changes in booking flow without disrupting existing doctor-centric experience.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
"use client";
|
||||
|
||||
const MAX_PREVIEW_SERVICES = 3;
|
||||
|
||||
/**
|
||||
* زیرنویس کارت: نام سرویسها تا سهتا و بعد «+n».
|
||||
*
|
||||
* شمارِ خالی («۳ سرویس») به بیمار نمیگوید این دستگاه به کارش میآید یا نه، و او را
|
||||
* مجبور میکند کورکورانه کلیک کند.
|
||||
*/
|
||||
function servicesLabel(services = []) {
|
||||
if (services.length === 0) return null;
|
||||
|
||||
const shown = services.slice(0, MAX_PREVIEW_SERVICES).map((s) => s.name);
|
||||
const rest = services.length - shown.length;
|
||||
|
||||
return rest > 0 ? `${shown.join("، ")} +${rest}` : shown.join("، ");
|
||||
}
|
||||
|
||||
/**
|
||||
* انتخاب نوع نوبت — پیش از انتخاب سرویس و روز، و فقط وقتی این پزشک در این محل
|
||||
* دستکم یک منبعِ قابل رزرو دارد.
|
||||
*
|
||||
* «منبع» واژهٔ داخلی است و به بیمار نشان داده نمیشود؛ کارتها نام واقعی دستگاه/اتاق
|
||||
* و نوعشان را نشان میدهند. کارت اول همیشه جریان همیشگی است: نوبت با خودِ پزشک.
|
||||
*/
|
||||
function ResourceSelect({ resources = [], selected = null, onSelect, allowDoctorOption = true }) {
|
||||
const cardClass = (active) =>
|
||||
`w-full text-right p-[16px] rounded-[8px] border border-solid transition-colors ${
|
||||
active
|
||||
? "border-[#5559CE] bg-[rgba(85,89,206,0.06)]"
|
||||
: "border-[#EFEFEF] bg-[#FFF] hover:border-[#C7C9EC]"
|
||||
}`;
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-[520px] mx-auto">
|
||||
<h2 className="text-[16px] font-bold text-[#3B3B3B] mb-4">۱. انتخاب نوع نوبت</h2>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
{/* پزشکی که برنامهٔ هفتگی ندارد، ویزیتِ مستقیم هم ندارد؛ نشاندادن این کارت
|
||||
کاربر را به فهرست ساعتِ همیشهخالی میبرد. */}
|
||||
{allowDoctorOption && (
|
||||
<button type="button" onClick={() => onSelect(null)} className={cardClass(selected === null)}>
|
||||
<p className="text-[14px] md:text-[16px] font-bold text-[#3B3B3B]">نوبت با پزشک</p>
|
||||
<p className="mt-[6px] text-[13px] text-[#616161] font-normal">
|
||||
ویزیت معمول در ساعتهای کاری پزشک
|
||||
</p>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{resources.map((resource) => {
|
||||
const active = selected?.uuid === resource.uuid;
|
||||
const label = servicesLabel(resource.services);
|
||||
|
||||
return (
|
||||
<button
|
||||
key={resource.uuid}
|
||||
type="button"
|
||||
onClick={() => onSelect(resource)}
|
||||
className={cardClass(active)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-[8px]">
|
||||
<p className="text-[14px] md:text-[16px] font-bold text-[#3B3B3B]">
|
||||
{resource.name}
|
||||
</p>
|
||||
{resource.type?.name && (
|
||||
<span className="text-[11px] text-[#5559CE] bg-[rgba(85,89,206,0.10)] rounded-[4px] px-[6px] py-[2px] shrink-0">
|
||||
{resource.type.name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{label && (
|
||||
<p className="mt-[6px] text-[13px] text-[#616161] font-normal">{label}</p>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResourceSelect;
|
||||
Reference in New Issue
Block a user