# ریسپانسیو کردن پنل ادمین برای موبایل (iPhone 8 / ۳۷۵px)

## پروژه

`clinicpro` (admin frontend — React 19 + `assets/admin/styles.css`)

## زمینه

پنل `/admin` روی صفحه‌های کوچک (مثل iPhone 8 عرض ۳۷۵px) به‌هم می‌ریزد و افقی اسکرول می‌شود. زیرساخت ریسپانسیو **تا حدی هست** (viewport meta درست است، drawer سایدبار + scrim در `@media (max-width: 860px)`، `.settings-grid`/`.stat-grid` در media collapse می‌شوند)، ولی **بی‌اثر شده** چون در بیشتر صفحه‌ها `grid-template-columns` به‌صورت **inline `style={{}}`** روی المان‌ها نوشته شده و **inline style بر قوانین `@media` در CSS اولویت دارد** → گریدها روی موبایل جمع نمی‌شوند و از عرض صفحه بیرون می‌زنند.

نمونهٔ واقعی: `PaymentsPage.tsx` → `<div className="stat-grid" style={{ gridTemplateColumns: 'repeat(4,1fr)' }}>`؛ روی ۳۷۵px می‌شود ۴ ستون ۹۰px‌ای که overflow می‌دهد. `.stat-grid` در CSS در `@media (max-width:560px)` به `1fr 1fr` می‌رود ولی inline آن را باطل می‌کند.

## مشکل / هدف

پنل ادمین روی ۳۷۵px بدون اسکرول افقی و خوانا شود:
1. گریدهای inline ثابت که media را می‌شکنند اصلاح شوند (بهترین راه: `repeat(auto-fit, minmax(...))` که بدون media خودکار جمع می‌شود، یا حذف inline و واگذاری به CSS).
2. breakpoint منوی موبایل (hamburger) با drawer یکسان شود.
3. جدول‌ها و محتوای عریض داخل کانتینر اسکرول‌شونده باشند، نه کل صفحه.
4. گارد سراسری ضد-overflow افقی.

## فایل‌های مرتبط

| فایل | نقش |
|------|-----|
| `clinicpro/assets/admin/styles.css` | `.stat-grid` auto-fit، breakpoint drawer، گارد overflow، paddingهای موبایل |
| `clinicpro/assets/admin/components/layout/Topbar.tsx` | هماهنگی breakpoint hamburger با CSS |
| صفحه‌های با گرید inline (لیست پایین) | حذف/ریسپانسیو کردن `gridTemplateColumns` inline |

**صفحه‌ها/کامپوننت‌های دارای `gridTemplateColumns` inline** (`grep -rn "gridTemplateColumns:" assets/admin`):
`PaymentsPage`, `DoctorsPage`, `StaffPage`, `FinancialReportPage`, `RepresentationFinancePage`, `RepresentationSettlementPage`, `DashboardPage` (چند مورد)، `ClinicDetailPage` (`1fr 300px`)، `DoctorDetailPage` (`1fr 1fr auto`)، `AppointmentsPage` (`1fr auto`)، `SettingsPage` (`1fr auto`)، `ServiceTariffModal` (`150px 1fr auto`)، `DashboardPage` ردیف `160px 1fr 52px`. (`PersianCalendar` گریدهای `repeat(7,1fr)`/`repeat(3,1fr)` داخل popover کوچک‌اند و مشکل‌ساز نیستند — دست نزن.)

## وضعیت فعلی

### CSS (`assets/admin/styles.css`)

```css
:root { --sidebar-w: 252px; }
.main { flex: 1; min-width: 0; margin-right: var(--sidebar-w); }

.stat-grid { /* base بدون grid-template-columns صریح؟ inlineها آن را ست می‌کنند */ }

.scrim { display: none; }
@media (max-width: 1080px) {
  .stat-grid { grid-template-columns: repeat(3, 1fr); }
  .dash-main, .dash-3 { grid-template-columns: 1fr; }
}
@media (max-width: 860px) {         /* ← drawer فقط تا 860 */
  .main { margin-right: 0 !important; }
  .sidebar { transform: translateX(100%); box-shadow: var(--shadow-lg); }
  .app[data-mobile-open="true"] .sidebar { transform: translateX(0); width: var(--sidebar-w); }
  .app[data-mobile-open="true"] .scrim { display: block; position: fixed; inset: 0; /* ... */ }
  .topbar-search { display: none; }
  .grid-2 { grid-template-columns: 1fr; }
}
@media (max-width: 560px) { .stat-grid { grid-template-columns: 1fr 1fr; } }
```

