feat: implement new API endpoints for multi-role dashboard and update documentation
This commit is contained in:
@@ -13,14 +13,44 @@
|
||||
|
||||
---
|
||||
|
||||
## وضعیت فعلی (مهم — قبل از تغییر بخوان)
|
||||
## وضعیت فعلی API (مهم — قبل از پیادهسازی بخوان)
|
||||
|
||||
### API های موجود (استفاده کن، تغییر نده)
|
||||
|
||||
| Endpoint | داکیومنت | توضیح |
|
||||
|----------|----------|-------|
|
||||
| `GET /oauth/userinfo` | [auth.md](../docs/api/auth.md) | اطلاعات پایه کاربر — بدون `primary_role` و `context` |
|
||||
| `GET /api/v1/admin/dashboard/stats` | [admin.md](../docs/api/admin.md) | KPI های داشبورد ادمین |
|
||||
| `GET /api/v1/admin/dashboard/charts` | [admin.md](../docs/api/admin.md) | نمودار ۳۰ روزه ادمین |
|
||||
| `GET /api/v1/admin/dashboard/recent` | [admin.md](../docs/api/admin.md) | آخرین فعالیتها برای ادمین |
|
||||
| `GET /api/v1/admin/appointments` | [admin.md](../docs/api/admin.md) | لیست همه نوبتها — فقط ROLE_ADMIN |
|
||||
| `GET /api/v1/appointments/doctor/{doctorUuid}` | [appointment.md](../docs/api/appointment.md) | نوبتهای یک دکتر خاص |
|
||||
| `GET /api/v1/appointments/user` | [appointment.md](../docs/api/appointment.md) | نوبتهای کاربر جاری |
|
||||
| `GET /api/v1/clinics/{uuid}` | [clinic.md](../docs/api/clinic.md) | جزئیات کلینیک |
|
||||
| `GET /api/v1/admin/clinics` | [admin.md](../docs/api/admin.md) | لیست کلینیکها — فقط ROLE_ADMIN |
|
||||
|
||||
### API های جدید که باید ساخته شوند
|
||||
|
||||
| Endpoint | فایل کنترلر | توضیح |
|
||||
|----------|-------------|-------|
|
||||
| `GET /api/v1/me` | `src/Auth/Controller/MeController.php` | شناسایی نقش + context کاربر |
|
||||
| `GET /api/v1/dashboard/clinic` | `src/Dashboard/Controller/DashboardController.php` | داشبورد صاحب کلینیک |
|
||||
| `GET /api/v1/dashboard/doctor` | همان | داشبورد دکتر |
|
||||
| `GET /api/v1/dashboard/secretary` | همان | داشبورد منشی |
|
||||
| `GET /api/v1/my/appointments` | `src/Appointment/Controller/MyAppointmentsController.php` | نوبتهای فیلترشده بر اساس نقش |
|
||||
|
||||
> بعد از ساخت هر API جدید، فایل مستندات متناظر در `docs/api/` را نیز بهروز کن.
|
||||
|
||||
---
|
||||
|
||||
## وضعیت فعلی کد (مهم — قبل از تغییر بخوان)
|
||||
|
||||
### بکاند
|
||||
- کلاس `AdminApiController` با `#[IsGranted('ROLE_ADMIN')]` روی کل کلاس — تمام endpoint های داشبورد فعلی فقط برای ادمین
|
||||
- موجودیتها:
|
||||
- `User`: فیلد `roles: array` — مقادیر ممکن: `ROLE_USER`, `ROLE_ADMIN`, `ROLE_DOCTOR`, `ROLE_CLINIC`, `ROLE_SECRETARY`
|
||||
- `Clinic`: فیلد `user` (ManyToOne به User) — صاحب کلینیک. رابطه ManyToMany با `Doctor` از طریق جدول `clinic_doctors`
|
||||
- `Doctor`: فیلد `user` (OneToOne به User). دارای `mobileNumber` و رابطه با `Specialty`
|
||||
- `Doctor`: فیلد `user` (OneToOne به User). دارای `mobileNumber` و رابطه با `Specialty`. متد `findByUser(User)` در `DoctorRepository` موجود است.
|
||||
- `DoctorSecretary`: فیلد `doctor` (ManyToOne)، `secretary` (ManyToOne به User)، `permissions` (JSON):
|
||||
```
|
||||
{ version:1, resources: {
|
||||
@@ -30,22 +60,19 @@
|
||||
insurances: { view, create, update, delete }
|
||||
}}
|
||||
```
|
||||
- `ClinicRepository::findByUser(User $user)` موجود است.
|
||||
- `DoctorRepository::findByUser(User $user)` موجود است.
|
||||
- JWT: از LexikJWTBundle — payload شامل: `username` (mobile_number)، `roles` (آرایه)، `iat`، `exp`
|
||||
- endpoint های فعلی داشبورد:
|
||||
- `GET /api/v1/admin/dashboard/stats` → ۱۲ KPI عمومی
|
||||
- `GET /api/v1/admin/dashboard/charts` → نمودار ۳۰ روزه
|
||||
- `GET /api/v1/admin/dashboard/recent` → آخرین نوبتها، پرداختها، کاربران
|
||||
- همه با `ROLE_ADMIN`
|
||||
|
||||
### فرانتاند
|
||||
- `authStore.ts` (Zustand + persist در `clinicpro-auth`): فقط `token`، `refreshToken`، `isAuthenticated`
|
||||
- `Sidebar.tsx`: لیست ثابت — بدون هیچ فیلتر نقشی
|
||||
- `App.tsx`: همه routes با `PrivateRoute` (فقط isAuthenticated بررسی میشود)
|
||||
- `DashboardPage.tsx`: سه query + نمودار Recharts + mini lists فعلی
|
||||
- `DashboardPage.tsx`: سه query به `/api/v1/admin/dashboard/*` + نمودار Recharts + mini lists
|
||||
|
||||
---
|
||||
|
||||
## مرحله ۱ — endpoint شناسایی کاربر (بکاند)
|
||||
## مرحله ۱ — endpoint شناسایی کاربر (بکاند) — API جدید
|
||||
|
||||
### فایل جدید: `src/Auth/Controller/MeController.php`
|
||||
|
||||
@@ -53,6 +80,8 @@
|
||||
GET /api/v1/me [IS_AUTHENTICATED_FULLY]
|
||||
```
|
||||
|
||||
> این endpoint با `/oauth/userinfo` فرق دارد — علاوه بر اطلاعات پایه، `primary_role` و `context` (مثل uuid دکتر/کلینیک) را هم برمیگرداند.
|
||||
|
||||
پاسخ:
|
||||
```json
|
||||
{
|
||||
@@ -80,7 +109,7 @@ GET /api/v1/me [IS_AUTHENTICATED_FULLY]
|
||||
|
||||
**پر کردن `context`**:
|
||||
- `ROLE_DOCTOR`: از `DoctorRepository::findByUser($user)` → `{doctor_uuid, doctor_name}`
|
||||
- `ROLE_CLINIC`: از `ClinicRepository::findOneBy(['user' => $user])` → `{clinic_uuid, clinic_name, clinic_logo}`
|
||||
- `ROLE_CLINIC`: از `ClinicRepository::findByUser($user)` → `{clinic_uuid, clinic_name, clinic_logo}`
|
||||
- `ROLE_SECRETARY`: از `DoctorSecretaryRepository::findActiveBySecretary($user)` (متد جدید) → `{doctor_uuid, doctor_name, secretary_uuid, permissions}`
|
||||
- اگر موجودیت پیدا نشد: `context: null`
|
||||
|
||||
@@ -92,7 +121,7 @@ public function findActiveBySecretary(User $user): ?DoctorSecretary
|
||||
}
|
||||
```
|
||||
|
||||
**security.yaml** — endpoint `/api/v1/me` را به firewall `api` اضافه کن (نه public_endpoints، چون نیاز به احراز هویت دارد — فایروال `api` آن را پوشش میدهد).
|
||||
**داکیومنت**: بعد از پیادهسازی، endpoint را به `docs/api/auth.md` اضافه کن.
|
||||
|
||||
---
|
||||
|
||||
@@ -206,11 +235,12 @@ function buildSections(
|
||||
|
||||
---
|
||||
|
||||
## مرحله ۵ — endpoint های داشبورد جدید (بکاند)
|
||||
## مرحله ۵ — endpoint های داشبورد جدید (بکاند) — API های جدید
|
||||
|
||||
### فایل جدید: `src/Dashboard/Controller/DashboardController.php`
|
||||
|
||||
سه endpoint جداگانه — هر سه از `BaseController` extend میکنند:
|
||||
سه endpoint جداگانه — هر سه از `BaseController` extend میکنند.
|
||||
پاسخ با `$this->success($data)` — یعنی فرانت با `data?.data` میخواند.
|
||||
|
||||
---
|
||||
|
||||
@@ -219,29 +249,33 @@ function buildSections(
|
||||
پاسخ:
|
||||
```json
|
||||
{
|
||||
"clinic": { "uuid":"...", "name":"...", "is_active": true, "logo":"..." },
|
||||
"stats": {
|
||||
"total_doctors": 5,
|
||||
"today_appointments": 12,
|
||||
"this_month_appointments": 87,
|
||||
"pending_invitations": 2
|
||||
},
|
||||
"today_appointments": [
|
||||
{ "uuid":"...", "patient_name":"...", "doctor_name":"...", "slot_start": 1234567890, "status":"reserved" }
|
||||
],
|
||||
"doctors": [
|
||||
{ "uuid":"...", "name":"دکتر ...", "specialty":"...", "today_count": 3 }
|
||||
]
|
||||
"success": true,
|
||||
"data": {
|
||||
"clinic": { "uuid":"...", "name":"...", "is_active": true, "logo":"..." },
|
||||
"stats": {
|
||||
"total_doctors": 5,
|
||||
"today_appointments": 12,
|
||||
"this_month_appointments": 87,
|
||||
"pending_invitations": 2
|
||||
},
|
||||
"today_appointments": [
|
||||
{ "uuid":"...", "patient_name":"...", "doctor_name":"...", "slot_start": 1234567890, "status":"reserved" }
|
||||
],
|
||||
"doctors": [
|
||||
{ "uuid":"...", "name":"دکتر ...", "specialty":"...", "today_count": 3 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
پیادهسازی:
|
||||
- کلینیک را از `ClinicRepository::findOneBy(['user' => $user])` بگیر
|
||||
- اگر نبود: `throw new AppException('ERR_NOT_FOUND_001', 'کلینیک یافت نشد', 404)`
|
||||
- کلینیک را از `ClinicRepository::findByUser($user)` بگیر
|
||||
- اگر نبود: `return $this->error('ERR_NOT_FOUND_001', 'کلینیک یافت نشد', 404)`
|
||||
- `today_appointments` و `this_month_appointments`: از جدول `appointments` با JOIN به `clinic_doctors` فیلتر کن
|
||||
- `pending_invitations`: از `clinic_doctor_invitations` با `status='pending'` بشمار
|
||||
- `today_appointments` لیست: ۵ نوبت اخیر امروز این کلینیک (از طریق JOIN `clinic_doctors`)
|
||||
- `doctors`: لیست پزشکان کلینیک با شمارش نوبت امروز آنها
|
||||
- از DQL array hydration استفاده کن (`.getArrayResult()`)
|
||||
|
||||
---
|
||||
|
||||
@@ -250,29 +284,33 @@ function buildSections(
|
||||
پاسخ:
|
||||
```json
|
||||
{
|
||||
"doctor": { "uuid":"...", "name":"...", "degree":"...", "profile_image":"..." },
|
||||
"stats": {
|
||||
"today_appointments": 5,
|
||||
"tomorrow_appointments": 3,
|
||||
"this_month_appointments": 42,
|
||||
"avg_rating": 4.7,
|
||||
"total_ratings": 18
|
||||
},
|
||||
"today_appointments": [
|
||||
{ "uuid":"...", "patient_name":"...", "patient_mobile":"...", "slot_start": 1234567890, "status":"reserved" }
|
||||
],
|
||||
"clinics": [
|
||||
{ "uuid":"...", "name":"کلینیک ...", "logo":"..." }
|
||||
]
|
||||
"success": true,
|
||||
"data": {
|
||||
"doctor": { "uuid":"...", "name":"...", "degree":"..." },
|
||||
"stats": {
|
||||
"today_appointments": 5,
|
||||
"tomorrow_appointments": 3,
|
||||
"this_month_appointments": 42,
|
||||
"avg_rating": 4.7,
|
||||
"total_ratings": 18
|
||||
},
|
||||
"today_appointments": [
|
||||
{ "uuid":"...", "patient_name":"...", "patient_mobile":"...", "slot_start": 1234567890, "status":"reserved" }
|
||||
],
|
||||
"clinics": [
|
||||
{ "uuid":"...", "name":"کلینیک ...", "logo":"..." }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
پیادهسازی:
|
||||
- دکتر از `DoctorRepository::findByUser($user)` — اگر نبود `404`
|
||||
- دکتر از `DoctorRepository::findByUser($user)` — اگر نبود `return $this->error(..., 404)`
|
||||
- `today_appointments`: نوبتهای این دکتر با `slot_start` در بازه ابتدا تا انتهای امروز
|
||||
- `tomorrow_appointments`: همان برای فردا
|
||||
- `avg_rating`: AVG(overall) از جدول `ratings` برای این دکتر
|
||||
- `avg_rating`: AVG(overall) از جدول `ratings` برای این دکتر — با DQL
|
||||
- `clinics`: کلینیکهایی که این دکتر در `clinic_doctors` آنهاست
|
||||
- از DQL array hydration استفاده کن
|
||||
|
||||
---
|
||||
|
||||
@@ -281,24 +319,29 @@ function buildSections(
|
||||
پاسخ:
|
||||
```json
|
||||
{
|
||||
"doctor": { "uuid":"...", "name":"...", "degree":"..." },
|
||||
"permissions": { ... },
|
||||
"stats": {
|
||||
"today_appointments": 4,
|
||||
"tomorrow_appointments": 2
|
||||
},
|
||||
"today_appointments": [
|
||||
{ "uuid":"...", "patient_name":"...", "patient_mobile":"...", "slot_start": 1234567890, "status":"reserved" }
|
||||
]
|
||||
"success": true,
|
||||
"data": {
|
||||
"doctor": { "uuid":"...", "name":"...", "degree":"..." },
|
||||
"permissions": { "version": 1, "resources": { ... } },
|
||||
"stats": {
|
||||
"today_appointments": 4,
|
||||
"tomorrow_appointments": 2
|
||||
},
|
||||
"today_appointments": [
|
||||
{ "uuid":"...", "patient_name":"...", "patient_mobile":"...", "slot_start": 1234567890, "status":"reserved" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
پیادهسازی:
|
||||
- از `DoctorSecretaryRepository::findActiveBySecretary($user)` اولین رابطه فعال بگیر
|
||||
- اگر نبود: `throw new AppException('ERR_FORBIDDEN_001', 'دسترسی منشی تنظیم نشده', 403)`
|
||||
- اگر نبود: `return $this->error('ERR_FORBIDDEN_001', 'دسترسی منشی تنظیم نشده', 403)`
|
||||
- بررسی `appointments.view = true` در permissions — اگر false بود، `today_appointments` آرایه خالی برگردان
|
||||
- نوبتهای دکتر مربوطه را برگردان
|
||||
|
||||
**داکیومنت**: بعد از پیادهسازی، فایل `docs/api/dashboard.md` جدید بساز.
|
||||
|
||||
---
|
||||
|
||||
## مرحله ۶ — DashboardPage.tsx چند-نقشه (فرانتاند)
|
||||
@@ -318,23 +361,26 @@ export default function DashboardPage() {
|
||||
}
|
||||
```
|
||||
|
||||
**AdminDashboard**: کد فعلی DashboardPage عیناً — فقط در یک تابع بپیچ
|
||||
**AdminDashboard**: کد فعلی DashboardPage عیناً — فقط در یک تابع بپیچ. از endpointهای موجود `/api/v1/admin/dashboard/*` استفاده میکند.
|
||||
|
||||
**ClinicDashboard**:
|
||||
- یک query به `/api/v1/dashboard/clinic`
|
||||
- یک query به `GET /api/v1/dashboard/clinic` (جدید)
|
||||
- استخراج: `data?.data` (چون `$this->success()` یکبار nest میکند)
|
||||
- ۴ کارت KPI: تعداد پزشکان / نوبت امروز / نوبت این ماه / دعوتنامه در انتظار
|
||||
- جدول نوبتهای امروز (ستون: بیمار، پزشک، زمان، وضعیت)
|
||||
- لیست پزشکان با تعداد نوبت امروز
|
||||
- دکمه "مدیریت کلینیک" → navigate به `/admin/my-clinic`
|
||||
|
||||
**DoctorDashboard**:
|
||||
- یک query به `/api/v1/dashboard/doctor`
|
||||
- یک query به `GET /api/v1/dashboard/doctor` (جدید)
|
||||
- استخراج: `data?.data`
|
||||
- ۴ کارت KPI: نوبت امروز / فردا / این ماه / میانگین امتیاز (با ستاره)
|
||||
- جدول نوبتهای امروز (ستون: بیمار، موبایل `dir="ltr"`, زمان، وضعیت)
|
||||
- لیست کلینیکهای عضو به شکل badge
|
||||
|
||||
**SecretaryDashboard**:
|
||||
- یک query به `/api/v1/dashboard/secretary`
|
||||
- یک query به `GET /api/v1/dashboard/secretary` (جدید)
|
||||
- استخراج: `data?.data`
|
||||
- نام دکتر مربوطه در header کارت
|
||||
- ۲ کارت KPI: نوبت امروز / فردا
|
||||
- جدول نوبتهای امروز
|
||||
@@ -357,6 +403,7 @@ export default function MyClinicPage() {
|
||||
</div>
|
||||
);
|
||||
|
||||
// از endpoint موجود GET /api/v1/clinics/{uuid} استفاده کن
|
||||
// همان محتوای ClinicDetailPage — اما uuid از context
|
||||
// دکمه "حذف کلینیک" نشان داده نشود
|
||||
// بقیه همه فعال: ویرایش، تغییر وضعیت، آپلود لوگو، گالری، دعوت پزشک
|
||||
@@ -365,35 +412,48 @@ export default function MyClinicPage() {
|
||||
|
||||
بهترین رویکرد: کد مشترک را از `ClinicDetailPage.tsx` در یک کامپوننت `ClinicDetailView` جدا کن که `uuid` و `showDeleteButton` را به عنوان prop میگیرد. هر دو صفحه از آن استفاده کنند.
|
||||
|
||||
> `GET /api/v1/clinics/{uuid}` و `PATCH /api/v1/clinic/{uuid}` هر دو موجودند — نیازی به API جدید نیست.
|
||||
|
||||
---
|
||||
|
||||
## مرحله ۸ — نوبتهای فیلترشده (بکاند + فرانتاند)
|
||||
## مرحله ۸ — نوبتهای فیلترشده (بکاند + فرانتاند) — API جدید
|
||||
|
||||
### بکاند — endpoint جدید: `GET /api/v1/my/appointments` `[IS_AUTHENTICATED_FULLY]`
|
||||
|
||||
در یک Controller جدید یا در `AppointmentController`:
|
||||
فایل جدید: `src/Appointment/Controller/MyAppointmentsController.php`
|
||||
|
||||
```
|
||||
GET /api/v1/my/appointments?page=1&limit=15&status=...&search=...
|
||||
```
|
||||
|
||||
بر اساس نقش فیلتر:
|
||||
- `ROLE_ADMIN`: همه نوبتها (redirect به `/api/v1/admin/appointments`)
|
||||
- `ROLE_ADMIN`: forward به همان query موجود در `AdminApiController::appointments()`
|
||||
- `ROLE_CLINIC`: نوبتهایی که doctor آن در `clinic_doctors` این کلینیک است
|
||||
- `ROLE_DOCTOR`: نوبتهای این دکتر
|
||||
- `ROLE_DOCTOR`: نوبتهای این دکتر — از `DoctorRepository::findByUser($user)` uuid بگیر
|
||||
- `ROLE_SECRETARY`: نوبتهای دکتری که این منشی به آن وصل است (اگر `appointments.view = true`)
|
||||
|
||||
پاسخ: همان فرمت `paginated()` موجود.
|
||||
پاسخ: همان فرمت `$this->paginated()` موجود:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [ { "uuid":"...", "doctor_name":"...", "patient_name":"...", "slot_start":..., "status":"..." } ],
|
||||
"meta": { "totalRecords": 100, "totalPages": 7, "currentPage": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
**داکیومنت**: بعد از پیادهسازی، endpoint را به `docs/api/appointment.md` اضافه کن.
|
||||
|
||||
### فرانتاند — `AppointmentsPage.tsx`
|
||||
|
||||
```tsx
|
||||
const primaryRole = useAuthStore(s => s.primaryRole);
|
||||
const endpoint = primaryRole === 'admin'
|
||||
? `/api/v1/admin/appointments?...`
|
||||
: `/api/v1/my/appointments?...`;
|
||||
? `/api/v1/admin/appointments`
|
||||
: `/api/v1/my/appointments`;
|
||||
```
|
||||
|
||||
> برای ادمین از endpoint موجود استفاده میشود؛ برای بقیه نقشها از endpoint جدید.
|
||||
|
||||
---
|
||||
|
||||
## نکات مهم پیادهسازی
|
||||
@@ -408,22 +468,29 @@ const endpoint = primaryRole === 'admin'
|
||||
- هیچ Tailwind نیست
|
||||
|
||||
### پاسخهای API
|
||||
- `$this->success($data)` → `{ success, data: $data }` — برای single resource
|
||||
- `$this->paginated($items, $total, $page, $limit)` → `{ success, data: $items[], meta: {...} }`
|
||||
- `$this->success($data)` → `{ success, data: $data }` — فرانت با `data?.data` میخواند
|
||||
- `$this->paginated($items, $total, $page, $limit)` → `{ success, data: $items[], meta: {...} }` — فرانت با `data?.data` و `data?.meta?.totalRecords`
|
||||
- `$this->error(...)` → `{ success:false, errors:[...] }`
|
||||
|
||||
### ترتیب اجرا (پیشنهادی)
|
||||
1. `MeController` + بکاند test با curl
|
||||
2. `authStore.ts` — اضافه کردن fetchMe + فیلدهای جدید
|
||||
3. `App.tsx` — fetchMe در mount
|
||||
4. `DashboardController` — هر سه endpoint
|
||||
5. `DashboardPage.tsx` — sub-dashboardها
|
||||
6. `Sidebar.tsx` — پویا
|
||||
7. `App.tsx` — RoleRoute
|
||||
8. `MyClinicPage.tsx`
|
||||
9. `AppointmentsPage.tsx` — فیلتر endpoint
|
||||
### بکاند — قوانین کلی
|
||||
- همه Admin queries از DQL array hydration استفاده کنند (`.getArrayResult()`)
|
||||
- Timestamps همه integer Unix هستند
|
||||
- نوبتهای «امروز»: بازه `strtotime('today midnight')` تا `strtotime('tomorrow midnight') - 1`
|
||||
|
||||
بعد از هر مرحله: `ddev exec php bin/console cache:clear` و `ddev exec yarn dev`
|
||||
### ترتیب اجرا (پیشنهادی)
|
||||
1. `DoctorSecretaryRepository::findActiveBySecretary()` — متد جدید
|
||||
2. `MeController` — بکاند، تست با curl
|
||||
3. `authStore.ts` — اضافه کردن fetchMe + فیلدهای جدید
|
||||
4. `App.tsx` — fetchMe در mount
|
||||
5. `DashboardController` — هر سه endpoint
|
||||
6. `DashboardPage.tsx` — sub-dashboardها
|
||||
7. `Sidebar.tsx` — پویا
|
||||
8. `App.tsx` — RoleRoute
|
||||
9. `MyClinicPage.tsx`
|
||||
10. `MyAppointmentsController` — بکاند
|
||||
11. `AppointmentsPage.tsx` — فیلتر endpoint
|
||||
12. بعد از هر مرحله بکاند: `ddev exec php bin/console cache:clear`
|
||||
13. بعد از هر مرحله فرانتاند: `ddev exec yarn dev`
|
||||
|
||||
---
|
||||
|
||||
@@ -432,6 +499,7 @@ const endpoint = primaryRole === 'admin'
|
||||
### بکاند (جدید)
|
||||
- `src/Auth/Controller/MeController.php`
|
||||
- `src/Dashboard/Controller/DashboardController.php`
|
||||
- `src/Appointment/Controller/MyAppointmentsController.php`
|
||||
|
||||
### بکاند (تغییر)
|
||||
- `src/Secretary/Repository/DoctorSecretaryRepository.php` — اضافه: `findActiveBySecretary()`
|
||||
@@ -445,3 +513,8 @@ const endpoint = primaryRole === 'admin'
|
||||
|
||||
### فرانتاند (جدید)
|
||||
- `assets/admin/pages/MyClinicPage.tsx`
|
||||
|
||||
### داکیومنت (بهروزرسانی/جدید)
|
||||
- `docs/api/auth.md` — اضافه: `GET /api/v1/me`
|
||||
- `docs/api/appointment.md` — اضافه: `GET /api/v1/my/appointments`
|
||||
- `docs/api/dashboard.md` — **فایل جدید** برای سه endpoint داشبورد
|
||||
|
||||
Reference in New Issue
Block a user