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 && ( -
- + {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 ; +}