feat: update primaryRole handling in authStore and adjust context role for clinics
This commit is contained in:
@@ -0,0 +1,168 @@
|
|||||||
|
# رفع باگ تغییر محیط کاری — primaryRole آپدیت نمیشود
|
||||||
|
|
||||||
|
## زمینه
|
||||||
|
|
||||||
|
دکتری که عضو چند کلینیک است، در صفحه «انتخاب محیط کاری» میتواند بین مطب شخصی و کلینیکهای مختلف سوئیچ کند. اما بعد از سوئیچ، `primaryRole` در authStore تغییر نمیکند — هنوز `'doctor'` میماند. نتیجه: وقتی به `/admin/profile` میرود، کد `uuid = dbUuid` (که حالا uuid کلینیک است) را به `GET /api/v1/doctor/{clinicUuid}` میفرستد → 404.
|
||||||
|
|
||||||
|
## مشکل / هدف
|
||||||
|
|
||||||
|
وقتی `switchContext` صدا زده میشود:
|
||||||
|
- `dbUuid` در store تغییر میکند (به uuid کلینیک)
|
||||||
|
- اما `primaryRole` تغییر **نمیکند** (هنوز `'doctor'` میماند)
|
||||||
|
- `context.role` در store ذخیره میشود اما از آن برای آپدیت `primaryRole` استفاده نمیشود
|
||||||
|
|
||||||
|
## فایلهای مرتبط
|
||||||
|
|
||||||
|
| فایل | نقش |
|
||||||
|
|------|-----|
|
||||||
|
| `src/Auth/Controller/AuthController.php` | `switchContext` endpoint — خط 571 |
|
||||||
|
| `assets/admin/stores/authStore.ts` | `switchContext` action — خط 105 |
|
||||||
|
| `assets/admin/pages/DoctorDetailPage.tsx` | `uuid = isOwnProfile ? dbUuid : paramUuid` — خط 2162 |
|
||||||
|
| `assets/admin/pages/SelectContextPage.tsx` | `handleSelect` — خط 23 |
|
||||||
|
|
||||||
|
## وضعیت فعلی
|
||||||
|
|
||||||
|
### Backend — `switchContext` response (خط 595-599)
|
||||||
|
|
||||||
|
```php
|
||||||
|
return $this->success([
|
||||||
|
'db_uuid' => $dbUuid,
|
||||||
|
'db_key' => $this->buildDbKey($dbUuid),
|
||||||
|
'context' => $matched,
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
`$matched` از `buildAvailableContexts()` میآید و دارای فیلد `role` است:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// برای context کلینیکهایی که دکتر در آنها عضو است:
|
||||||
|
$contexts[] = [
|
||||||
|
'type' => 'clinic',
|
||||||
|
'db_uuid' => $clinic->getUuid(),
|
||||||
|
'name' => $clinic->getName() ?? '',
|
||||||
|
'role' => 'doctor', // ← نه 'clinic'، چون دکتر است نه صاحب کلینیک
|
||||||
|
];
|
||||||
|
|
||||||
|
// برای context مطب شخصی:
|
||||||
|
$contexts[] = [
|
||||||
|
'type' => 'doctor',
|
||||||
|
'db_uuid' => $doctor->getUuid(),
|
||||||
|
'name' => 'مطب شخصی ...',
|
||||||
|
'role' => 'doctor',
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
پس `context.role` موجود است اما backend آن را به عنوان `primary_role` جداگانه برنمیگرداند.
|
||||||
|
|
||||||
|
### Frontend — `switchContext` در authStore (خط 105-117)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
switchContext: async (dbUuid: string) => {
|
||||||
|
const res = await apiFetch('/api/v1/auth/switch-context', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ db_uuid: dbUuid }),
|
||||||
|
});
|
||||||
|
if (res.success) {
|
||||||
|
set({
|
||||||
|
dbUuid: res.data.db_uuid,
|
||||||
|
dbKey: res.data.db_key,
|
||||||
|
context: res.data.context,
|
||||||
|
// ← primaryRole آپدیت نمیشود!
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend — DoctorDetailPage (خط 2162)
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const uuid = isOwnProfile ? (dbUuid ?? undefined) : paramUuid;
|
||||||
|
// وقتی context کلینیک است، dbUuid = uuid کلینیک است
|
||||||
|
// اما primaryRole هنوز 'doctor' است
|
||||||
|
// پس GET /api/v1/doctor/{clinicUuid} → 404
|
||||||
|
```
|
||||||
|
|
||||||
|
## وظایف
|
||||||
|
|
||||||
|
### ۱. Frontend — آپدیت `primaryRole` در `switchContext`
|
||||||
|
|
||||||
|
در `assets/admin/stores/authStore.ts`، خط 110-115، `primaryRole` را از `res.data.context.role` بخوان و set کن:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
switchContext: async (dbUuid: string) => {
|
||||||
|
const res = await apiFetch('/api/v1/auth/switch-context', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ db_uuid: dbUuid }),
|
||||||
|
});
|
||||||
|
if (res.success) {
|
||||||
|
set({
|
||||||
|
dbUuid: res.data.db_uuid,
|
||||||
|
dbKey: res.data.db_key,
|
||||||
|
context: res.data.context,
|
||||||
|
primaryRole: res.data.context?.role ?? null, // ← اضافه شود
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۲. Backend — بررسی `role` در context کلینیکهای عضو دکتر
|
||||||
|
|
||||||
|
در `buildAvailableContexts()` (خط 626-633)، وقتی دکتر به context یک کلینیک سوئیچ میکند، role باید `'clinic'` باشد نه `'doctor'` — چون در آن محیط باید به عنوان نماینده کلینیک عمل کند:
|
||||||
|
|
||||||
|
```php
|
||||||
|
foreach ($this->clinicRepo->findByDoctor($doctor) as $clinic) {
|
||||||
|
$contexts[] = [
|
||||||
|
'type' => 'clinic',
|
||||||
|
'db_uuid' => $clinic->getUuid(),
|
||||||
|
'name' => $clinic->getName() ?? '',
|
||||||
|
'role' => 'clinic', // ← از 'doctor' به 'clinic' تغییر دهید
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**نکته مهم:** این تغییر باعث میشود وقتی دکتر در context کلینیک است، `primaryRole === 'clinic'` شود. در `DoctorDetailPage` این رفتار درست است — read-only view کلینیک فعال میشود و از `GET /api/v1/clinic/my-doctor/{uuid}` استفاده میکند.
|
||||||
|
|
||||||
|
### ۳. رفع مشکل `/admin/profile` برای context کلینیک
|
||||||
|
|
||||||
|
در `DoctorDetailPage.tsx` خط 2162:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const uuid = isOwnProfile ? (dbUuid ?? undefined) : paramUuid;
|
||||||
|
```
|
||||||
|
|
||||||
|
وقتی `isOwnProfile = true` و `primaryRole === 'clinic'`، صفحه profile پزشک منطقی نیست — دکتر در حال مشاهده به عنوان نماینده کلینیک است. دو گزینه:
|
||||||
|
|
||||||
|
**گزینه A (توصیهشده):** در `DoctorProfilePage.tsx` و route `/admin/profile`، وقتی `primaryRole === 'clinic'`، به dashboard هدایت کن:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// assets/admin/pages/DoctorProfilePage.tsx
|
||||||
|
export default function DoctorProfilePage() {
|
||||||
|
const primaryRole = useAuthStore(s => s.primaryRole);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (primaryRole === 'clinic') navigate('/admin/dashboard', { replace: true });
|
||||||
|
}, [primaryRole, navigate]);
|
||||||
|
|
||||||
|
return <DoctorDetailPage isOwnProfile />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**گزینه B:** در `App.tsx` route `profile` را فقط به role `doctor` محدود کن:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// App.tsx — خط 142
|
||||||
|
<Route path="profile" element={<RoleRoute roles={['doctor']}><DoctorProfilePage /></RoleRoute>} />
|
||||||
|
// این از قبل هست — مشکل این است که primaryRole درست آپدیت نمیشود
|
||||||
|
```
|
||||||
|
|
||||||
|
گزینه B کافی است اگر وظیفه ۱ و ۲ درست اجرا شوند — بعد از switch، `primaryRole === 'clinic'` میشود و `RoleRoute` کاربر را به dashboard redirect میکند.
|
||||||
|
|
||||||
|
## نکات مهم
|
||||||
|
|
||||||
|
- **migration لازم نیست** — هیچ entity تغییر نمیکند
|
||||||
|
- **`context.role` از قبل در response موجود است** — backend فقط نیاز به بررسی مقدار دارد، endpoint تغییر نمیکند
|
||||||
|
- بعد از اصلاح، context کلینیکهای عضو دکتر `role: 'clinic'` خواهند داشت — این با guard های `primaryRole === 'clinic'` در DoctorDetailPage سازگار است
|
||||||
|
- `cache:clear` بعد از تغییر backend لازم است
|
||||||
|
- `yarn dev` بعد از تغییر frontend لازم است
|
||||||
|
- مستندات: `docs/api/auth.md` — توضیح رفتار `context.role` در `switch-context` response بهروزرسانی شود
|
||||||
@@ -1783,11 +1783,11 @@ function ScheduleSection({ doctorUuid }: { doctorUuid: string }) {
|
|||||||
|
|
||||||
const locationsQ = useQuery({
|
const locationsQ = useQuery({
|
||||||
queryKey: ['available-locations', doctorUuid],
|
queryKey: ['available-locations', doctorUuid],
|
||||||
queryFn: () => api.get<ApiResponse<AddressData[]>>(`/api/v1/appointment-settings/available-locations/${doctorUuid}`),
|
queryFn: () => api.get<ApiResponse<any>>(`/api/v1/appointment-settings/available-locations/${doctorUuid}`),
|
||||||
enabled: !!doctorUuid,
|
enabled: !!doctorUuid,
|
||||||
staleTime: 30_000,
|
staleTime: 30_000,
|
||||||
});
|
});
|
||||||
const availableLocations: AddressData[] = locationsQ.data?.data ?? [];
|
const availableLocations: AddressData[] = locationsQ.data?.data?.data ?? locationsQ.data?.data ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="cp-card p-6">
|
<div className="cp-card p-6">
|
||||||
|
|||||||
@@ -109,9 +109,10 @@ export const useAuthStore = create<AuthState>()(
|
|||||||
});
|
});
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
set({
|
set({
|
||||||
dbUuid: res.data.db_uuid,
|
dbUuid: res.data.db_uuid,
|
||||||
dbKey: res.data.db_key,
|
dbKey: res.data.db_key,
|
||||||
context: res.data.context,
|
context: res.data.context,
|
||||||
|
primaryRole: res.data.context?.role ?? null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ lexik_jwt_authentication:
|
|||||||
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
|
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
|
||||||
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
|
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
|
||||||
pass_phrase: '%env(JWT_PASSPHRASE)%'
|
pass_phrase: '%env(JWT_PASSPHRASE)%'
|
||||||
token_ttl: 3600
|
token_ttl: 604800
|
||||||
|
|||||||
+10
-2
@@ -265,7 +265,7 @@ Authorization: Bearer <token>
|
|||||||
"type": "clinic",
|
"type": "clinic",
|
||||||
"db_uuid": "clinic-uuid-...",
|
"db_uuid": "clinic-uuid-...",
|
||||||
"name": "کلینیک سلامت",
|
"name": "کلینیک سلامت",
|
||||||
"role": "doctor"
|
"role": "clinic"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -287,6 +287,14 @@ Authorization: Bearer <token>
|
|||||||
- `ROLE_SECRETARY` → `"secretary"`
|
- `ROLE_SECRETARY` → `"secretary"`
|
||||||
- بقیه → `"user"`
|
- بقیه → `"user"`
|
||||||
|
|
||||||
|
**قانون `context.role`** — نقشی که در آن محیط کاری فعال است:
|
||||||
|
- context مطب شخصی دکتر: `"doctor"`
|
||||||
|
- context کلینیک که دکتر **عضو** آن است: `"clinic"` (read-only — دسترسی به اطلاعات کلینیک)
|
||||||
|
- context کلینیک که دکتر **صاحب** آن است: `"clinic"`
|
||||||
|
- context منشی: `"secretary"`
|
||||||
|
|
||||||
|
> **نکته frontend:** پس از `switchContext`، `primaryRole` در store باید از `context.role` آپدیت شود.
|
||||||
|
|
||||||
**قانون `db_uuid`**:
|
**قانون `db_uuid`**:
|
||||||
- اگر یک context وجود دارد: خودکار فعال میشود
|
- اگر یک context وجود دارد: خودکار فعال میشود
|
||||||
- اگر چند context وجود دارد و کاربر هنوز انتخاب نکرده: `null` — frontend باید صفحه انتخاب نشان دهد
|
- اگر چند context وجود دارد و کاربر هنوز انتخاب نکرده: `null` — frontend باید صفحه انتخاب نشان دهد
|
||||||
@@ -327,7 +335,7 @@ Authorization: Bearer <token>
|
|||||||
"type": "clinic",
|
"type": "clinic",
|
||||||
"db_uuid": "clinic-uuid-...",
|
"db_uuid": "clinic-uuid-...",
|
||||||
"name": "کلینیک سلامت",
|
"name": "کلینیک سلامت",
|
||||||
"role": "doctor"
|
"role": "clinic"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -628,7 +628,7 @@ class AuthController extends BaseController
|
|||||||
'type' => 'clinic',
|
'type' => 'clinic',
|
||||||
'db_uuid' => $clinic->getUuid(),
|
'db_uuid' => $clinic->getUuid(),
|
||||||
'name' => $clinic->getName() ?? '',
|
'name' => $clinic->getName() ?? '',
|
||||||
'role' => 'doctor',
|
'role' => 'clinic',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user