Implement comprehensive dark/light mode overhaul for admin panel
- Refactor color palette in `ui-design-spec.md` to utilize CSS variables exclusively, eliminating fixed hex values and Tailwind utility classes. - Complete dark mode implementation in `uiStore.ts`, ensuring proper theme application via `applyTheme()` and `applyBrand()`. - Create `admin-theme-dark-light-audit.md` to document the transition process, outlining issues with inline styles and fixed colors. - Introduce `theme-tokens.test.ts` to enforce rules against fixed hex colors and ensure compliance with the design system. - Update various components and styles to replace inline styles and fixed colors with CSS variables, ensuring consistent theming across light and dark modes. - Ensure all changes maintain visual integrity in both light and dark modes, with a focus on accessibility and contrast standards.
This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
# اصلاح کامل لایتمود / دارکمود در پنل ادمین (SPA)
|
||||
|
||||
## پروژه
|
||||
|
||||
`clinicpro` — فقط فرانتاند پنل ادمین (`assets/admin/`). بکاند و API دست نمیخورد.
|
||||
|
||||
## زمینه
|
||||
|
||||
پنل ادمین یک design-system کامل مبتنی بر CSS variable دارد که در `assets/admin/styles.css` تعریف شده:
|
||||
|
||||
- `:root` مقادیر لایت (`--bg`, `--surface`, `--text`, `--text-2`, `--border`, `--primary`, `--danger`, …)
|
||||
- `[data-theme="dark"]` همان توکنها را برای دارک بازتعریف میکند
|
||||
- `assets/admin/stores/uiStore.ts` → `applyTheme()` روی `document.documentElement` هم کلاس `dark` و هم `data-theme` را ست میکند، پس **هم `dark:` واریانت Tailwind و هم `var(--token)` هر دو فعالاند**
|
||||
|
||||
مشکل این است که بخش بزرگی از صفحات این سیستم را دور میزنند و رنگ ثابت مینویسند. آمار واقعی امروز (اندازهگیریشده روی `assets/admin`، بدون فایلهای تست):
|
||||
|
||||
| الگو | تعداد |
|
||||
|---|---|
|
||||
| `style={{ color/background: ... }}` | ۱۰۳۷ نقطه در ~۶۰ فایل |
|
||||
| hex ثابت داخل `.tsx` | ۷۵۲ |
|
||||
| utility رنگ ثابت Tailwind (`bg-white`, `text-slate-400`, `border-gray-100`, …) | ۸۸۵ در ۲۰ فایل |
|
||||
| از اینها با جفت `dark:` | ۳۲۳ |
|
||||
| کلاسهای Tailwind با توکن (`text-[var(--text-3)]`) | فقط ۱۵ مورد |
|
||||
|
||||
## مشکل / هدف
|
||||
|
||||
در دارکمود بخشهایی از پنل روشن/ناخوانا میمانند. سه دستهٔ خرابی، به ترتیب اهمیت:
|
||||
|
||||
### دستهٔ A — `style` inline با رنگ ثابت (خرابی قطعی)
|
||||
|
||||
inline style قابل override با `dark:` نیست. در ~۹۰ نقطه دقیقاً همین اشتباه رخ داده: کنار inline style یک کلاس `dark:` نوشته شده که **هیچوقت اعمال نمیشود** چون specificity کلاس همیشه کمتر از style attribute است.
|
||||
|
||||
فایلهای آلوده به این الگوی مرده:
|
||||
|
||||
| فایل | تعداد نقطهٔ `dark:` مرده |
|
||||
|---|---|
|
||||
| `components/session/PaymentStep.tsx` | ۲۷ |
|
||||
| `components/session/DetailsStep.tsx` | ۲۶ |
|
||||
| `components/session/CreateStep.tsx` | ۱۴ |
|
||||
| `components/SessionPaymentAccordion.tsx` | ۷ |
|
||||
| `components/PatientCaseBanner.tsx` | ۷ |
|
||||
| `components/SessionServiceCard.tsx` | ۵ |
|
||||
| `pages/SessionPaymentPage.tsx`, `pages/NewSessionPage.tsx`, `pages/EditSessionPage.tsx`, `components/AppointmentTurnCard.tsx` | ۲ هرکدام |
|
||||
|
||||
پرمصرفترین فایلها از نظر کل inline style رنگی: `pages/MyPatientsPage.tsx` (۶۷)، `pages/PatientDetailPage.tsx` (۶۲)، `pages/SmsWalletPage.tsx` (۴۶)، `components/session/PaymentStep.tsx` (۴۳)، `pages/SubscriptionPage.tsx` (۳۱)، `pages/DoctorDetailPage.tsx` (۳۰)، `pages/ClinicServicesPage.tsx` (۲۹)، `components/session/DetailsStep.tsx` (۲۸)، `components/session/CreateStep.tsx` (۲۷)، `pages/PatientsListPage.tsx` (۲۶).
|
||||
|
||||
### دستهٔ B — utility روشن بدون جفت `dark:`
|
||||
|
||||
| فایل | utility رنگی | تعداد `dark:` |
|
||||
|---|---|---|
|
||||
| `pages/RepresentationDetailPage.tsx` | ۳۳ | ۱ |
|
||||
| `pages/AppointmentDetailPage.tsx` | ۲۷ | ۲ |
|
||||
| `pages/PaymentDetailPage.tsx` | ۱۲ | ۲ |
|
||||
| `components/ImageCropModal.tsx` | ۲۳ | ۹ (ناقص) |
|
||||
|
||||
### دستهٔ C — CSS ثابت در `styles.css` و آیکونهای SVG
|
||||
|
||||
- `.inv-table thead tr { background: #e1e1e1 }` و `.inv-table th { color: #616161 }` و `.inv-table td { border-bottom: 1px solid #DBDBDB }` — هیچ override `.dark` ندارند (فقط `td` border و hover دارند)
|
||||
- `.inv-tabs`, `.inv-tab`, `.inv-badge.*` با hex ثابت + override دستی `.dark` — بهجای توکن
|
||||
- آیکونهای SVG با `stroke="#616161"` / `stroke="#3B3B3B"` ثابت (مثلاً `pages/MySecretariesPage.tsx` خط ۲۶–۳۵، `components/icons/`) در دارکمود روی پسزمینهٔ تیره محو میشوند
|
||||
|
||||
**هدف:** هر رنگ سطحی/متنی/حاشیهای در پنل ادمین از توکنهای `styles.css` بیاید، طوری که toggle دارکمود بدون هیچ نقطهٔ روشن/ناخوانا کار کند — و رگرسیون بعدی با تست گارد گرفته شود.
|
||||
|
||||
## معیار پذیرش
|
||||
|
||||
- ✅ **موفق:** با `darkMode: true` (کلید `clinicpro-ui` در localStorage یا دکمهٔ toggle در topbar)، در هر صفحهٔ فهرست «فایلهای مرتبط» هیچ کارت/متن/حاشیه/badge با رنگ لایت باقی نمیماند؛ کنتراست متن اصلی روی `--surface` حداقل ۴.۵:۱ و متن ثانویه حداقل ۳:۱.
|
||||
- ✅ **موفق (لایت):** بعد از تغییرات، همان صفحات در لایتمود **دقیقاً** مثل قبل رندر میشوند — این ریفکتور نباید ظاهر لایت را عوض کند (توکنها همان hexهای فعلیاند).
|
||||
- ❌ **خطا / رگرسیون:** یک تست گارد (`assets/admin/test/theme-tokens.test.ts`) سورس را اسکن میکند و اگر در فایلهای پاکشده دوباره hex ثابت رنگی یا utility رنگی بدون جفت `dark:` اضافه شود، fail میشود.
|
||||
- ⚠️ **مرزی ۱:** تغییر brand hue (`setBrandHue`، مثلاً هیوی «نارنجی» که `fixed` دارد) در **هر دو** مود باید رنگ اصلی را عوض کند؛ هیچ جایی نباید `#5559CE` ثابت بماند.
|
||||
- ⚠️ **مرزی ۲:** `density: compact` و مرورگر قدیمی بدون پشتیبانی `color-mix/oklch` (بلوک `@supports` در `styles.css`) — رنگها باید روی fallback sRGB هم درست باشند، نه اینکه به transparent بیفتند.
|
||||
- ⚠️ **مرزی ۳:** جدول انبار (`.inv-table`) در دارکمود — thead، badgeها (`in-stock`/`low-stock`/`out-of-stock`) و tabها خوانا باشند.
|
||||
|
||||
## فایلهای مرتبط
|
||||
|
||||
| فایل | نقش |
|
||||
|---|---|
|
||||
| `assets/admin/styles.css` | تعریف توکنها (`:root`, `[data-theme="dark"]`, `@supports`) + کلاسهای `.inv-*` که هنوز hex ثابت دارند |
|
||||
| `assets/admin/stores/uiStore.ts` | `applyTheme()` / `applyBrand()` — منبع کلاس `dark` و `data-theme` و `--brand-h/--brand-c` |
|
||||
| `assets/admin/components/session/PaymentStep.tsx` | بیشترین inline style رنگی + `dark:`های مرده |
|
||||
| `assets/admin/components/session/DetailsStep.tsx` | همان الگو |
|
||||
| `assets/admin/components/session/CreateStep.tsx` | همان الگو |
|
||||
| `assets/admin/components/SessionPaymentAccordion.tsx`, `SessionServiceCard.tsx`, `PatientCaseBanner.tsx`, `AppointmentTurnCard.tsx` | `dark:`های مرده کنار inline style |
|
||||
| `assets/admin/pages/MyPatientsPage.tsx`, `PatientDetailPage.tsx`, `SmsWalletPage.tsx`, `SubscriptionPage.tsx`, `ClinicServicesPage.tsx`, `PatientsListPage.tsx`, `MySecretariesPage.tsx` | حجم بالای inline style رنگی |
|
||||
| `assets/admin/pages/RepresentationDetailPage.tsx`, `AppointmentDetailPage.tsx`, `PaymentDetailPage.tsx` | utility روشن بدون `dark:` |
|
||||
| `assets/admin/components/ImageCropModal.tsx` | جفت `dark:` ناقص |
|
||||
| `assets/admin/components/icons/` | SVGهای با stroke/fill ثابت |
|
||||
| `docs/admin-ui/ui-design-spec.md` | سند UI — بخش ۱۶ «Dark Mode (اختیاری — فاز دوم)» کهنه است و با پیادهسازی فعلی نمیخواند |
|
||||
|
||||
## وضعیت فعلی
|
||||
|
||||
### الگوی مردهٔ `dark:` کنار inline style — `components/session/PaymentStep.tsx`
|
||||
|
||||
```tsx
|
||||
// خط ۳۳–۳۵ — استایلهای مشترک، همه hex ثابت
|
||||
const fieldLabel: React.CSSProperties = { fontSize: 14, color: '#6B7280', marginBottom: 8, display: 'block' };
|
||||
const primaryBtn: React.CSSProperties = { background: '#5559CE', color: '#fff', border: 'none', borderRadius: 4, height: 46, fontSize: 14, fontWeight: 500, cursor: 'pointer' };
|
||||
const ghostBtn: React.CSSProperties = { background: 'transparent', color: '#5559CE', border: '1px solid #5559CE', borderRadius: 4, height: 46, fontSize: 14, fontWeight: 500, cursor: 'pointer' };
|
||||
|
||||
// خط ۱۵۰ — کلاس dark: هیچ اثری ندارد چون inline style غالب است
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 700, color: '#525252' }}>{formatRial(finalPrice)}</span>
|
||||
|
||||
// خط ۱۵۵ — همان اشتباه روی border/background
|
||||
<div className="dark:border-[#35343D]" style={{ border: '1px solid #E8EBFF', background: '#F7F8FF', borderRadius: 8, padding: 12, marginBottom: 16 }}>
|
||||
```
|
||||
|
||||
### utility روشن بدون جفت dark — `pages/AppointmentDetailPage.tsx`
|
||||
|
||||
```tsx
|
||||
// خط ۱۴۱–۱۴۲
|
||||
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm p-6">
|
||||
<h3 className="font-semibold text-gray-800 mb-4">اطلاعات بیمار</h3>
|
||||
```
|
||||
|
||||
### utility روشن بدون جفت dark — `pages/RepresentationDetailPage.tsx`
|
||||
|
||||
```tsx
|
||||
// خط ۲۵۱
|
||||
className="flex items-center gap-1.5 px-3 py-2 text-sm rounded-[10px] border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
// خط ۳۰۹
|
||||
<p className="text-sm text-gray-400 text-center py-6">اطلاعات بانکی ثبت نشده</p>
|
||||
```
|
||||
|
||||
### CSS ثابت — `assets/admin/styles.css` خط ۹۵۸–۹۷۰
|
||||
|
||||
```css
|
||||
.inv-table { width: 100%; border-collapse: collapse; }
|
||||
.inv-table thead tr { background: #e1e1e1; } /* بدون override دارک */
|
||||
.inv-table th {
|
||||
color: #616161; font-size: 14px; font-weight: 400; /* بدون override دارک */
|
||||
padding: 10px 18px; text-align: start; white-space: nowrap;
|
||||
}
|
||||
.inv-table td {
|
||||
padding: 12px 18px; text-align: start; white-space: nowrap;
|
||||
font-size: 16px; font-weight: 500; color: var(--text-2);
|
||||
border-bottom: 1px solid #DBDBDB;
|
||||
}
|
||||
.dark .inv-table td { border-color: var(--border); }
|
||||
.inv-table tbody tr:hover { background: #f4f5fd; }
|
||||
.dark .inv-table tbody tr:hover { background: var(--surface-2); }
|
||||
```
|
||||
|
||||
### الگوی درستِ موجود در پروژه (مرجع سبک) — `components/FreeVisitPrice.tsx`
|
||||
|
||||
```tsx
|
||||
<p style={{ fontSize: 12, color: 'var(--text-3)', margin: '0 0 12px', lineHeight: 1.7 }}>
|
||||
<span style={{ color: 'var(--danger)' }}> *</span>
|
||||
```
|
||||
|
||||
این همان الگویی است که باید همهجا اعمال شود: inline style میماند، فقط مقدار رنگ به توکن تبدیل میشود.
|
||||
|
||||
## وظایف
|
||||
|
||||
### ۱. تثبیت جدول نگاشت رنگ → توکن
|
||||
|
||||
قبل از هر تغییر، این جدول را مبنا بگیر. hexها از شمارش واقعی فایلهای `.tsx` استخراج شدهاند (ستون «تعداد» = تکرار در سورس):
|
||||
|
||||
| hex فعلی (لایت) | تعداد | hex فعلی (دارک، در `dark:`ها) | توکن مقصد |
|
||||
|---|---|---|---|
|
||||
| `#525252`, `#3b3b3b`, `#2f2f2f`, `#111827` | ۴۵+۱۳+۱۶+۱۱ | `#d7d8ed` (۷۶) | `var(--text)` |
|
||||
| `#616161`, `#7e7e7e`, `#6b7280` | ۵۱+۱۹+۳۱ | `#a1a1a1` (۵۰) | `var(--text-2)` |
|
||||
| `#9ca3af` | ۱۰ | — | `var(--text-3)` |
|
||||
| `#5559ce` | ۷۵ | — | `var(--primary)` |
|
||||
| `#f17732`, `#f0753b` | ۳۴+۴ | — | `var(--accent)` |
|
||||
| `#ffffff` (سطح کارت) | ۴ | `#222433` (۲۵) | `var(--surface)` |
|
||||
| `#fafafa`, `#f1f1f1` (پسزمینهٔ صفحه/فیلد) | ۶+۴ | — | `var(--bg)` / `var(--surface-2)` |
|
||||
| `#efefef`, `#ededed`, `#e7e7e7` | ۱۸+۴+۴ | `#35343d` (۲۲), `#343645` (۱۱) | `var(--border)` |
|
||||
| `#e0e0e0`, `#e1e1e1`, `#dbdbdb`, `#d7d7d7` | ۱۴+۸+… | `#404040` (۱۴) | `var(--border-2)` |
|
||||
| `#ef4444`, `#d32f2f` | ۱۰+۹ | — | `var(--danger)` |
|
||||
| `#2e7d32`, `#3c9a4f` | ۸+۷ | — | `var(--success)` |
|
||||
| `#f59e0b` | ۵ | — | `var(--warning)` |
|
||||
| پسزمینهٔ نرم بنفش `#f7f8ff`, `#f4f5fd`, `#e8ebff` | — | — | `var(--primary-soft)` / `var(--primary-soft2)` |
|
||||
|
||||
**نکته:** بعضی نگاشتها دقیقاً یکسان نیستند (`#525252` در برابر `--text: #3b3b3b`). این اختلاف عمدی پذیرفته میشود چون هدف یکدستسازی است؛ اما اگر جایی اختلاف بصری محسوس شد (مثلاً متن اصلی کارت که واضحاً تیرهتر/روشنتر میشود)، **همانجا را در گزارش پایانی لیست کن** بهجای اینکه توکن جدید بسازی.
|
||||
|
||||
**نحوه تست:** جدول تصمیم است، نه کد. بعد از تعریف، در `docs/admin-ui/ui-design-spec.md` ثبتش کن (وظیفهٔ ۷).
|
||||
|
||||
### ۲. پاکسازی `dark:`های مرده (دستهٔ A، اولویت اول)
|
||||
|
||||
در ۱۰ فایلی که در بخش «مشکل» لیست شد: هر جا `className` شامل `dark:` کنار `style={{ color|background|border|borderColor: '#…' }}` است:
|
||||
|
||||
1. کلاس `dark:` مرده را **حذف** کن
|
||||
2. مقدار hex داخل `style` را طبق جدول وظیفهٔ ۱ به `var(--token)` تبدیل کن
|
||||
|
||||
```tsx
|
||||
// قبل
|
||||
<span className="dark:text-[#D7D8ED]" style={{ fontSize: 14, fontWeight: 700, color: '#525252' }}>
|
||||
|
||||
// بعد
|
||||
<span style={{ fontSize: 14, fontWeight: 700, color: 'var(--text)' }}>
|
||||
```
|
||||
|
||||
```tsx
|
||||
// قبل
|
||||
<div className="dark:border-[#35343D]" style={{ border: '1px solid #E8EBFF', background: '#F7F8FF', ... }}>
|
||||
|
||||
// بعد
|
||||
<div style={{ border: '1px solid var(--border)', background: 'var(--primary-soft)', ... }}>
|
||||
```
|
||||
|
||||
ثابتهای ماژولسطح (`fieldLabel`, `primaryBtn`, `ghostBtn` در `PaymentStep.tsx`) هم همینطور:
|
||||
|
||||
```tsx
|
||||
const fieldLabel: React.CSSProperties = { fontSize: 14, color: 'var(--text-2)', marginBottom: 8, display: 'block' };
|
||||
const primaryBtn: React.CSSProperties = { background: 'var(--primary)', color: 'var(--on-primary)', ... };
|
||||
const ghostBtn: React.CSSProperties = { background: 'transparent', color: 'var(--primary)', border: '1px solid var(--primary)', ... };
|
||||
```
|
||||
|
||||
**نحوه تست:**
|
||||
- `ddev exec npm run test` سبز بماند (تستهای موجود `PaymentStep`/`SessionPaymentAccordion`/… نباید بشکنند)
|
||||
- تأیید صفر شدن الگو:
|
||||
`grep -rn "dark:" --include='*.tsx' assets/admin | grep -E "style=\{\{[^}]*(color|background)" | grep -v '\.test\.' | wc -l` → باید `0` شود
|
||||
- بصری: `/admin` → جلسه (session) → مراحل ایجاد/جزئیات/پرداخت، یکبار لایت و یکبار دارک
|
||||
|
||||
### ۳. تبدیل بقیهٔ inline styleهای رنگی به توکن (دستهٔ A، ادامه)
|
||||
|
||||
فایلبهفایل، به ترتیب حجم (MyPatientsPage → PatientDetailPage → SmsWalletPage → SubscriptionPage → ClinicServicesPage → PatientsListPage → MySecretariesPage → DoctorFormPage → DashboardPage → AdminSubscriptionPage → TurnsTimeline → …).
|
||||
|
||||
قواعد:
|
||||
- رنگ ثابت داخل `style` → `var(--token)`
|
||||
- کلاسهای arbitrary دوتایی مثل `className="text-[#525252] dark:text-[#D7D8ED]"` → `className="text-[var(--text)]"` (یک کلاس، هر دو مود)
|
||||
- `bg-[#FAFAFA] dark:bg-[#222433]` → `bg-[var(--surface)]` یا `bg-[var(--bg)]` بسته به اینکه سطح کارت است یا پسزمینهٔ فیلد
|
||||
- استثنا: hex داخل SVG که با prop رنگ میگیرد (مثل `<PatientsGridView color={...} />`) — در وظیفهٔ ۶
|
||||
- **هر فایل یک commit/گام مستقل**؛ فایل بعدی را قبل از تأیید بصری فایل قبلی شروع نکن
|
||||
|
||||
**نحوه تست:** بعد از هر فایل: `ddev exec npm run test`، سپس بازکردن صفحهٔ متناظر در `/admin` در هر دو مود و مقایسه با اسکرینشات قبلِ تغییر (لایت باید بدون تغییر باشد).
|
||||
|
||||
### ۴. اضافه کردن پوشش دارک به دستهٔ B
|
||||
|
||||
در `pages/RepresentationDetailPage.tsx`، `pages/AppointmentDetailPage.tsx`، `pages/PaymentDetailPage.tsx`، `components/ImageCropModal.tsx`:
|
||||
|
||||
utilityهای پالت ثابت را با کلاس توکنی جایگزین کن — نه اینکه جفت `dark:` اضافه کنی (جفتسازی بدهی را دو برابر میکند و با brand hue هم هماهنگ نمیشود):
|
||||
|
||||
```tsx
|
||||
// قبل
|
||||
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm p-6">
|
||||
<h3 className="font-semibold text-gray-800 mb-4">اطلاعات بیمار</h3>
|
||||
|
||||
// بعد
|
||||
<div className="bg-[var(--surface)] rounded-2xl border border-[var(--border)] shadow-sm p-6">
|
||||
<h3 className="font-semibold text-[var(--text)] mb-4">اطلاعات بیمار</h3>
|
||||
```
|
||||
|
||||
```tsx
|
||||
// قبل (RepresentationDetailPage خط ۲۵۱)
|
||||
className="… border border-gray-300 text-gray-700 hover:bg-gray-50 …"
|
||||
// بعد
|
||||
className="… border border-[var(--border-2)] text-[var(--text)] hover:bg-[var(--surface-2)] …"
|
||||
```
|
||||
|
||||
برای رنگهای وضعیتی (`text-red-600`, `border-red-300`, `bg-red-50`) → `text-[var(--danger)]`, `border-[var(--danger)]/30`, `bg-[var(--danger-bg)]`.
|
||||
|
||||
**نحوه تست:** `/admin/representations/{id}`، `/admin/appointments/{id}`، `/admin/payments/{id}` و مودال کراپ تصویر (آپلود آواتار پزشک) — هر کدام در دارک و لایت.
|
||||
|
||||
### ۵. پاکسازی `styles.css`
|
||||
|
||||
- `.inv-table thead tr` → `background: var(--surface-2)`
|
||||
- `.inv-table th` → `color: var(--text-2)`
|
||||
- `.inv-table td` → `border-bottom: 1px solid var(--border)` و حذف `.dark .inv-table td { border-color: … }` که دیگر لازم نیست
|
||||
- `.inv-table tbody tr:hover` → `background: var(--surface-2)` و حذف override `.dark`
|
||||
- `.inv-tabs` → `border-bottom: 1px solid var(--border)` و حذف `.dark .inv-tabs`
|
||||
- `.inv-tab` → `color: var(--text-2)`؛ `.inv-tab.active` → `color: var(--primary); border-bottom: 3px solid var(--primary)`؛ حذف هر دو override `.dark`
|
||||
- `.inv-badge.in-stock|low-stock|out-of-stock` → `background: var(--success-bg)|var(--warning-bg)|var(--danger-bg)` و `color: var(--success)|var(--warning)|var(--danger)`؛ حذف سه override `.dark`
|
||||
|
||||
`color: #fff` روی `.btn`/`.avatar` (خطوط ۲۶۳، ۴۱۱، ۵۹۸، ۶۷۱، ۷۷۵) → `var(--on-primary)` هر جا روی پسزمینهٔ برند است؛ `rgba(8,13,22,.5)` اسکریم مودال (خطوط ۶۹۶، ۷۹۸) عمداً در هر دو مود یکسان است و **دست نمیخورد** — در گزارش ذکرش کن.
|
||||
|
||||
**نحوه تست:** `/admin` → انبار (inventory): جدول، تبها و badgeهای موجودی در هر دو مود؛ `ddev exec npm run build` بدون خطا.
|
||||
|
||||
### ۶. آیکونهای SVG
|
||||
|
||||
آیکونهای inline با `stroke="#616161"` / `#3B3B3B` / `fill="#7E7E7E"` (مثلاً `pages/MySecretariesPage.tsx` خطوط ۲۶–۳۵ و فایلهای `components/icons/`) را به `stroke="currentColor"` تبدیل کن و رنگ را از کلاس والد بگیر (`text-[var(--text-2)]`).
|
||||
|
||||
استثنا: آیکونهای چندرنگ برند (`stroke="#F17732"` در `components/icons/FilesServiceIcons.tsx`، ۲۲ مورد) — اینها هویت رنگی دارند؛ به `var(--accent)` تبدیل کن، نه `currentColor`.
|
||||
|
||||
```tsx
|
||||
// قبل
|
||||
<path d="…" stroke="#616161" strokeWidth="1.5" … />
|
||||
// بعد — رنگ از والد
|
||||
<span className="text-[var(--text-2)]">
|
||||
<path d="…" stroke="currentColor" strokeWidth="1.5" … />
|
||||
</span>
|
||||
```
|
||||
|
||||
**نحوه تست:** صفحهٔ «منشیهای من» و «سرویسها» در دارکمود — آیکونها باید دیده شوند نه اینکه در پسزمینه گم شوند.
|
||||
|
||||
### ۷. تست گارد + بهروزرسانی سند
|
||||
|
||||
**۷.۱ تست گارد** — `assets/admin/test/theme-tokens.test.ts` (Vitest، همان setup موجود در `assets/admin/test/`):
|
||||
|
||||
```ts
|
||||
// اسکن سورس؛ فایلهای پاکشده در فاز جاری در allowlist نیستند و باید تمیز بمانند.
|
||||
const CLEANED = [ /* مسیر فایلهایی که در وظایف ۲–۴ پاک شدند */ ];
|
||||
|
||||
it('فایلهای پاکشده hex ثابت رنگی ندارند', () => { /* regex #rrggbb داخل style/className، بهجز SVG برند */ });
|
||||
it('الگوی مردهٔ dark: کنار inline style رنگی وجود ندارد', () => { /* کل assets/admin */ });
|
||||
```
|
||||
|
||||
تست دوم باید روی **کل** `assets/admin` اجرا شود (نه فقط allowlist) چون بعد از وظیفهٔ ۲ باید صفر باشد و هرگز برنگردد.
|
||||
|
||||
**۷.۲ سند** — `docs/admin-ui/ui-design-spec.md`:
|
||||
- بخش ۱۶ فعلی («Dark Mode (اختیاری — فاز دوم)» با `.dark { }` و پالت Tailwind) با پیادهسازی واقعی نمیخواند → بازنویسی شود: دارکمود پیادهشده است، از طریق `data-theme`/کلاس `dark` روی `<html>` توسط `uiStore.applyTheme()`، و منبع رنگ **فقط** توکنهای `styles.css` است.
|
||||
- جدول نگاشت وظیفهٔ ۱ + قاعدهٔ الزامی: «رنگ ثابت (hex یا utility پالت Tailwind) در `assets/admin` ممنوع؛ `var(--token)` یا `*-[var(--token)]`».
|
||||
- بخش ۱ (Color Palette) که هنوز `--color-bg-sidebar: #0f172a` دارد با توکنهای واقعی همخوان شود.
|
||||
|
||||
**نحوه تست:** `ddev exec npm run test` — گارد جدید سبز؛ برای اطمینان از اینکه واقعاً گارد است، یکبار موقتاً `color: '#525252'` به یکی از فایلهای allowlist اضافه کن، ببین تست قرمز میشود، بعد برگردان.
|
||||
|
||||
### ۸. (فاز ۳ — فقط با تأیید صریح کاربر) مهاجرت سه فایل بزرگ
|
||||
|
||||
`pages/DoctorDetailPage.tsx` (۳۱۳ utility رنگی / ۱۴۲ `dark:`)، `components/schedule/ScheduleSection.tsx` (۲۶۰ / ۱۲۰)، `pages/UserDetailPage.tsx` (۱۹۸ / ۹۱).
|
||||
|
||||
اینها **در دارکمود کار میکنند** (جفت `slate/gray` + `dark:` دارند) ولی از design-system منحرفاند: با تغییر brand hue هماهنگ نمیشوند و رنگ خاکستریشان با `--surface`/`--text` مو نمیزند. مهاجرت ۷۷۱ نقطه ریسک رگرسیون بصری بالایی دارد و سود عملکردی فوری ندارد.
|
||||
|
||||
**توصیه:** این وظیفه را در همین پاس انجام نده. اگر کاربر تأیید کرد، فایلبهفایل و با اسکرینشات قبل/بعد در هر دو مود.
|
||||
|
||||
## نکات مهم
|
||||
|
||||
- **علت ریشهای، نه علامت:** الگوی `className="dark:…"` کنار `style={{color:…}}` نشان میدهد قبلاً تلاش شده دارکمود «وصله» شود بدون اینکه specificity در نظر گرفته شود. راهحل درست حذف رنگ ثابت است، نه اضافه کردن `!important` یا جفت `dark:` بیشتر. اگر جایی واقعاً به override نیاز شد، اول ببین آیا میشود کلاً از `style` به `className` رفت.
|
||||
- **ترتیب اجرا اجباری است:** وظیفهٔ ۲ (خرابی قطعی، ۹۰ نقطه) قبل از وظیفهٔ ۳ (حجیم). اگر بودجه/زمان تمام شد، وظایف ۲، ۴، ۵ حداقل قابل تحویل مستقلاند.
|
||||
- **لایتمود نباید تغییر کند.** توکنهای لایت همان hexهای رایجاند؛ هر تغییر بصری محسوس در لایت یعنی نگاشت اشتباه انتخاب شده — گزارش کن، خودسرانه توکن جدید نساز.
|
||||
- **توکن جدید فقط با دلیل.** اگر جایی هیچ توکن مناسبی نبود، اول بررسی کن آیا واقعاً رنگ جدید لازم است یا نزدیکترین توکن کافی است (guidelines §۵ — abstraction بیمصرف ممنوع). توکن جدید اگر اضافه شد باید در **هر سه** بلوک `:root`، `[data-theme="dark"]` و در صورت لزوم `@supports` تعریف شود.
|
||||
- **سازگاری مرورگر قدیمی:** بلوک `@supports (color: color-mix(in oklch, red, blue))` عمداً fallback sRGB دارد (iPhone 8 / WebKit قدیمی). هیچ توکنی نباید **فقط** داخل `@supports` تعریف شود.
|
||||
- **`brandHue.fixed`:** هیوی «نارنجی» در `uiStore.ts` مقادیر `--primary*` را inline روی root میگذارد. یعنی هر جا `#5559CE` ثابت مانده باشد با انتخاب این هیو ناهماهنگ میشود — این یک تست بصری مستقل است، نه فقط دارک/لایت.
|
||||
- **بدون تغییر backend/API.** این تسک صفر تغییر در `src/` و `docs/api/` دارد؛ تنها سند متأثر `docs/admin-ui/ui-design-spec.md` است.
|
||||
- **دستورها داخل ddev اجرا میشوند:** `ddev exec npm run test`، `ddev exec npm run build`. برای مشاهدهٔ زنده `ddev exec npm run watch`.
|
||||
- **حساب تست:** `09390039833 / 09390039833` روی `https://clinic-pro.ddev.site/admin`.
|
||||
Reference in New Issue
Block a user