diff --git a/.claude/prompt/doctor-profile-booking-state-from-locations.md b/.claude/prompt/doctor-profile-booking-state-from-locations.md
new file mode 100644
index 0000000..1fa4fc1
--- /dev/null
+++ b/.claude/prompt/doctor-profile-booking-state-from-locations.md
@@ -0,0 +1,131 @@
+# وضعیت «نوبتدهی فعال/غیرفعال» پروفایل پزشک از booking_locations
+
+## پروژه
+
+`nobat724_front`
+
+پرامپت همتای backend که **باید اول اجرا شود**:
+`clinicpro/.claude/prompt/fix-public-booking-state-and-admin-context-slots.md`
+
+## زمینه
+
+برای «دکتر تست» (`bcabb3a8-cae3-45ec-876c-548f9c1e1569`) صفحهٔ
+`/doctor/bcabb3a8-…` پیام «نوبتدهی غیرفعال است» نشان میدهد در حالی که نوبتدهی
+کلینیکش فعال است و همین صفحه، `booking_locations` معتبر (با `next_available_at` غیرتهی)
+را server-side میگیرد.
+
+علت (با curl تأیید شد): فیلدهای `active` / `free_turn` پاسخ `GET /api/v1/doctor/{uuid}`
+در backend فقط از **برنامهٔ شخصی** پزشک ساخته میشدند؛ برنامهٔ شخصیِ دکتر تست غیرفعال
+است و برنامهٔ کلینیکش دیده نمیشد. پرامپت همتا این را با تجمیع همهٔ برنامهها اصلاح میکند.
+
+این پرامپت سمت سایت را defensive میکند: بنر و دکمهٔ نوبتدهی پروفایل نباید فقط به
+`doctor.active` تکیه کند وقتی خودِ صفحه دادهٔ دقیقتر (`booking_locations`) را در دست دارد.
+دو منبع نباید بتوانند حرف متناقض بزنند.
+
+## مشکل / هدف
+
+قاعدهٔ واحد برای پروفایل پزشک:
+
+- **نوبتدهی فعال** ⇔ `booking_locations` غیرخالی است (backend فقط محلهای واقعاً
+ قابلرزرو را برمیگرداند: آدرسدار + شیفت فعال روی همان آدرس).
+- برچسب «اولین نوبت آزاد» از کمینهٔ `next_available_at` بین محلها؛ اگر همه `null` بودند،
+ محلها فعالند ولی فعلاً ظرفیت ندارند → «فعلاً نوبت خالی ندارد» (نه «غیرفعال»).
+- `doctor.active === false` همراه با `booking_locations` خالی → «نوبتدهی غیرفعال است»
+ (رفتار فعلی، درست).
+
+## فایلهای مرتبط
+
+| فایل | نقش |
+|------|-----|
+| `app/doctor/[slug]/page.js:43,128-150` | `getBookingLocations` server-side — از قبل موجود |
+| `components/doctor/appointmentList/index.js:8,20-24` | نوار موبایل — `doctor?.active === false \|\| !doctor?.free_turn` |
+| `components/doctor/appointmentList/ItemAppointment.js:10,42-56` | کارت دسکتاپ — `bookingDisabled = turn?.active === false` |
+| `app/component/ItemDoctor.js:89` | کارت لیست پزشکان — فقط فیلد backend (بدون تغییر) |
+
+## وضعیت فعلی
+
+### کارت دسکتاپ — `components/doctor/appointmentList/ItemAppointment.js:10`
+
+```js
+function ItemAppointment({ turn, loading, doctorSlug }) {
+ const bookingDisabled = !loading && turn?.active === false;
+ ...
+ {bookingDisabled || !turn?.free_turn
+ ? "نوبتدهی غیرفعال است"
+ : `اولین نوبت آزاد: ${turn.free_turn}`}
+```
+
+### نوار موبایل — `components/doctor/appointmentList/index.js:20-24`
+
+```js
+ {doctor?.active === false || !doctor?.free_turn
+ ? "نوبتدهی غیرفعال است"
+ : `اولین نوبت آزاد: ${doctor.free_turn}`}
+```
+
+`AppointmentList` از `app/doctor/[slug]/page.js` رندر میشود که `bookingLocations` را
+همانجا دارد (`:128-129`) ولی به این کامپوننت پاس نمیدهد.
+
+## وظایف
+
+### ۱. پاسدادن `bookingLocations` به AppointmentList
+
+در `app/doctor/[slug]/page.js` همان آرایهٔ گرفتهشده را prop بده:
+
+```jsx
+
+```
+
+(نام prop و محل رندر را با کد واقعی صفحه تطبیق بده — `AppointmentList` ممکن است از طریق
+کامپوننت میانی رندر شود؛ در آن صورت prop را از همان مسیر عبور بده.)
+
+### ۲. قاعدهٔ واحد فعال/غیرفعال
+
+یک helper کوچک در همان `components/doctor/appointmentList/` (نه util سراسری جدید):
+
+```js
+export function bookingState(doctor, bookingLocations) {
+ const hasLocations = Array.isArray(bookingLocations) && bookingLocations.length > 0;
+ if (!hasLocations && doctor?.active === false) return { enabled: false, label: "نوبتدهی غیرفعال است" };
+ if (!hasLocations) return { enabled: doctor?.active !== false, label: doctor?.free_turn ? `اولین نوبت آزاد: ${doctor.free_turn}` : "نوبتدهی غیرفعال است" };
+ const earliest = bookingLocations
+ .map((l) => l.next_available_at)
+ .filter((t) => t != null)
+ .sort((a, b) => a - b)[0] ?? null;
+ return {
+ enabled: true,
+ label: earliest != null
+ ? `اولین نوبت آزاد: ${formatJalali(earliest)}`
+ : "فعلاً نوبت خالی ندارد",
+ };
+}
+```
+
+- `formatJalali` با `jalali-moment`/`moment-jalaali` موجود پروژه: روزِ هفته + ساعت
+ (مثل «شنبه ۰۹:۰۰») تا با فرمت `free_turn` backend همخانواده باشد. الگوی
+ `moment.unix(ts)` مثل `app/component/date/dateTime/index.js`.
+- هر دو کامپوننت (`index.js` نوار موبایل و `ItemAppointment.js`) از همین helper استفاده
+ کنند؛ شرطهای تکراری فعلی حذف شوند.
+- دکمهٔ «دریافت نوبت» با `enabled === true` فعال است حتی وقتی `earliest === null`
+ (صفحهٔ رزرو خودش روزهای بدون ظرفیت را نشان میدهد).
+
+### ۳. کارت لیست پزشکان — بدون تغییر
+
+`app/component/ItemDoctor.js:89` فقط `free_turn`/`active` پاسخ لیست را دارد و
+`booking_locations` برای هر آیتم لیست fetch نمیشود (N درخواست اضافه ممنوع). بعد از فیکس
+backend همین فیلدها درست میشوند — این فایل را دست نزن.
+
+## نکات مهم
+
+- **backend اول.** قبل از اجرای پرامپت همتا، `doctor.active` برای دکتر تست همچنان false
+ برمیگردد؛ helper این را میپوشاند ولی تست کامل فقط بعد از هر دو ممکن است.
+- پاسخهای API double-nested: `json?.data?.data ?? json?.data` — `getBookingLocations`
+ موجود در صفحه همین را رعایت میکند؛ عوضش نکن.
+- `booking_locations` فقط محلهای معتبر را دارد (فیلتر backend از پرامپتهای قبلی) —
+ دوباره فیلتر نکن.
+- رشتههای جدید فارسی، RTL، تم موجود (MUI v5 + Tailwind، فونت Vazir) — طراحی جدید نساز.
+- slug پزشک = `uuid`؛ `params` همیشه `await`؛ صفحه `generateMetadata` دارد — دست نزن.
+- تست دستی: `/doctor/bcabb3a8-cae3-45ec-876c-548f9c1e1569` باید «اولین نوبت آزاد» و دکمهٔ
+ فعال «دریافت نوبت» نشان دهد (دکتر تست فقط برنامهٔ کلینیکی فعال دارد). یک پزشک واقعاً
+ غیرفعال (بدون هیچ محل) همچنان «نوبتدهی غیرفعال است» ببیند.
+- بعد از تغییرات: `npm run lint` و `npm run build` هر دو سبز.
diff --git a/app/doctor/[slug]/page.js b/app/doctor/[slug]/page.js
index 1ad6582..6c8ff71 100644
--- a/app/doctor/[slug]/page.js
+++ b/app/doctor/[slug]/page.js
@@ -267,6 +267,7 @@ async function Doctor({ params }) {
rateAggregate={rateAggregate}
addresses={addresses}
bookableAddressUuids={[...bookableAddressUuids]}
+ bookingLocations={bookingLocations}
slug={slug}
/>
diff --git a/components/doctor/appointmentList/ItemAppointment.js b/components/doctor/appointmentList/ItemAppointment.js
index 3e69014..852f409 100644
--- a/components/doctor/appointmentList/ItemAppointment.js
+++ b/components/doctor/appointmentList/ItemAppointment.js
@@ -5,9 +5,11 @@ import GreenCalendarTurn from "@/components/icons/GreenCalendarTurn";
import { Button } from "@mui/material";
import Link from "next/link";
import DoctorAvatar from "@/app/component/DoctorAvatar";
+import { bookingState } from "./bookingState";
-function ItemAppointment({ turn, loading, doctorSlug }) {
- const bookingDisabled = !loading && turn?.active === false;
+function ItemAppointment({ turn, loading, doctorSlug, booking }) {
+ const state = booking ?? bookingState(turn, []);
+ const bookingDisabled = !loading && !state.enabled;
return (
- {bookingDisabled || !turn?.free_turn
- ? "نوبتدهی غیرفعال است"
- : `اولین نوبت آزاد: ${turn.free_turn}`}
+ {state.label}
diff --git a/components/doctor/appointmentList/bookingState.js b/components/doctor/appointmentList/bookingState.js
new file mode 100644
index 0000000..5882dbd
--- /dev/null
+++ b/components/doctor/appointmentList/bookingState.js
@@ -0,0 +1,53 @@
+import moment from "moment-jalaali";
+
+const DAY_NAMES = [
+ "یکشنبه",
+ "دوشنبه",
+ "سهشنبه",
+ "چهارشنبه",
+ "پنجشنبه",
+ "جمعه",
+ "شنبه",
+];
+
+const formatJalali = (ts) => {
+ const m = moment.unix(ts);
+ return `${DAY_NAMES[m.day()]} ${m.format("HH:mm")}`;
+};
+
+/**
+ * قاعدهٔ واحد فعال/غیرفعال بودن نوبتدهی در پروفایل پزشک.
+ *
+ * booking_locations دقیقتر از doctor.active است: backend فقط محلهای واقعاً
+ * قابلرزرو را برمیگرداند. تا وقتی حداقل یک محل هست، نوبتدهی فعال است — حتی
+ * اگر فیلد قدیمی active به هر دلیل false باشد. بدون محل، همان فیلدهای doctor
+ * ملاک میمانند (سازگاری با پزشک بدون برنامه).
+ */
+export function bookingState(doctor, bookingLocations) {
+ const hasLocations =
+ Array.isArray(bookingLocations) && bookingLocations.length > 0;
+
+ if (!hasLocations) {
+ const enabled = doctor?.active !== false && !!doctor?.free_turn;
+ return {
+ enabled,
+ label: enabled
+ ? `اولین نوبت آزاد: ${doctor.free_turn}`
+ : "نوبتدهی غیرفعال است",
+ };
+ }
+
+ const earliest =
+ bookingLocations
+ .map((l) => l.next_available_at)
+ .filter((t) => t != null)
+ .sort((a, b) => a - b)[0] ?? null;
+
+ return {
+ enabled: true,
+ label:
+ earliest != null
+ ? `اولین نوبت آزاد: ${formatJalali(earliest)}`
+ : "فعلاً نوبت خالی ندارد",
+ };
+}
diff --git a/components/doctor/appointmentList/index.js b/components/doctor/appointmentList/index.js
index 0a0a818..0d85b1b 100644
--- a/components/doctor/appointmentList/index.js
+++ b/components/doctor/appointmentList/index.js
@@ -4,24 +4,30 @@ import { Button } from "@mui/material";
import ItemAppointment from "./ItemAppointment";
import ArrowLeftD from "@/components/icons/ArrowLeftD";
import Link from "next/link";
+import { bookingState } from "./bookingState";
-function AppointmentList({ doctor, loading, doctorSlug }) {
+function AppointmentList({ doctor, loading, doctorSlug, bookingLocations = [] }) {
+ const booking = bookingState(doctor, bookingLocations);
return (
<>
- {doctor?.active === false || !doctor?.free_turn
- ? "نوبتدهی غیرفعال است"
- : `اولین نوبت آزاد: ${doctor.free_turn}`}
+ {booking.label}
- {doctor?.active === false ? (
+ {!booking.enabled ? (