### Topbar breakpoint (ناهماهنگ با CSS)

```tsx
// assets/admin/components/layout/Topbar.tsx:26
onClick={typeof window !== 'undefined' && window.innerWidth < 1024 ? onMobileMenuOpen : toggleSidebar}
```
`< 1024` منوی موبایل را باز می‌کند، ولی drawer CSS فقط `≤ 860` است → در بازهٔ ۸۶۱–۱۰۲۳ hamburger «باز» می‌کند اما سایدبار حالت drawer ندارد (منطقهٔ خراب).

### نمونهٔ گرید inline (تکرارشده در ۲۰ فایل)

```tsx
<div className="stat-grid" style={{ gridTemplateColumns: 'repeat(4,1fr)' }}>   // PaymentsPage:116
<div className="stat-grid" style={{ gridTemplateColumns: 'repeat(5,1fr)' }}>   // DoctorsPage:212
<div style={{ display: 'grid', gridTemplateColumns: '1fr 300px', gap: 'var(--gap)' }}>  // ClinicDetailPage:784
```

## وظایف

### ۱. `.stat-grid` را ذاتاً ریسپانسیو کن (CSS)

در `styles.css` به `.stat-grid` یک base با `auto-fit` بده تا **بدون نیاز به media و بدون تأثیرپذیری از تعداد ستون** خودش جمع شود:

```css
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--gap);
}
```
و قوانین media مربوط به `.stat-grid` (خطوط ۷۰۴ و ۷۲۰ فعلی) را **حذف** کن (دیگر لازم نیستند؛ auto-fit همه را پوشش می‌دهد).

### ۲. حذف `gridTemplateColumns` inline از stat-gridها

در همهٔ صفحه‌هایی که `<div className="stat-grid" style={{ gridTemplateColumns: 'repeat(N,1fr)', ... }}>` دارند، فقط **کلید `gridTemplateColumns` را از inline style حذف کن** (بقیهٔ propها مثل `marginBottom` بمانند). با تسک ۱، خودکار ریسپانسیو می‌شوند.

فایل‌ها: `PaymentsPage`, `DoctorsPage`, `StaffPage`, `FinancialReportPage`, `RepresentationFinancePage`, `RepresentationSettlementPage`, `DashboardPage` (خطوط ۹۵۰/۱۰۵۶/۱۰۷۲).

مثال:
```tsx
// قبل
<div className="stat-grid" style={{ gridTemplateColumns: 'repeat(4,1fr)' }}>
// بعد
<div className="stat-grid">
// اگر propهای دیگری بود:
<div className="stat-grid" style={{ marginBottom: 'var(--gap)' }}>
```

### ۳. لایه‌های دو/سه‌ستونهٔ inline → ریسپانسیو

برای گریدهای غیرِ stat که ستون ثابت دارند و باید روی موبایل عمودی شوند، به‌جای inline از کلاس ریسپانسیو استفاده کن. یک کلاس عمومی در CSS اضافه کن:

```css
/* لایهٔ دوستونه که زیر ۸۶۰ عمودی می‌شود */
.split-2 { display: grid; grid-template-columns: 1fr minmax(0, 320px); gap: var(--gap); align-items: start; }
@media (max-width: 860px) { .split-2 { grid-template-columns: 1fr; } }

/* ردیف فیلد + اکشن که زیر ۶۰۰ عمودی می‌شود */
.form-row { display: grid; grid-template-columns: 1fr auto; gap: 12px; align-items: end; }
@media (max-width: 600px) { .form-row { grid-template-columns: 1fr; } }
```

سپس:
- `ClinicDetailPage:784` (`1fr 300px`) → `className="split-2"` (حذف inline grid).
- `DoctorDetailPage:1107` (`1fr 1fr auto`) و `AppointmentsPage:409` (`1fr auto`) و `SettingsPage:431` (`1fr auto`) و `ServiceTariffModal:95` (`150px 1fr auto`) → از `form-row` استفاده کن یا اگر ساختار خاص است، همان inline را با wrap در media نگه‌دار ولی مطمئن شو زیر ۶۰۰ تک‌ستون می‌شود. (برای المان‌های inline که کلاس‌گذاری سخت است، می‌توانی `gridTemplateColumns` را با `repeat(auto-fit, minmax(140px, 1fr))` جایگزین کنی تا خودکار بشکند.)
- `DashboardPage:119` (`160px 1fr 52px`) و `DashboardPage:514` (`quick-grid` `repeat(3,1fr)`): `quick-grid` را هم مثل stat به auto-fit ببر؛ ردیف ۱۶۰px را زیر ۵۶۰ به `1fr auto` یا wrap تبدیل کن.

