Files
docs/ui-modernization/BACKOFFICE-STORE-UNIFICATION.md
T

196 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🏪 یکسان‌سازی فروشگاه عادی و تخفیفی — BackOffice
> **تاریخ:** ۱۴۰۴/۱۱/۲۴ (February 13, 2026)
> **وضعیت:** ✅ کامل
> **Build:** 0 Error ✅
---
## ۱. هدف
فروشگاه عادی و فروشگاه تخفیفی در پنل مدیریت باید از نظر **ظاهری و UX** کاملاً یکسان باشند.
قبل از این تغییرات، صفحات فروشگاه تخفیفی ظاهر و ساختار متفاوتی داشتند. هدف این فاز:
1. **NavMenu** — جداسازی دو فروشگاه در گروه‌بندی‌های مجزا
2. **دسته‌بندی‌ها** — ظاهر یکسان با فروشگاه عادی (ستون‌ها، درخت، اکشن‌ها)
3. **محصولات** — ظاهر یکسان (گالری، فیلترها، ستون‌های گرید، اکسپورت)
4. **سفارشات** — حذف گزارش‌های کوچک اضافی، فقط لیست خالص + رفع باگ لیست خالی
---
## ۲. خلاصه تغییرات
### ۲.۱ بازسازی NavMenu
| قبل | بعد |
|-----|-----|
| یک بخش «فروشگاه» با زیرگروه‌های محصولات + دسته‌بندی + سفارش + ویرایش دسته‌جمعی | دو گروه مجزا: «فروشگاه عادی» و «فروشگاه تخفیفی» |
| ویرایش دسته‌جمعی در منو | حذف شد از منو |
| انبارداری داخل فروشگاه | انبارداری گروه مجزا |
| پکیج‌ها داخل فروشگاه | پکیج‌ها آیتم مستقل |
**ساختار جدید:**
```
فروشگاه (بخش)
├── پکیج‌ها (مستقل)
├── فروشگاه عادی (NavGroup)
│ ├── محصولات → /ProductsPage/
│ ├── دسته‌بندی‌ها → /CategoryPage/
│ └── سفارشات و گزارش → /OrdersPage/
├── انبارداری (NavGroup مستقل)
│ ├── موجودی انبار
│ ├── محصولات کم‌موجود
│ ├── مدیریت انبارها
│ └── تاریخچه تغییرات
└── فروشگاه تخفیفی (NavGroup)
├── محصولات → /discount-products
├── دسته‌بندی‌ها → /discount-categories
└── سفارشات و گزارش → /discount-orders
```
**فایل:** `Shared/NavMenu.razor`
---
### ۲.۲ رفع لیست خالی سفارشات + حذف گزارش‌های کوچک
**مشکل ۱ — لیست خالی:**
- `PaymentDate.ToDateTime()` بدون null check باعث exception در WASM می‌شد
- Exception در Blazor WASM silent است و grid خالی نشان می‌دهد
- **رفع:** اضافه کردن `@if (context.Item.PaymentDate != null)` با fallback `"-"`
**مشکل ۲ — گزارش‌های اضافی:**
- کارت‌های آماری (تعداد سفارشات + مجموع مبلغ) و نمودار Bar وضعیت ارسال بالای گرید بودند
- این آمار اضافی بود چون تب جداگانه «گزارش فروش» وجود دارد
- **رفع:** حذف کامل `MudGrid` (کارت‌ها)، `MudChart` (نمودار)، فیلدهای `_stats`/`_statusChartLabels`/`_statusChartSeries`، متد `UpdateStats()`، کلاس `OrderStatsViewModel`
- عنوان تولبار از «سفارش‌های کاربر» به «لیست سفارشات» تغییر کرد
**فایل‌ها:**
- `Pages/UserOrder/UserOrderMainPage.razor`
- `Pages/UserOrder/UserOrderMainPage.razor.cs`
---
### ۲.۳ بازنویسی صفحه محصولات تخفیفی
**قبل:** markup سفارشی بدون `BasePageComponent`، ستون‌های ساده، بدون image preview
**بعد:** کاملاً مطابق با `ProductsMainPage` فروشگاه عادی
| ویژگی | قبل | بعد |
|-------|-----|-----|
| Wrapper | markup دستی | `BasePageComponent` |
| فیلترها | جستجو + دسته‌بندی | جستجو + دسته‌بندی + وضعیت + موجودی |
| ستون عنوان | متن ساده | تصویر inline (MudAvatar) + متن truncate + tooltip |
| ستون موجودی | عدد ساده | چیپ رنگی (قرمز/نارنجی/سبز) |
| ستون وضعیت | متن | چیپ Error/Success |
| خروجی Excel | ✅ (داشت) | ✅ (حفظ شد) |
| گالری تصاویر | ✅ (داشت) | ✅ (حفظ شد) |
| Server-side paging | ✅ | ✅ |
**فایل‌ها:**
- `Pages/DiscountShop/DiscountProductsMainPage.razor` — بازنویسی کامل
- `Pages/DiscountShop/DiscountProductsMainPage.razor.cs` — بازنویسی کامل (code-behind)
---
### ۲.۴ بازنویسی صفحه دسته‌بندی‌های تخفیفی
**قبل:** markup دستی بدون `BasePageComponent`، ستون‌های متفاوت
**بعد:** کاملاً مطابق با `CategoryMainPage` فروشگاه عادی
| ویژگی | قبل | بعد |
|-------|-----|-----|
| Wrapper | markup دستی | `BasePageComponent` |
| لایوت | درخت + گرید | درخت (3 col) + گرید (9 col) — بدون تغییر |
| ستون‌ها | شناسه، عنوان، توضیحات، وضعیت | شناسه، نام لاتین، عنوان، دسته‌بندی والد، تعداد محصولات، ترتیب، فعال؟ |
| ستون والد | نداشت | resolve نام والد از لیست |
| ستون محصولات | نداشت | چیپ Info |
| ستون ترتیب | نداشت | PropertyColumn |
| فیلتر | داخل page | داخل `BasePageComponent` |
| حذف با فرزند | disabled | disabled (حفظ شد) |
**فایل‌ها:**
- `Pages/DiscountShop/DiscountCategoriesMainPage.razor` — بازنویسی کامل
- `Pages/DiscountShop/DiscountCategoriesMainPage.razor.cs` — ایجاد (code-behind جدید)
---
## ۳. فایل‌های تغییر یافته
| فایل | نوع تغییر | توضیح |
|------|----------|-------|
| `Shared/NavMenu.razor` | ✏️ ویرایش | بازسازی ساختار فروشگاه |
| `Pages/UserOrder/UserOrderMainPage.razor` | ✏️ ویرایش | حذف آمار، رفع PaymentDate |
| `Pages/UserOrder/UserOrderMainPage.razor.cs` | ✏️ ویرایش | حذف فیلدها/متدهای آمار |
| `Pages/DiscountShop/DiscountProductsMainPage.razor` | 🔄 بازنویسی | BasePageComponent + ستون‌های جدید |
| `Pages/DiscountShop/DiscountProductsMainPage.razor.cs` | 🔄 بازنویسی | code-behind کامل |
| `Pages/DiscountShop/DiscountCategoriesMainPage.razor` | 🔄 بازنویسی | BasePageComponent + ستون‌های جدید |
| `Pages/DiscountShop/DiscountCategoriesMainPage.razor.cs` | 🆕 ایجاد | code-behind جدید (از @code درون‌خطی) |
---
## ۴. الگوی پیاده‌سازی — BasePageComponent
تمام صفحات لیست در BackOffice از `BasePageComponent` استفاده می‌کنند:
```razor
<BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
<Filters>
<!-- فیلدهای فیلتر در MudItem -->
</Filters>
<Content>
<!-- MudDataGrid اصلی -->
</Content>
</BasePageComponent>
```
**در code-behind:**
```csharp
private BasePageComponent _basePage = default!;
private async Task OnFilterSubmit()
{
_basePage.IsFiltered = true;
// اعمال فیلتر
}
private async Task OnFilterCleared()
{
_basePage.IsFiltered = false;
// ریست فیلترها
}
```
---
## ۵. الگوی Code-Behind
به دلیل محدودیت Razor source generator در پروژه، **همه فایل‌هایی که سرویس inject دارند باید code-behind داشته باشند**:
```
Page.razor → فقط markup (بدون @code)
Page.razor.cs → partial class با [Inject] و منطق
```
**نکته مهم:** سرویس‌های global از `_Imports.razor` نباید دوباره با `[Inject]` تعریف شوند:
-`[Inject] public IDialogService DialogService { get; set; }` — از قبل global
-`[Inject] public ISnackbar Snackbar { get; set; }` — از قبل global
-`[Inject] public IJSRuntime jsRuntime { get; set; }` — از قبل global (حرف کوچک!)
-`[Inject] public IDiscountProductService DiscountProductService { get; set; }` — باید inject شود
---
## ۶. مقایسه نهایی فروشگاه عادی و تخفیفی
| جنبه | فروشگاه عادی | فروشگاه تخفیفی | وضعیت |
|------|-------------|---------------|-------|
| ارتباط با بکند | gRPC/Protobuf | HTTP REST (IDiscountXxxService) | تفاوت ذاتی |
| BasePageComponent | ✅ | ✅ | 🟢 یکسان |
| فیلترهای محصول | جستجو+دسته‌بندی+وضعیت | جستجو+دسته‌بندی+وضعیت+موجودی | 🟢 یکسان+ |
| ستون‌های محصول | تصویر+عنوان، قیمت، موجودی (چیپ)، وضعیت (چیپ) | تصویر+عنوان، قیمت، تخفیف، موجودی (چیپ)، وضعیت (چیپ) | 🟢 یکسان+ |
| گالری تصاویر | ✅ GalleryDialog | ✅ ProductImageGallery | 🟢 هر دو دارند |
| خروجی Excel | ✅ | ✅ | 🟢 یکسان |
| درخت دسته‌بندی | ✅ | ✅ | 🟢 یکسان |
| ستون‌های دسته‌بندی | شناسه+نام+عنوان+والد+محصولات+ترتیب+فعال | شناسه+نام+عنوان+والد+محصولات+ترتیب+فعال | 🟢 یکسان |
| سفارشات Hub | MudTabs (سفارشات + گزارش فروش) | MudTabs (سفارشات + گزارش فروش) | 🟢 یکسان |