Files
clinicpro/.claude/prompt/admin-user-detail-show-profile.md
T
hamedandClaude Opus 4.8 94d9f88cb2 fix(user-profile): don't lazy-create when an admin views another user
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>
2026-06-15 22:04:19 +03:30

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 نامطلوب است. یکی را انتخاب کن:
    1. (ساده) همین رفتار را بپذیر — یک پروفایل خالی برای کاربر ساخته می‌شود؛ بی‌ضرر ولی side-effect دارد.
    2. (تمیزتر، توصیه‌شده) در 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 -30
    • ddev 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.