### ۴. هماهنگی breakpoint منوی موبایل

drawer را با hamburger یکسان کن. **سریع‌ترین راه:** در `styles.css` بلوک `@media (max-width: 860px)` را به `@media (max-width: 1024px)` تغییر بده (drawer/scrim/main-margin/topbar-search همگی زیر ۱۰۲۴). این با `window.innerWidth < 1024` در `Topbar` هم‌خوان می‌شود و منطقهٔ خرابِ ۸۶۱–۱۰۲۳ رفع می‌شود.

> اگر ترجیح می‌دهی سایدبار روی تبلت بماند، به‌جای آن `1024` در `Topbar.tsx` را به `860` تغییر بده. یکی از دو راه — نه هر دو. (توصیه: drawer تا ۱۰۲۴.)

### ۵. گارد سراسری ضد-overflow افقی + padding موبایل

در `styles.css`:
```css
html, body { max-width: 100%; overflow-x: hidden; }
.main { min-width: 0; }                      /* اجازهٔ کوچک‌شدن flex child */

@media (max-width: 560px) {
  .page, .main > * { padding-left: 12px; padding-right: 12px; }  /* اگر padding بزرگ‌تری دارند */
  .page-head, .toolbar { flex-wrap: wrap; gap: 10px; }          /* هدر/تولبار در چند خط */
}
```
(کلاس‌های واقعی padding صفحه را از `styles.css` پیدا کن — اگر نام `.page`/`.content` فرق دارد، همان را هدف بگیر.)

### ۶. جدول‌ها داخل کانتینر اسکرول‌شونده

هر `<table>` عریض باید داخل `.table-wrap` (که `overflow-x: auto` دارد) باشد تا فقط جدول اسکرول شود نه کل صفحه. فایل‌های دارای `<table>` بدون wrap را بررسی و در صورت نبودِ wrapper، دور جدول `<div className="table-wrap">` بگذار:
`PreRegistrationsPage`, `UsersPage`, `ClinicsPage`, `RepresentationFinancePage`, `AppointmentsPage`, `RepresentationSettlementPage`, `AdminSubscriptionPage`, `DoctorsPage`, `SmsWalletPage`, `RepresentationProfilePage`. (اگر از کامپوننت `DataTable` مشترک استفاده می‌کنند و آن خودش wrap دارد، نیازی نیست — اول بررسی کن.)

## نکات مهم

- **چرا inline بد است:** `style={{gridTemplateColumns}}` روی المان، specificity بالاتر از هر `@media` در stylesheet دارد؛ پس یا حذف شود یا خودش `auto-fit`/ریسپانسیو باشد. این هستهٔ باگ است.
- `auto-fit + minmax` بهترین انتخاب برای stat/quick گریدهاست: بدون media، با هر تعداد کارت، روی هر عرض درست جمع می‌شود.
- **RTL**: از `padding-inline`/`margin-inline` و رفتار موجود پیروی کن؛ چیزی که RTL را بشکند اضافه نکن.
- کلاس CSS جدید فقط در حد `.split-2`/`.form-row` (یا معادل)؛ کتابخانهٔ CSS جدید نصب نکن.
- بعد از تغییر: `ddev exec yarn dev` و `ddev exec npx tsc --noEmit` بدون خطا؛ سپس در DevTools با عرض **375px (iPhone 8)** این صفحه‌ها را چک کن: Dashboard، Payments، Doctors، Appointments، Settings، یک Detail (Clinic/Doctor) — نباید اسکرول افقی داشته باشند و کارت‌ها/جدول‌ها باید درست بچینند.
- تغییر فقط frontend/CSS است؛ backend و docs تغییری ندارند.
- این‌ها فایل‌های `.tsx`/CSS پنل‌اند؛ اگر جای دیگری هم `gridTemplateColumns` inline ثابت دیدی که overflow می‌دهد، همان الگو را اعمال کن.
