diff --git a/docs/mobile-responsive.md b/docs/mobile-responsive.md new file mode 100644 index 0000000..d2b46c7 --- /dev/null +++ b/docs/mobile-responsive.md @@ -0,0 +1,108 @@ +# Mobile-Responsive UI — Change Log (v1.4) + +هدف: تجربه‌ی کاربری قابل‌استفاده برای کاربران موبایل (اکثریت کاربران)، بدون اینکه +ظاهر دسکتاپ هیچ تغییری بکند. هیچ بک‌اند، nginx، docker-compose یا ساختار جدیدی +اضافه نشده — همه‌چیز داخل همان اپ React است. + +## Core principle + +یک کدبیس، دو نمایش. همه‌ی تغییرات «فقط موبایل» هستند و پشت بریک‌پوینت +Tailwind `max-md:` (زیر 768px) یا `md:hidden` (فقط روی موبایل نمایش داده می‌شود) +قفل شده‌اند. دسکتاپ (≥768px) همان استایل‌های قبلی را می‌خواند و بنابراین +پیکسل‌به‌پیکسل بدون تغییر می‌ماند — به‌ساختار. + +## What changed + +### 1. `frontend/src/styles/globals.css` — consolidated mobile block (new) +یک بلوک `@media (max-width: 767.98px)` در انتهای فایل اضافه شد: + +| Rule | Effect (mobile only) | +|---|---| +| `input/select/textarea { font-size: 16px !important }` | جلوگیری از زوم خودکار iOS روی فوکوس (فونت‌های ۱۲–۱۴px زوم می‌شدند) | +| `.units-layout { grid-template-columns: 1fr }` | سایدبار ۲۲۰px دسته‌ها روی موبایل بالای پنل محتوا قرار می‌گیرد | +| `.item-row-head { display: none }` + `.wizard-item-row` دوستونه | ردیف اقلام ویزارد (گرید ۸ ستونه) روی موبایل می‌شود: فیلدهای متنی تمام‌عرض، عددها دوستونه؛ placeholderها نقش برچسب را دارند | +| `.wizard-item-row > :last-child` تمام‌عرض + حداقل ۴۴×۴۰px | دکمه‌ی حذف قلم بزرگ و قابل لمس | +| `.dd-panels { grid-template-columns: 1fr }` | پنل‌های درگ‌اندروپ مرحله‌ی ۴ تک‌ستونه | +| `.flow-cols` تک‌ستونه + `.flow-lines { display: none }` | نمودار جریان باکس‌ها روی هم؛ خطوط SVG اتصال که فقط در چیدمان دوستونه معنی داشتند روی موبایل مخفی می‌شوند | +| `.conv-form` تک‌ستونه، `.conv-row` wrap | فرم تبدیل واحد (گرید ۵ ستونه) روی موبایل ستونی | +| touch targets بزرگ‌تر (`.chip`, `.quick-chip`, `.spec-chip`, `.dd-item`, `.ut-cat`, `.btn-line-file`, دکمه‌های × و ...) | ارتفاع هدف لمسی به ~۴۰px رسید | + +### 2. `frontend/src/components/ui/dialog.tsx` — dialogs become bottom sheets +`DialogContent` با کلاس‌های `max-md:` به bottom-sheet تمام‌عرض تبدیل شد +(`inset-x-0 bottom-0`، گوشه‌های گرد فقط بالا، `max-h-[85dvh]`، padding با +`safe-area-inset-bottom`). چون همه‌ی مودال‌ها (ویرایش، پذیرش، تایید حذف) از همین +کامپوننت استفاده می‌کنند، همه یک‌جا درست شدند. دکمه‌ی بستن هم بزرگ‌تر شد. + +### 3. Form grids stacked — one-line additions each +- `EditModal.tsx` — گرید تأمین‌کننده/پروژه و گرید تاریخ‌ها: `max-md:grid-cols-1` +- `Wizard/Step1TopicSupplier.tsx` — فرم تأمین‌کننده‌ی جدید: `max-md:grid-cols-1` +- `Wizard/Step5Review.tsx` — گرید تاریخ‌ها: `max-md:grid-cols-1` +- `Suppliers/CategoryPage.tsx` — فرم افزودن تأمین‌کننده: `max-md:grid-cols-1` + +### 4. `frontend/src/components/layout/Header.tsx` — sticky header + clearance +- هدر فقط روی موبایل `sticky top-0 z-40` شد (`max-md:`) +- دکمه‌ی بازگشت بزرگ‌تر شد +- `PageLayout` روی موبایل `pb-24` گرفت تا نوار تب پایین روی محتوا نیفتد + +### 5. `frontend/src/components/layout/MobileTabBar.tsx` — NEW mobile bottom tab bar +نوار تب پایین (فقط `<768px` با `md:hidden`): +- **ادمین / مدیر تدارکات در دفتر:** داشبورد / تأمین‌کننده‌ها / پروژه‌ها / ضرایب +- **ادمین / مدیر تدارکات در پنل انبار:** انبار / کنترل روزانه / کالاهای در راه / سازمان‌دهی (آینه‌ی ناوبری دسکتاپ) +- **مدیر پروژه:** انبار / تغییر موجودی / کنترل روزانه / کالاهای در راه +- هدف لمسی ≥۵۶px، padding برای `safe-area-inset-bottom`، هایلایت تب فعال +- در `/wizard` نمایش داده نمی‌شود (تا با جابه‌جایی تصادفی کارِ کاربر از بین نرود) + +### 6. `frontend/src/App.tsx` — renders `` + +### 7. `frontend/src/pages/Wizard/Step4Projects.tsx` — touch drag & drop +`TouchSensor` با `{ delay: 150, tolerance: 8 }` به سنسورهای dnd-kit اضافه شد: +درگ لمسی کار می‌کند بدون اینکه اسکرول صفحه را بدزدد. مسیر کلیک-قلم-بعد-کلیک-پروژه +هم‌چنان مسیر جایگزین است. دسکتاپ همان `PointerSensor` قبلی را دارد. + +### 8. `frontend/src/components/JalaliDatePicker.tsx` — dropdown width + targets +- داپداون تقویم: `max-w-[calc(100vw-2rem)]` (در صفحه‌های باریک سرریز نمی‌شود) +- سلول‌های روز و ورودی‌های ساعت/دقیقه روی موبایل بزرگ‌تر شدند + +### 9. PWA layer (optional installability) +- `frontend/public/manifest.webmanifest` — NEW (name فارسی، `dir: rtl`، + `display: standalone`، theme `#4f46e5`، آیکون 192/512 + maskable) +- `frontend/public/icons/icon-192.png`, `icon-512.png` — NEW + (تولیدشده با `frontend/scripts/gen-pwa-icons.ps1` — NEW، بدون وابستگی) +- `frontend/index.html` — `theme-color` + لینک manifest + آیکون‌ها اضافه شد +- روی HTTP مثل میان‌بر صفحه‌ی اصلی کار می‌کند؛ پرامپت نصب رسمی PWA به HTTPS + نیاز دارد (بعداً با پروکسی/Let's Encrypt — بدون تغییر کد). + +## Deliberately NOT changed + +- **`SpecDrawer.tsx`**: کد مرده است (هیچ‌جا import نمی‌شود) — تصمیم تیم: دست نمی‌زنیم، فایل می‌ماند. +- **`.e-item-row` / `.spec-drawer` در CSS**: کلاس‌های بلااستفاده — دست‌نخورده ماندند. +- هیچ کلاس/استایل دسکتاپی ویرایش نشد؛ تمام تغییرات داخل `max-md:` یا کامپوننت‌های `md:hidden` است. + +## Verification performed + +- `npm run lint` (oxlint) → 0 errors (4 هشدار از قبل موجود) +- `npm run build` (tsc + vite) → موفق +- تست مرورگر روی Vite dev (viewport واقعی 375px): + - نوار تب: `fixed bottom-0`، قابل‌مشاهده، ۴ تب با برچسب درست، ارتفاع ≥56px + - هدر `sticky`، ورودی‌ها 16px، padding پایین صفحه 96px (جای نوار تب) + - صفحات ضرایب/ویزارد چیدمان تک‌ستونه (`.units-layout` فقط بعد از انتخاب موضوع رندر می‌شود) +- رگرسیون دسکتاپ: کلاس‌های دسکتاپ دست‌نخورده‌اند (بریک‌پوینت‌گیت)؛ + `npm run build` سلامت باندل را تایید می‌کند. + +## How to test manually + +```powershell +cd C:\Users\Reza\Desktop\v1.4 +docker compose up -d --build # بازسازی ایمیج فرانت +``` + +سپس در مرورگر دسکتاپ DevTools → device toolbar (مثلاً iPhone SE 375px) یا +مستقیم با گوشی (بخش «دیدن روی گوشی» در README/جواب نهایی). دسکتاپ را هم در +1280px چک کنید — باید دقیقاً مثل قبل باشد. + +## Notes / known considerations + +- خطوط اتصال نمودار جریان (SVG) روی موبایل عمداً مخفی شد — فقط باکس‌ها روی هم + قرار می‌گیرند؛ اگر روزی خواستید اتصال‌ها را روی موبایل هم ببینید باید + `FlowDiagram` برای حالت تک‌ستونه بازنویسی شود. +- نوار تب موبایل «انبارها» را ندارد (PMها از منوی همبرگری به آن می‌رسند). diff --git a/frontend/index.html b/frontend/index.html index c810dc5..701ce25 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,6 +3,10 @@ + + + + @@ -65,8 +66,9 @@ function HomeRoute() { export default function App() { return ( - }> - + <> + }> + } /> } /> - - + + + + ); } diff --git a/frontend/src/components/JalaliDatePicker.tsx b/frontend/src/components/JalaliDatePicker.tsx index 53b6aa4..0c28d9a 100644 --- a/frontend/src/components/JalaliDatePicker.tsx +++ b/frontend/src/components/JalaliDatePicker.tsx @@ -183,6 +183,8 @@ export function JalaliDatePicker({
@@ -217,7 +219,7 @@ export function JalaliDatePicker({ key={i} type="button" className={cn( - "cursor-pointer rounded py-0.5 text-[12px] hover:bg-slate-bg", + "cursor-pointer rounded py-0.5 text-[12px] hover:bg-slate-bg max-md:py-1.5", cell.isToday && !selectedIso && "bg-blue-bg font-bold text-[#1d4ed8]", cell.isToday && selectedIso && "font-bold text-[#1d4ed8]", cell.iso === selectedIso && "bg-accent text-white hover:bg-accent-dark", @@ -235,7 +237,7 @@ export function JalaliDatePicker({ ساعت دقیقه +
{menu &&
{menu}
} {(icon || brand) && ( @@ -38,7 +38,7 @@ export function Header({ title, sub, backTo, backLabel = "بازگشت", actions