From 277922d4aec6aa8974ece6748b5460aa8c140192 Mon Sep 17 00:00:00 2001
From: hamed <15238-genius.ha@users.noreply.drupalcode.org>
Date: Thu, 11 Jun 2026 19:06:59 +0330
Subject: [PATCH] feat: add doctor profile page with sidebar integration and
routing
---
.claude/prompt/doctor-profile-page.md | 202 +++++++++++++++++++++
.claude/skills/run-prompt/SKILL.md | 50 ++++-
TEST_USERS.md | 62 ++++---
assets/admin/App.tsx | 2 +
assets/admin/components/layout/Sidebar.tsx | 7 +
assets/admin/pages/DoctorDetailPage.tsx | 95 ++++++----
assets/admin/pages/DoctorProfilePage.tsx | 5 +
7 files changed, 347 insertions(+), 76 deletions(-)
create mode 100644 .claude/prompt/doctor-profile-page.md
create mode 100644 assets/admin/pages/DoctorProfilePage.tsx
diff --git a/.claude/prompt/doctor-profile-page.md b/.claude/prompt/doctor-profile-page.md
new file mode 100644
index 00000000..096629be
--- /dev/null
+++ b/.claude/prompt/doctor-profile-page.md
@@ -0,0 +1,202 @@
+# صفحه پروفایل پزشک (Doctor Profile Page)
+
+## هدف
+
+پزشک باید صفحهای داشته باشد که بتواند تمام اطلاعات مربوط به خودش را مشاهده و ویرایش کند — دقیقاً مشابه `/admin/doctors/{uuid}` که ادمین میبیند، اما:
+- به صورت خودکار UUID پزشک لاگینشده از `authStore.dbUuid` خوانده میشود
+- آدرس صفحه: `/admin/profile`
+- در سایدبار دکتر یک آیتم "پروفایل من" اضافه میشود
+
+---
+
+## وضعیت فعلی (چه داریم)
+
+### صفحه موجود
+- `DoctorDetailPage.tsx` در مسیر `/admin/doctors/:uuid` برای **ادمین + دکتر** قابل دسترسی است
+- صفحه کامل است و شامل: اطلاعات پروفایل، آدرس مطب، تنظیمات نوبتدهی، تعطیلات، تاریخهای خاص
+- دکتر با UUID خودش میتواند این صفحه را ببیند اما **لینکی در منوی دکتر وجود ندارد**
+
+### سایدبار دکتر (فعلی)
+```
+عمومی:
+ - داشبورد
+
+مدیریت:
+ - نوبتهای من
+```
+
+### Auth Store
+- `authStore.dbUuid` = UUID دکتر لاگینشده
+- `authStore.primaryRole` = 'doctor'
+
+---
+
+## APIهای موجود (بررسیشده)
+
+| Endpoint | وضعیت | کاربرد |
+|----------|-------|---------|
+| `GET /api/v1/doctor/{uuid}` | ✅ PUBLIC | خواندن پروفایل (double-nested: `data?.data?.data`) |
+| `PATCH /api/v1/doctor/{uuid}` | ✅ AUTH | ویرایش پروفایل |
+| `POST /api/v1/clinic-pro/doctor-address` | ✅ AUTH | افزودن آدرس مطب |
+| `PATCH /api/v1/clinic-pro/doctor-address/{id}` | ✅ AUTH | ویرایش آدرس |
+| `DELETE /api/v1/clinic-pro/doctor-address/{id}` | ✅ AUTH | حذف آدرس |
+| `GET /api/v1/clinic-pro/doctor-addresses/{doctorId}` | ✅ AUTH | لیست آدرسها |
+| `GET /api/v1/appointment-settings/weekly-schedule/{doctorUuid}` | ✅ AUTH | برنامه هفتگی |
+| `POST /api/v1/appointment-settings/weekly-schedule` | ✅ AUTH | ساخت برنامه هفتگی |
+| `PATCH /api/v1/appointment-settings/weekly-schedule/{uuid}` | ✅ AUTH | ویرایش برنامه هفتگی |
+| `GET /api/v1/appointment-settings/date-override/list/{doctorUuid}` | ✅ AUTH | تاریخهای خاص |
+| `GET /api/v1/appointment-settings/holidays/list/{doctorUuid}` | ✅ AUTH | تعطیلات |
+| `GET /api/v1/my/appointments/today-stats?date=...` | ✅ AUTH | آمار نوبتها |
+
+---
+
+## قابلیتهایی که باید ساخته شود
+
+### قابلیت ۱ — Route + Sidebar
+
+**فایلها:**
+- `assets/admin/App.tsx` — افزودن route `/admin/profile`
+- `assets/admin/components/layout/Sidebar.tsx` — افزودن "پروفایل من" به منوی دکتر
+
+**Route:**
+```tsx
+} />
+```
+
+**Sidebar (doctor):**
+```
+عمومی:
+ - داشبورد
+
+پروفایل:
+ - پروفایل من ← /admin/profile
+
+مدیریت:
+ - نوبتهای من
+ - تنظیمات نوبتدهی ← /admin/appointment-settings (اختیاری)
+```
+
+---
+
+### قابلیت ۲ — `DoctorProfilePage.tsx`
+
+**ساختار صفحه** (با tab برای سازماندهی):
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ [عکس] دکتر [نام] ویرایش پروفایل │
+│ [تخصص] [درجه] │
+└──────────────────────────────────────────────────────────────┘
+
+[ اطلاعات عمومی ] [ آدرس مطب ] [ تنظیمات نوبتدهی ]
+ ↑ تبها
+
+──── محتوای هر تب ────
+```
+
+**تب ۱ — اطلاعات عمومی** (ویرایش inline):
+- نام کامل
+- جنسیت (مرد/زن)
+- درجه علمی (عمومی / متخصص / فوقتخصص / فلوشیپ)
+- کد نظام پزشکی
+- تخصصها (multi-select از `GET /api/v1/categorys/specially_doctor`)
+- سرویسها (multi-select از `GET /api/v1/categorys/doctor_services` یا `GET /api/v1/doctor-services`)
+- بیوگرافی (textarea)
+- بارگذاری عکس پروفایل
+
+فرم: React Hook Form + Zod
+Submit: `PATCH /api/v1/doctor/{uuid}`
+
+**تب ۲ — آدرس مطب** (CRUD):
+- لیست آدرسها با نام + آدرس + تلفن
+- دکمه "افزودن آدرس" → Modal با فرم:
+ - نام مطب
+ - آدرس
+ - تلفن
+ - استان + شهر (از category API)
+ - نقشه (Leaflet — اختیاری اگر پیچیده باشد)
+- ویرایش + حذف هر آدرس
+
+**تب ۳ — تنظیمات نوبتدهی** (مشابه آنچه در DoctorDetailPage وجود دارد):
+- برنامه هفتگی (weekly schedule) — مشاهده + ویرایش
+- تعطیلات (holidays) — لیست + افزودن + حذف
+- تاریخهای خاص (date overrides) — لیست + مدیریت
+
+---
+
+## نکات پیادهسازی
+
+### گرفتن UUID دکتر
+```tsx
+const uuid = useAuthStore(s => s.dbUuid);
+```
+
+### Double-nested response برای GET doctor
+```tsx
+// پاسخ: { success: true, data: { data: { uuid, name, ... } } }
+const doctor = data?.data?.data;
+```
+
+### استفاده مجدد از DoctorDetailPage
+اگر `DoctorDetailPage.tsx` قابل reuse است:
+- میتوان یک wrapper ساده ساخت که فقط `uuid` را از `authStore.dbUuid` میگیرد و پاس میدهد
+- اما اگر رفتار صفحه برای ادمین و دکتر تفاوت دارد (مثلاً ادمین میتواند دکتر را غیرفعال کند ولی دکتر نمیتواند) باید برخی بخشها را با `isAdmin` شرطی کرد
+
+### بخشهایی که فقط ادمین میبیند (باید پنهان شوند):
+- دکمه "غیرفعال کردن حساب دکتر"
+- دکمه "حذف دکتر"
+- لیست کلینیکهایی که دکتر عضو است (فقط نمایش — بدون حذف از کلینیک)
+
+### بخشهایی که دکتر اضافه میبیند (اختیاری):
+- آمار خلاصه نوبتهای امروز (از `GET /api/v1/my/appointments/today-stats`)
+
+---
+
+## فایلهای تأثیرپذیر
+
+| فایل | تغییر |
+|------|-------|
+| `assets/admin/pages/DoctorProfilePage.tsx` | **جدید** — صفحه پروفایل پزشک |
+| `assets/admin/App.tsx` | افزودن route `/admin/profile` |
+| `assets/admin/components/layout/Sidebar.tsx` | افزودن "پروفایل من" به منوی دکتر |
+| `assets/admin/pages/DoctorDetailPage.tsx` | اختیاری — اگر نیاز به تغییر رفتار ادمین/دکتر |
+
+---
+
+## رویکرد پیشنهادی
+
+**سادهترین راه** (بدون duplicate کد):
+
+1. در `App.tsx` route جدید:
+```tsx
+} />
+```
+
+اما این روش نیاز به دسترسی به `dbUuid` در سطح App دارد.
+
+**روش بهتر:**
+```tsx
+// DoctorProfilePage.tsx — wrapper
+export default function DoctorProfilePage() {
+ const dbUuid = useAuthStore(s => s.dbUuid);
+ if (!dbUuid) return
در حال بارگذاری...
;
+ return ;
+}
+```
+
+و در `DoctorDetailPage.tsx` یک prop `isOwnProfile` اضافه کن که:
+- دکمههای ادمین-only را پنهان کند
+- عنوان صفحه را تغییر دهد ("پروفایل من" به جای "جزئیات پزشک")
+
+**یا سادهتر:** فقط لینک sidebar را به `/admin/doctors/{dbUuid}` تنظیم کن و DoctorDetailPage را بدون تغییر reuse کن — چون دکتر از طریق `RoleRoute roles={['admin', 'doctor']}` به آن صفحه دسترسی دارد.
+
+---
+
+## الزامات
+
+1. وقتی دکتر وارد پنل میشود، از سایدبار به "پروفایل من" دسترسی داشته باشد
+2. پروفایل خودش را ببیند و ویرایش کند (نه پروفایل دکتر دیگری)
+3. تنظیمات نوبتدهیاش را مدیریت کند
+4. آدرس مطبهایش را مدیریت کند
+5. دکمههای ادمین-only (حذف/غیرفعال کردن) نمایش داده نشوند
+6. همه تاریخها شمسی نمایش داده شوند
diff --git a/.claude/skills/run-prompt/SKILL.md b/.claude/skills/run-prompt/SKILL.md
index 0cc71c03..9720afef 100644
--- a/.claude/skills/run-prompt/SKILL.md
+++ b/.claude/skills/run-prompt/SKILL.md
@@ -10,13 +10,14 @@ description: اجرای یک فایل پرامپت .md به صورت گامب
---
-## قبل از شروع — تحلیل پرامپت
+## قبل از شروع — تحلیل پرامپت و ساخت Todo
۱. فایل پرامپت را کامل بخوان
۲. مستندات موجود را بررسی کن: `docs/api/`
۳. کد مرتبط در `src/` و `assets/admin/` را مرور کن
۴. لیست قابلیتها را از پرامپت استخراج کن
-۵. به کاربر نمایش بده:
+۵. **با `TodoWrite` یک todo کامل بساز** — یک آیتم به ازای هر قابلیت
+۶. به کاربر نمایش بده:
```
📋 قابلیتهای شناساییشده:
@@ -31,23 +32,44 @@ description: اجرای یک فایل پرامپت .md به صورت گامب
## قوانین اجرا (اجباری — هیچ استثنایی ندارد)
-### ۱. یک قابلیت در هر مرحله
+### ۱. Todo — ستون فقرات اجرا
+
+**در ابتدای هر اجرا** یک todo کامل با `TodoWrite` بساز:
+- یک آیتم به ازای هر قابلیت با وضعیت `pending`
+- آیتمهای زیر-مجموعه اضافه کن اگر قابلیت چند بخش مستقل دارد
+
+**در طول اجرا:**
+- هر قابلیت را که شروع میکنی → وضعیتش را `in_progress` کن
+- هر قابلیت را که تست و تأیید شد → فوری `completed` کن
+- هرگز دو آیتم را همزمان `in_progress` نگذار
+- اگر خطا داری که قابلیت را block کرده → یک آیتم `in_progress` باقی بماند تا رفع شود
+
+**نمونه todo اولیه:**
+```
+[ ] قابلیت ۱: Stats Bar backend endpoint
+[ ] قابلیت ۲: Stats Bar frontend component
+[ ] قابلیت ۳: PersianCalendar popup
+[ ] قابلیت ۴: AppointmentStatusDropdown
+[ ] قابلیت ۵: بازطراحی AppointmentsPage
+```
+
+### ۲. یک قابلیت در هر مرحله
- هرگز دو قابلیت را همزمان پیادهسازی نکن
- هرگز بدون تأیید موفقیت مرحله قبل به مرحله بعد نرو
-### ۲. ترتیب اجرای هر قابلیت
+### ۳. ترتیب اجرای هر قابلیت
```
-① تحلیل ② طراحی ③ پیادهسازی ④ تست ⑤ رفع خطا ⑥ مستندسازی ⑦ گزارش
+① تحلیل ② طراحی ③ پیادهسازی ④ تست ⑤ رفع خطا ⑥ مستندسازی ⑦ Todo + گزارش
```
-### ۳. سازگاری با پروژه
+### ۴. سازگاری با پروژه
- همه تغییرات باید با Symfony 7 + React 19 سازگار باشند
- از الگوهای موجود پروژه پیروی کن (BaseController، TanStack Query، Zod، ...)
- اگر Entity تغییر کرد: `doctrine:migrations:diff` و `doctrine:migrations:migrate` اجرا کن
- اگر API تغییر کرد: همه بخشهای وابسته (frontend types، api calls، ...) را اصلاح کن
-### ۴. اصول کدنویسی
+### ۵. اصول کدنویسی
- SOLID و Clean Code
- هیچ کامنت غیرضروری اضافه نکن
- نامگذاری معنادار
@@ -74,6 +96,7 @@ description: اجرای یک فایل پرامپت .md به صورت گامب
### مرحله ③ — پیادهسازی
+- قابلیت را در todo روی `in_progress` بگذار
- **Backend اول**: Entity → Migration → Repository → Service → Controller
- **Frontend بعد**: Types → API call → Component → Route
- یک فایل در هر Edit/Write — نه bulk
@@ -138,9 +161,12 @@ ddev exec npx tsc --noEmit --project tsconfig.json 2>&1 | head -30
4. تمام status codes و error codes
5. اگر پارامتر query داشت → همه را مستند کن
-### مرحله ⑦ — گزارش
+### مرحله ⑦ — Todo + گزارش
-بعد از اتمام هر قابلیت، گزارش کوتاه:
+بعد از موفقیت تست و مستندسازی:
+
+۱. **آیتم todo را `completed` کن** با `TodoWrite`
+۲. گزارش کوتاه نمایش بده:
```
✅ قابلیت [شماره]: [نام] — تکمیل شد
@@ -150,6 +176,12 @@ ddev exec npx tsc --noEmit --project tsconfig.json 2>&1 | head -30
• assets/admin/...
• docs/api/...
+📊 پیشرفت کلی:
+ ✅ قابلیت ۱: [نام] — تکمیل
+ ✅ قابلیت ۲: [نام] — تکمیل
+ ⏳ قابلیت ۳: [نام] — در صف
+ ...
+
─────────────────────────────────
▶ قابلیت بعدی: [شماره] — [نام]
آیا ادامه دهم؟
diff --git a/TEST_USERS.md b/TEST_USERS.md
index ea29ff7c..1114e2be 100644
--- a/TEST_USERS.md
+++ b/TEST_USERS.md
@@ -6,11 +6,11 @@
## ادمین
-| فیلد | مقدار |
-|------|-------|
+| فیلد | مقدار |
+| ------ | ------------- |
| موبایل | `09120671713` |
-| پسورد | `admin123` |
-| نقش | `ROLE_ADMIN` |
+| پسورد | `admin123` |
+| نقش | `ROLE_ADMIN` |
---
@@ -18,49 +18,49 @@
### کاربر ۱ — دکتر در چند کلینیک
-| فیلد | مقدار |
-|------|-------|
-| موبایل | `09100000011` |
-| پسورد | `Test1234` |
-| نقش | `ROLE_DOCTOR` |
-| Contexts | مطب شخصی دکتر آرمان رضایی |
+| فیلد | مقدار |
+| -------- | -------------------------- |
+| موبایل | `09100000011` |
+| پسورد | `Test1234` |
+| نقش | `ROLE_DOCTOR` |
+| Contexts | مطب شخصی دکتر آرمان رضایی |
| | کلینیک جامع حضرت علی (عضو) |
-| | بیمارستان ابن سینا (عضو) |
+| | بیمارستان ابن سینا (عضو) |
---
### کاربر ۲ — صاحب کلینیک
-| فیلد | مقدار |
-|------|-------|
-| موبایل | `09100000022` |
-| پسورد | `Test1234` |
-| نقش | `ROLE_CLINIC` |
-| Contexts | کلینیک تست پگاه |
+| فیلد | مقدار |
+| ----------- | -------------------------------------- |
+| موبایل | `09100000022` |
+| پسورد | `Test1234` |
+| نقش | `ROLE_CLINIC` |
+| Contexts | کلینیک تست پگاه |
| UUID کلینیک | `0730373f-7c0a-4610-8120-d58ecf4a9916` |
---
### کاربر ۳ — منشی
-| فیلد | مقدار |
-|------|-------|
-| موبایل | `09100000033` |
-| پسورد | `Test1234` |
-| نقش | `ROLE_SECRETARY` |
+| فیلد | مقدار |
+| -------- | ---------------------- |
+| موبایل | `09100000033` |
+| پسورد | `Test1234` |
+| نقش | `ROLE_SECRETARY` |
| Contexts | مطب دکتر مهناز سلیمانی |
---
### کاربر ۴ — دکتر + صاحب کلینیک
-| فیلد | مقدار |
-|------|-------|
-| موبایل | `09100000044` |
-| پسورد | `Test1234` |
-| نقش | `ROLE_DOCTOR` + `ROLE_CLINIC` |
-| Contexts | مطب شخصی دکتر سامان علوی |
-| | کلینیک تخصصی علوی |
+| فیلد | مقدار |
+| ----------- | -------------------------------------- |
+| موبایل | `09100000044` |
+| پسورد | `Test1234` |
+| نقش | `ROLE_DOCTOR` + `ROLE_CLINIC` |
+| Contexts | مطب شخصی دکتر سامان علوی |
+| | کلینیک تخصصی علوی |
| UUID کلینیک | `26a1ff95-32aa-4a96-b0fd-03e75e810be9` |
---
@@ -72,3 +72,7 @@
```bash
ddev exec php create_test_users.php
```
+
+09212908096
+
+09212908096
diff --git a/assets/admin/App.tsx b/assets/admin/App.tsx
index 5af887e8..381eec00 100644
--- a/assets/admin/App.tsx
+++ b/assets/admin/App.tsx
@@ -28,6 +28,7 @@ import BlogFormPage from './pages/BlogFormPage';
import SecretariesPage from './pages/SecretariesPage';
import MyClinicPage from './pages/MyClinicPage';
import SettingsPage from './pages/SettingsPage';
+import DoctorProfilePage from './pages/DoctorProfilePage';
// ── Guards ──────────────────────────────────────────────────────────────────
@@ -132,6 +133,7 @@ export default function App() {
} />
} />
} />
+ } />
} />
diff --git a/assets/admin/components/layout/Sidebar.tsx b/assets/admin/components/layout/Sidebar.tsx
index 60704f58..52ba7e7d 100644
--- a/assets/admin/components/layout/Sidebar.tsx
+++ b/assets/admin/components/layout/Sidebar.tsx
@@ -14,6 +14,7 @@ import {
KeyIcon,
StarIcon,
TagIcon,
+ UserCircleIcon,
UserGroupIcon,
UsersIcon,
} from "@heroicons/react/24/outline";
@@ -92,6 +93,12 @@ function buildSections(primaryRole: string | null, dbUuid: string | null): Secti
{ to: '/admin/dashboard', icon: ChartBarIcon, label: 'داشبورد' },
],
},
+ {
+ label: 'پروفایل',
+ items: [
+ { to: '/admin/profile', icon: UserCircleIcon, label: 'پروفایل من' },
+ ],
+ },
{
label: 'مدیریت',
items: [
diff --git a/assets/admin/pages/DoctorDetailPage.tsx b/assets/admin/pages/DoctorDetailPage.tsx
index a989f024..e8c916ca 100644
--- a/assets/admin/pages/DoctorDetailPage.tsx
+++ b/assets/admin/pages/DoctorDetailPage.tsx
@@ -1776,12 +1776,14 @@ type EditForm = z.infer;
// ── Main Page ──────────────────────────────────────────────────────────────
-export default function DoctorDetailPage() {
- const { uuid } = useParams<{ uuid: string }>();
+export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfile?: boolean }) {
+ const { uuid: paramUuid } = useParams<{ uuid: string }>();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const qc = useQueryClient();
const primaryRole = useAuthStore(s => s.primaryRole);
+ const dbUuid = useAuthStore(s => s.dbUuid);
+ const uuid = isOwnProfile ? (dbUuid ?? undefined) : paramUuid;
const [editOpen, setEditOpen] = useState(searchParams.get('edit') === '1');
const [deleteOpen, setDeleteOpen] = useState(false);
@@ -1967,12 +1969,25 @@ export default function DoctorDetailPage() {
{/* Breadcrumb */}
-
-
/
-
پروفایل پزشک
+ {isOwnProfile ? (
+ <>
+
+
/
+
پروفایل من
+ >
+ ) : (
+ <>
+
+
/
+
پروفایل پزشک
+ >
+ )}
{/* Profile Card */}
@@ -2026,39 +2041,43 @@ export default function DoctorDetailPage() {
ویرایش
- {/* Toggle active button — styled by current state */}
-
+ {/* Toggle active / Delete — فقط ادمین */}
+ {!isOwnProfile && (
+ <>
+
-
-
- {menuOpen && (
-
-
+ >
+ )}
diff --git a/assets/admin/pages/DoctorProfilePage.tsx b/assets/admin/pages/DoctorProfilePage.tsx
new file mode 100644
index 00000000..34fb7885
--- /dev/null
+++ b/assets/admin/pages/DoctorProfilePage.tsx
@@ -0,0 +1,5 @@
+import DoctorDetailPage from './DoctorDetailPage';
+
+export default function DoctorProfilePage() {
+ return ;
+}