resolveProfile created an empty profile for any caller with create-intent, including an admin merely viewing someone else's profile. Restrict lazy-create to the user's own profile; an admin reading another user's missing profile now gets 404 with no side-effect record. Doc updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9.1 KiB
نمایش پروفایل کاربر در صفحهی جزئیات کاربرِ پنل ادمین
پروژه
clinicpro (Admin React frontend). تغییر عمدتاً frontend است؛ endpoint پروفایل از قبل موجود است و به ادمین دسترسی میدهد. (یک تصمیم دربارهی side-effect لازم است — به بخش «نکات» نگاه کن.)
زمینه
صفحهی /admin/users/{uuid} (assets/admin/pages/UserDetailPage.tsx) فقط اطلاعات حساب کاربری را از GET /api/v1/admin/users/{uuid} نشان میدهد: نام، موبایل، ایمیل، نقشها، وضعیت، UUID. اما پروفایل بیمار (کد ملی، جنسیت، تاریخ تولد، گروه خونی، وضعیت تأهل، تحصیلات، شغل، آدرس، تلفنها، بیمه، سوابق پزشکی) را نشان نمیدهد. ادمین باید بتواند پروفایل کامل کاربر را همینجا ببیند.
پروفایل از GET /api/v1/user-profile/{uuid} میآید که هم با user-uuid کار میکند (resolve شده) و به ROLE_ADMIN اجازهی دیدن هر پروفایلی را میدهد (canAccess → admin مجاز). پاسخ دوبار تودرتو است: { success, data: { data: {...profile...} } }.
مشکل / هدف
به UserDetailPage.tsx یک بخش «پروفایل کاربر» اضافه کن که با GET /api/v1/user-profile/{uuid} پروفایل را میگیرد و فیلدها را بهصورت فقط-خواندنی و خوانا (کارتهای اطلاعات + بخش سوابق پزشکی) نمایش میدهد. اگر کاربر پروفایل پر نکرده، حالت «ثبت نشده» نشان داده شود (نه خطا).
فایلهای مرتبط
| فایل | نقش |
|---|---|
assets/admin/pages/UserDetailPage.tsx |
صفحهی جزئیات کاربر — محل افزودن بخش پروفایل |
assets/admin/lib/api.ts |
api.get, ApiResponse, JWT از localStorage['clinicpro-auth'] |
assets/admin/lib/utils.ts |
formatDate, formatDateTime (و در صورت نیاز formatNumber) |
src/UserProfile/Controller/UserProfileController.php |
show() — مرجع قرارداد (تغییر نمیکند، مگر تصمیم read-only گرفته شود) |
src/UserProfile/Entity/UserProfile.php |
toArray() — فیلدهای پروفایل |
وضعیت فعلی (کد واقعی)
UserDetailPage.tsx — فقط user query
const { data, isLoading, isError } = useQuery({
queryKey: ['admin-user', uuid],
queryFn: () => api.get<ApiResponse<AdminUserDetail>>(`/api/v1/admin/users/${uuid}`),
enabled: !!uuid,
});
const user: AdminUserDetail | undefined = (data?.data as any)?.data ?? data?.data;
// ... فقط نام/موبایل/ایمیل/نقش/وضعیت/UUID رندر میشود؛ پروفایل نیست
قرارداد پروفایل (UserProfile.toArray)
{
"uuid": "...", "user_uuid": "...",
"label": null, "family": null, "fathers_name": null,
"national_code": null, "national_code_approved": false,
"gender": null, "date_of_birth": null, "blood_type": null,
"marital_status": null, "education": null, "job": null,
"address": null, "home_phone": null, "work_phone": null,
"insurance_id": null, "basic_insurance_id": null, "supplementary_insurance_id": null,
"other": { "disease": [...], "allergies": [...], "medications": [...], "surgeries": [...], "family_history": [...], "relatives": [...] },
"sharing_with_user": false, "description": null,
"created_at": ..., "updated_at": ...
}
پاسخ:
{ success, data: { data: {...} } }→ استخراج باres?.data?.data ?? res?.data.
وظایف
۱. کوئری پروفایل در UserDetailPage.tsx
interface UserProfileData {
uuid: string; user_uuid: string;
label: string | null; family: string | null; fathers_name: string | null;
national_code: string | null; gender: string | null; date_of_birth: number | null;
blood_type: string | null; marital_status: string | null; education: string | null;
job: string | null; address: string | null; home_phone: string | null; work_phone: string | null;
basic_insurance_id: number | null; supplementary_insurance_id: number | null;
other: Record<string, any> | null;
}
const profileQ = useQuery({
queryKey: ['admin-user-profile', uuid],
queryFn: () => api.get<ApiResponse<any>>(`/api/v1/user-profile/${uuid}`),
enabled: !!uuid,
retry: false,
});
const profile: UserProfileData | undefined =
(profileQ.data as any)?.data?.data ?? (profileQ.data as any)?.data;
۲. بخش «پروفایل کاربر» (read-only) در رندر
- یک کارت
cp-cardجدید بعد از گرید اطلاعات موجود اضافه کن با عنوان «پروفایل کاربر». - از همان کامپوننت
InfoCardموجود برای فیلدها استفاده کن (آیکن + label + value؛ مقدار null → «ثبت نشده» خودش هندل میشود). - فیلدها با برچسب فارسی: نام، نام خانوادگی، نام پدر، کد ملی، جنسیت (
male→مرد/female→زن)، تاریخ تولد (شمسی)، گروه خونی، وضعیت تأهل، تحصیلات، شغل، آدرس، تلفن منزل، تلفن محل کار، شماره بیمه پایه/تکمیلی. - بخش سوابق پزشکی (از
other): آلرژیها، داروها، جراحیها، سابقهی خانوادگی، بیماریها — هرکدام لیست؛ اگر خالی، «موردی ثبت نشده». ساختار آیتمهایother.*را از دادهی واقعی استنتاج کن (با یکcurlنمونه)، توهمسازی نکن. - تاریخ تولد: اگر
date_of_birthعدد (Unix) است باformatDateشمسی؛ اگر رشتهی شمسی ذخیره شده، همان را نشان بده (شکل واقعی را با curl چک کن).
۳. حالتهای loading / خالی
- وقتی
profileQ.isLoading→ skeleton کوچک. - وقتی پروفایل همهی فیلدهایش null است (کاربر چیزی پر نکرده) → پیام «این کاربر هنوز پروفایلی تکمیل نکرده است» بهجای کارتهای خالی، یا کارتها با «ثبت نشده».
- خطا (۴۰۳/۴۰۴ نامحتمل چون ادمین است) → یک پیام کوچک، نه شکستن کل صفحه.
نکات مهم
- side-effect مهم — تصمیم لازم:
GET /api/v1/user-profile/{uuid}در حال حاضر برای کاربری که پروفایل ندارد، یک پروفایل خالی lazy-create میکند (درresolveProfile). برای «دیدن» توسط ادمین، ساختِ رکورد هنگام GET نامطلوب است. یکی را انتخاب کن:- (ساده) همین رفتار را بپذیر — یک پروفایل خالی برای کاربر ساخته میشود؛ بیضرر ولی side-effect دارد.
- (تمیزتر، توصیهشده) در
UserProfileController::resolveProfile، lazy-create را فقط وقتی انجام بده که درخواستدهنده خودِ کاربر است، نه ادمینی که پروفایل دیگری را میبیند؛ یعنی برای ادمینِ بیننده، اگر پروفایل نبودnullبرگردان (→ ۴۰۴ یا «پروفایل ندارد») بدون ساخت. این یک تغییر کوچک backend است (docs/api/user-profile.mdرا هم بهروز کن).
- اگر گزینه ۲ را انتخاب کردی، این بخش backend است؛ آن را اول انجام بده و سند را بهروز کن.
- double-nesting: پروفایل در
res.data.dataاست (مثل بقیهیsuccess(['data'=>...])). - فقط-خواندنی: ادمین اینجا فقط پروفایل را میبیند؛ ویرایش پروفایل بیمار توسط ادمین خارج از این پرامپت است (اگر خواستی پیشنهاد بده ولی پیاده نکن).
- از کلاسهای CSS و کامپوننتهای موجود (
cp-card,InfoCard,skeleton) استفاده کن؛ کتابخانهی جدید نیار؛ RTL. - تاریخها شمسی با
formatDate/formatDateTimeموجود. - تست:
ddev exec npx tsc --noEmit --project tsconfig.json 2>&1 | head -30ddev exec yarn dev(build admin) — خطای CSS lightningcss از قبل هست و بلوک نمیکند؛ فقط خطای TS مهم است.- یک
curlواقعی رویGET /api/v1/user-profile/7a63a8d2-6483-4ae7-9c38-54c399cde699با توکن ادمین برای دیدن شکل دقیقother/date_of_birth. - در مرورگر:
/admin/users/{uuid}بخش پروفایل را با دادهی واقعی یا «ثبت نشده» نشان دهد. - اگر گزینه ۲ را انتخاب کردی:
ddev exec php -l+ بهروزرسانیdocs/api/user-profile.md.