feat(ui-kit): design tokens + StatCard + button variants (foundation)
Add accent (orange) + pastel stat-card tokens (light/dark), button accent/outline/lg/block variants, and a pastel StatCard primitive, aligning the admin design system with the clinic-pro-tauri reference. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
# بازطراحی پنل ادمین بر اساس UI Kit الهامگرفته از clinic-pro-tauri
|
||||
|
||||
## پروژه
|
||||
|
||||
`clinicpro` (admin frontend — `assets/admin/`)
|
||||
|
||||
## زمینه
|
||||
|
||||
ظاهر هدف، اپ دسکتاپ `clinic-pro-tauri` است (اسکرینشاتها توسط کاربر ارائه شده). میخواهیم کل پنل ادمین `https://clinic-pro.ddev.site/admin/*` — داشبورد و همهی صفحات، مودالها، دکمهها، فیلدها و همهی المانها — ظاهری یکدست و مشابه آن داشته باشند.
|
||||
|
||||
**اختلاف مهم استک (نباید نادیده گرفته شود):**
|
||||
- `clinic-pro-tauri`: React 18 + Vite + **MUI v6** + `styled-components` + `@mui/x-charts` + Tailwind (config خالی، ظاهر از MUI theme میآید).
|
||||
- `clinicpro` admin: React 19 + **Webpack Encore** + **Tailwind v4** + CSS variables دستساز (`assets/admin/styles.css`) + TanStack Query + heroicons. **MUI ندارد و نباید اضافه شود.**
|
||||
|
||||
بنابراین کامپوننتهای tauri قابل کپی ۱:۱ نیستند. کار = استخراج **design tokens + الگوهای بصری** از اسکرینشاتها/tauri و **بازسازی بهصورت native در استک فعلی clinicpro** (Tailwind v4 + CSS vars + کامپوننتهای React موجود). کتابخانهی CSS/UI جدید (MUI، styled-components، …) اضافه نشود.
|
||||
|
||||
## مشکل / هدف
|
||||
|
||||
۱. یک **UI Kit** (کتابخانهی کامپوننتهای پایه + توکنها) در `assets/admin/components/ui/` بساز/تکمیل کن که دقیقاً مطابق ظاهر tauri باشد.
|
||||
۲. سپس صفحات admin را روی این UI Kit بازطراحی کن تا همه یکدست شوند.
|
||||
۳. کل کار روی یک **برنچ جدا** انجام شود.
|
||||
|
||||
## وضعیت فعلی
|
||||
|
||||
`clinicpro` از قبل یک design system مبتنی بر CSS variable دارد (`assets/admin/styles.css`) — رنگ primary با hue ۲۵۶ (بنفش/indigo) که **به ظاهر tauri نزدیک است**:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--brand-h: 256; --brand-c: 0.15;
|
||||
--bg: #eef2f8; --surface: #ffffff; --surface-2: #f6f8fc;
|
||||
--border: #e4e9f1; --text: #0f1b2e; --text-2: #56657c; --text-3: #8a98ad;
|
||||
--primary: oklch(0.55 var(--brand-c) var(--brand-h));
|
||||
--success:#15a35a; --warning:#d98a09; --danger:#e0394a; --info:#2b86d8; --violet:#7c5cf0;
|
||||
--success-bg:#e6f6ed; --warning-bg:#fcf2df; --danger-bg:#fdebed; --info-bg:#e7f1fb;
|
||||
--shadow-sm; --shadow; --shadow-lg;
|
||||
--r-xs:7px; --r-sm:10px; --r:14px; --r-lg:18px; --r-xl:24px; --r-pill:999px;
|
||||
--sidebar-w:252px; --topbar-h:64px;
|
||||
}
|
||||
```
|
||||
|
||||
کامپوننتهای UI موجود: `assets/admin/components/ui/` شامل `DataTable`, `Modal`, `ConfirmDialog`, `PageHeader`, `StatusBadge`, `Pagination`, `SearchableSelect`, `PersianDateInput`, `PersianCalendar`, `MobileInput`, `PriceInput`, `NotificationMobileCard`, `FeatureGate`.
|
||||
|
||||
صفحات: `assets/admin/pages/*.tsx` (Dashboard، MyPatientsPage، ClinicServicesPage، AppointmentsPage، …) — اکثراً ترکیبی از کلاسهای CSS (`.card`, `.btn`, `.field`, `.badge`) و inline-style هستند؛ ناهماهنگی بصری دارند.
|
||||
|
||||
نمودارها: داشبورد tauri از نمودار خطی (درآمد) و میلهای (تعداد بیماران) استفاده میکند. در clinicpro کتابخانهی چارت فعلی را شناسایی کن (اگر نبود، یک گزینهی سبک سازگار با React 19 پیشنهاد بده — مثل `recharts`؛ فقط در صورت تأیید لازم).
|
||||
|
||||
## ویژگیهای بصری هدف (از اسکرینشاتهای tauri)
|
||||
|
||||
- **Layout:** سایدبار سمت **راست** (RTL)، لوگو `clinicPro +`، آیتم فعال با پسزمینهی primary-soft و متن primary؛ topbar با جستجوی full-width، آیکونهای نوتیفیکیشن/تنظیمات/تم تیره، پروفایل کاربر (آواتار + نام + نقش).
|
||||
- **Stat cards:** کارتهای آماری با پسزمینهی **پاستلی ملایم** (نارنجی/بنفش/سبز/صورتی)، آیکون رنگی گرد گوشه، عدد بزرگ + برچسب.
|
||||
- **Cards:** گوشهگرد (`--r`)، سایهی ملایم، بوردر `--border`.
|
||||
- **Buttons:** primary پر (بنفش)، secondary outline، دکمهی نارنجی برای CTA ثانویه (مثل «خرید اشتراک»، «یادداشت»)؛ گوشهگرد، حالتهای hover/disabled/loading.
|
||||
- **Fields:** ورودی با label بالا، گوشهگرد، آیکون داخل (در صورت لزوم)، حالت focus با ring؛ select با chevron؛ textarea؛ date-picker شمسی.
|
||||
- **Status pills:** سبز («ویزیت شده/پرداخت شده/انجام شد»)، نارنجی/زرد («در حال پیگیری/در انتظار/تسویه نشده»)، قرمز («لغو شده»)، آبی («نوبت جدید»). هم بهصورت badge و هم dropdown.
|
||||
- **Tabs:** نوار تب افقی با زیرخط primary برای تب فعال (صفحهی پرونده: سرویسها/اطلاعات پرونده/نوبتها/پرداختها/کیف پول/کال سنتر/پرونده پزشکی/ضمیمه/پیامها).
|
||||
- **Tables:** سربرگ خاکستری ملایم، ردیفهای راهراه، ستون عملیات با dropdown «عملیات».
|
||||
- **Patient cards (پروندهها):** کارت با آواتار نارنجی گرد، نام، شماره پرونده، موبایل، chips برچسب رنگی (`+۲`)، حالت grid/list toggle.
|
||||
- **Timeline نوبتها:** نمای زمانی با نقاط رنگی و کارتهای رنگبندیشده بر اساس وضعیت.
|
||||
|
||||
## وظایف
|
||||
|
||||
### ۱. استخراج و یکسانسازی توکنها
|
||||
|
||||
- اسکرینشاتهای tauri + (در صورت نیاز) `clinic-pro-tauri/src/theme/` و کامپوننتهای `clinic-pro-tauri/src/element/`, `src/components/shared|common|layout` را مرور کن و مقادیر دقیق رنگ primary/accent، شعاعها، سایهها، رنگهای پاستلی stat-cards را دربیاور.
|
||||
- `assets/admin/styles.css` (`:root` و `.dark`) را طوری تنظیم کن که primary/accent/پاستلها **دقیقاً** با tauri همخوان شوند (رنگ نارنجی accent را بهعنوان توکن جدید اضافه کن، مثلاً `--accent` / `--accent-bg`).
|
||||
- توکنهای تیره (dark mode) را هم هماهنگ کن (دکمهی تم تیره در topbar وجود دارد).
|
||||
|
||||
### ۲. ساخت/تکمیل UI Kit در `assets/admin/components/ui/`
|
||||
|
||||
برای هر primitive یک کامپوننت TSX با propهای تایپشده بساز یا کامپوننت موجود را به ظاهر tauri برسان. حداقل:
|
||||
|
||||
- `Button` (variants: `primary | secondary | outline | accent | ghost | danger`، sizes، `loading`, `disabled`, آیکون چپ/راست)
|
||||
- `Card` / `StatCard` (پاستلی + آیکون رنگی)
|
||||
- `Modal` (موجود — همتراز با ظاهر tauri)
|
||||
- `Field` wrapper + `Input` + `Textarea` + `Select` (label بالا، ring focus)
|
||||
- `Tabs` (زیرخط primary)
|
||||
- `StatusPill` / `StatusDropdown` (نگاشت وضعیت → رنگ، فارسی)
|
||||
- `Table` (سربرگ، راهراه، ستون عملیات) — یا ارتقای `DataTable` موجود
|
||||
- `Avatar`, `Chip/Tag` (برچسب رنگی + `+N`)
|
||||
- `SectionHeader`, `Breadcrumb` (صفحهی پرونده)
|
||||
|
||||
> همه با CSS variables موجود؛ کلاسهای مشترک را در `styles.css` تعریف کن، نه inline تکراری. RTL، فونت Vazirmatn.
|
||||
|
||||
اگر مفید بود، یک صفحهی نمایشِ کیت (`assets/admin/pages/UiKitPage.tsx` تحت روت dev مثل `/admin/ui-kit`) بساز که همهی primitiveها را رندر کند تا بصری چک شوند (اختیاری ولی توصیهشده).
|
||||
|
||||
### ۳. بازطراحی صفحات روی UI Kit
|
||||
|
||||
ترتیب پیشنهادی (هر صفحه یک commit جدا روی همان برنچ):
|
||||
|
||||
1. **Layout** (Sidebar راست + Topbar) — مبنای همه.
|
||||
2. **Dashboard** (`DashboardPage`) — stat cards پاستلی + دو چارت + جدول «لیست نوبتهای جدید».
|
||||
3. **پروندهها** (`MyPatientsPage` لیست کارتی + toggle grid/list).
|
||||
4. **جزئیات پرونده** — تبها (سرویسها/اطلاعات/نوبتها/پرداختها/…)، کارت سرویس، فرم اطلاعات، لیست پرداختهای آکاردئونی.
|
||||
5. **نوبتها** (`AppointmentsPage`) — stat cards + نمای جدولی/زمانی + تب پزشکان.
|
||||
6. **پرداختها، بیماران، خدمات، تنظیمات (پلنهای اشتراک)، پیامها، انبارداری** — یکییکی همتراز با کیت.
|
||||
|
||||
هر صفحه: inlineهای پراکنده با primitiveهای کیت جایگزین شوند؛ رفتار/داده (TanStack Query، APIها) تغییر نکند — فقط ظاهر.
|
||||
|
||||
### ۴. تست هر مرحله
|
||||
|
||||
```bash
|
||||
ddev exec npx tsc --noEmit --project tsconfig.json
|
||||
ddev exec yarn dev
|
||||
```
|
||||
بدون خطای TS و build سبز. بعد از هر صفحه بصری چک شود.
|
||||
|
||||
## نکات مهم
|
||||
|
||||
- **برنچ جدا:** کل کار روی برنچ `feat/admin-ui-kit` (از `main`). در پایان merge نشود مگر با تأیید کاربر.
|
||||
- **بدون تغییر رفتار/داده:** فقط لایهی presentation. APIها، query keys، منطق فرمها (React Hook Form + Zod) دستنخورده.
|
||||
- **MUI/styled-components اضافه نشود** — استک فعلی (Tailwind v4 + CSS vars + heroicons) حفظ شود. tauri صرفاً مرجع بصری است.
|
||||
- RTL، فونت Vazirmatn، تاریخ شمسی با `formatDate()`.
|
||||
- چارتها: اگر کتابخانهای در clinicpro نیست، قبل از افزودن `recharts` (یا مشابه) از کاربر تأیید بگیر؛ در غیر این صورت با SVG/CSS سبک بساز.
|
||||
- این تغییر frontend-only است؛ نیازی به migration یا تغییر `docs/api/` نیست.
|
||||
- خروجی باید **یکدستی کامل** باشد: همهی دکمهها/فیلدها/مودالها/بَجها در همهی صفحات از کیت واحد بیایند.
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
|
||||
type StatTone = 'amber' | 'violet' | 'green' | 'pink';
|
||||
|
||||
const TONE: Record<StatTone, { bg: string; fg: string }> = {
|
||||
amber: { bg: 'var(--stat-amber-bg)', fg: 'var(--stat-amber-fg)' },
|
||||
violet: { bg: 'var(--stat-violet-bg)', fg: 'var(--stat-violet-fg)' },
|
||||
green: { bg: 'var(--stat-green-bg)', fg: 'var(--stat-green-fg)' },
|
||||
pink: { bg: 'var(--stat-pink-bg)', fg: 'var(--stat-pink-fg)' },
|
||||
};
|
||||
|
||||
interface Props {
|
||||
tone: StatTone;
|
||||
label: string;
|
||||
value: React.ReactNode;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function StatCard({ tone, label, value, icon }: Props) {
|
||||
const c = TONE[tone];
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
background: c.bg,
|
||||
borderRadius: 'var(--r-lg)',
|
||||
padding: '18px 20px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 14,
|
||||
}}
|
||||
>
|
||||
{icon && (
|
||||
<div
|
||||
style={{
|
||||
width: 44, height: 44, borderRadius: 12, flexShrink: 0,
|
||||
background: c.fg, color: '#fff',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ flex: 1, minWidth: 0, textAlign: 'right' }}>
|
||||
<div style={{ fontSize: 18, fontWeight: 800, color: 'var(--text)' }}>{value}</div>
|
||||
<div style={{ fontSize: 12.5, color: 'var(--text-2)', marginTop: 3 }}>{label}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -43,6 +43,15 @@
|
||||
--info-bg: #e7f1fb;
|
||||
--violet: #7c5cf0;
|
||||
--violet-bg: #efeafe;
|
||||
/* accent (نارنجی tauri) برای CTAهای ثانویه و آواتارها */
|
||||
--accent: #f0682a;
|
||||
--accent-600: #db5a1f;
|
||||
--accent-bg: #fdeee4;
|
||||
/* رنگهای پاستلی stat-cards داشبورد (همخوان با tauri) */
|
||||
--stat-amber-bg: #fdf0e3; --stat-amber-fg: #e08a1e;
|
||||
--stat-violet-bg: #efeefb; --stat-violet-fg: #6d5cf0;
|
||||
--stat-green-bg: #e7f6ef; --stat-green-fg: #16a35a;
|
||||
--stat-pink-bg: #fdeee9; --stat-pink-fg: #f0682a;
|
||||
--ring: color-mix(in oklch, var(--primary) 32%, transparent);
|
||||
--shadow-sm: 0 1px 2px rgba(15,27,46,.06), 0 1px 3px rgba(15,27,46,.05);
|
||||
--shadow: 0 2px 6px rgba(15,27,46,.06), 0 8px 24px rgba(15,27,46,.06);
|
||||
@@ -84,6 +93,13 @@
|
||||
--danger-bg: color-mix(in oklch, var(--danger) 18%, var(--surface));
|
||||
--info-bg: color-mix(in oklch, var(--info) 18%, var(--surface));
|
||||
--violet-bg: color-mix(in oklch, var(--violet) 20%, var(--surface));
|
||||
--accent: #f0763d;
|
||||
--accent-600: #e0651f;
|
||||
--accent-bg: color-mix(in oklch, var(--accent) 18%, var(--surface));
|
||||
--stat-amber-bg: color-mix(in oklch, var(--warning) 16%, var(--surface)); --stat-amber-fg: var(--warning);
|
||||
--stat-violet-bg: color-mix(in oklch, var(--violet) 18%, var(--surface)); --stat-violet-fg: var(--violet);
|
||||
--stat-green-bg: color-mix(in oklch, var(--success) 16%, var(--surface)); --stat-green-fg: var(--success);
|
||||
--stat-pink-bg: color-mix(in oklch, var(--accent) 16%, var(--surface)); --stat-pink-fg: var(--accent);
|
||||
--shadow-sm: 0 1px 2px rgba(0,0,0,.4);
|
||||
--shadow: 0 2px 8px rgba(0,0,0,.4), 0 12px 30px rgba(0,0,0,.35);
|
||||
--shadow-lg: 0 18px 44px rgba(0,0,0,.55);
|
||||
@@ -467,7 +483,13 @@ body {
|
||||
.btn.soft:hover { background: var(--primary-soft2); }
|
||||
.btn.danger { background: var(--danger-bg); color: var(--danger); border-color: transparent; }
|
||||
.btn.danger:hover { filter: brightness(.97); }
|
||||
.btn.accent { background: var(--accent); color: #fff; box-shadow: 0 4px 14px color-mix(in oklch, var(--accent) 35%, transparent); }
|
||||
.btn.accent:hover { background: var(--accent-600); }
|
||||
.btn.outline { background: transparent; border-color: var(--primary); color: var(--primary); }
|
||||
.btn.outline:hover { background: var(--primary-soft); }
|
||||
.btn.sm { height: 36px; padding: 0 14px; font-size: 13px; }
|
||||
.btn.lg { height: 48px; padding: 0 24px; font-size: 15px; }
|
||||
.btn.block { width: 100%; }
|
||||
.btn:disabled { opacity: .5; cursor: not-allowed; }
|
||||
|
||||
/* ── Tables ──────────────────────────────────────────────────── */
|
||||
|
||||
Reference in New Issue
Block a user