- Created a new JSON file for the PaymentGatewayInterface.php, detailing its methods and return types. - Added a JSON file for payment.md, documenting various API endpoints and their parameters, responses, and errors. - Introduced a JSON file for fix-admin-modal-and-calendar.md, outlining issues and solutions related to admin modal and calendar functionalities.
158 lines
11 KiB
Markdown
158 lines
11 KiB
Markdown
# ریسپانسیو کردن پنل ادمین برای موبایل (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 میدهد، همان الگو را اعمال کن.
|