feat: redesign new-visit modal with insurance selects and live breakdown

Sectioned layout (insurance & price / services / payment / summary),
base+supplementary insurance selects auto-filling visit price and
discount from insurance-pricing, live cost breakdown, unified تومان unit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-23 15:15:12 +03:30
co-authored by Claude Opus 4.8
parent 89191eee57
commit 59fb590d65
2 changed files with 215 additions and 14 deletions
+89
View File
@@ -0,0 +1,89 @@
# بازطراحی UI/UX مودال «ثبت مراجعه جدید»
## پروژه
`clinicpro` (admin frontend)
## زمینه
مودال «ثبت مراجعه جدید» در صفحه‌ی پرونده بیمار، UX ضعیفی دارد: فیلدها بدون گروه‌بندی، label و input بدون فاصله‌ی منطقی، بخش خدمات و خلاصه‌ی قیمت بی‌سازمان، و ارتباط بین «قیمت ویزیت / تخفیف بیمه پایه / تخفیف مکمل» با بیمه‌ی واقعی بیمار مشخص نیست. (تصویر فعلی توسط کاربر ارائه شده.)
## مشکل / هدف
بازطراحی مودال با اصول UI/UX حرفه‌ای:
- گروه‌بندی منطقی: «بیمه و مبلغ» / «خدمات» / «پرداخت و یادداشت» / «خلاصه»
- انتخاب بیمه‌ی پایه و مکمل به‌صورت select (از بیمه‌های entity)، و **پرکردن خودکار قیمت ویزیت و تخفیف/سهم** بر اساس قیمت‌گذاری بیمه‌ی پروفایل (خروجی پرامپت `insurance-visit-pricing.md`)
- نمایش زنده‌ی محاسبه: آزاد → با بیمه پایه → با بیمه مکمل → مبلغ نهایی
- واحد پول یکدست (تومان یا ریال — یکی، نه مخلوط؛ الان «ریال» در ورودی و «تومان» در خلاصه مخلوط است)
- فاصله‌گذاری، عنوان بخش‌ها، حالت‌های loading/disabled درست
## فایل‌های مرتبط
| فایل | نقش |
|------|-----|
| `assets/admin/pages/MyPatientsPage.tsx` | مودال `sessionModal` (حدود خط ۸۰۹ به بعد) و `handleSubmitSession` |
| `assets/admin/components/ui/Modal.tsx` | کامپوننت مودال (props: `size` = sm/md/lg/xl) |
| `assets/admin/components/ui/SearchableSelect.tsx` | select قابل‌جستجو (برای بیمه/خدمات) |
| `assets/admin/lib/utils.ts` | `formatRial`, `formatNumber` |
| `src/Patient/Controller/PatientController.php` | endpoint `POST /api/v1/patient/{uuid}/session` |
| `src/Patient/Service/PatientService.php` | محاسبه‌ی نهایی سمت سرور |
## وضعیت فعلی
- اسکیمای فرم:
```tsx
const sessionSchema = z.object({
visit_price_rials: z.coerce.number().min(0),
base_insurance_discount_percent: z.coerce.number().min(0).max(100),
supplementary_discount_percent: z.coerce.number().min(0).max(100),
payment_method: z.enum(["cash", "card", "insurance", "online", "pending"]),
notes: z.string().optional(),
});
```
- محاسبه‌ی قیمت سمت کلاینت:
```tsx
function calcFinalPrice(visitPrice, baseDiscount, suppDiscount, servicesTotal) {
const afterBase = visitPrice * (1 - baseDiscount / 100);
const afterSupp = afterBase * (1 - suppDiscount / 100);
return Math.round(afterSupp) + servicesTotal;
}
```
- خلاصه‌ی پایین مودال «جمع خدمات» و «مبلغ نهایی» را با «تومان» نشان می‌دهد ولی ورودی‌ها «ریال» هستند — ناهماهنگ.
- backend (`PatientService::createSession`) همین فیلدها + `insurance_base_id` / `insurance_supplementary_id` را می‌پذیرد (موجودیت `PatientSession` این دو ستون را دارد) اما فرم فعلی آن‌ها را ارسال نمی‌کند.
## وظایف
### ۱. ساختار بصری مودال
مودال را به بخش‌های واضح با عنوان تقسیم کن (می‌توانی `size="lg"` بدهی):
1. **بیمه و مبلغ ویزیت**: select بیمه‌ی پایه + select بیمه‌ی مکمل + قیمت ویزیت + تخفیف/سهم
2. **خدمات**: انتخاب بخش/سرویس + chipهای انتخاب‌شده (الگوی فعلی، فقط منظم‌تر)
3. **پرداخت و یادداشت**: روش پرداخت + یادداشت
4. **خلاصه**: آزاد / با بیمه پایه / با بیمه مکمل / جمع خدمات / مبلغ نهایی
### ۲. اتصال بیمه به قیمت‌گذاری پروفایل
- بیمه‌های entity و قیمت‌گذاری را از endpoint پرامپت `insurance-visit-pricing.md` (`GET /api/v1/insurance-pricing`) بگیر.
- با انتخاب بیمه‌ی پایه/مکمل، قیمت ویزیت آزاد و سهم بیمار خودکار پر شود (کاربر بتواند دستی override کند).
- `insurance_base_id` و `insurance_supplementary_id` را در body ارسال کن (backend از قبل پشتیبانی می‌کند).
### ۳. واحد پول یکدست
همه‌جا یک واحد. توصیه: نمایش به تومان با `formatRial` اگر این util تومان می‌دهد را بررسی کن؛ ورودی و خلاصه باید یکی باشند.
### ۴. تست
`ddev exec npx tsc --noEmit` و `ddev exec yarn dev`؛ بدون خطا.
## نکات مهم
- وابسته به `insurance-visit-pricing.md`؛ اگر آن endpoint هنوز نیست، حداقل ساختار UI و select بیمه‌ها از `GET /api/v1/insurances` را پیاده کن و auto-fill را با TODO علامت بزن.
- فرم با React Hook Form + Zod؛ server state با TanStack Query.
- از کامپوننت‌های `ui/` موجود استفاده کن؛ کتابخانه‌ی CSS جدید اضافه نکن؛ RTL.
- این تغییر صرفاً frontend است؛ تغییر API لازم نیست (مگر اینکه فیلد بیمه‌ی session در پاسخ کم باشد).