From 55ab2f5dfc663ffb319de2b55d71eea5a3c24c2c Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Mon, 27 Jul 2026 16:41:52 +0330 Subject: [PATCH] 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. --- .../prompt/admin-theme-dark-light-audit.md | 324 ++++++++++++++++ .../admin/components/AppointmentTurnCard.tsx | 18 +- assets/admin/components/FreeVisitPrice.tsx | 4 +- assets/admin/components/ImageCropModal.tsx | 16 +- .../admin/components/InvoiceSummaryModal.tsx | 4 +- .../admin/components/NewAppointmentDrawer.tsx | 2 +- assets/admin/components/PatientCaseBanner.tsx | 22 +- assets/admin/components/PatientTagsCell.tsx | 2 +- .../admin/components/PatientsFilterModal.tsx | 2 +- .../components/SessionPaymentAccordion.tsx | 28 +- .../admin/components/SessionServiceCard.tsx | 30 +- .../admin/components/SessionStepper.test.tsx | 6 +- assets/admin/components/SessionStepper.tsx | 8 +- .../components/TenantInsuranceContracts.tsx | 2 +- .../appointments/ServiceSlotPicker.tsx | 6 +- .../components/appointments/TurnsStatInfo.tsx | 8 +- .../components/appointments/TurnsTimeline.tsx | 6 +- .../components/appointments/turnStatus.ts | 86 +---- .../dashboard/NewAppointmentsTable.tsx | 16 +- .../components/dashboard/TauriCharts.test.tsx | 4 +- .../components/dashboard/TauriCharts.tsx | 12 +- .../dashboard/TauriDashboardView.tsx | 10 +- .../components/dashboard/TauriStatCards.tsx | 14 +- .../components/dashboard/dashboardIcons.tsx | 6 +- .../components/icons/FilesServiceIcons.tsx | 362 +++++++++--------- .../components/icons/FilesToolbarIcons.tsx | 54 +-- .../components/inventory/AddPackageModal.tsx | 8 +- .../inventory/InventoryActionsMenu.tsx | 2 +- .../inventory/InventoryItemsTable.tsx | 2 +- .../components/inventory/PackagesView.tsx | 8 +- .../layout/PurchaseSubscriptionSidebar.tsx | 2 +- assets/admin/components/layout/Topbar.tsx | 2 +- .../components/schedule/ScheduleSection.tsx | 166 ++++---- .../admin/components/session/CreateStep.tsx | 66 ++-- .../admin/components/session/DetailsStep.tsx | 70 ++-- .../admin/components/session/PaymentStep.tsx | 102 ++--- .../components/session/SessionAuditModal.tsx | 8 +- .../ui/AppointmentStatusDropdown.tsx | 22 +- .../admin/components/ui/PersianCalendar.tsx | 6 +- assets/admin/components/ui/StatCard.tsx | 2 +- .../ui/appointmentStatusMeta.test.ts | 2 +- assets/admin/pages/AppointmentCreatePage.tsx | 8 +- assets/admin/pages/AppointmentDetailPage.tsx | 34 +- assets/admin/pages/AppointmentsPage.tsx | 2 +- assets/admin/pages/BlogFormPage.tsx | 4 +- assets/admin/pages/CategoriesPage.tsx | 2 +- assets/admin/pages/ClinicDetailPage.tsx | 2 +- assets/admin/pages/ClinicServicesPage.tsx | 2 +- assets/admin/pages/DashboardPage.tsx | 10 +- assets/admin/pages/DoctorDetailPage.tsx | 238 ++++++------ assets/admin/pages/DoctorFormPage.tsx | 2 +- assets/admin/pages/EditSessionPage.tsx | 8 +- assets/admin/pages/LoginPage.tsx | 2 +- assets/admin/pages/MyPatientsPage.tsx | 18 +- assets/admin/pages/MyPaymentDetailPage.tsx | 2 +- assets/admin/pages/MyPaymentsPage.tsx | 2 +- assets/admin/pages/MySecretariesPage.tsx | 146 +++---- assets/admin/pages/NewSessionPage.tsx | 8 +- assets/admin/pages/PatientDetailPage.tsx | 26 +- assets/admin/pages/PatientsListPage.tsx | 48 +-- assets/admin/pages/PaymentDetailPage.tsx | 10 +- .../pages/RepresentationBlogFormPage.tsx | 4 +- .../admin/pages/RepresentationDetailPage.tsx | 46 +-- assets/admin/pages/SelectContextPage.tsx | 2 +- assets/admin/pages/SessionPaymentPage.tsx | 8 +- assets/admin/pages/SmsPage.tsx | 2 +- assets/admin/pages/SmsWalletPage.tsx | 6 +- assets/admin/pages/SubscriptionPage.tsx | 24 +- assets/admin/pages/UserDetailPage.tsx | 126 +++--- assets/admin/pages/subscriptionIcons.tsx | 48 +-- assets/admin/styles.css | 38 +- assets/admin/test/theme-tokens.test.ts | 83 ++++ docs/admin-ui/ui-design-spec.md | 114 +++--- 73 files changed, 1485 insertions(+), 1110 deletions(-) create mode 100644 .claude/prompt/admin-theme-dark-light-audit.md create mode 100644 assets/admin/test/theme-tokens.test.ts diff --git a/.claude/prompt/admin-theme-dark-light-audit.md b/.claude/prompt/admin-theme-dark-light-audit.md new file mode 100644 index 00000000..bcc7298a --- /dev/null +++ b/.claude/prompt/admin-theme-dark-light-audit.md @@ -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 غالب است +{formatRial(finalPrice)} + +// خط ۱۵۵ — همان اشتباه روی border/background +
+``` + +### utility روشن بدون جفت dark — `pages/AppointmentDetailPage.tsx` + +```tsx +// خط ۱۴۱–۱۴۲ +
+

