# 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` تعریف شود. ```css /* 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 border `4px 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 ```jsx // وضعیت نوبت در انتظار پرداخت /* waiting_for_payment */ رزرو شده /* reserved */ ورود به مطب /* checked_in */ در صف انتظار /* waiting */ در حال ویزیت /* in_progress */ ویزیت شده /* visited / completed */ لغو شده /* cancelled_* */ لغو خودکار /* auto_cancel_unpaid */ غیبت /* no_show */ // وضعیت پرداخت در انتظار /* pending */ موفق /* received */ لغو شده /* canceled */ استرداد /* refund */ // وضعیت SMS Template پیش‌نویس /* draft */ در انتظار تأیید /* pending_approval */ تأیید شده /* approved */ رد شده /* 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: `` --- ## 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()` روی `` هم‌زمان کلاس `dark` (برای واریانت Tailwind) و `data-theme="dark"` (برای توکن‌های CSS) را ست می‌کند و مقدارش در `localStorage['clinicpro-ui']` می‌ماند. `applyBrand()` هم `--brand-h/--brand-c` و در صورت وجود `BrandFixed` مقادیر `--primary*` را inline می‌گذارد. ```css /* 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.ts` fail می‌دهد. - `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](https://themeforest.net/item/panelix-premium-react-admin-dashboard-template/63163276) - فونت: [Vazirmatn](https://rastikerdar.github.io/vazirmatn/) - آیکون: [Heroicons](https://heroicons.com/) - رنگ‌بندی: [Tailwind CSS Colors](https://tailwindcss.com/docs/customizing-colors)