- 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.
18 KiB
بازطراحی UI/UX پنج صفحه کلینیکپرو
زمینه
پروژه ClinicPro یک پنل مدیریت کلینیک است با React 19 + TypeScript و یک سیستم CSS دیزاین اختصاصی. SubscriptionPage اخیراً بازطراحی شده و الگویی ایجاد کرده که بقیه صفحات باید با آن همراستا شوند. ۵ صفحه هنوز از ساختار قدیمی استفاده میکنند: جدولهای ساده، کارتهای بدون سلسلهمراتب بصری، و فرمهای بدون هویت. هدف این است که هر صفحه را به یک تجربه منسجم، مدرن و RTL-first تبدیل کنیم.
سیستم CSS موجود
متغیرها و کلاسهای قابل استفاده (در assets/admin/styles.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 ثبت مراجعه با سهستونه برای قیمت/بیمه/روش پرداخت.
// لیست ساده فعلی
<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
// فعلی — جدول + دو 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
// جدول دستی (نه 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
// دو پنل 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
// دو کارت بالا
<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: تعداد پرسنل کل، تعداد فعال، تعداد غیرفعال - داده از همان
staffquery محاسبه شود (نه API جدید):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 جذاب.
الف — جدول منشی ها:
- جدول دستی را حذف کن و از
DataTablecomponent استفاده کن - ستون «نام»: آواتار کوچک (initial از
user_name) + نام بولد - ستون «موبایل»: با
maskMobile()وdir="ltr"+ آیکون تلفن - ستون «وضعیت»:
<ActiveBadge> - ستون «تاریخ»:
formatDate() - ستون «عملیات»: دو دکمه
sm(ویرایش دسترسیها + غیرفعالسازی)
ب — Empty State:
- وقتی
secretaries.length === 0:<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)+ backgroundvar(--primary-subtle) - افزودن بخش: دکمه با آیکون + متن «بخش جدید» (نه فقط آیکون)
ب — پنل آیتمها (ستون 1fr):
- بالای جدول یک toolbar: نام بخش به عنوان breadcrumb + تعداد آیتمها + دکمه «سرویس جدید»
- هر row آیتم:
- badge «فعال» / «غیرفعال» برای
item.active - قیمت با
formatRial()و رنگ primary - نام پرسنل با avatar خیلی کوچک اگر وجود داشت
- badge «فعال» / «غیرفعال» برای
- Empty state وقتی
items.length === 0:<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 قرار بده:
<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 کن:
<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):
{ (["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»
نکات مهم
- فقط فرانتاند — هیچ endpoint، migration، یا تغییر backend لازم نیست
- CSS موجود را استفاده کن — هیچ کتابخانه جدید اضافه نکن. inline style برای تنظیمات جزئی قابل قبول است
- DataTable component موجود در
assets/admin/components/ui/DataTable.tsx— برای جداول از آن استفاده کن (ترجیحاً بر inline table) - RTL رعایت شود — padding/margin/icon placement همه RTL-first باشد
- هر صفحه مستقل پیادهسازی شود — نه refactor مشترک
- SubscriptionPage را به عنوان مرجع visual نگه دار — gradient، progress bar، و card layout آن مدل است
- بعد از هر صفحه
ddev exec yarn devاجرا کن تا TypeScript error نداشته باشد - SessionRow در MyPatientsPage: قبل از ویرایش، آن component را کامل بخوان (احتمالاً inline در همان فایل است)
maskMobile()از../lib/utils— برای نمایش شماره موبایل منشی ها استفاده کنformatDate()وformatDateTime()— همه تاریخها شمسی نمایش داده شوند