196 lines
9.5 KiB
Markdown
196 lines
9.5 KiB
Markdown
# 🏪 یکسانسازی فروشگاه عادی و تخفیفی — 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 (سفارشات + گزارش فروش) | 🟢 یکسان |
|