# بازطراحی صفحه بیمه و قیمت‌گذاری — تفکیک نوع بیمه با Tab + ردیف Expandable + انتقال ویزیت آزاد ## پروژه `clinicpro` (پنل ادمین React — فقط frontend؛ backend موجود کافی است) ## زمینه مسیر `/admin/insurance-pricing` امروز سه بخش را در یک صفحه نشان می‌دهد: «قیمت ویزیت آزاد» (`FreeVisitPrice`)، و «مدیریت بیمه» (`TenantInsuranceContracts`) که همه‌ی قراردادهای بیمه‌ی پایه و تکمیلی را در یک جدول مسطح فهرست می‌کند. مدیر مطب هنگام افزودن بیمه باید نوع بیمه را دستی از یک `select` انتخاب کند و اطلاعات کلیدی هر بیمه (پوشش، فرانشیز، سقف) فقط به‌صورت یک زیرنویس کمرنگ در ستون نام دیده می‌شود. جزئیات کامل قرارداد جایی نمایش داده نمی‌شود. هدف: تجربه‌ی مدیریت بیمه را برای یک مدیر حرفه‌ای مطب/کلینیک سریع و خوانا کنیم — تفکیک پایه/تکمیلی با Tab، حذف انتخاب دستی نوع، نمایش خلاصه‌ی خوانا در ردیف، و ردیف‌های Expandable برای جزئیات کامل. همچنین «قیمت ویزیت آزاد» به صفحه‌ی «تنظیمات نوبت‌دهی» منتقل شود. ## backend — نیازی به تغییر نیست (اول گشتم) طبق قاعده‌ی «اول بگرد، بعد بساز» endpointهای موجود کافی‌اند؛ **هیچ تغییر backend لازم نیست**: - `GET /api/v1/insurance-pricing` (در [src/Insurance/Controller/InsuranceController.php](src/Insurance/Controller/InsuranceController.php) خط ۲۲۰) هر بیمه‌ی فعال را با فیلد `type` (`'basic'` | `'supplementary'`) و `free_visit_price_rials` برمی‌گرداند. - `GET /api/v1/billing/tenant-insurances` (خط ۳۰۶) برای هر قرارداد `insurance_kind` (`kind` قرارداد یا در نبودش `type` کاتالوگ) و همه‌ی فیلدهای پوشش/فرانشیز/سقف/تاریخ را برمی‌گرداند. - `POST /api/v1/billing/tenant-insurances` (خط ۳۳۳) فیلد `kind` را در payload می‌پذیرد و ذخیره می‌کند. - `PUT /api/v1/insurance-pricing` (خط ۲۵۸) با `{ free_visit_price_rials }` قیمت ویزیت آزاد را ذخیره می‌کند. پس فیلتر بر اساس نوع بیمه کاملاً **سمت frontend** انجام می‌شود (روی داده‌های موجود همین دو endpoint). ## فایل‌های مرتبط | فایل | نقش | تغییر | |------|-----|-------| | [assets/admin/pages/InsurancePricingPage.tsx](assets/admin/pages/InsurancePricingPage.tsx) | صفحه‌ی بیمه و قیمت‌گذاری | حذف `` و توضیح مربوطه | | [assets/admin/pages/AppointmentSettingsPage.tsx](assets/admin/pages/AppointmentSettingsPage.tsx) | صفحه‌ی تنظیمات نوبت‌دهی | افزودن `` | | [assets/admin/components/FreeVisitPrice.tsx](assets/admin/components/FreeVisitPrice.tsx) | کارت قیمت ویزیت آزاد | بدون تغییر (فقط جابه‌جا می‌شود) | | [assets/admin/components/TenantInsuranceContracts.tsx](assets/admin/components/TenantInsuranceContracts.tsx) | جدول مدیریت بیمه | بازنویسی: Tab پایه/تکمیلی + ردیف Expandable + خلاصه‌ی خوانا | | [assets/admin/components/InsuranceModal.tsx](assets/admin/components/InsuranceModal.tsx) | مودال افزودن/ویرایش بیمه | حذف `select` نوع بیمه؛ `kind` از prop می‌آید | ## وضعیت فعلی ### `InsurancePricingPage.tsx` ```tsx ``` ### `AppointmentSettingsPage.tsx` (بخش render) ```tsx ) : ( )} ``` ### `InsuranceModal.tsx` — انتخاب دستی نوع (حذف شود) ```tsx
``` ### `TenantInsuranceContracts.tsx` — یک جدول مسطح، خلاصه فقط در زیرنویس نام ```tsx const contracts: Contract[] = (contractsQuery.data as any)?.data?.data ?? []; const allInsurances: InsuranceOption[] = (pricingQuery.data as any)?.data?.insurances ?? []; const activeIds = new Set(contracts.map((c) => c.insurance_id)); const available = allInsurances.filter((i) => !activeIds.has(i.insurance_id)); ... {c.insurance_name ?? `#${c.insurance_id}`}
پوشش {c.coverage_percent}٪ {c.franchise_rials > 0 && ` · فرانشیز ${formatRial(c.franchise_rials)}`} {c.annual_ceiling_rials != null && ` · سقف ${formatRial(c.annual_ceiling_rials)}`}
``` ## وظایف ### ۱. انتقال «قیمت ویزیت آزاد» به تنظیمات نوبت‌دهی **۱.۱ حذف از `InsurancePricingPage.tsx`:** خط `import FreeVisitPrice ...` و `` را بردار. توضیح `PageHeader` را به «قراردادهای بیمه پایه و تکمیلی» تغییر بده (دیگر ویزیت آزاد اینجا نیست). **۱.۲ افزودن به `AppointmentSettingsPage.tsx`:** `import FreeVisitPrice from '../components/FreeVisitPrice';` و کارت را **بالای** `WeeklyScheduleTab` رندر کن. نکته‌ی مهم — گاردِ «فقط پزشک»: `FreeVisitPrice` از `GET/PUT /api/v1/insurance-pricing` استفاده می‌کند که entity را از روی نقش کاربر (`ROLE_DOCTOR` یا `ROLE_CLINIC`) resolve می‌کند (خط ۴۸ کنترلر)، پس هم برای پزشک و هم کلینیک کار می‌کند — اما `AppointmentSettingsPage` وقتی `uuid` پزشک نباشد کل محتوا را با پیام «این بخش فقط برای پزشک در دسترس است» جایگزین می‌کند. کارت قیمت ویزیت آزاد را **بیرون از** شرط `!uuid` و **قبل از** آن قرار بده تا مستقل از داشتن `doctorUuid` همیشه نمایش داده شود: ```tsx return (

مدیریت نوبت دهی

{!uuid ? (
این بخش فقط برای پزشک در دسترس است.
) : isLoading ? ( ... ) : ( )}
); ``` ### ۲. حذف انتخاب دستی نوع بیمه از `InsuranceModal` نوع بیمه دیگر دستی انتخاب نمی‌شود؛ از Tab فعال می‌آید. **۲.۱** بلوک `