Files
clinicpro/.claude/prompt/ui-ux-pages-redesign.md
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

18 KiB
Raw Permalink Blame History

بازطراحی 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: تعداد پرسنل کل، تعداد فعال، تعداد غیرفعال
  • داده از همان staff query محاسبه شود (نه 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 جذاب.

الف — جدول منشی ها:

  • جدول دستی را حذف کن و از DataTable component استفاده کن
  • ستون «نام»: آواتار کوچک (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) + background var(--primary-subtle)
  • افزودن بخش: دکمه با آیکون + متن «بخش جدید» (نه فقط آیکون)

ب — پنل آیتم‌ها (ستون 1fr):

  • بالای جدول یک toolbar: نام بخش به عنوان breadcrumb + تعداد آیتم‌ها + دکمه «سرویس جدید»
  • هر row آیتم:
    • badge «فعال» / «غیرفعال» برای item.active
    • قیمت با formatRial() و رنگ primary
    • نام پرسنل با avatar خیلی کوچک اگر وجود داشت
  • 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»

نکات مهم

  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() — همه تاریخ‌ها شمسی نمایش داده شوند