Files
clinicpro/.claude/prompt/ui-ux-pages-redesign.md
T
hamed df7a784701 feat: implement realistic data seeding for doctors, clinics, and secretaries
- Added seed_realistic_data.php to clean existing data and populate the database with realistic entries for doctors, clinics, and secretaries.
- Created a structured approach to generate 100 doctors per city with diverse specialties and services.
- Implemented database cleanup routines to ensure a fresh start for data seeding.
- Enhanced the DoctorSecretaryRepository with improved comments for clarity.
2026-06-15 14:18:25 +03:30

396 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# بازطراحی UI/UX پنج صفحه کلینیک‌پرو
## زمینه
پروژه ClinicPro یک پنل مدیریت کلینیک است با React 19 + TypeScript و یک سیستم CSS دیزاین اختصاصی. SubscriptionPage اخیراً بازطراحی شده و الگویی ایجاد کرده که بقیه صفحات باید با آن هم‌راستا شوند. ۵ صفحه هنوز از ساختار قدیمی استفاده می‌کنند: جدول‌های ساده، کارت‌های بدون سلسله‌مراتب بصری، و فرم‌های بدون هویت. هدف این است که هر صفحه را به یک تجربه منسجم، مدرن و RTL-first تبدیل کنیم.
## سیستم CSS موجود
متغیرها و کلاس‌های قابل استفاده (در `assets/admin/styles.css`):
```css
/* متغیرها */
--primary: oklch(0.55 0.2 256) --primary-subtle: oklch(0.97 0.02 256)
--surface: #fff --border: oklch(0.9 0 0) --text-1 / --text-2 / --text-3 --r
(border-radius base) --gap / --card-pad /* کلاس‌های کاربردی */ .card
/* white surface با shadow و border-radius */ .card-pad
/* padding داخل card */ .stat /* عدد آماری بزرگ + label */ .stat-grid
/* grid چند ستونه برای stat‌ها */ .badge green/red/yellow/blue/gray
/* برچسب وضعیت */ .btn primary/sm/ghost /* دکمه */ .field
/* label + input wrapper */ .seg /* segment/tab selector */ .bar
/* نوار پیشرفت */ .grid-2 /* دو‌ستونه responsive */ .avatar sm
/* آواتار دایره‌ای */;
```
**الگوی header صفحات:** `<PageHeader title="..." description="..." action={<button>} />`
## فایل‌های مرتبط
| فایل | نقش |
| ------------------------------------------- | -------------------------------------------------------- |
| `assets/admin/pages/MyPatientsPage.tsx` | پرونده بیماران — دو حالت: لیست + جزئیات بیمار |
| `assets/admin/pages/StaffPage.tsx` | مدیریت پرسنل — CRUD |
| `assets/admin/pages/MySecretariesPage.tsx` | مدیریت منشی ها — CRUD + permissions matrix |
| `assets/admin/pages/ClinicServicesPage.tsx` | سرویس‌های کلینیک — دو پنل: بخش‌ها + آیتم‌ها |
| `assets/admin/pages/SmsWalletPage.tsx` | کیف پول پیامک — balance + settings + logs |
| `assets/admin/styles.css` | سیستم CSS کامل |
| `assets/admin/pages/SubscriptionPage.tsx` | **مرجع طراحی** — بازطراحی‌شده با gradient + progress bar |
## وضعیت فعلی هر صفحه
### ۱. MyPatientsPage
دو حالت: حالت لیست و حالت پرونده بیمار.
**حالت لیست:** یک `.card` با `<DataTable>` ساده + search input بدون styling در header.
**حالت پرونده بیمار:** `PageHeader` با نام + برگشت. یک `.card` با `sessions.map(SessionRow)`. Modal ثبت مراجعه با سه‌ستونه برای قیمت/بیمه/روش پرداخت.
```tsx
// لیست ساده فعلی
<div className="card">
<div style={{ padding: '12px 16px 0', display: 'flex', gap: 8 }}>
<div style={{ position: 'relative', flex: 1 }}>
<MagnifyingGlassIcon ... />
<input ... placeholder="جستجو بر اساس نام یا تلفن..." />
</div>
</div>
<DataTable columns={recordColumns} data={records} loading={isLoading} ... />
<Pagination ... />
</div>
```
**ستون‌های لیست بیماران:** آواتار + نام + تلفن، تاریخ ثبت، دکمه «پرونده».
**SessionRow:** هنوز خوانده نشده — احتمالاً نمایش تجمیعی اطلاعات مراجعه.
### ۲. StaffPage
```tsx
// فعلی — جدول + دو modal
<PageHeader title="مدیریت پرسنل" ... action={<button>افزودن پرسنل</button>} />
<div className="card">
<DataTable columns={columns} data={staff} loading={isLoading} />
</div>
// Modal ایجاد و ویرایش با StaffFormFields (فیلدهای: full_name, phone, job_title, address, national_code)
```
ستون‌ها: نام، سمت، تلفن، کد ملی، وضعیت (ActiveBadge)، تاریخ ثبت، عملیات.
هیچ KPI یا آمار مقدماتی ندارد.
### ۳. MySecretariesPage
```tsx
// جدول دستی (نه DataTable)
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13.5 }}>
<thead>
<tr style={{ borderBottom: '1px solid var(--border)' }}>
<th style={{ textAlign: 'right', padding: '10px 16px', color: 'var(--text-3)', fontWeight: 500 }}>نام</th>
...
</tr>
</thead>
<tbody>
{secretaries.map((s, i) => (
<tr key={s.uuid} style={{ borderBottom: ... }}>
<td style={{ padding: '10px 16px' }}><b>{s.user_name}</b></td>
...
</tr>
))}
</tbody>
</table>
```
PermissionsMatrix: جدول checkbox با ۶ ستون برای ۴ بخش — داده‌محور و قابل استفاده اما بصری ضعیف.
حالت Empty state: فقط یک div با متن `هنوز منشی‌ای ثبت نشده است`.
### ۴. ClinicServicesPage
```tsx
// دو پنل side-by-side
<div style={{ display: 'grid', gridTemplateColumns: '280px 1fr', gap: 16, alignItems: 'start' }}>
{/* پنل بخش‌ها */}
<div className="card">
<div style={{ display: 'flex', ... marginBottom: 12 }}>
<b style={{ fontSize: 14 }}>بخش‌ها</b>
<button className="btn primary sm"><PlusIcon /></button>
</div>
{sections.map((s) => (
<div style={{ padding: '8px 10px', borderRadius: 6, cursor: 'pointer', background: selected ? 'var(--primary-subtle)' : ... }}>
...
</div>
))}
</div>
{/* پنل آیتم‌ها */}
<div className="card">
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
...
</table>
</div>
</div>
```
مشکل: بخش‌ها فقط text + دو آیکون هستند. active/inactive badge ندارند. جدول آیتم‌ها خیلی ساده است.
### ۵. SmsWalletPage
```tsx
// دو کارت بالا
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 20 }}>
{/* موجودی */}
<div className="card">
<div style={{ fontSize: 13, color: 'var(--text-3)', marginBottom: 4 }}>موجودی کیف پول</div>
<div style={{ fontSize: 28, fontWeight: 700 }}>{formatRial(balance?.balance_rials ?? 0)}</div>
<div style={{ fontSize: 13, color: 'var(--text-3)' }}>معادل {formatNumber(...)} پیامک</div>
<button className="btn primary sm">شارژ کیف پول</button>
</div>
{/* تنظیمات */}
<div className="card">...</div>
</div>
{/* جدول تراکنش‌ها */}
<div className="card">
<table style={{ ... fontSize: 13 }}>...</table>
</div>
```
کارت موجودی: عدد بزرگ اما بدون visual hierarchy. نوار پیشرفت و badge «موجودی» ندارد.
## وظایف
### ۱. بازطراحی MyPatientsPage
**هدف:** لیست با identity واضح + پرونده بیمار با مینی‌داشبورد.
**الف — حالت لیست بیماران:**
- Header اختصاصی: `<PageHeader>` با description «مراجعه‌کنندگان ثبت‌شده شما»
- Search bar را با یک wrapper card جداگانه به یک toolbar تبدیل کن (نه داخل card جدول)
- `DataTable` را با ردیف‌های بهتر جایگزین کن: هر ردیف آواتار gradient + نام بولد + شماره با آیکون تلفن + تاریخ ثبت شمسی
- دکمه «پرونده» به آیکون `FolderOpenIcon` + متن «مشاهده پرونده» تبدیل شود (btn primary sm)
- وقتی بیماری نیست: Empty State با آیکون بزرگ + متن راهنما
**ب — حالت پرونده بیمار (selectedRecord):**
- PageHeader با نام بیمار + شماره + تاریخ ثبت
- یک بنر/info-card بالای صفحه: نام، تلفن، تعداد مراجعات (از `totalSes`)
- SessionRow باید بهتر شود — هر مراجعه یک card مستقل با:
- سربرگ: تاریخ شمسی + روش پرداخت به عنوان badge
- بدنه: قیمت ویزیت + تخفیف بیمه + جمع خدمات + قیمت نهایی در یک `.grid-2` یا flex row
- footer: لیست خدمات (services) اگر وجود داشت
- دکمه «ویرایش» در گوشه بالا سمت چپ کارت
**ج — Modal ثبت مراجعه:**
- سه فیلد قیمت/بیمه پایه/بیمه تکمیلی را در یک `.grid-2` یا grid سه‌ستونه گذاشتن خوب است — این کافیست
- بخش «خدمات» را با یک separator بصری از فیلدهای اصلی جدا کن
- لیست خدمات انتخاب‌شده را با badge یا chip نشان بده (نه فقط متن)
**نکته:** SessionRow را کامل بخوان قبل از پیاده‌سازی.
---
### ۲. بازطراحی StaffPage
**هدف:** صفحه‌ای با KPI bar + جدول بهتر + فرم واضح‌تر.
**الف — KPI bar:**
- بالای جدول، یک `.stat-grid` با ۳ card: تعداد پرسنل کل، تعداد فعال، تعداد غیرفعال
- داده از همان `staff` query محاسبه شود (نه API جدید):
```ts
const total = staff.length;
const active = staff.filter((s) => s.active).length;
const inactive = total - active;
```
**ب — جدول بهتر:**
- ستون «نام» را بهبود بده: `full_name` + `job_title` زیرش (مثل MyPatientsPage)
- ستون «تلفن» و «کد ملی» را در موبایل پنهان کن (CSS media query اگر لازم بود)
- ردیف‌های hover state با background تغییر کند
**ج — فرم StaffFormFields:**
- فیلدهای `full_name` و `job_title` را در یک ردیف (`.grid-2`) قرار بده
- فیلدهای `phone` و `national_code` را در یک ردیف دیگر
- فیلد `address` تنها بماند (textarea یا input تمام عرض)
---
### ۳. بازطراحی MySecretariesPage
**هدف:** جدول دستی را به DataTable شیک تبدیل کن + PermissionsMatrix بصری‌تر + Empty State جذاب.
**الف — جدول منشی ها:**
- جدول دستی را **حذف کن** و از `DataTable` component استفاده کن
- ستون «نام»: آواتار کوچک (initial از `user_name`) + نام بولد
- ستون «موبایل»: با `maskMobile()` و `dir="ltr"` + آیکون تلفن
- ستون «وضعیت»: `<ActiveBadge>`
- ستون «تاریخ»: `formatDate()`
- ستون «عملیات»: دو دکمه `sm` (ویرایش دسترسی‌ها + غیرفعال‌سازی)
**ب — Empty State:**
- وقتی `secretaries.length === 0`:
```tsx
<div
className="card card-pad"
style={{ textAlign: "center", padding: "60px 24px" }}
>
<IdentificationIcon
style={{ width: 48, color: "var(--text-3)", margin: "0 auto 16px" }}
/>
<div style={{ fontWeight: 600, marginBottom: 8 }}>
هنوز منشی‌ای اضافه نشده
</div>
<div
style={{ color: "var(--text-3)", fontSize: 13.5, marginBottom: 20 }}
>
منشی می‌تواند نوبت‌ها و اطلاعات کلینیک را مدیریت کند
</div>
<button className="btn primary sm" onClick={() => setCreateOpen(true)}>
<PlusIcon style={{ width: 16 }} /> افزودن اولین منشی
</button>
</div>
```
**ج — PermissionsMatrix:**
- ردیف‌های جدول با رنگ متناوب (striped) یا hover state
- checkbox‌ها با `accent-color: var(--primary)` (قبلاً بود، نگه دار)
- header ستون‌ها را bold و با background کم‌رنگ کن
- برای cell‌های `` (N/A) رنگ `var(--border)` خوب است
---
### ۴. بازطراحی ClinicServicesPage
**هدف:** پنل بخش‌ها بهتر + جدول آیتم‌ها با badge فعال/غیرفعال.
**الف — پنل بخش‌ها (ستون ۲۸۰px):**
- هر بخش یک item با:
- آیکون `WrenchScrewdriverIcon` در سمت راست (color از theme)
- نام بخش bold
- badge «فعال» / «غیرفعال» بر اساس `s.active`
- دکمه‌های edit/delete فقط هنگام hover نشان داده شوند (opacity transition)
- انتخاب شده: border `var(--primary)` + background `var(--primary-subtle)`
- افزودن بخش: دکمه با آیکون + متن «بخش جدید» (نه فقط آیکون)
**ب — پنل آیتم‌ها (ستون 1fr):**
- بالای جدول یک toolbar: نام بخش به عنوان breadcrumb + تعداد آیتم‌ها + دکمه «سرویس جدید»
- هر row آیتم:
- badge «فعال» / «غیرفعال» برای `item.active`
- قیمت با `formatRial()` و رنگ primary
- نام پرسنل با avatar خیلی کوچک اگر وجود داشت
- Empty state وقتی `items.length === 0`:
```tsx
<div style={{ textAlign: 'center', padding: '48px 0', color: 'var(--text-3)' }}>
<PlusIcon style={{ width: 32, margin: '0 auto 12px', display: 'block' }} />
سرویسی در این بخش وجود ندارد
<br />
<button className="btn primary sm" style={{ marginTop: 12 }} onClick={...}>افزودن سرویس</button>
</div>
```
**ج — State اولیه (هیچ بخشی انتخاب نشده):**
- جای پیام «یک بخش را از سمت راست انتخاب کنید» یک آنیمیشن ساده یا placeholder card با رنگ dashed border قرار بده:
```tsx
<div
style={{
border: "2px dashed var(--border)",
borderRadius: 10,
padding: "60px 0",
textAlign: "center",
color: "var(--text-3)",
}}
>
<WrenchScrewdriverIcon style={{ width: 40, margin: "0 auto 12px" }} />
<div>یک بخش انتخاب کنید تا سرویس‌های آن را ببینید</div>
</div>
```
---
### ۵. بازطراحی SmsWalletPage
**هدف:** کارت موجودی با visual hierarchy قوی + تنظیمات واضح‌تر + جدول تراکنش‌ها با visual type indicator.
**الف — کارت موجودی:**
- gradient background (مثل SubscriptionPage): `background: linear-gradient(135deg, oklch(0.52 0.22 256), oklch(0.40 0.18 256))`
- رنگ متن: white
- موجودی را بزرگ‌تر کن: `fontSize: 36, fontWeight: 800`
- زیر موجودی: نوار پیشرفت `.bar` که نشان می‌دهد چند پیامک باقی‌مانده (نسبت به یک سقف فرضی مثلاً ۵۰۰ پیامک یا بر اساس estimated_sms_count)
- badge status: اگر `estimated_sms_count < 10` → badge قرمز «موجودی کم»، اگر ≥ 10 → badge سبز «فعال»
- دکمه شارژ: white با primary text (برعکس از theme معمول)
**ب — کارت تنظیمات:**
- عنوان با divider پایین
- هر toggle را با یک card داخلی wrapper کن:
```tsx
<div style={{ background: 'var(--primary-subtle)', borderRadius: 8, padding: '10px 14px', display: 'flex', alignItems: 'center', gap: 10 }}>
<input type="checkbox" ... />
<div>
<div style={{ fontWeight: 600, fontSize: 13.5 }}>یادآوری قبل از نوبت</div>
<div style={{ color: 'var(--text-3)', fontSize: 12 }}>ارسال خودکار چند ساعت قبل از نوبت</div>
</div>
</div>
```
- input «چند ساعت قبل» را با border و padding واضح‌تر style کن
**ج — جدول تراکنش‌ها:**
- ستون «نوع» را با یک آیکون بهبود بده:
- `credit`: `ArrowUpCircleIcon` با رنگ سبز
- `debit`: `ArrowDownCircleIcon` با رنگ قرمز
- ستون «مبلغ» را بر اساس نوع رنگ بده: credit → سبز، debit → قرمز
- header table با background خاکستری کم‌رنگ
**د — Modal شارژ:**
- انتخاب gateway را به radio card تبدیل کن (مثل SubscriptionPage):
```tsx
{
(["mellat", "sep"] as const).map((gw) => (
<div
key={gw}
onClick={() => setGateway(gw)}
style={{
border: `2px solid ${gateway === gw ? "var(--primary)" : "var(--border)"}`,
borderRadius: 8,
padding: "10px 14px",
cursor: "pointer",
background: gateway === gw ? "var(--primary-subtle)" : "",
transition: "all 0.15s",
flex: 1,
textAlign: "center",
}}
>
<div style={{ fontWeight: 600 }}>
{gw === "mellat" ? "بانک ملت" : "سپ"}
</div>
</div>
));
}
```
- پیش‌نمایش مبلغ: وقتی کاربر عدد می‌زند، زیر input بنویس «پرداخت X ریال از طریق Y»
## نکات مهم
1. **فقط فرانت‌اند** — هیچ endpoint، migration، یا تغییر backend لازم نیست
2. **CSS موجود را استفاده کن** — هیچ کتابخانه جدید اضافه نکن. inline style برای تنظیمات جزئی قابل قبول است
3. **DataTable component موجود** در `assets/admin/components/ui/DataTable.tsx` — برای جداول از آن استفاده کن (ترجیحاً بر inline table)
4. **RTL** رعایت شود — padding/margin/icon placement همه RTL-first باشد
5. **هر صفحه مستقل** پیاده‌سازی شود — نه refactor مشترک
6. **SubscriptionPage** را به عنوان مرجع visual نگه دار — gradient، progress bar، و card layout آن مدل است
7. **بعد از هر صفحه** `ddev exec yarn dev` اجرا کن تا TypeScript error نداشته باشد
8. **SessionRow** در MyPatientsPage: قبل از ویرایش، آن component را کامل بخوان (احتمالاً inline در همان فایل است)
9. **`maskMobile()`** از `../lib/utils` — برای نمایش شماره موبایل منشی ها استفاده کن
10. **`formatDate()` و `formatDateTime()`** — همه تاریخ‌ها شمسی نمایش داده شوند