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.
This commit is contained in:
hamed
2026-06-15 14:18:25 +03:30
parent 68dfe613a2
commit df7a784701
15 changed files with 3024 additions and 1417 deletions
+129 -102
View File
@@ -10,41 +10,31 @@
```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 /* آواتار دایره‌ای */
--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 |
| فایل | نقش |
| ------------------------------------------- | -------------------------------------------------------- |
| `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 |
## وضعیت فعلی هر صفحه
@@ -173,6 +163,7 @@ PermissionsMatrix: جدول checkbox با ۶ ستون برای ۴ بخش — د
**هدف:** لیست با identity واضح + پرونده بیمار با مینی‌داشبورد.
**الف — حالت لیست بیماران:**
- Header اختصاصی: `<PageHeader>` با description «مراجعه‌کنندگان ثبت‌شده شما»
- Search bar را با یک wrapper card جداگانه به یک toolbar تبدیل کن (نه داخل card جدول)
- `DataTable` را با ردیف‌های بهتر جایگزین کن: هر ردیف آواتار gradient + نام بولد + شماره با آیکون تلفن + تاریخ ثبت شمسی
@@ -180,15 +171,17 @@ PermissionsMatrix: جدول checkbox با ۶ ستون برای ۴ بخش — د
- وقتی بیماری نیست: Empty State با آیکون بزرگ + متن راهنما
**ب — حالت پرونده بیمار (selectedRecord):**
- PageHeader با نام بیمار + شماره + تاریخ ثبت
- یک بنر/info-card بالای صفحه: نام، تلفن، تعداد مراجعات (از `totalSes`)
- SessionRow باید بهتر شود — هر مراجعه یک card مستقل با:
- سربرگ: تاریخ شمسی + روش پرداخت به عنوان badge
- بدنه: قیمت ویزیت + تخفیف بیمه + جمع خدمات + قیمت نهایی در یک `.grid-2` یا flex row
- footer: لیست خدمات (services) اگر وجود داشت
- دکمه «ویرایش» در گوشه بالا سمت چپ کارت
- سربرگ: تاریخ شمسی + روش پرداخت به عنوان badge
- بدنه: قیمت ویزیت + تخفیف بیمه + جمع خدمات + قیمت نهایی در یک `.grid-2` یا flex row
- footer: لیست خدمات (services) اگر وجود داشت
- دکمه «ویرایش» در گوشه بالا سمت چپ کارت
**ج — Modal ثبت مراجعه:**
- سه فیلد قیمت/بیمه پایه/بیمه تکمیلی را در یک `.grid-2` یا grid سه‌ستونه گذاشتن خوب است — این کافیست
- بخش «خدمات» را با یک separator بصری از فیلدهای اصلی جدا کن
- لیست خدمات انتخاب‌شده را با badge یا chip نشان بده (نه فقط متن)
@@ -202,20 +195,23 @@ PermissionsMatrix: جدول checkbox با ۶ ستون برای ۴ بخش — د
**هدف:** صفحه‌ای با 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;
```
```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 تمام عرض)
@@ -226,7 +222,8 @@ PermissionsMatrix: جدول checkbox با ۶ ستون برای ۴ بخش — د
**هدف:** جدول دستی را به DataTable شیک تبدیل کن + PermissionsMatrix بصری‌تر + Empty State جذاب.
**الف — جدول منشیان:**
**الف — جدول منشی ها:**
- جدول دستی را **حذف کن** و از `DataTable` component استفاده کن
- ستون «نام»: آواتار کوچک (initial از `user_name`) + نام بولد
- ستون «موبایل»: با `maskMobile()` و `dir="ltr"` + آیکون تلفن
@@ -235,21 +232,32 @@ PermissionsMatrix: جدول checkbox با ۶ ستون برای ۴ بخش — د
- ستون «عملیات»: دو دکمه `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 }}>
منشی می‌تواند نوبت‌ها و اطلاعات کلینیک را مدیریت کند
```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>
<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 کم‌رنگ کن
@@ -262,38 +270,49 @@ PermissionsMatrix: جدول checkbox با ۶ ستون برای ۴ بخش — د
**هدف:** پنل بخش‌ها بهتر + جدول آیتم‌ها با badge فعال/غیرفعال.
**الف — پنل بخش‌ها (ستون ۲۸۰px):**
- هر بخش یک item با:
- آیکون `WrenchScrewdriverIcon` در سمت راست (color از theme)
- نام بخش bold
- badge «فعال» / «غیرفعال» بر اساس `s.active`
- دکمه‌های edit/delete فقط هنگام hover نشان داده شوند (opacity transition)
- آیکون `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 خیلی کوچک اگر وجود داشت
- 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>
```
```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>
```
```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>
```
---
@@ -302,6 +321,7 @@ PermissionsMatrix: جدول checkbox با ۶ ستون برای ۴ بخش — د
**هدف:** کارت موجودی با 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`
@@ -310,48 +330,55 @@ PermissionsMatrix: جدول checkbox با ۶ ستون برای ۴ بخش — د
- دکمه شارژ: 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>
```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>
</div>
```
```
- input «چند ساعت قبل» را با border و padding واضح‌تر style کن
**ج — جدول تراکنش‌ها:**
- ستون «نوع» را با یک آیکون بهبود بده:
- `credit`: `ArrowUpCircleIcon` با رنگ سبز
- `debit`: `ArrowDownCircleIcon` با رنگ قرمز
- `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>
))}
```
```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»
## نکات مهم
@@ -364,5 +391,5 @@ PermissionsMatrix: جدول checkbox با ۶ ستون برای ۴ بخش — د
6. **SubscriptionPage** را به عنوان مرجع visual نگه دار — gradient، progress bar، و card layout آن مدل است
7. **بعد از هر صفحه** `ddev exec yarn dev` اجرا کن تا TypeScript error نداشته باشد
8. **SessionRow** در MyPatientsPage: قبل از ویرایش، آن component را کامل بخوان (احتمالاً inline در همان فایل است)
9. **`maskMobile()`** از `../lib/utils` — برای نمایش شماره موبایل منشیان استفاده کن
9. **`maskMobile()`** از `../lib/utils` — برای نمایش شماره موبایل منشی ها استفاده کن
10. **`formatDate()` و `formatDateTime()`** — همه تاریخ‌ها شمسی نمایش داده شوند