fix: update PurchaseSubscriptionSidebar and AppointmentSettingsPage for pixel-perfect alignment and color consistency

This commit is contained in:
hamed
2026-07-15 17:30:26 +03:30
parent 810293764a
commit ffc4351038
4 changed files with 165 additions and 3 deletions
@@ -0,0 +1,159 @@
# پیکسل‌به‌پیکسل کردن منوی تنظیمات + ریشه‌گرفتن رنگ‌ها از شخصی‌سازی
## پروژه
`clinicpro` (فقط پنل ادمین React — تغییر backend ندارد).
اپ دسکتاپ `clinic-pro-tauri` فقط **مرجع بصری** است، نه هدف تغییر. صفحه‌ی مرجع:
`http://127.0.0.1:5170/setting/purchase-subscription`
(کد آن: `clinic-pro-tauri/src/components/setting/listMenu/purchaseSubscription/PurchaseSubscriptionSidebar.jsx`)
## زمینه
پنل ادمین `clinicpro` یک ناحیه‌ی «تنظیمات» دارد که کنار محتوا یک ساید‌منوی مشترک (`PurchaseSubscriptionSidebar`) نمایش می‌دهد. این منو در همه‌ی صفحات تنظیمات از طریق `SettingsLayout` رندر می‌شود.
- صفحه‌ی `/admin/subscription` (`SubscriptionPage.tsx`) درست و پیکسل‌به‌پیکسل است — **مرجع درون‌پروژه‌ای**.
- صفحه‌ی `/admin/appointment-settings` (`AppointmentSettingsPage.tsx`) **خراب** است: محتوا روی زمینه‌ی خاکستری `#fafafa` نشسته، پنل سفید ندارد و فاصله‌ها با مرجع فرق دارد.
## مشکل / هدف
سه اصلاح مشخص:
1. **رنگ‌ها باید از شخصی‌سازی ریشه بگیرند.** رنگ ردیف فعال منو در `PurchaseSubscriptionSidebar.tsx` به‌صورت هاردکد `#f17732` است. باید از متغیر برند شخصی‌سازی (`var(--accent)`) استفاده کند تا با «رنگ اصلی» انتخاب‌شده در پنل شخصی‌سازی (`Topbar.tsx``setBrandHue`) هماهنگ شود.
2. **محتوای appointment-settings باید روی پنل سفید `#ffffff` بنشیند، نه `#fafafa`.** مرجع (tauri و نیز `SubscriptionPage.tsx`) محتوا را داخل یک پنل با `background: var(--surface)` (سفید) می‌گذارد. در appointment-settings محتوا مستقیم روی `--bg` صفحه (`#fafafa`) است. `#fafafa` رنگِ **ناحیه‌ی منو** است، نه محتوا.
3. **منو باید چسبیده به نوارِ کناری اصلی (سمت راست) باشد.** در مرجع tauri منوی تنظیمات فلاش به ساید‌بار اصلی می‌چسبد؛ در clinicpro به‌خاطر padding محتوا فاصله افتاده.
## فایل‌های مرتبط
| فایل | نقش |
|------|-----|
| `assets/admin/components/layout/PurchaseSubscriptionSidebar.tsx` | ساید‌منوی مشترک تنظیمات — رنگ ردیف فعال اینجاست |
| `assets/admin/pages/AppointmentSettingsPage.tsx` | صفحه‌ی خراب که باید پیکسل‌به‌پیکسل شود |
| `assets/admin/pages/SubscriptionPage.tsx` | **مرجع درست** — الگوی wrap محتوا در پنل سفید |
| `assets/admin/components/layout/SettingsLayout.tsx` | شل تنظیمات (grid: `[248px_minmax(0,1fr)] gap-5`) — محل اصلاح فلاش‌شدن |
| `assets/admin/styles.css` | تعریف متغیرها: `--surface:#ffffff`، `--bg:#fafafa`، `--accent:#f0682a`، `--gap:20px`، `.content{padding:var(--gap);max-width:1480px;margin:0 auto}` |
| `clinic-pro-tauri/.../PurchaseSubscriptionSidebar.jsx` | مرجع بصری (تغییر نده) |
## وضعیت فعلی
### ۱) رنگ هاردکد در ساید‌منو — `PurchaseSubscriptionSidebar.tsx`
```tsx
const rowStyle: React.CSSProperties = {
borderRadius: 12, height: 44, marginBottom: 8,
display: 'flex', alignItems: 'center', justifyContent: 'flex-start',
padding: '0 14px', fontSize: 16, fontWeight: isActive ? 700 : 500,
lineHeight: 1, textAlign: 'right',
background: isActive ? '#f17732' : 'transparent', // ← هاردکد
color: isActive ? '#FFFFFF' : 'var(--text-2)',
cursor: item.to ? 'pointer' : 'not-allowed',
transition: 'background .14s',
};
// ...
onMouseEnter={(e) => { if (!isActive) (e.currentTarget as HTMLElement).style.background = 'var(--surface-2)'; }}
onMouseLeave={(e) => { if (!isActive) (e.currentTarget as HTMLElement).style.background = 'transparent'; }}
```
### ۲) صفحه‌ی خراب — `AppointmentSettingsPage.tsx` (بازگشت فعلی)
```tsx
return (
<SettingsLayout active="appointment">
<div className="fade-in"> {/* ← fade-in تکراری؛ SettingsLayout خودش fade-in دارد */}
<h1 className="section-title" style={{ marginBottom: 16 }}>مدیریت نوبت دهی</h1>
<FreeVisitPrice />
{!uuid ? (
<div className="card" style={{ padding: 32, textAlign: 'center', ... }}>این بخش فقط برای پزشک در دسترس است.</div>
) : isLoading ? (
<div style={{ padding: 16, ... }}>در حال بارگذاری...</div>
) : (
<WeeklyScheduleTab doctorUuid={uuid} addresses={addresses} /> {/* ← مستقیم روی #fafafa */}
)}
</div>
</SettingsLayout>
);
```
### مرجع درست — `SubscriptionPage.tsx` (محتوا داخل پنل سفید)
```tsx
return (
<SettingsLayout active="subscription">
<div style={{ background: 'var(--surface)', minWidth: 0 }}> {/* ← پنل سفید #ffffff */}
<div dir="ltr" style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 32, paddingTop: 8 }}>
{/* محتوا */}
</div>
</div>
</SettingsLayout>
);
```
## وظایف
### ۱. رنگ ردیف فعال ساید‌منو را از شخصی‌سازی بگیر
در `PurchaseSubscriptionSidebar.tsx`:
- `background: isActive ? '#f17732'``background: isActive ? 'var(--accent)'`
- بررسی کن که در حالت hover و active در تم تیره هم درست باشد (متغیرهای `--accent`، `--surface-2` هر دو تم را در `styles.css` پوشش می‌دهند — نیازی به `.dark &` جدا نیست).
- رنگ متن ردیف فعال `#FFFFFF` بماند (روی برند خوانا است).
### ۲. محتوای appointment-settings را داخل پنل سفید بگذار و ساختار را مثل مرجع کن
در `AppointmentSettingsPage.tsx`:
- `<div className="fade-in">` تکراری را حذف کن (چون `SettingsLayout` خودش `fade-in` دارد) و به‌جای آن دقیقاً الگوی `SubscriptionPage` را به‌کار ببر:
```tsx
return (
<SettingsLayout active="appointment">
<div style={{ background: 'var(--surface)', minWidth: 0 }}>
<h1 className="section-title" style={{ marginBottom: 16 }}>مدیریت نوبت دهی</h1>
<FreeVisitPrice />
{!uuid ? (
<div className="card" style={{ padding: 32, textAlign: 'center', color: 'var(--text-3)', fontSize: 14 }}>
این بخش فقط برای پزشک در دسترس است.
</div>
) : isLoading ? (
<div style={{ padding: 16, color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>
) : (
<WeeklyScheduleTab doctorUuid={uuid} addresses={addresses} />
)}
</div>
</SettingsLayout>
);
```
- بعد از تغییر، چشمی چک کن که `WeeklyScheduleTab` و `FreeVisitPrice` روی سفید (`--surface`) بنشینند و padding/فاصله‌ها با `SubscriptionPage` یکی باشد. اگر `WeeklyScheduleTab` خودش زمینه/کارت داخلی دارد، مطمئن شو با پنل سفید بیرونی دوبار‌کارت (double-card) نمی‌شود.
### ۳. منوی تنظیمات را فلاش به نوار کناری اصلی بچسبان
مشکل: `.content` در `styles.css` دارای `padding: var(--gap)` (۲۰px) و `max-width:1480px; margin:0 auto` است؛ همین padding سمت راست، بین نوار کناری اصلی و منوی تنظیمات فاصله می‌اندازد.
در `SettingsLayout.tsx` کاری کن ستون منو تا لبه‌ی نوار کناری اصلی کشیده شود؛ گزینه‌ی پیشنهادی: یک negative margin سمت راست روی ریشه‌ی `SettingsLayout` برابر `--gap` تا padding محتوا خنثی شود، بدون شکستن `max-width` بخش محتوا:
```tsx
return (
<div className="fade-in" dir="rtl" style={{ width: '100%' }}>
<div className="grid grid-cols-1 lg:grid-cols-[248px_minmax(0,1fr)] gap-5"
style={{ marginInlineStart: 'calc(-1 * var(--gap))' /* منو تا لبه‌ی نوار کناری */ }}>
<PurchaseSubscriptionSidebar active={active} />
<div style={{ minWidth: 0 }}>{children}</div>
</div>
</div>
);
```
- **مهم:** RTL است؛ نوار کناری اصلی سمت راست است و منوی تنظیمات هم سمت راستِ گرید. جهت negative margin را با تست چشمی درست کن (`marginInlineStart`/`marginInlineEnd`) تا منو دقیقاً به نوار کناری بچسبد و لبه‌ی چپِ محتوا از قاب بیرون نزند.
- چون `SettingsLayout` مشترک است، بعد از این تغییر **همه‌ی صفحات تنظیمات** (subscription، appointment، tags، insurance، …) را چک کن که هم‌زمان درست بمانند.
## نکات مهم
- **هیچ تغییری در tauri نده** — فقط مرجع بصری است.
- منبع رنگ‌ها = متغیرهای CSS در `assets/admin/styles.css`؛ رنگ برند شخصی‌سازی از `--accent` می‌آید (پنل `Topbar.tsx``setBrandHue`). هر رنگ هاردکد جدید ممنوع.
- هر دو تم روشن/تیره باید سالم بمانند (`--surface`، `--accent`، `--surface-2` در هر دو تم تعریف شده‌اند).
- تست بصری پیکسل‌به‌پیکسل: `SubscriptionPage` (درست) را کنار `AppointmentSettingsPage` بگذار؛ عرض منو، فاصله، زمینه‌ی سفید و رنگ ردیف فعال باید یکسان باشند.
- تست‌های موجود را نگه‌دار: `PurchaseSubscriptionSidebar.test.tsx`، `SettingsLayout.test.tsx`، `AppointmentSettingsPage.test.tsx` — بعد از تغییر `npm test` (یا `ddev exec`) اجرا کن. اگر تستی رنگ هاردکد `#f17732` را چک می‌کند، آن را به `var(--accent)` به‌روزرسانی کن.
- backend/API دست نمی‌خورد → نیازی به به‌روزرسانی `docs/api/*` نیست.
@@ -78,7 +78,7 @@ export default function PurchaseSubscriptionSidebar({ active }: { active: string
display: 'flex', alignItems: 'center', justifyContent: 'flex-start',
padding: '0 14px', fontSize: 16, fontWeight: isActive ? 700 : 500,
lineHeight: 1, textAlign: 'right',
background: isActive ? '#f17732' : 'transparent',
background: isActive ? 'var(--accent)' : 'transparent',
color: isActive ? '#FFFFFF' : 'var(--text-2)',
cursor: item.to ? 'pointer' : 'not-allowed',
transition: 'background .14s',
@@ -50,7 +50,10 @@ export function menuForRole(role: string | null | undefined): SettingsMenuItem[]
export default function SettingsLayout({ active, children }: { active: string; children: React.ReactNode }) {
return (
<div className="fade-in" dir="rtl" style={{ width: '100%' }}>
<div className="grid grid-cols-1 lg:grid-cols-[248px_minmax(0,1fr)] gap-5">
<div
className="grid grid-cols-1 lg:grid-cols-[248px_minmax(0,1fr)] gap-5"
style={{ marginInlineStart: 'calc(-1 * var(--gap))' }}
>
<PurchaseSubscriptionSidebar active={active} />
<div style={{ minWidth: 0 }}>{children}</div>
</div>
@@ -27,7 +27,7 @@ export default function AppointmentSettingsPage() {
return (
<SettingsLayout active="appointment">
<div className="fade-in">
<div style={{ background: 'var(--surface)', minWidth: 0 }}>
<h1 className="section-title" style={{ marginBottom: 16 }}>مدیریت نوبت دهی</h1>
<FreeVisitPrice />