اطلاعات بیمار

+``` + +### 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" +// خط ۳۰۹ +

اطلاعات بانکی ثبت نشده

+``` + +### 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 +

+ * +``` + +این همان الگویی است که باید همه‌جا اعمال شود: 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 +// قبل + + +// بعد + +``` + +```tsx +// قبل +

+ +// بعد +
+``` + +ثابت‌های ماژول‌سطح (`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 رنگ می‌گیرد (مثل ``) — در وظیفهٔ ۶ +- **هر فایل یک 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 +// قبل +
+

اطلاعات بیمار

+ +// بعد +
+

اطلاعات بیمار

+``` + +```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 +// قبل + +// بعد — رنگ از والد + + + +``` + +**نحوه تست:** صفحهٔ «منشی‌های من» و «سرویس‌ها» در دارک‌مود — آیکون‌ها باید دیده شوند نه اینکه در پس‌زمینه گم شوند. + +### ۷. تست گارد + به‌روزرسانی سند + +**۷.۱ تست گارد** — `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` روی `` توسط `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`. diff --git a/assets/admin/components/AppointmentTurnCard.tsx b/assets/admin/components/AppointmentTurnCard.tsx index d8bfa328..f3ebe1d5 100644 --- a/assets/admin/components/AppointmentTurnCard.tsx +++ b/assets/admin/components/AppointmentTurnCard.tsx @@ -26,13 +26,13 @@ function InfoRow({ icon, label, value, chip = false, valueStyle }: {
{icon} - {label}: + {label}:
@@ -55,7 +55,7 @@ export default function AppointmentTurnCard({ appointment, queryKey }: { const title = appointment.service_name || 'نوبت'; return (
{/* Header */} @@ -63,13 +63,13 @@ export default function AppointmentTurnCard({ appointment, queryKey }: {
- {title} + {title}
- +
-
+
{/* Middle: date & time chips */}
@@ -77,7 +77,7 @@ export default function AppointmentTurnCard({ appointment, queryKey }: { } label="ساعت" chip value={formatTime(appointment.starts_at)} />
-
+
{/* Bottom: personnel & status */}
diff --git a/assets/admin/components/FreeVisitPrice.tsx b/assets/admin/components/FreeVisitPrice.tsx index 5c532182..94262e80 100644 --- a/assets/admin/components/FreeVisitPrice.tsx +++ b/assets/admin/components/FreeVisitPrice.tsx @@ -79,7 +79,7 @@ export default function FreeVisitPrice({ doctorUuid, readOnly = false }: { docto