- Refactored role metadata styles in UserDetailPage for consistency with design tokens. - Replaced hardcoded avatar colors with a utility function to generate gradients based on user ID. - Improved InfoCard component styles for better hover effects and accessibility. - Removed deprecated color classes and adjusted background gradients for various components. - Updated theme token tests to reflect the removal of deferred files and ensure compliance with design standards. - Added new avatarColors utility file to manage avatar gradient definitions.
24 KiB
ClinicPro Admin — UI Design Specification
سبک بصری: Panelix Premium React Admin Dashboard
1. Design System پایه
رنگبندی (Color Palette)
منبع حقیقت
assets/admin/styles.cssاست. توکنها در:rootتعریف و در[data-theme="dark"]بازتعریف میشوند؛ نسخهٔ oklch/color-mix زیر@supportsمیآید و مرورگر قدیمی روی همان مقادیر sRGB میماند. هیچ توکنی نباید فقط داخل@supportsتعریف شود.
/* Brand — indigo (نه بنفشِ این سند در نسخههای قدیمی) */
--primary --primary-600 --primary-700 --primary-soft --primary-soft2 --on-primary
/* Accent — نارنجی همخانواده با clinic-pro-tauri */
--accent --accent-600 --accent-bg
/* سطوح و متن */
--bg --bg-2 --surface --surface-2 --surface-3 --border --border-2
--text --text-2 --text-3
/* وضعیت */
--success/-bg --warning/-bg --danger/-bg --info/-bg --violet/-bg
/* کارتهای آمار داشبورد */
--stat-{amber,violet,green,pink}-{bg,fg}
قاعدهٔ الزامی: در assets/admin رنگ ثابت ممنوع است — نه hex، نه کلاس پالت Tailwind
(bg-white, text-gray-800, border-slate-200, …). بهجایش:
| بهجای | بنویس |
|---|---|
style={{ color: '#525252' }} |
style={{ color: 'var(--text)' }} |
className="text-[#525252] dark:text-[#D7D8ED]" |
className="text-[var(--text)]" |
className="bg-white border border-gray-100" |
className="bg-[var(--surface)] border border-[var(--border)]" |
stroke="#616161" روی SVG |
style={{ stroke: 'var(--text-2)' }} یا stroke="currentColor" |
نگاشت hexهای قدیمی به توکن (از آدیت ۱۴۰۵/۰۵):
| hex | توکن |
|---|---|
#525252 #3b3b3b #2f2f2f #111827 #374151 · دارک: #d7d8ed |
--text |
#616161 #6b7280 #7e7e7e #858d9d · دارک: #a1a1a1 |
--text-2 |
#9ca3af #94a3b8 #9b9b9b |
--text-3 |
#5559ce |
--primary · #494cb3 → --primary-600 · #3b3f9f → --primary-700 |
#f17732 #f0682a #f97316 |
--accent |
#ffffff (سطح کارت) · دارک: #222433 |
--surface |
#efefef #ededed #e7e7e7 · دارک: #35343d #343645 |
--border |
#e0e0e0 #e1e1e1 #dbdbdb #d7d7d7 · دارک: #404040 |
--border-2 |
#ef4444 #d32f2f #e53935 |
--danger (پسزمینه: --danger-bg) |
#2e7d32 #3c9a4f #22c55e |
--success (پسزمینه: --success-bg) |
#f59e0b #fdd835 #ffc051 |
--warning (پسزمینه: --warning-bg) |
#f4f5fd #f7f8ff #eef0ff |
--primary-soft · #e8ebff #c7cef4 → --primary-soft2 |
استثناهای پذیرفتهشده (در تست گارد ثبت شدهاند): پالت انتخابی برچسبها
(TagsSettingsPage)، اسکریم مودال (bg-black/50، مثل .overlay)، و BrandFixed
هیوی نارنجی در stores/uiStore.ts.
تایپوگرافی
Font Family: "Vazirmatn", "Inter", sans-serif ← فارسی + لاتین
Direction: RTL
Heading 1: 28px / font-bold / gray-900
Heading 2: 22px / font-bold / gray-900
Heading 3: 18px / font-semibold / gray-800
Heading 4: 16px / font-semibold / gray-700
Body: 14px / font-normal / gray-600
Caption: 12px / font-normal / gray-500
Label: 12px / font-medium / gray-700 / uppercase + tracking-wide
Spacing & Border Radius
Spacing scale: 4px base (4, 8, 12, 16, 20, 24, 32, 40, 48, 64)
Border radius:
sm: 6px (badges, chips)
md: 10px (inputs, buttons)
lg: 16px (cards)
xl: 24px (modals)
full: 9999px (avatars, toggles)
Box shadow:
card: 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.06)
modal: 0 20px 60px rgba(0,0,0,.15)
dropdown: 0 4px 20px rgba(0,0,0,.10)
2. Layout Structure
┌─────────────────────────────────────────────────────────┐
│ TOPBAR (64px) │
├────────────┬────────────────────────────────────────────┤
│ │ │
│ SIDEBAR │ MAIN CONTENT │
│ (260px) │ │
│ │ ┌──────────────────────────────────────┐ │
│ collapsed │ │ Page Header (title + breadcrumb) │ │
│ → 72px │ ├──────────────────────────────────────┤ │
│ │ │ │ │
│ │ │ Content Area (padding 24px) │ │
│ │ │ │ │
│ │ └──────────────────────────────────────┘ │
└────────────┴────────────────────────────────────────────┘
3. Sidebar
حالت باز (260px)
┌──────────────────────────────┐
│ ◉ ClinicPro [← collapse] │ ← logo + toggle button
├──────────────────────────────┤
│ 🔍 جستجوی سریع... │ ← search input
├──────────────────────────────┤
│ GENERAL │ ← section label (gray-500, 11px, uppercase)
│ ◉ داشبورد │ ← active item (purple bg + purple text + bold)
│ ○ کاربران │
│ ○ پزشکان │
│ ○ کلینیکها │
├──────────────────────────────┤
│ MANAGEMENT │
│ ○ نوبتها [3] │ ← badge count
│ ○ پرداختها │
│ ○ تسویهحساب [5] │
├──────────────────────────────┤
│ CONTENT │
│ ○ نظرات [12] │
│ ○ امتیازها │
│ ○ بلاگ │
│ ○ پیامک │
├──────────────────────────────┤
│ SYSTEM │
│ ○ دستهبندیها │
│ ○ نمایندگان │
│ ○ منشیها │
├──────────────────────────────┤
│ ┌────────────────────────┐ │
│ │ 👤 Admin │ ← admin profile card at bottom
│ │ admin@clinicpro.ir │
│ │ [تنظیمات] [خروج] │
│ └────────────────────────┘ │
└──────────────────────────────┘
حالت جمعشده (72px) — Flyout on hover
┌──────┐
│ ◉ │ ← logo icon
├──────┤
│ 🔍 │ ← hover → flyout search
├──────┤
│ ⊞ │ ← icon only, hover → flyout label + submenu
│ 👥 │
│ 🩺 │
│ 🏥 │
│ 📅 │
│ 💳 │
│ 🏦 │ ← badge dot (نه عدد)
│ 💬 │ ← badge dot
│ ⭐ │
│ 📝 │
│ 📱 │
│ 🗂 │
│ 🤝 │
│ 🔐 │
└──────┘
رفتار sidebar:
transition: width 300ms cubic-bezier(0.4, 0, 0.2, 1)- Overlay در موبایل (< 768px)
- Active item:
bg-primary-500/15+ right border4px solid #8b5cf6 - Hover item:
bg-gray-700/40
4. Topbar
┌─────────────────────────────────────────────────────────────┐
│ ≡ [Breadcrumb: داشبورد / پزشکان] 🔔 5 👤 Admin ▾ │
└─────────────────────────────────────────────────────────────┘
- ارتفاع: 64px
- پسزمینه: سفید +
box-shadow: 0 1px 0 #e5e7eb - Notification Bell: dropdown با لیست آخرین رویدادها
- User Menu: تصویر آواتار + نام + dropdown (پروفایل / تنظیمات / خروج)
5. Cards
Stat Card (آمار خلاصه)
┌──────────────────────────────────┐
│ ┌────┐ │
│ │ 🩺 │ کل پزشکان │ ← icon در مربع رنگی (purple-100)
│ └────┘ 1,284 │ ← عدد بزرگ (28px bold)
│ ↑ 12% نسبت به ماه قبل │ ← trend badge (سبز/قرمز)
└──────────────────────────────────┘
bg: white, radius: 16px, shadow: card, padding: 24px
Data Card (محتوا / جداول)
┌────────────────────────────────────────────┐
│ عنوان کارت [اقدام ▾] │ ← header
├────────────────────────────────────────────┤
│ │
│ محتوا (جدول / نمودار / فرم) │
│ │
└────────────────────────────────────────────┘
6. DataTable (جدول داده)
┌─────────────────────────────────────────────────────────────────┐
│ [🔍 جستجو...] [فیلتر ▾] [ستونها ▾] [صادرکردن ↓] │
├──────────┬────────────┬──────────┬────────┬─────────────────────┤
│ ☐ نام │ موبایل │ نقش │ وضعیت │ اقدامات │
├──────────┼────────────┼──────────┼────────┼─────────────────────┤
│ ☐ علی م. │ 0912*** │ پزشک │ ● فعال │ 👁 ✏️ 🗑 │
│ ☐ سارا ح │ 0935*** │ کلینیک │ ○ غیر │ 👁 ✏️ 🗑 │
├──────────┴────────────┴──────────┴────────┴─────────────────────┤
│ نمایش 1-10 از 284 [← قبلی] 1 2 3 ... 29 [بعدی →] │
└─────────────────────────────────────────────────────────────────┘
ویژگیها:
- Sortable columns (کلیک روی header → ↑↓)
- Row hover:
bg-gray-50 - Sticky header هنگام scroll
- Loading state: skeleton rows (shimmer animation)
- Empty state: آیکون + پیام توصیفی + دکمه اقدام
- Bulk actions: با انتخاب checkbox ها → نوار بالا ظاهر میشود
7. Status Badges
// وضعیت نوبت
<Badge variant="yellow">در انتظار پرداخت</Badge> /* waiting_for_payment */
<Badge variant="blue">رزرو شده</Badge> /* reserved */
<Badge variant="purple">ورود به مطب</Badge> /* checked_in */
<Badge variant="orange">در صف انتظار</Badge> /* waiting */
<Badge variant="indigo">در حال ویزیت</Badge> /* in_progress */
<Badge variant="green">ویزیت شده</Badge> /* visited / completed */
<Badge variant="red">لغو شده</Badge> /* cancelled_* */
<Badge variant="gray">لغو خودکار</Badge> /* auto_cancel_unpaid */
<Badge variant="rose">غیبت</Badge> /* no_show */
// وضعیت پرداخت
<Badge variant="yellow">در انتظار</Badge> /* pending */
<Badge variant="green">موفق</Badge> /* received */
<Badge variant="red">لغو شده</Badge> /* canceled */
<Badge variant="blue">استرداد</Badge> /* refund */
// وضعیت SMS Template
<Badge variant="gray">پیشنویس</Badge> /* draft */
<Badge variant="yellow">در انتظار تأیید</Badge> /* pending_approval */
<Badge variant="green">تأیید شده</Badge> /* approved */
<Badge variant="red">رد شده</Badge> /* rejected */
ساختار badge:
padding: 2px 10px
border-radius: 9999px
font-size: 12px / font-medium
با dot رنگی (●) در ابتدا
8. فرمها (Forms)
Input
┌─────────────────────────────────┐
│ برچسب │
│ ┌─────────────────────────────┐ │
│ │ 🔍 placeholder... │ │ ← icon اختیاری
│ └─────────────────────────────┘ │
│ پیام خطا (قرمز، 12px) │
└─────────────────────────────────┘
- Border:
1px solid #d1d5db→ focus:2px solid #8b5cf6 - Height input: 44px
- Border-radius: 10px
- Error state: border قرمز + shake animation
- Disabled: opacity 50%
Select / Dropdown
- کتابخانه:
react-selectبا استایل custom (RTL support) - Multi-select برای تخصصها، بیمهها، تگها
Permission Matrix (منشی)
مشاهده ایجاد ویرایش حذف
نوبتها ☑ ☑ ☐ ☐
آدرسها ☑ ☐ ☐ ☐
اطلاعات کلینیک ☑ — ☐ —
بیمهها ☑ ☐ ☐ ☐
9. نمودارها (Charts)
داشبورد اصلی
Row 1: [Stat Card x4] ← کاربران / پزشکان / نوبت امروز / درآمد امروز
Row 2: [Area Chart — درآمد ماهانه (60%)] | [Donut Chart — نوبتها بر اساس وضعیت (40%)]
Row 3: [Bar Chart — آمار ماهانه نمایندگان (60%)] | [لیست آخرین نوبتها (40%)]
کتابخانه: Recharts یا ApexCharts
- رنگ اصلی نمودارها: shades of purple + secondary colors
- Tooltip: سفید با سایه، اعداد فارسی
- X-axis: نام ماههای شمسی (فروردین ... اسفند)
- Responsive:
<ResponsiveContainer width="100%" height={300}>
10. Modal / Dialog
┌──────────────────────────────────────────────────┐
│ │ ← backdrop: rgba(0,0,0,.4)
│ ┌────────────────────────────────────────────┐ │
│ │ عنوان Modal ✕ │ │ ← header: border-bottom
│ ├────────────────────────────────────────────┤ │
│ │ │ │
│ │ محتوا │ │
│ │ │ │
│ ├────────────────────────────────────────────┤ │
│ │ [لغو] [تأیید / ذخیره] │ │ ← footer: border-top
│ └────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────┘
- انیمیشن ورود:
scale(0.95) → scale(1)+opacity 0 → 1(200ms) - Confirm Dialog برای حذف: دکمه «حذف» قرمز + آیکون هشدار
- Width: sm=400px / md=600px / lg=800px / xl=1000px
11. Toast Notifications
موقعیت: top-left (RTL)
┌─────────────────────────────────┐
│ ✓ پزشک با موفقیت ویرایش شد. │ ← success (سبز)
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ ✕ خطا در ذخیره اطلاعات. │ ← error (قرمز)
└─────────────────────────────────┘
- Auto dismiss: 4 ثانیه
- Stack: حداکثر 3 نوتیفیکیشن همزمان
- کتابخانه:
react-hot-toastیاsonner
12. Empty States & Loading
Loading (Skeleton)
┌──────────────────────────────┐
│ ▓▓▓▓▓▓▓▓▓ ░░░░░░░░░░░ │ ← shimmer animation
│ ░░░░░░░░░░░░░░░░░░░░░░░░ │
│ ░░░░░░░░░░░ ▓▓▓▓▓▓▓▓▓▓ │
└──────────────────────────────┘
animate-pulseبا رنگgray-200
Empty State
┌──────────────────────────────────┐
│ │
│ [SVG Illustration] │
│ │
│ هیچ موردی یافت نشد │
│ توضیح کوتاه... │
│ │
│ [افزودن اولین مورد] │
│ │
└──────────────────────────────────┘
13. Page Header (هر صفحه)
┌─────────────────────────────────────────────────────────┐
│ پزشکان [+ افزودن پزشک] │
│ داشبورد / پزشکان │ ← breadcrumb
└─────────────────────────────────────────────────────────┘
14. تکنولوژی Stack
| لایه | کتابخانه |
|---|---|
| Framework | React 19 + TypeScript |
| Routing | React Router v7 |
| Styling | Tailwind CSS v4 |
| State (server) | TanStack Query v5 |
| State (client) | Zustand |
| Forms | React Hook Form + Zod |
| Charts | Recharts |
| Table | TanStack Table v8 |
| Icons | Heroicons v2 |
| Date (Jalali) | @date-io/date-fns-jalali + react-datepicker |
| Numbers | react-number-format |
| Toast | sonner |
| Select | react-select |
| Rich Text | @tiptap/react |
| File Upload | react-dropzone |
| RTL | dir="rtl" + Tailwind rtl: variants |
| Font | Vazirmatn (از Google Fonts یا CDN) |
15. Responsive Breakpoints
| نام | عرض | رفتار |
|---|---|---|
| mobile | < 768px | Sidebar → Drawer overlay |
| tablet | 768px–1024px | Sidebar collapsed (72px) |
| desktop | > 1024px | Sidebar باز (260px) |
16. Dark Mode (پیادهشده)
دارکمود کامل شیپ شده است. stores/uiStore.ts → applyTheme() روی <html> همزمان
کلاس dark (برای واریانت Tailwind) و data-theme="dark" (برای توکنهای CSS) را ست
میکند و مقدارش در localStorage['clinicpro-ui'] میماند. applyBrand() هم
--brand-h/--brand-c و در صورت وجود BrandFixed مقادیر --primary* را inline میگذارد.
/* styles.css */
@custom-variant dark (&:where(.dark, .dark *));
:root { --surface: #ffffff; --text: #3b3b3b; /* … */ }
[data-theme="dark"] { --surface: #222433; --text: #D7D8ED; /* … */ }
قواعد:
- رنگ از توکن بیاید؛ آنوقت دارکمود خودکار درست است و
dark:لازم نیست. dark:فقط وقتی که واقعاً توکنِ متفاوتی در دو تم لازم است — نه برای جبران hex ثابت.- inline style همیشه بر کلاس غالب است.
style={{ color: '#525252' }}کنارclassName="dark:text-[#D7D8ED]"یعنی کلاس هیچوقت اعمال نمیشود؛ این الگو در تست گاردassets/admin/test/theme-tokens.test.tsfail میدهد. var(--token)داخلstyleکار میکند؛ برای SVG یاstroke="currentColor"بگذار و رنگ را از والد بگیر، یاstyle={{ stroke: 'var(--…)' }}.- الفای هگزی (
${color}30) روی توکن بیمعنی است —color-mix(in srgb, var(--x) 19%, transparent).
کل assets/admin مهاجرت کرده است؛ تست گارد DEFERRED ندارد و فقط سه استثنای عمدی
دارد: پالت برچسبها، BrandFixed در uiStore، و گرادیان تزئینی آواتار
(lib/avatarColors.ts). اسکریم bg-black/NN هم مجاز است.
نکتهٔ تاریخی: کلاسهایی مثل
text-primary-600/bg-primary-100هیچوقت رنگی تولید نمیکردند — بلوک@themeفقط--font-sansرا تعریف میکند و مقیاسprimary-*در Tailwind وجود ندارد. ۴۳ نمونه از این کلاسهای مرده در آدیت به توکن تبدیل شدند.
17. نمونه رنگبندی صفحه داشبورد
[صفحه] bg: #f1f5f9
│
├── Sidebar (bg: #0f172a, text: gray-400, active: purple-500)
│
└── Main
├── Topbar (bg: white, border-bottom: gray-200)
│
└── Content (padding: 24px)
├── [Stat Card] bg:white, icon-box: purple-100
├── [Stat Card] bg:white, icon-box: green-100
├── [Stat Card] bg:white, icon-box: blue-100
└── [Stat Card] bg:white, icon-box: orange-100
منابع
- طراحی مرجع: Panelix Premium React Admin Dashboard
- فونت: Vazirmatn
- آیکون: Heroicons
- رنگبندی: Tailwind CSS Colors