From ad31c8be9762d992c35b065cd289a63fccf59a7d Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 16 Feb 2026 00:59:16 +0330 Subject: [PATCH] Refactor code structure for improved readability and maintainability --- INDEX.md | 42 +- SHOP-UNIFICATION.md | 521 ++++++ business/DISCOUNT-STORE-STATUS.md | 196 +++ cms/FILE-MANAGEMENT-ARCHITECTURE.md | 251 +++ cms/REGISTRATION-FLOW-FIXES.md | 187 +++ ui-modernization/BACKOFFICE-ARCHITECTURE.md | 359 +++++ .../BACKOFFICE-STORE-UNIFICATION.md | 195 +++ ui-modernization/PHASE-1-COMPLETE.md | 118 ++ ui-modernization/PHASE-3-COMPLETE.md | 104 ++ ui-modernization/UI-MODERNIZATION-PLAN.md | 1418 +++++++++++++++++ 10 files changed, 3388 insertions(+), 3 deletions(-) create mode 100644 SHOP-UNIFICATION.md create mode 100644 business/DISCOUNT-STORE-STATUS.md create mode 100644 cms/FILE-MANAGEMENT-ARCHITECTURE.md create mode 100644 cms/REGISTRATION-FLOW-FIXES.md create mode 100644 ui-modernization/BACKOFFICE-ARCHITECTURE.md create mode 100644 ui-modernization/BACKOFFICE-STORE-UNIFICATION.md create mode 100644 ui-modernization/PHASE-1-COMPLETE.md create mode 100644 ui-modernization/PHASE-3-COMPLETE.md create mode 100644 ui-modernization/UI-MODERNIZATION-PLAN.md diff --git a/INDEX.md b/INDEX.md index 6bada60..a0893aa 100644 --- a/INDEX.md +++ b/INDEX.md @@ -1,10 +1,32 @@ # 📚 FourSat Documentation Index -> آخرین بروزرسانی: February 10, 2026 +> آخرین بروزرسانی: February 17, 2026 > ۲۲۰ فایل → ۳۰ فایل (تجمیع ۳ فازی + cleanup نهایی) --- +## 🔍 راهنمای سریع — کدام مستند را باید ببینم؟ + +| می‌خواهم بدانم... | مستند | +|-------------------|-------| +| **کل تغییرات BackOffice چه بوده؟** | [`BackOffice/docs/BACKOFFICE-CHANGELOG.md`](../BackOffice/docs/BACKOFFICE-CHANGELOG.md) | +| **ساختار و معماری BackOffice چیست؟** | [`ui-modernization/BACKOFFICE-ARCHITECTURE.md`](ui-modernization/BACKOFFICE-ARCHITECTURE.md) | +| **یکسان‌سازی فروشگاه‌ها چه بوده؟** | [`ui-modernization/BACKOFFICE-STORE-UNIFICATION.md`](ui-modernization/BACKOFFICE-STORE-UNIFICATION.md) | +| **وضعیت فروشگاه تخفیفی؟** | [`business/DISCOUNT-STORE-STATUS.md`](business/DISCOUNT-STORE-STATUS.md) | +| **بیزینس فروشگاه تخفیفی چگونه کار می‌کند؟** | [`business/discount-shop-business.md`](business/discount-shop-business.md) | +| **سیستم کمیسیون چگونه کار می‌کند؟** | [`business/club-commission-system-complete.md`](business/club-commission-system-complete.md) | +| **چگونه deploy کنم؟** | [`deployment/OFFLINE-DEPLOYMENT-GUIDE.md`](deployment/OFFLINE-DEPLOYMENT-GUIDE.md) | +| **وضعیت CI/CD چیست؟** | [`deployment/CICD-PIPELINE-GUIDE.md`](deployment/CICD-PIPELINE-GUIDE.md) | +| **مشخصات سرور و زیرساخت؟** | [`deployment/INFRASTRUCTURE-GUIDE.md`](deployment/INFRASTRUCTURE-GUIDE.md) | +| **مهاجرت BFF→CMS چگونه بوده؟** | [`migration/BACKOFFICE-BFF-MIGRATION.md`](migration/BACKOFFICE-BFF-MIGRATION.md) | +| **مهاجرت FrontOffice→CMS؟** | [`migration/FRONTOFFICE-TO-CMS-MIGRATION.md`](migration/FRONTOFFICE-TO-CMS-MIGRATION.md) | +| **نقشه نوسازی UI فرانت؟** | [`ui-modernization/UI-MODERNIZATION-PLAN.md`](ui-modernization/UI-MODERNIZATION-PLAN.md) | +| **معماری مدیریت فایل و تصاویر؟** | [`cms/FILE-MANAGEMENT-ARCHITECTURE.md`](cms/FILE-MANAGEMENT-ARCHITECTURE.md) | +| **فیکس فلوی ثبت‌نام FrontOffice؟** | [`cms/REGISTRATION-FLOW-FIXES.md`](cms/REGISTRATION-FLOW-FIXES.md) | +| **Audit report کامل BackOffice؟** | [`BackOffice/docs/BACKOFFICE-AUDIT.md`](../BackOffice/docs/BACKOFFICE-AUDIT.md) | + +--- + ## 📂 business/ — مستندات بیزنسی (۷ فایل) | فایل | توضیح | @@ -17,7 +39,7 @@ | [discount-shop-business.md](business/discount-shop-business.md) | فروشگاه تخفیفی: پرداخت ترکیبی، درصد تخفیف، entity design | | [manual-payment-system.md](business/manual-payment-system.md) | پرداخت دستی: کارت به کارت، تأیید ادمین، آپلود FMS | -## 📂 cms/ — مستندات فنی CMS (۱۳ فایل) +## 📂 cms/ — مستندات فنی CMS (۱۵ فایل) | فایل | توضیح | |------|-------| @@ -35,6 +57,8 @@ | [email-sms-configuration.md](cms/email-sms-configuration.md) | تنظیمات SMS/Email: Kavenegar templates، Gmail | | [PRODUCT-BUNDLE-FEATURE.md](cms/PRODUCT-BUNDLE-FEATURE.md) | 🟡 فیچر آینده: طراحی Product Bundle | | [FRONTOFFICE-RELEASE-NOTES-v1.5.0.md](cms/FRONTOFFICE-RELEASE-NOTES-v1.5.0.md) | 🆕 یادداشت انتشار FrontOffice v1.5.0 (فارسی): هفته‌نما، گزارش هفتگی، امتیاز انتقالی | +| [FILE-MANAGEMENT-ARCHITECTURE.md](cms/FILE-MANAGEMENT-ARCHITECTURE.md) | 🆕 معماری جامع مدیریت فایل: IFileManager, LocalFileManager, ImagePathResolverInterceptor, UploadsController (HTTP سرو عمومی + FMS Fallback), ذخیره دیسکی | +| [REGISTRATION-FLOW-FIXES.md](cms/REGISTRATION-FLOW-FIXES.md) | 🆕 فیکس فلوی ثبت‌نام: ایجاد کاربر جدید در VerifyOtpToken، رفع lookup موبایل AcceptContract، رفع sync IsCompleteRegister | ## 📂 deployment/ — مستندات استقرار (۴ فایل) @@ -46,6 +70,14 @@ | [SERVER-MIRRORS-CONFIG.md](deployment/SERVER-MIRRORS-CONFIG.md) | تنظیمات mirror: K3s registries.yaml، containerd | | [INGRESS-NGINX-WARNING.md](deployment/INGRESS-NGINX-WARNING.md) | ⚠️ هشدار K3s: مشکل hostNetwork در ingress-nginx | +## 📂 ui-modernization/ — مستندات نوسازی UI (۳ فایل) + +| فایل | توضیح | +|------|-------| +| [UI-MODERNIZATION-PLAN.md](ui-modernization/UI-MODERNIZATION-PLAN.md) | 🆕 طرح جامع نوسازی UI فرانت‌آفیس: سیستم بلاگ، صفحات دینامیک، لندینگ، Mobile-First — ۷ فاز، ~۱۲۴ فایل جدید | +| [BACKOFFICE-ARCHITECTURE.md](ui-modernization/BACKOFFICE-ARCHITECTURE.md) | 🆕 مرجع معماری BackOffice: ساختار پوشه‌ها، الگوهای BasePageComponent/Hub/CodeBehind/ExcelExport، مسیرها، permission‌ها، نقشه NavMenu | +| [BACKOFFICE-STORE-UNIFICATION.md](ui-modernization/BACKOFFICE-STORE-UNIFICATION.md) | 🆕 یکسان‌سازی فروشگاه عادی و تخفیفی: NavMenu restructure، حذف آمار سفارشات، رفع PaymentDate، بازنویسی ۴ صفحه | + ## 📂 migration/ — مستندات مهاجرت BFF→CMS (۶ فایل) | فایل | توضیح | @@ -69,4 +101,8 @@ | فاز ۳ (ساختاردهی + merge) | **28** | ۱۳ | | cleanup نهایی (+2 فایل جدید) | **30** | — | | session CI/CD + Admin fix (+2) | **32** | — | -| **نهایی** | **32 + INDEX** | **۱۹۱ فایل حذف/ادغام** | +| session UI Modernization plan (+1) | **33** | — | +| session Store Unification (+2 docs) | **35** | — | +| session File Mgmt + Content (+1 doc) | **36** | — | +| session Registration Flow Fix (+1 doc) | **37** | — | +| **نهایی** | **37 + INDEX** | **۱۹۱ فایل حذف/ادغام** | diff --git a/SHOP-UNIFICATION.md b/SHOP-UNIFICATION.md new file mode 100644 index 0000000..8ffb43f --- /dev/null +++ b/SHOP-UNIFICATION.md @@ -0,0 +1,521 @@ +# یکسان‌سازی فروشگاه عادی و فروشگاه تخفیفی (BackOffice) + +**تاریخ:** ۱۳۹۴/۱۱/۲۴ (2026-02-13) +**وضعیت:** ✅ فاز ۱ تا ۶ — تکمیل شده (CMS + BackOffice Build Succeeded — 0 Error) + +--- + +## هدف + +هر دو فروشگاه (عادی و تخفیفی) از نظر **UI/UX، ساختار صفحات، اکشن‌ها و قابلیت‌ها** عین‌به‌عین یکسان باشند. +**تنها تفاوت مجاز:** منطق پرداخت — فروشگاه تخفیفی از کیف‌پول تخفیفی + درگاه، فروشگاه عادی فقط از کیف‌پول عادی. + +### معیار یکسان‌سازی +- **مبنا:** فروشگاه عادی (Products, Category, UserOrder) +- **استثنا:** مزایای بدیهی فروشگاه تخفیفی به فروشگاه عادی هم اضافه شد +- **Bulk Operations:** بیخیال شد (طبق درخواست کاربر) + +--- + +## تفاوت‌های زیرساختی (تغییر نکرده — بی‌تأثیر روی UX) + +| موضوع | فروشگاه عادی | فروشگاه تخفیفی | +|---|---|---| +| ارتباط با سرور | gRPC مستقیم (`ProductsContractClient`) | سرویس اینترفیس (`IDiscountProductService`) که داخلاً gRPC صدا می‌زنه | +| مدل داده | Protobuf models | C# DTOs | + +> **نکته:** هر دو در نهایت از همان gRPC backend استفاده می‌کنند. تفاوت فقط در لایه abstraction است و تأثیری روی UX ندارد. + +--- + +## تغییرات انجام‌شده + +### ۱. صفحه محصولات (`DiscountProductsMainPage`) + +| تغییر | قبل | بعد | +|---|---|---| +| نمایش تصویر | `MudAvatar` | کامپوننت `Image` (مطابق فروشگاه عادی) | +| برش عنوان | `Substring(0, 20) + "…"` | `Truncate(20, true)` (extension method مشترک) | +| گالری تصاویر | `ProductFormDialog` (کلاینت‌ساید) | `GalleryDialog` سرور-محور (مطابق فروشگاه عادی) | +| پیش‌نمایش تصویر | HTML inline در `ShowMessageBox` | `ImagePreviewDialog` (مطابق فروشگاه عادی) | +| عنوان تولبار | «مدیریت محصولات تخفیفی» | «مدیریت محصولات» | + +**اکشن‌های جدید اضافه‌شده:** +- ✅ دکمه «مدیریت دسته‌بندی (درگ و دراپ)» → ناوبری به `ProductCategoriesDragDropPage` + +**فایل‌های تغییر یافته:** +- `Pages/DiscountShop/DiscountProductsMainPage.razor` +- `Pages/DiscountShop/DiscountProductsMainPage.razor.cs` + +--- + +### ۲. صفحه دسته‌بندی‌ها (`DiscountCategoriesMainPage`) + +**اکشن‌های جدید اضافه‌شده:** +- ✅ دکمه «مدیریت محصولات این دسته (درگ و دراپ)» → ناوبری به `CategoryProductsDragDropPage` + +**فایل‌های تغییر یافته:** +- `Pages/DiscountShop/DiscountCategoriesMainPage.razor` +- `Pages/DiscountShop/DiscountCategoriesMainPage.razor.cs` + +--- + +### ۳. صفحه سفارشات (`DiscountOrdersMainPage`) — بازنویسی کامل + +| تغییر | قبل | بعد | +|---|---|---| +| الگوی فیلتر | فیلترهای inline با دکمه جستجو | `BasePageComponent` با OnSubmit/OnClear (مطابق فروشگاه عادی) | +| لایه‌بندی | `MudPaper` تو در تو | `BasePageComponent > Filters + Content` | +| اکشن‌ها | فقط مشاهده جزئیات + تغییر وضعیت | جزئیات + تغییر وضعیت + حذف (مطابق فروشگاه عادی) | +| آیکون‌های اکشن | `Visibility` + `Edit` | `Info` + `LocalShipping` + `DeleteOutline` (مطابق فروشگاه عادی) | +| ساختار تولبار | بدون تولبار | تولبار با عنوان + دکمه Excel (مطابق فروشگاه عادی) | + +**ستون‌ها (حفظ شده — خاص فروشگاه تخفیفی):** +- شماره سفارش، تاریخ ثبت، مبلغ کل، **تخفیف کیف‌پول**، **پرداخت درگاه**، تعداد آیتم، وضعیت، پرداخت + +**فایل‌های تغییر یافته:** +- `Pages/DiscountShop/DiscountOrdersMainPage.razor` ← بازنویسی کامل +- `Pages/DiscountShop/DiscountOrdersMainPage.razor.cs` ← بازنویسی کامل + +--- + +### ۴. گزارش فروش (`SalesReports`) + +| تغییر | قبل | بعد | +|---|---|---| +| نمودارها | روند فروش + محصولات پرفروش | روند فروش + محصولات پرفروش + **وضعیت سفارش‌ها** | +| عنوان | «گزارش فروش فروشگاه تخفیفی» | «گزارش فروش فروشگاه» | +| توضیح | «آمار فروش، تخفیف و وضعیت سفارش‌های فروشگاه تخفیفی...» | «آمار فروش و وضعیت سفارش‌ها بر اساس بازه تاریخ و وضعیت سفارش» | + +**نمودار جدید:** +- ✅ «وضعیت سفارش‌ها» — نمودار میله‌ای تعداد سفارش بر اساس وضعیت (مطابق فروشگاه عادی) + +**فایل‌های تغییر یافته:** +- `Pages/DiscountShop/SalesReports.razor` + +--- + +### ۵. هاب سفارشات (`DiscountShopHub`) + +| تغییر | قبل | بعد | +|---|---|---| +| عنوان | «فروشگاه تخفیفی» | «سفارشات فروشگاه تخفیفی» | +| آیکون تب سفارشات | `ShoppingCart` | `ReceiptLong` (مطابق `OrdersHub` فروشگاه عادی) | + +**فایل‌های تغییر یافته:** +- `Pages/DiscountShop/DiscountShopHub.razor` + +--- + +### ۶. تغییرات فروشگاه عادی (مزایای تخفیفی ← عادی) + +#### CategoryMainPage +- ✅ ستون **«ترتیب»** (`SortOrder`) اضافه شد (از تخفیفی) +- ✅ **غیرفعال‌سازی حذف** دسته‌بندی دارای زیردسته (از تخفیفی) + +**فایل‌های تغییر یافته:** +- `Pages/Category/CategoryMainPage.razor` +- `Pages/Category/CategoryMainPage.razor.cs` + +--- + +## تغییرات فاز ۲ — ارتقاء به Admin RPC و فیلدهای پیشرفته + +### ۷. سرویس سفارشات تخفیفی — سوئیچ به Admin RPC + +| تغییر | قبل | بعد | +|---|---|---| +| RPC مورد استفاده | `GetUserOrders` (user-scoped) | `GetAllDiscountOrders` (admin-scoped) | +| DTO | `OrderSummaryDto` (محدود) | `AdminOrderDto` (کامل با user_full_name, user_mobile, shipping_address, payment_date, vat_amount...) | +| فیلترها | فقط userId, paymentCompleted, deliveryStatus | userId, paymentStatus, deliveryStatus, userMobile, trackingCode, fromDate, toDate, minAmount, maxAmount | +| گزارش فروش | محاسبه کلاینت‌ساید + N+1 (۵۰ فراخوان gRPC جداگانه!) | `GetDiscountSalesReport` RPC سرور-ساید با fallback | + +**فایل‌های تغییر یافته:** +- `Services/DiscountOrder/IDiscountOrderService.cs` ← فیلترهای جدید + DTOهای گزارش فروش +- `Services/DiscountOrder/DiscountOrderService.cs` ← سوئیچ به `GetAllDiscountOrdersAsync` + `GetDiscountSalesReportAsync` + +### ۸. ستون‌ها و فیلترهای ادمین در سفارشات تخفیفی + +**ستون‌های جدید اضافه‌شده:** +- ✅ **نام کاربر** (لینک به پروفایل — مطابق فروشگاه عادی) +- ✅ **موبایل کاربر** +- ✅ **وضعیت پرداخت** (Pending/Completed/Failed/Refunded — مطابق فروشگاه عادی) +- ✅ **تاریخ پرداخت** +- ✅ **آدرس** (truncated با tooltip — مطابق فروشگاه عادی) +- ✅ **وضعیت ارسال** (جدا از وضعیت پرداخت — مطابق فروشگاه عادی) + +**فیلترهای جدید اضافه‌شده:** +- ✅ شناسه سفارش +- ✅ جستجوی کاربر (UserAutoComplete) +- ✅ موبایل کاربر +- ✅ کد رهگیری +- ✅ از تاریخ / تا تاریخ +- ✅ وضعیت پرداخت (Pending/Completed/Failed/Refunded) +- ✅ وضعیت ارسال + +**فایل‌های تغییر یافته:** +- `Pages/DiscountShop/DiscountOrdersMainPage.razor` ← ستون‌ها + فیلترها +- `Pages/DiscountShop/DiscountOrdersMainPage.razor.cs` ← فیلدها + متدهای وضعیت پرداخت + +### ۹. گزارش فروش تخفیفی — حذف مشکل N+1 + +| تغییر | قبل | بعد | +|---|---|---| +| محصولات پرفروش | ۵۰ فراخوان gRPC جداگانه (`GetByIdAsync` × 50) | یک فراخوان `GetDiscountSalesReport` | +| خلاصه آماری | محاسبه کلاینت‌ساید | سرور-ساید (دقیق‌تر + سریع‌تر) | +| نمودار روند | GroupBy کلاینت‌ساید | `SalesPeriodDto` از سرور | +| جدول سفارش‌ها | فقط شماره سفارش | شناسه + نام کاربر (لینک) | +| خروجی Excel/PDF | فقط OrderNumber | شناسه + نام کاربر + موبایل | + +**فایل‌های تغییر یافته:** +- `Pages/DiscountShop/SalesReports.razor` + +### ۱۰. دسته‌بندی فروشگاه عادی — فیلد ImagePath + +- ✅ فیلد `ImagePath` به دیالوگ ایجاد/ویرایش دسته‌بندی اضافه شد (مطابق فروشگاه تخفیفی) + +**فایل‌های تغییر یافته:** +- `Pages/Category/Components/CreateOrUpdateCategoryDialog.razor` +- `Pages/Category/Components/CreateOrUpdateCategoryDialog.razor.cs` + +### ۱۱. گزارش فروش عادی — نمودار محصولات پرفروش + خروجی PDF + +- ✅ نمودار «محصولات پرفروش (بر اساس مبلغ)» از FactorDetails سفارشات +- ✅ دکمه خروجی PDF (نسخه متنی) — مطابق فروشگاه تخفیفی + +**فایل‌های تغییر یافته:** +- `Pages/UserOrder/OrderSalesReports.razor` +- `Pages/UserOrder/OrderSalesReports.razor.cs` + +--- + +## تغییرات فاز ۳ — تگ‌های محصول و نهایی‌سازی + +### ۱۲. تگ‌های محصول در فروشگاه تخفیفی + +- ✅ دکمه «تگ‌های محصول» (`Label` icon) به ستون عملیات صفحه محصولات تخفیفی اضافه شد +- ✅ از همان `AssignTagsDialog` فروشگاه عادی استفاده شد (کامپوننت مشترک) +- ✅ سرویس `ProductTagContract` مشترک بین هر دو فروشگاه — بدون نیاز به API جدید + +> **نکته:** `ProductTagContract` یک سرویس ژنریک `product_id ↔ tag_id` است و محدود به فروشگاه خاصی نیست. + +**فایل‌های تغییر یافته:** +- `Pages/DiscountShop/DiscountProductsMainPage.razor` ← دکمه تگ +- `Pages/DiscountShop/DiscountProductsMainPage.razor.cs` ← متد `OpenTagAssignment` + using + +--- + +## تغییرات فاز ۴ — یکسان‌سازی دیالوگ‌ها و جزئیات تکمیلی + +### ۱۳. دیالوگ جزئیات سفارش تخفیفی — Timeline + ویرایش Inline + +| تغییر | قبل | بعد | +|---|---|---| +| Timeline وضعیت | ❌ فاقد | ✅ ۵ مرحله (ثبت → پرداخت → آماده‌سازی → ارسال → تحویل/مرجوعی) | +| ویرایش وضعیت | ❌ فقط خواندنی | ✅ درون‌خطی (وضعیت + کد رهگیری + یادداشت ادمین) | +| دکمه ذخیره | ❌ فقط «بستن» | ✅ «ثبت تغییرات» + اسپینر بارگذاری | +| هشدارهای شرطی | ❌ فاقد | ✅ هشدار لغو/مرجوعی + اطلاع‌رسانی ارسال | +| Code-behind | `@code` درون‌خطی | فایل جداگانه `.razor.cs` | + +**فایل‌های تغییر یافته:** +- `Pages/DiscountShop/Components/OrderDetailsDialog.razor` ← Timeline + ویرایش inline +- `Pages/DiscountShop/Components/OrderDetailsDialog.razor.cs` ← فایل جدید — timeline + save logic + +### ۱۴. دیالوگ تغییر وضعیت فروشگاه عادی — ارتقاء + +| تغییر | قبل | بعد | +|---|---|---| +| فرم | `MudStack` ساده | `MudForm` با validation | +| فیلدها | فقط وضعیت | ✅ وضعیت + **کد رهگیری** + **توضیحات ارسال** | +| هشدارهای شرطی | ❌ فاقد | ✅ هشدار مرجوعی + اطلاع‌رسانی ارسال | +| دکمه | رنگ ثابت `Primary` | ✅ رنگ داینامیک بر اساس وضعیت + اسپینر | +| عنوان | بدون آیکون | ✅ آیکون `Edit` + عنوان | +| RPC | فقط `UpdateOrderStatusAsync` | ✅ `UpdateOrderStatusAsync` + `UpdateUserOrderAsync` (کد رهگیری + توضیحات) | + +**فایل‌های تغییر یافته:** +- `Pages/UserOrder/Components/ChangeOrderStatusDialog.razor` +- `Pages/UserOrder/Components/ChangeOrderStatusDialog.razor.cs` + +### ۱۵. گزارش فروش تخفیفی — Empty chart guard + ترتیب نمودارها + +| تغییر | قبل | بعد | +|---|---|---| +| نمودار روند فروش | بدون بررسی خالی بودن | ✅ نمایش «داده کافی برای نمایش نمودار وجود ندارد» | +| ترتیب نمودارها | روند → پرفروش → وضعیت | ✅ روند → **وضعیت** → پرفروش (مطابق فروشگاه عادی) | + +**فایل‌های تغییر یافته:** +- `Pages/DiscountShop/SalesReports.razor` + +### ۱۶. گزارش فروش عادی — لینک نام کاربر + +- ✅ نام کاربر در جدول سفارش‌ها از متن ساده به `MudLink` تبدیل شد (لینک به پروفایل کاربر) + +**فایل‌های تغییر یافته:** +- `Pages/UserOrder/OrderSalesReports.razor` + +### ۱۷. دیالوگ دسته‌بندی فروشگاه عادی — MudForm + UX + +| تغییر | قبل | بعد | +|---|---|---| +| اعتبارسنجی | ❌ بدون validation | ✅ `MudForm` با `Required` + `RequiredError` | +| فیلد فعال | `MudCheckBox` | ✅ `MudSwitch` رنگی (مطابق فروشگاه تخفیفی) | +| Helper text | ❌ فاقد | ✅ «عدد کمتر = اولویت بالاتر» + «برای دسته اصلی خالی بگذارید» | +| دکمه ذخیره | همیشه «ثبت» | ✅ «ذخیره تغییرات» / «ایجاد دسته‌بندی» (داینامیک) | +| اسپینر بارگذاری | ❌ فاقد | ✅ `MudProgressCircular` + غیرفعال‌سازی دکمه | +| عنوان | بدون آیکون | ✅ آیکون `Edit`/`Add` + عنوان داینامیک | +| غیرفعال‌سازی دکمه | ❌ فاقد | ✅ غیرفعال تا validation سبز نشود | + +**فایل‌های تغییر یافته:** +- `Pages/Category/CreateOrUpdateCategoryDialog.razor` +- `Pages/Category/CreateOrUpdateCategoryDialog.razor.cs` + +### ۱۸. سفارشات تخفیفی — لغو سفارش واقعی + +| تغییر | قبل | بعد | +|---|---|---| +| دکمه «حذف» | آیکون `DeleteOutline` — فقط snackbar (بدون API) | ✅ آیکون `Cancel` — فراخوان `UpdateStatusAsync` با `Cancelled` | +| متن تأییدیه | «آیا از حذف مطمئن هستید؟» | «آیا از لغو سفارش مطمئن هستید؟ وضعیت به لغو شده تغییر می‌کند» | +| عملکرد | ❌ هیچ | ✅ واقعی — `UpdateStatusAsync(Cancelled)` | + +**فایل‌های تغییر یافته:** +- `Pages/DiscountShop/DiscountOrdersMainPage.razor` ← آیکون + tooltip +- `Pages/DiscountShop/DiscountOrdersMainPage.razor.cs` ← فراخوان API + +--- + +## تغییرات فاز ۵ — فرمت‌بندی قیمت، زیردسته و برچسب فیلدها + +### ۵.۱ فرمت‌بندی قیمت در لیست محصولات عادی + +**مشکل:** ستون قیمت عدد خام بدون separator و بدون واحد نشان می‌داد. +**راه‌حل:** تبدیل `PropertyColumn` به `TemplateColumn` با `Price.ToString("N0") ریال` — مطابق فروشگاه تخفیفی. + +**فایل تغییر یافته:** +- `Pages/Products/ProductsMainPage.razor` ← TemplateColumn + فرمت N0 + ریال + +### ۵.۲ دکمه «افزودن زیردسته» در دسته‌بندی‌های عادی + +**مشکل:** فروشگاه تخفیفی دکمه «افزودن زیردسته» در ستون عملیات داشت ولی فروشگاه عادی نداشت. +**راه‌حل:** افزودن `MudIconButton` با آیکون `CreateNewFolder` + رنگ `Color.Primary` + متد `CreateSubcategory(parent)` که دیالوگ را با `ParentId = parent.Id` باز می‌کند. + +**فایل‌های تغییر یافته:** +- `Pages/Category/CategoryMainPage.razor` ← دکمه جدید در ستون عملیات +- `Pages/Category/CategoryMainPage.razor.cs` ← متد `CreateSubcategory` + +### ۵.۳ برچسب واحد ریال در دیالوگ‌های محصول + +**مشکل:** فیلد قیمت در دیالوگ‌های ایجاد/ویرایش محصول عادی `Label="قیمت"` داشت — بدون واحد. +**راه‌حل:** تغییر به `Label="قیمت (ریال)"` — مطابق فروشگاه تخفیفی. + +**فایل‌های تغییر یافته:** +- `Pages/Products/Components/CreateDialog.razor` ← قیمت (ریال) +- `Pages/Products/Components/UpdateDialog.razor` ← قیمت (ریال) + +--- + +## تغییرات فاز ۶ — تغییرات بکند (Proto + Handler + gRPC Service) + +این فاز تمام موارد "محدودیت API" که در فازهای قبلی شناسایی شده بودند را حل می‌کند. + +### ۶.۱ ستون وضعیت فعال/غیرفعال محصولات (`is_active`) + +**مشکل:** پروتوباف `GetAllProductsByFilterResponseModel` فیلد `is_active` نداشت. محصولات عادی ستون وضعیت نداشتند. +**راه‌حل (end-to-end):** +- **Proto:** افزودن `bool is_active = 15` به `GetAllProductsByFilterResponseModel` + `google.protobuf.BoolValue is_active = 15` به `GetAllProductsByFilterFilter` +- **DTO:** افزودن `bool IsActive` به `CustomerProductModel` +- **Query:** افزودن `bool? IsActive` به `GetCustomerProductsByFilterQuery` +- **Handler:** فیلتر `IsDeleted != IsActive` + مپ `IsActive = !p.IsDeleted` +- **gRPC Service:** مپ `IsActive` در فیلتر و ریسپانس `ProductsService` +- **Frontend:** ستون `TemplateColumn` با `MudChip` رنگی در `ProductsMainPage.razor` + +**فایل‌های تغییر یافته:** +- `CMS/.../Protos/products.proto` +- `CMS/.../GetCustomerProductsByFilterResponseDto.cs` +- `CMS/.../GetCustomerProductsByFilterQuery.cs` +- `CMS/.../GetCustomerProductsByFilterQueryHandler.cs` +- `CMS/.../Services/ProductsService.cs` +- `BackOffice/.../Pages/Products/ProductsMainPage.razor` + +### ۶.۲ ستون تعداد محصولات دسته‌بندی (`product_count`) + +**مشکل:** پروتوباف `GetAllCategoryByFilterResponseModel` فیلد `product_count` نداشت. +**راه‌حل (end-to-end):** +- **Proto:** افزودن `int32 product_count = 9` به `GetAllCategoryByFilterResponseModel` +- **DTO:** افزودن `int ProductCount` به `GetAllCategoryByFilterResponseModel` (C#) +- **Handler:** تغییر از `ProjectToType<>()` (Mapster auto-map) به manual `Select()` با `ProductCount = x.ProductCategories.Count` +- **gRPC Service:** auto-map Mapster (نام یکسان) +- **Frontend:** ستون `PropertyColumn` جدید در `CategoryMainPage.razor` + +**فایل‌های تغییر یافته:** +- `CMS/.../Protos/category.proto` +- `CMS/.../GetAllCategoryByFilterResponseDto.cs` +- `CMS/.../GetAllCategoryByFilterQueryHandler.cs` +- `BackOffice/.../Pages/Category/CategoryMainPage.razor` + +### ۶.۳ فیلتر بازه تاریخ سفارشات (`from_date` / `to_date`) + +**مشکل:** پروتوباف `GetAllUserOrderByFilterFilter` فقط یک `payment_date` داشت. امکان فیلتر بازه تاریخ وجود نداشت. +**راه‌حل (end-to-end):** +- **Proto:** افزودن `google.protobuf.Timestamp from_date = 11` و `google.protobuf.Timestamp to_date = 12` به فیلتر +- **gRPC Service:** مپ `FromDate` از `from_date` (با fallback به `payment_date`)، `ToDate` از `to_date` +- **Handler:** بدون تغییر — از قبل `FromDate`/`ToDate` را ساپورت می‌کرد +- **Frontend:** افزودن `MudDatePicker` دوم (تا تاریخ)، مپ به `Filter.FromDate`/`Filter.ToDate` + +**فایل‌های تغییر یافته:** +- `CMS/.../Protos/userorder.proto` +- `CMS/.../Services/UserOrderService.cs` +- `BackOffice/.../Pages/UserOrder/UserOrderMainPage.razor` +- `BackOffice/.../Pages/UserOrder/UserOrderMainPage.razor.cs` + +### ۶.۴ تصویر محصول در آیتم‌های سفارش تخفیفی (`image_path`) + +**مشکل:** `OrderItemDto` در proto و C# فیلد `image_path` نداشت. آیتم‌های سفارش بدون تصویر بودند. +**راه‌حل (end-to-end):** +- **Proto:** افزودن `string image_path = 9` و `string thumbnail_path = 10` به `OrderItemDto` در `discountorder.proto` +- **Backend DTO:** افزودن `ImagePath`/`ThumbnailPath` به `OrderItemDto` (C# Application layer) +- **Handler:** مپ `ImagePath = od.Product.ImagePath` در `GetOrderByIdQueryHandler` (Product nav property از قبل Include شده بود) +- **Frontend DTO:** افزودن فیلدها به `IDiscountOrderService.OrderItemDto` +- **Frontend Service:** مپ در `DiscountOrderService` +- **Frontend Dialog:** `MudImage` + `MudStack` برای نمایش تصویر کنار نام محصول + +**فایل‌های تغییر یافته:** +- `CMS/.../Protos/discountorder.proto` +- `CMS/.../GetOrderByIdQuery.cs` (OrderItemDto) +- `CMS/.../GetOrderByIdQueryHandler.cs` +- `BackOffice/.../Services/DiscountOrder/IDiscountOrderService.cs` +- `BackOffice/.../Services/DiscountOrder/DiscountOrderService.cs` +- `BackOffice/.../Pages/DiscountShop/Components/OrderDetailsDialog.razor` + +--- + +## وضعیت مقایسه‌ای نهایی + +### محصولات +| قابلیت | عادی | تخفیفی | +|---|:---:|:---:| +| لیست با MudDataGrid + server-side | ✅ | ✅ | +| فیلتر با BasePageComponent | ✅ | ✅ | +| نمایش تصویر (Image component) | ✅ | ✅ | +| برش عنوان (Truncate) | ✅ | ✅ | +| ایجاد/ویرایش/حذف | ✅ | ✅ | +| گالری تصاویر (GalleryDialog سرور-محور) | ✅ | ✅ | +| پیش‌نمایش تصویر (ImagePreviewDialog) | ✅ | ✅ | +| مدیریت دسته‌بندی (درگ و دراپ) | ✅ | ✅ | +| خروجی Excel | ✅ | ✅ | +| تگ‌های محصول (AssignTagsDialog مشترک) | ✅ | ✅ | +| فرمت قیمت (N0 + ریال) | ✅ | ✅ | +| ستون وضعیت فعال/غیرفعال | ✅ | ✅ | +| Bulk Edit/Delete/Toggle | ✅ | ❌ (طبق درخواست — بیخیال) | + +### دسته‌بندی‌ها +| قابلیت | عادی | تخفیفی | +|---|:---:|:---:| +| درخت سایدبار | ✅ | ✅ | +| گرید با MudDataGrid | ✅ | ✅ | +| ستون‌ها: شناسه، نام لاتین، عنوان، والد، ترتیب، فعال | ✅ | ✅ | +| ستون تعداد محصولات | ✅ | ✅ | +| ایجاد/ویرایش/حذف | ✅ | ✅ | +| غیرفعال‌سازی حذف دارای زیردسته | ✅ | ✅ | +| افزودن زیردسته | ✅ | ✅ | +| درگ‌اندراپ محصولات دسته | ✅ | ✅ | + +### سفارشات +| قابلیت | عادی | تخفیفی | +|---|:---:|:---:| +| BasePageComponent با فیلتر | ✅ | ✅ | +| MudDataGrid + تولبار | ✅ | ✅ | +| فیلتر شناسه / کاربر / تاریخ / وضعیت | ✅ | ✅ | +| فیلتر بازه تاریخ (از تاریخ + تا تاریخ) | ✅ | ✅ | +| ستون نام کاربر (لینک به پروفایل) | ✅ | ✅ | +| ستون وضعیت پرداخت (Chip رنگی) | ✅ | ✅ | +| ستون وضعیت ارسال (Chip رنگی) | ✅ | ✅ | +| ستون تاریخ پرداخت | ✅ | ✅ | +| ستون آدرس (truncated + tooltip) | ✅ | ✅ | +| جزئیات سفارش (Timeline + ویرایش inline) | ✅ | ✅ | +| تصویر محصول در آیتم‌های سفارش | ✅ | ✅ | +| تغییر وضعیت (MudForm + کد رهگیری + هشدار) | ✅ | ✅ | +| لغو سفارش | ✅ | ✅ (via UpdateStatus) | +| خروجی Excel | ✅ | ✅ | +| ستون‌های مالی تخفیف (DiscountBalanceUsed/GatewayAmount) | ❌ (مربوط نیست) | ✅ | +| اعمال تخفیف | ✅ | ❌ (API ندارد) | + +### گزارش فروش +| قابلیت | عادی | تخفیفی | +|---|:---:|:---:| +| فیلتر تاریخ + وضعیت | ✅ | ✅ | +| کارت‌های خلاصه | ✅ | ✅ | +| نمودار روند فروش | ✅ | ✅ | +| نمودار وضعیت سفارش‌ها | ✅ | ✅ | +| نمودار محصولات پرفروش | ✅ | ✅ | +| خروجی Excel | ✅ | ✅ | +| خروجی PDF | ✅ | ✅ | + +--- + +## موارد باقی‌مانده + +این موارد نیاز به تغییرات بیشتر دارند: + +| مورد | جزئیات | وضعیت | +|---|---|---| +| **اعمال تخفیف سفارش تخفیفی** | `discountorder.proto` فاقد `ApplyDiscountToOrder` RPC | نیاز به RPC جدید + لاجیک سرور | +| **لغو سفارش با بازپرداخت** | لغو وضعیت ✅ ولی refund/بازگشت موجودی نیاز به RPC اختصاصی | نیاز به `CancelOrder` RPC | + +> **نکته:** موارد قبلی (is_active، product_count، from_date/to_date، image_path) در **فاز ۶** حل شدند. + +--- + +## ساختار فایل‌های تغییریافته + +``` +CMS/src/ +├── CMSMicroservice.Protobuf/Protos/ +│ ├── products.proto ← is_active (filter + response field 15) +│ ├── category.proto ← product_count (response field 9) +│ ├── userorder.proto ← from_date/to_date (filter fields 11,12) +│ └── discountorder.proto ← image_path/thumbnail_path (OrderItemDto fields 9,10) +├── CMSMicroservice.Application/ +│ ├── ProductsCQ/Queries/GetCustomerProductsByFilter/ +│ │ ├── GetCustomerProductsByFilterQuery.cs ← IsActive filter +│ │ ├── GetCustomerProductsByFilterQueryHandler.cs ← IsActive filter + mapping +│ │ └── GetCustomerProductsByFilterResponseDto.cs ← IsActive field +│ ├── CategoryCQ/Queries/GetAllCategoryByFilter/ +│ │ ├── GetAllCategoryByFilterQueryHandler.cs ← manual Select + ProductCount +│ │ └── GetAllCategoryByFilterResponseDto.cs ← ProductCount field +│ └── DiscountShopCQ/Queries/GetOrderById/ +│ ├── GetOrderByIdQuery.cs ← ImagePath/ThumbnailPath +│ └── GetOrderByIdQueryHandler.cs ← Product image mapping +└── CMSMicroservice.WebApi/Services/ + ├── ProductsService.cs ← IsActive filter + response mapping + └── UserOrderService.cs ← FromDate/ToDate mapping + +BackOffice/src/BackOffice/ +├── Services/DiscountOrder/ +│ ├── IDiscountOrderService.cs ← OrderItemDto + ImagePath/ThumbnailPath +│ └── DiscountOrderService.cs ← image mapping +├── Pages/ +│ ├── Category/ +│ │ ├── CategoryMainPage.razor ← ستون ProductCount + SortOrder + زیردسته +│ │ ├── CategoryMainPage.razor.cs ← HasChildren + CreateSubcategory +│ │ ├── CreateOrUpdateCategoryDialog.razor ← MudForm + ImagePath +│ │ └── CreateOrUpdateCategoryDialog.razor.cs ← validation + loading +│ ├── DiscountShop/ +│ │ ├── DiscountShopHub.razor +│ │ ├── DiscountProductsMainPage.razor/.cs +│ │ ├── DiscountCategoriesMainPage.razor/.cs +│ │ ├── DiscountOrdersMainPage.razor/.cs +│ │ ├── SalesReports.razor +│ │ └── Components/ +│ │ ├── OrderDetailsDialog.razor ← تصویر محصول + Timeline +│ │ └── OrderDetailsDialog.razor.cs +│ ├── UserOrder/ +│ │ ├── UserOrderMainPage.razor ← فیلتر بازه تاریخ (از + تا) +│ │ ├── UserOrderMainPage.razor.cs ← FromDate/ToDate mapping +│ │ ├── OrderSalesReports.razor/.cs +│ │ └── Components/ChangeOrderStatusDialog.razor/.cs +│ └── Products/ +│ ├── ProductsMainPage.razor ← ستون IsActive + فرمت قیمت +│ └── Components/ +│ ├── CreateDialog.razor ← قیمت (ریال) +│ └── UpdateDialog.razor ← قیمت (ریال) +``` diff --git a/business/DISCOUNT-STORE-STATUS.md b/business/DISCOUNT-STORE-STATUS.md new file mode 100644 index 0000000..670059f --- /dev/null +++ b/business/DISCOUNT-STORE-STATUS.md @@ -0,0 +1,196 @@ +# فروشگاه تخفیفی — وضعیت پیاده‌سازی و تسک‌ها + +> **تاریخ:** ۱۴۰۴/۱۱/۲۲ (2026-02-11) +> **آخرین بروزرسانی:** ۱۴۰۴/۱۱/۲۳ +> **وضعیت کلی:** بکند کامل ✅ | بک‌آفیس کامل ✅ | فرانت‌آفیس کامل ✅ + +--- + +## ۱. خلاصه بیزینس + +فروشگاه تخفیفی یک فروشگاه **مجزا** از فروشگاه معمولی است که: +- محصولات خاص خود را دارد (`DiscountProduct` — نه `Product`) +- پرداخت **ترکیبی** (Hybrid) دارد: + - بخشی از **موجودی کیف پول تخفیفی** (`DiscountBalance`) کسر می‌شود + - مابقی از **درگاه پرداخت** (IPG) پرداخت می‌شود +- هر محصول یک `MaxDiscountPercent` دارد (مثلاً ۳۰٪) — حداکثر درصدی که از کیف تخفیفی قابل پرداخت است +- مالیات فقط روی مبلغ درگاه محاسبه می‌شود + +--- + +## ۲. وضعیت لایه‌ها + +### ✅ Domain Entities — کامل (۷ entity) + +| Entity | مسیر | توضیح | +|--------|------|-------| +| `DiscountProduct` | `CMS/.../Entities/DiscountStore/` | محصول (Title, Price, MaxDiscountPercent, RemainingCount, ...) | +| `DiscountProductCategory` | ↑ | دسته‌بندی درختی | +| `DiscountProductCategoryMapping` | ↑ | M:N محصول ↔ دسته‌بندی | +| `DiscountProductImage` | ↑ | گالری تصاویر | +| `DiscountShoppingCart` | ↑ | سبد خرید (UserId, ProductId, Count) | +| `DiscountOrder` | ↑ | سفارش (TotalAmount, DiscountBalanceUsed, GatewayAmountPaid, VAT) | +| `DiscountOrderDetail` | ↑ | جزئیات سفارش (UnitPrice, DiscountPercent, DiscountAmount, FinalPrice) | + +### ✅ EF Configurations — کامل (۷ فایل + ۶ migration) + +### ✅ Application (CQRS) — کامل (~۵۰ فایل) +- DiscountProductCQ: Create, Update, Delete, GetById, GetProducts + Image CRUD +- DiscountCategoryCQ: Create, Update, Delete, GetCategories +- DiscountOrderCQ: PlaceOrder, CompleteOrderPayment, UpdateOrderStatus, GetById, GetUserOrders, GetAll, SalesReport +- DiscountShoppingCartCQ: AddToCart, RemoveFromCart, UpdateCount, GetUserCart, ClearCart +- WalletCQ: ChargeDiscountWallet, VerifyDiscountWalletCharge + +### ✅ Proto Definitions — کامل (۴ فایل) + +| Proto | Namespace | RPCs | +|-------|-----------|------| +| `discountproduct.proto` | `CMSMicroservice.Protobuf.Protos.DiscountProduct` | DiscountProductContract (10 RPCs) | +| `discountcategory.proto` | `CMSMicroservice.Protobuf.Protos.DiscountCategory` | DiscountCategoryContract (4 RPCs) | +| `discountshoppingcart.proto` | `CMSMicroservice.Protobuf.Protos.DiscountShoppingCart` | DiscountShoppingCartContract (5 RPCs) | +| `discountorder.proto` | `CMSMicroservice.Protobuf.Protos.DiscountOrder` | DiscountOrderContract (7 RPCs) | + +### ✅ gRPC Services (CMS WebApi) — کامل (۴ سرویس + mapping) + +### ✅ BackOffice (Admin Panel) — کامل +- ۴ صفحه: محصولات، دسته‌بندی‌ها، سفارشات، گزارش فروش +- ۵ کامپوننت: فرم محصول، فرم دسته‌بندی، گالری، جزئیات سفارش، تغییر وضعیت +- ۶ سرویس: DiscountProduct, DiscountCategory, DiscountOrder (+ interfaces) +- NavMenu: بخش "فروشگاه تخفیفی" با ۳ لینک (محصولات، دسته‌بندی‌ها، سفارشات و گزارش) +- **یکسان‌سازی UI (بهمن ۱۴۰۴):** تمام صفحات فروشگاه تخفیفی بازنویسی شدند تا از `BasePageComponent` استفاده کنند و ظاهری یکسان با فروشگاه عادی داشته باشند → [جزئیات](../ui-modernization/BACKOFFICE-STORE-UNIFICATION.md) + +### ✅ FrontOffice (مشتری) — پیاده‌سازی شده! + +**فایل‌های اضافه/ویرایش شده:** + +| فایل | نوع | توضیح | +|------|------|-------| +| `Utilities/RouteConstants.cs` | ویرایش | اضافه شدن بخش `DiscountStore` (6 مسیر) | +| `ConfigureServices.cs` | ویرایش | ثبت 3 سرویس + 4 gRPC client | +| `Utilities/DiscountProductService.cs` | جدید | سرویس محصولات تخفیفی (GetProducts, GetById, GetCategories) | +| `Utilities/DiscountCartService.cs` | جدید | سرویس سبد خرید تخفیفی (Add, Remove, Update, Clear) | +| `Utilities/DiscountOrderService.cs` | جدید | سرویس سفارش تخفیفی (PlaceOrder, CompletePayment, GetOrders) | +| `Pages/DiscountStore/Products.razor(.cs)` | جدید | لیست محصولات (جستجو + فیلتر دسته‌بندی + صفحه‌بندی) | +| `Pages/DiscountStore/ProductDetail.razor(.cs)` | جدید | جزئیات محصول + گالری + افزودن به سبد | +| `Pages/DiscountStore/Cart.razor(.cs)` | جدید | سبد خرید (Desktop: Table / Mobile: Cards) | +| `Pages/DiscountStore/Checkout.razor(.cs)` | جدید | پرداخت ترکیبی (آدرس + اسلایدر تخفیف + درگاه) | +| `Pages/DiscountStore/Orders.razor(.cs)` | جدید | لیست سفارشات (پرداخت/ارسال) | +| `Pages/DiscountStore/OrderDetail.razor(.cs)` | جدید | جزئیات سفارش + خلاصه مالی | +| `Pages/Profile/Index.razor.cs` | ویرایش | تایل "فروشگاه تخفیفی" در داشبورد | +| `Shared/MainLayout.razor` | ویرایش | لینک ناوبری دسکتاپ + drawer موبایل | +| `wwwroot/css/site.css` | ویرایش | ریجن CSS اختصاصی Discount Store | + +--- + +## ۳. تسک‌های FrontOffice (ترتیب اجرا) + +### تسک ۱: Routes — اضافه کردن مسیرها +``` +فایل: RouteConstants.cs +اضافه: public static class DiscountStore { + Products = "/discount-store" + ProductDetail = "/discount-store/product/" + Cart = "/discount-store/cart" + Checkout = "/discount-store/checkout" + Orders = "/discount-store/orders" + OrderDetail = "/discount-store/order/" +} +``` + +### تسک ۲: gRPC Clients — ثبت DI +``` +فایل: ConfigureServices.cs +اضافه: + using CMSMicroservice.Protobuf.Protos.DiscountProduct; + using CMSMicroservice.Protobuf.Protos.DiscountCategory; + using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; + using CMSMicroservice.Protobuf.Protos.DiscountOrder; + + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); +``` + +### تسک ۳: Services — سرویس‌های FrontOffice +``` +فایل‌های جدید در Utilities/: + DiscountProductService.cs — GetProducts (فیلتر + صفحه‌بندی), GetById, GetCategories + DiscountCartService.cs — Add, Remove, Update, GetCart, Clear + event OnChange + DiscountOrderService.cs — PlaceOrder, CompletePayment, GetUserOrders, GetOrderById +``` + +### تسک ۴: صفحات Blazor +``` +فایل‌های جدید در Pages/DiscountStore/: + Products.razor + .cs — لیست محصولات (فیلتر دسته‌بندی + جستجو + صفحه‌بندی) + ProductDetail.razor + .cs — جزئیات محصول + گالری + افزودن به سبد + Cart.razor + .cs — سبد خرید (نمایش تخفیف هر آیتم) + Checkout.razor + .cs — پرداخت (انتخاب آدرس + تعیین مبلغ از تخفیفی + درگاه) + Orders.razor + .cs — لیست سفارشات + OrderDetail.razor + .cs — جزئیات سفارش + وضعیت ارسال +``` + +### تسک ۵: Dashboard Tile +``` +فایل: Profile/Index.razor.cs +اضافه: تایل "فروشگاه تخفیفی" بعد از تایل "فروشگاه" موجود +``` + +### تسک ۶: Navigation +``` +فایل: MainLayout.razor +اضافه: لینک "فروشگاه تخفیفی" در drawer موبایل + bottom nav (اختیاری) +``` + +### تسک ۷: CSS +``` +فایل: site.css +اضافه: استایل‌های اختصاصی (checkout progress, discount badge, ...) +``` + +--- + +## ۴. فلوی پرداخت (مهم!) + +``` +کاربر سبد خرید دارد + ↓ +صفحه Checkout: + ├─ انتخاب آدرس تحویل + ├─ نمایش خلاصه سبد: + │ هر محصول: قیمت × تعداد + │ تخفیف هر محصول: price × count × maxDiscountPercent / 100 + │ جمع کل / جمع تخفیف / مبلغ درگاه + ├─ موجودی تخفیفی کاربر: XXX تومان + ├─ کاربر تعیین می‌کند چقدر از تخفیفی استفاده کند (≤ سقف مجاز) + └─ [پرداخت] + ↓ +PlaceOrder RPC: + ├─ بررسی موجودی + محاسبه + ├─ ساخت سفارش (Pending) + ├─ رزرو موجودی انبار + ├─ اگر gateway_amount > 0 → payment_url برگردانده می‌شود + └─ اگر gateway_amount = 0 → سفارش مستقیم تکمیل + ↓ +ریدایرکت به درگاه (اگر لازم باشد) + ↓ +CompleteOrderPayment RPC (بعد از callback): + ├─ success → کسر DiscountBalance + تأیید فروش + ثبت تراکنش + └─ failure → آزادسازی رزرو انبار + لغو سفارش +``` + +--- + +## ۵. تخمین زمان + +| تسک | تخمین | +|-----|-------| +| Routes + DI + Services | ۱ ساعت | +| Products + ProductDetail | ۲ ساعت | +| Cart | ۱ ساعت | +| Checkout (پیچیده‌ترین بخش) | ۲ ساعت | +| Orders + OrderDetail | ۱ ساعت | +| Dashboard tile + Nav | ۰.۵ ساعت | +| CSS + Polish | ۰.۵ ساعت | +| **مجموع** | **~۸ ساعت** | diff --git a/cms/FILE-MANAGEMENT-ARCHITECTURE.md b/cms/FILE-MANAGEMENT-ARCHITECTURE.md new file mode 100644 index 0000000..6b922d7 --- /dev/null +++ b/cms/FILE-MANAGEMENT-ARCHITECTURE.md @@ -0,0 +1,251 @@ +# 📁 معماری مدیریت فایل و تصاویر — CMS + +> **تاریخ:** ۱۴۰۴/۱۱/۲۸ (February 17, 2026) +> **وضعیت:** ✅ عملیاتی +> **Build:** 0 Error (هر ۳ پروژه) ✅ + +--- + +## ۱. پیش‌زمینه + +سیستم قبلی از **FMS (File Management Service)** در آدرس `https://dl.afrino.co` استفاده می‌کرد که غیرقابل دسترس/ناسازگار شده بود. در چندین فاز، معماری فایل‌ها به صورت کامل بازنویسی شد: + +| فاز | شرح | وضعیت | +|-----|------|-------| +| ۱. حذف FMS | حذف کامل ۳ فایل مرده FMS | ✅ | +| ۲. حالت base64 | ذخیره data URI مستقیم در DB | ✅ (بازنشسته) | +| ۳. ذخیره دیسکی | فایل در دیسک + مسیر در DB + تبدیل به base64 هنگام serve | ✅ | +| ۴. **سرو HTTP عمومی** | **اندپوینت `/uploads/{path}` + Fallback FMS** | **✅ جدید** | + +--- + +## ۲. معماری نهایی + +``` +BackOffice (Blazor WASM) + │ + │ MudFileUpload → IBrowserFile → byte[] → gRPC ImageFileModel + │ + ▼ +CMS gRPC Service + │ + │ proto ImageFileModel → Command.ImageFileBytes + │ + ▼ +MediatR Handler + │ + │ IFileManager.UploadImageAsync(folder, bytes, mime, name) + │ + ▼ +LocalFileManager + │ + ├─ Main Image → Uploads/Images/{folder}/{guid}.jpg (1200×1200, JPEG Q75) + ├─ Thumbnail → Uploads/Images/{folder}/{guid}_thumb.jpg (300×300, JPEG Q75) + │ + │ Returns: { Main.Path, Thumbnail.Path } (relative paths stored in DB) + │ + ▼ +ImagePathResolverInterceptor (gRPC response) + │ + │ Walks all response fields → reads file from disk → data:{mime};base64,{bytes} + │ + ▼ +BackOffice / FrontOffice ← receives base64 data URI directly in proto fields +``` + +--- + +## ۳. اجزای کلیدی + +### ۳.۱ `IFileManager` — Interface + +**مسیر:** `Application/Common/FileManager/IFileManager.cs` + +```csharp +public interface IFileManager +{ + Task UploadAsync(string folder, byte[] file, string mime, string? fileName, CancellationToken ct); + Task UploadImageAsync(string folder, byte[] file, string mime, string? fileName, CancellationToken ct); + Task DeleteAsync(string path, CancellationToken ct); + string? ResolveImageUrl(string? path); +} +``` + +- **`UploadAsync`** — آپلود فایل خام +- **`UploadImageAsync`** — بهینه‌سازی + ساخت thumbnail خودکار (SixLabors.ImageSharp) +- **`ResolveImageUrl`** — تبدیل مسیر نسبی به data URI (base64) + +### ۳.۲ `LocalFileManager` — پیاده‌سازی + +**مسیر:** `Infrastructure/Services/LocalFileManager.cs` + +| ویژگی | مقدار | +|-------|-------| +| ریشه آپلود | `FileStorage:UploadPath` یا `AppContext.BaseDirectory/Uploads` | +| فرمت تصویر اصلی | JPEG, Quality 75, حداکثر 1200×1200 | +| فرمت thumbnail | JPEG, Quality 75, حداکثر 300×300 | +| نام‌گذاری فایل | `{Guid}.jpg` + `{Guid}_thumb.jpg` | +| DI Registration | `services.AddSingleton()` | + +### ۳.۳ `ImagePathResolverInterceptor` — gRPC Interceptor + +**مسیر:** `WebApi/Interceptors/ImagePathResolverInterceptor.cs` + +اینترسپتور **خودکار** تمام فیلدهای تصویری را در response‌های gRPC پیدا کرده و مسیر نسبی را به data URI تبدیل می‌کند. + +**فیلدهای شناسایی‌شده:** +- `image_path`, `thumbnail_path`, `image_thumbnail_path` +- `featured_image_path`, `featured_image_thumbnail_path` +- `hero_image_path`, `product_thumbnail_path` +- `avatar_path`, `avatar_url` + +**قابلیت‌ها:** +- Walk بازگشتی پیام‌های proto +- پشتیبانی از `string` ساده و `Google.Protobuf.WellKnownTypes.StringValue` +- پشتیبانی از فیلدهای `repeated` (collection‌های تو در تو) +- اگر مقدار `data:` یا `http` باشد → رد می‌شود (تبدیل نمی‌شود) + +### ۳.۴ `LoggingBehaviour` — پاکسازی لاگ + +**مسیر:** `WebApi/Common/Behaviours/LoggingBehaviour.cs` + +- فرمت لاگ: `JsonFormatter.Default.Format()` به جای `{@Request}` +- پاکسازی فیلدهای باینری با regex (`File`, `ImageFile`, `image_file`, `file`) +- محدودیت طول لاگ: حداکثر 2000 کاراکتر + +### ۳.۵ `UploadsController` — سرو عمومی فایل‌ها (HTTP) 🆕 + +**مسیر:** `WebApi/Controllers/UploadsController.cs` + +اندپوینت عمومی REST برای سرو مستقیم تصاویر بدون نیاز به base64. مناسب برای بارگذاری تصاویر در تگ `` و کاهش پهنای باند. + +| ویژگی | مقدار | +|-------|-------| +| مسیر | `GET /uploads/{**path}` | +| احراز هویت | `[AllowAnonymous]` — عمومی | +| کش مرورگر | `ResponseCache 86400` ثانیه (۲۴ ساعت) | +| Content-Type | تشخیص خودکار از پسوند فایل (`FileExtensionContentTypeProvider`) | +| Range Requests | ✅ فعال (`enableRangeProcessing: true`) | +| محافظت مسیر | جلوگیری از path traversal (`..`, `\`, `Path.GetFullPath` validation) | + +**FMS Fallback:** +اگر فایل محلی وجود نداشته باشد و تنظیم `FMS:Address` پر باشد: +1. فایل از `{FMS:Address}/{relativePath}` دانلود می‌شود +2. Content-Type بررسی می‌شود (فقط `image/*` و `application/pdf` مجاز) +3. فایل روی دیسک محلی ذخیره و کش می‌شود +4. سپس فایل محلی سرو می‌شود + +``` +Client → GET /uploads/Images/BlogPosts/abc.jpg + │ + ├─ فایل محلی وجود دارد? → سرو مستقیم از دیسک + │ + └─ فایل محلی وجود ندارد? + └─ FMS:Address تنظیم شده? + ├─ بله → دانلود از dl.afrino.co → ذخیره محلی → سرو + └─ خیر → 404 Not Found +``` + +**وابستگی‌ها:** +- `IHttpClientFactory` با named client `"FMS"` (timeout: 30 ثانیه) +- ثبت در `Program.cs`: `builder.Services.AddHttpClient("FMS", ...)` + +--- + +## ۴. Proto Messages — ImageFileModel + +هر حوزه (DiscountProduct, BlogPost, SitePage) پیام مستقل `ImageFileModel` خود را دارد: + +### DiscountProduct +```protobuf +message ImageFileModel { + bytes file = 1; + string mime = 2; + string file_name = 3; +} +``` +**استفاده در:** `CreateDiscountProductRequest`, `UpdateDiscountProductRequest` + +### BlogPost +```protobuf +message BlogImageFileModel { + bytes file = 1; + string mime = 2; + string file_name = 3; +} +``` +**استفاده در:** `CreateBlogPostRequest`, `UpdateBlogPostRequest` + +### SitePage +```protobuf +message SitePageImageFileModel { + bytes file = 1; + string mime = 2; + string file_name = 3; +} +``` +**استفاده در:** `UpdateSitePageRequest`, `CreateSitePageSectionRequest`, `UpdateSitePageSectionRequest` + +--- + +## ۵. جریان آپلود تصویر (مثال: BlogPost) + +``` +1. کاربر در BackOffice → MudFileUpload → انتخاب فایل +2. BlogPostEditDialog.OnImageSelected() + → IBrowserFile.OpenReadStream() → byte[] + ContentType + FileName + → پیش‌نمایش base64 در UI +3. Submit → BlogPostEditDto { ImageFile = bytes, ImageMime, ImageFileName } +4. BlogPostService.CreateAsync() + → BlogImageFileModel { File = ByteString.CopyFrom(bytes), Mime, FileName } + → gRPC CreateBlogPostRequest +5. CMS BlogPostService (gRPC) → CreateBlogPostCommand + { ImageFileBytes = request.ImageFile.File.ToByteArray(), ... } +6. CreateBlogPostCommandHandler.Handle() + → _fileManager.UploadImageAsync("Images/BlogPosts", bytes, mime, name) + → post.FeaturedImagePath = result.Main.Path + → post.FeaturedImageThumbnailPath = result.Thumbnail.Path +7. Response → ImagePathResolverInterceptor + → featured_image_path → data:image/jpeg;base64,... + → featured_image_thumbnail_path → data:image/jpeg;base64,... +8. BackOffice / FrontOffice → نمایش مستقیم base64 data URI +``` + +--- + +## ۶. فایل‌های حذف‌شده (کد مرده FMS) + +| فایل | شرح | +|------|------| +| `Infrastructure/Services/FmsFileManager.cs` | پیاده‌سازی قدیمی FMS (HTTP upload) | +| `Application/Common/FileManager/FileManagementService.cs` | سرویس قدیمی مدیریت فایل | +| `Application/Common/FileManager/IFileManagementService.cs` | اینترفیس قدیمی | + +--- + +## ۷. تنظیمات + +### `appsettings.json` (CMS) +```json +{ + "FileStorage": { + "UploadPath": "/app/Uploads" + } +} +``` + +### محدودیت حجم gRPC +```csharp +// Program.cs +services.AddGrpc(o => o.MaxReceiveMessageSize = 50 * 1024 * 1024); // 50MB +``` + +### FrontOffice — `UrlUtility.GetImageUrl()` +```csharp +public static string GetImageUrl(string? path) +{ + if (string.IsNullOrWhiteSpace(path)) return string.Empty; + if (path.StartsWith("data:") || path.StartsWith("http")) return path; + return $"{DownloadUrl?.TrimEnd('/')}/{path.TrimStart('/')}"; +} +``` diff --git a/cms/REGISTRATION-FLOW-FIXES.md b/cms/REGISTRATION-FLOW-FIXES.md new file mode 100644 index 0000000..a175003 --- /dev/null +++ b/cms/REGISTRATION-FLOW-FIXES.md @@ -0,0 +1,187 @@ +# 🔐 فیکس فلوی ثبت‌نام / ورود FrontOffice + +**تاریخ:** بهمن ۱۴۰۴ (February 2026) + +--- + +## 📋 خلاصه + +بررسی کامل فلوی ثبت‌نام و ورود FrontOffice از UI تا دیتابیس انجام شد. **۳ باگ بحرانی** شناسایی و رفع شده: + +| # | شدت | مشکل | فایل | +|---|------|-------|------| +| 1 | 🔴 بحرانی | کاربران جدید ثبت‌نام نمی‌شوند | `UserCQ/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs` | +| 2 | 🔴 بحرانی | امضای قرارداد همیشه شکست می‌خورد | `UserCQ/AcceptContract/AcceptContractCommandHandler.cs` | +| 3 | 🟡 متوسط | منوی کناری وضعیت نادرست نشان می‌دهد | `FrontOffice.Main/Utilities/AuthService.cs` | + +--- + +## 🔴 باگ ۱ — کاربران جدید ثبت‌نام نمی‌شوند + +### مشکل + +FrontOffice از `UserContract.UserContractClient` (user.proto) استفاده می‌کند → `UserCQ/VerifyOtpTokenCommandHandler`. این handler وقتی کاربر یافت نمی‌شد فقط خطای **«کاربر یافت نشد»** برمی‌گرداند و کاربر جدید ایجاد **نمی‌کرد**. + +لاجیک ایجاد کاربر (شامل: اعتبارسنجی کد معرف، درخت باینری، موقعیت شاخه) در `OtpTokenCQ/VerifyOtpTokenCommandHandler` بود — سرویسی که FrontOffice اصلاً از آن استفاده نمی‌کند. + +### رفع + +اضافه شدن لاجیک کامل ایجاد کاربر جدید به `UserCQ/VerifyOtpTokenCommandHandler`: + +``` +if (user == null) +{ + // ۱. اعتبارسنجی کد معرف (ParentReferralCode) — الزامی + // ۲. بررسی وجود معرف و فعال بودن عضویت باشگاه + // ۳. بررسی ظرفیت (حداکثر ۲ زیرمجموعه مستقیم) + // ۴. تعیین شاخه (چپ اول، بعد راست) + // ۵. ایجاد User + UserRole + UserWallet + // ۶. رویدادهای دامنه: CreateNewUserEvent, CreateNewUserRoleEvent, CreateNewUserWalletEvent + // ۷. بارگذاری مجدد کاربر با روابط کامل + // ۸. تولید JWT token +} +``` + +### اعتبارسنجی‌ها + +| مرحله | شرط | پیام خطا | +|-------|------|---------| +| کد معرف | خالی یا null | «کد معرف الزامی است» | +| معرف | وجود نداشته باشد | «معرف وجود ندارد» | +| عضویت باشگاه | غیرفعال باشد | «لینک دعوت معرف فعال نیست» | +| ظرفیت | بیش از ۱ فرزند | «ظرفیت معرف تکمیل است» | +| شاخه | هر دو پُر باشند | «ظرفیت معرف تکمیل است» | + +### فایل + +`CMS/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs` + +--- + +## 🔴 باگ ۲ — امضای قرارداد همیشه شکست می‌خورد + +### مشکل + +`AcceptContractCommandHandler` از `_currentUserService.Username` برای پیدا کردن OTP و کاربر بر اساس شماره موبایل استفاده می‌کرد: + +```csharp +// ❌ قبل — Username = "{FirstName} {LastName}" نه شماره موبایل! +var otpToken = await _context.OtpTokens + .Where(x => x.Mobile == _currentUserService.Username ...) +var user = await _context.Users + .Where(x => x.Mobile == _currentUserService.Username ...) +``` + +**`CurrentUserService.Username`** مقدار `ClaimTypes.Name` را برمی‌گرداند که در JWT به صورت `"{FirstName} {LastName}"` ذخیره شده — **نه شماره موبایل!** + +### رفع + +اول کاربر بر اساس `UserId` (از `ClaimTypes.NameIdentifier`) پیدا شود، سپس از `user.Mobile` برای جستجوی OTP استفاده شود: + +```csharp +// ✅ بعد — ابتدا کاربر از UserId پیدا شود +var userId = long.Parse(_currentUserService.UserId); +var user = await _context.Users + .Where(x => x.Id == userId)... + +var otpToken = await _context.OtpTokens + .Where(x => x.Mobile == user.Mobile ...) +``` + +### فایل + +`CMS/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs` + +--- + +## 🟡 باگ ۳ — `IsCompleteRegister()` داده قدیمی می‌خواند + +### مشکل + +متد sync در `AuthService`: + +```csharp +// ❌ قبل — GetAwaiter() بدون GetResult() عملیات async را اجرا نمی‌کند +InitUserAuthInfo().GetAwaiter(); +``` + +`GetAwaiter()` فقط یک شیء awaiter برمی‌گرداند ولی عملیات را اجرا **نمی‌کند**. در نتیجه `_userAuthInfo` مقداردهی نمی‌شود و منوی کناری (`MainLayout.razor`) وضعیت نادرست نشان می‌دهد. + +### رفع + +```csharp +// ✅ بعد — عملیات async را همگام اجرا می‌کند +InitUserAuthInfo().GetAwaiter().GetResult(); +``` + +### محل استفاده + +`MainLayout.razor` خطوط ۱۰۶ و ۱۰۹: +```razor +Disabled="@(!AuthService.IsCompleteRegister())" +``` + +### فایل + +`FrontOffice/src/FrontOffice.Main/Utilities/AuthService.cs` + +--- + +## 🏗️ معماری فلوی ثبت‌نام (بعد از فیکس) + +``` +┌─────────────────────────────────────────────────────────────┐ +│ FrontOffice (Blazor WASM) │ +│ │ +│ LoginPage → SendOtp → VerifyOtp(mobile, code, referral) │ +│ ↓ gRPC-Web │ +├─────────────────────────────────────────────────────────────┤ +│ CMS Backend (gRPC) │ +│ │ +│ UserContract.VerifyOtpToken │ +│ ↓ │ +│ UserCQ/VerifyOtpTokenCommandHandler │ +│ │ │ +│ ├── OTP صحیح؟ → ❌ خطا │ +│ │ │ +│ ├── کاربر موجود؟ → ✅ تولید JWT Token │ +│ │ │ +│ └── کاربر جدید؟ │ +│ ├── اعتبارسنجی کد معرف │ +│ ├── بررسی ظرفیت درخت باینری │ +│ ├── ایجاد User + UserRole + UserWallet │ +│ ├── رویدادهای دامنه │ +│ └── تولید JWT Token │ +│ │ +│ بعد از ثبت‌نام → RegisterWizard: │ +│ Step 1: اطلاعات شخصی │ +│ Step 2: امضای قرارداد (AcceptContract + OTP) │ +│ Step 3: خرید پکیج │ +├─────────────────────────────────────────────────────────────┤ +│ Database │ +│ │ +│ Users ─── UserRoles ─── UserWallets │ +│ └── NetworkParentId, LegPosition (Binary Tree) │ +│ └── UserContracts, ClubMembership │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 📝 فایل‌های تغییر یافته + +| فایل | تغییر | +|------|-------| +| `CMS/.../UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs` | اضافه شدن لاجیک ایجاد کاربر جدید (86→165 خط) | +| `CMS/.../UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs` | تغییر lookup از Username به UserId | +| `FrontOffice/.../Utilities/AuthService.cs` | اضافه شدن `.GetResult()` به `GetAwaiter()` | + +--- + +## ✅ بیلد + +``` +CMS: 0 Error(s) ✅ +FrontOffice: 0 Error(s) ✅ +BackOffice: 0 Error(s) ✅ +``` diff --git a/ui-modernization/BACKOFFICE-ARCHITECTURE.md b/ui-modernization/BACKOFFICE-ARCHITECTURE.md new file mode 100644 index 0000000..1de10a9 --- /dev/null +++ b/ui-modernization/BACKOFFICE-ARCHITECTURE.md @@ -0,0 +1,359 @@ +# 🏗️ BackOffice — مرجع معماری و الگوها + +> **تاریخ:** ۱۴۰۴/۱۱/۲۴ (February 13, 2026) +> **پروژه:** BackOffice Admin Panel (Blazor WebAssembly) + +--- + +## ۱. معماری کلی + +``` +┌─────────────────────────────────────────────────┐ +│ BackOffice │ +│ (Blazor WebAssembly) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ +│ │ MudBlazor│ │ Mapster │ │ DateTimeCvt │ │ +│ │ v8 │ │ (mapping)│ │ (تاریخ شمسی) │ │ +│ └──────────┘ └──────────┘ └──────────────┘ │ +│ │ │ │ │ +│ ┌─────────────────────────────────────────┐ │ +│ │ Pages / Components / Shared │ │ +│ │ BasePageComponent, Hub Pages, Dialogs │ │ +│ └─────────────────────────────────────────┘ │ +│ │ │ +│ ┌──────────────┐ ┌──────────────────┐ │ +│ │ gRPC Clients │ │ HTTP REST Services│ │ +│ │ (Protobuf) │ │ (DiscountShop) │ │ +│ └──────┬───────┘ └────────┬─────────┘ │ +└─────────┼──────────────────────┼─────────────────┘ + │ │ + ▼ ▼ +┌──────────────────────────────────────────┐ +│ CMS Microservice │ +│ (ASP.NET Core + gRPC) │ +│ Domain → Application (CQRS) → Infra │ +└──────────────────────────────────────────┘ +``` + +--- + +## ۲. Technology Stack + +| لایه | تکنولوژی | نسخه | +|------|----------|------| +| Frontend Framework | Blazor WebAssembly | .NET 9 | +| UI Library | MudBlazor | v8 | +| Backend Communication (عادی) | gRPC / Protobuf | — | +| Backend Communication (تخفیفی) | HTTP REST | — | +| Object Mapping | Mapster | — | +| تاریخ شمسی | DateTimeConverterCL | — | +| Client State | Blazored.LocalStorage | — | +| Auth | JWT Role-based | Administrator, Admin, Author | +| Permission | IAuthorizationService.HasPermissionAsync | 18 permission | + +--- + +## ۳. ساختار پوشه‌ها + +``` +BackOffice/src/BackOffice/ +├── Common/ +│ ├── BaseComponents/ ← کامپوننت‌های پایه (BasePageComponent, DateRangePicker, Image) +│ ├── Utilities/ ← RouteConstance, Extensions, Helpers +│ └── ... +├── Pages/ +│ ├── Category/ ← دسته‌بندی فروشگاه عادی +│ ├── Products/ ← محصولات فروشگاه عادی +│ ├── UserOrder/ ← سفارشات + گزارش فروش (Hub) +│ ├── DiscountShop/ ← فروشگاه تخفیفی (محصولات + دسته‌بندی + سفارشات) +│ │ └── Components/ ← دیالوگ‌ها و کامپوننت‌های اختصاصی +│ ├── Inventory/ ← انبارداری (4 صفحه) +│ ├── Package/ ← پکیج‌ها +│ ├── Commission/ ← کمیسیون (5 صفحه) +│ ├── Network/ ← شبکه (4 صفحه) +│ ├── Club/ ← باشگاه مشتریان (Hub: اعضا + آمار + فیچرها) +│ ├── Blog/ ← بلاگ (Hub: پست + دسته‌بندی + تگ) +│ ├── Content/ ← صفحات سایت +│ ├── Wallet/ ← کیف‌پول (تب‌ها: لیست + تاریخچه) +│ ├── Contract/ ← قراردادها +│ ├── SystemManagement/ ← سیستم (Hub: تنظیمات + Worker + Health) +│ └── ... +├── Services/ +│ ├── DiscountProduct/ ← IDiscountProductService + implementation +│ ├── DiscountCategory/ ← IDiscountCategoryService + implementation +│ ├── DiscountOrder/ ← IDiscountOrderService + implementation +│ └── Authorization/ ← IAuthorizationService +├── Shared/ +│ ├── MainLayout.razor ← لایوت اصلی (AppBar + NavMenu + MudContainer) +│ ├── NavMenu.razor ← منوی ناوبری +│ ├── GlobalSearch.razor ← جستجوی سراسری +│ └── AppBreadcrumb.razor ← Breadcrumb فارسی +└── wwwroot/ + ├── js/main.js ← jsSaveAsFile (Excel export) + └── appsettings.json ← تنظیمات endpoints +``` + +--- + +## ۴. الگوهای اصلی + +### ۴.۱ BasePageComponent — پترن صفحات لیست + +**هر صفحه لیست** از `BasePageComponent` استفاده می‌کند: + +``` +┌──────────────────────────────────────┐ +│ BasePageComponent │ +│ ┌────────────────────────────────┐ │ +│ │ 📋 Filter Panel (collapsible) │ │ +│ │ [فیلد ۱] [فیلد ۲] [فیلد ۳] │ │ +│ │ [پاک کردن فیلتر] [جستجو] │ │ +│ └────────────────────────────────┘ │ +│ ┌────────────────────────────────┐ │ +│ │ 📊 Content (DataGrid) │ │ +│ │ ToolBar: [عنوان] [Excel] [+] │ │ +│ │ Columns: ... │ │ +│ │ Pager: 20/50/100 │ │ +│ └────────────────────────────────┘ │ +└──────────────────────────────────────┘ +``` + +**فایل:** `Common/BaseComponents/BasePageComponent.razor` + +**پراپرتی‌ها:** +- `RenderFragment Filters` — محتوای فیلتر +- `RenderFragment Content` — محتوای اصلی +- `EventCallback OnSubmitClick` — کلیک جستجو +- `EventCallback OnClearFilterClick` — کلیک پاک کردن +- `bool IsFiltered` — آیا فیلتر فعال است (نشان‌دهنده badge «فعال») + +--- + +### ۴.۲ Hub Pages — پترن ادغام صفحات + +صفحات مرتبط در یک Hub با `MudTabs` ادغام می‌شوند: + +| Hub | Route‌ها | تب‌ها | +|-----|---------|-------| +| `OrdersHub` | `/OrdersPage/`, `/OrdersSalesReportsPage/` | سفارشات + گزارش فروش | +| `DiscountShopHub` | `/discount-shop`, `/discount-orders`, `/discount-sales-reports` | سفارشات + گزارش فروش | +| `ClubHub` | `/club`, `/club/members`, `/club/statistics` | اعضا + آمار | +| `BlogHub` | `/blog`, `/blog/posts`, `/blog/categories`, `/tags` | پست + دسته‌بندی + تگ | +| `SystemHub` | `/system`, `/system/configuration`, `/system/worker-control`, `/system/health` | تنظیمات + Worker + Health | + +--- + +### ۴.۳ Code-Behind — پترن جداسازی markup/logic + +``` +MyPage.razor → فقط HTML/Razor markup +MyPage.razor.cs → partial class + [Inject] + methods +``` + +**قوانین:** +1. فایل‌هایی که سرویس inject دارند **باید** code-behind داشته باشند (محدودیت Razor source generator) +2. سرویس‌های global (`_Imports.razor`) **نباید** دوباره `[Inject]` شوند +3. `namespace` باید با مسیر فایل match کند + +**سرویس‌های Global (از `_Imports.razor`):** + +| سرویس | نام متغیر | توضیح | +|--------|-----------|-------| +| `IDialogService` | `DialogService` | دیالوگ MudBlazor | +| `ISnackbar` | `Snackbar` | نوتیفیکیشن MudBlazor | +| `IJSRuntime` | `jsRuntime` | ⚠️ حرف کوچک `j` | +| `NavigationManager` | `Navigation` | ناوبری | +| `ILocalStorageService` | `LocalStorageService` | ذخیره محلی | +| `AuthenticationStateProvider` | `AuthenticationStateProvider` | احراز هویت | + +--- + +### ۴.۴ Excel Export — پترن خروجی CSV + +```csharp +private async Task ExportToExcel() +{ + var sb = new StringBuilder(); + sb.AppendLine("ستون ۱,ستون ۲,ستون ۳"); // هدر فارسی + foreach (var item in items) + { + sb.AppendLine($"{EscapeCsv(item.Col1)},{item.Col2},{item.Col3}"); + } + var bytes = Encoding.UTF8.GetPreamble() // UTF-8 BOM + .Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray(); + var base64 = Convert.ToBase64String(bytes); + await jsRuntime.InvokeVoidAsync("jsSaveAsFile", "filename.csv", base64); +} + +private string EscapeCsv(string? value) +{ + if (string.IsNullOrEmpty(value)) return ""; + if (value.Contains(',') || value.Contains('"') || value.Contains('\n')) + return $"\"{value.Replace("\"", "\"\"")}\""; + return value; +} +``` + +**صفحات دارای Excel:** Products, UserOrders, ClubMembers, WithdrawalRequests, WeeklyReports, StockMovements, Users, DiscountOrders, ManualPayments, Inventory, DiscountProducts + +--- + +### ۴.۵ Server-Side DataGrid — پترن بارگذاری صفحه‌ای + +```razor + +``` + +```csharp +private async Task> LoadServerData(GridState state) +{ + var filter = new MyFilter + { + PageNumber = state.Page + 1, // MudDataGrid is 0-based + PageSize = state.PageSize + }; + var (items, totalCount, _) = await MyService.GetAsync(filter); + return new GridData { Items = items, TotalItems = totalCount }; +} +``` + +--- + +### ۴.۶ Permission System + +NavMenu از `IAuthorizationService.HasPermissionAsync()` برای نمایش/مخفی کردن آیتم‌ها استفاده می‌کند: + +| Permission | صفحه(ها) | +|-----------|----------| +| `dashboard.view` | داشبورد | +| `packages.manage` | پکیج‌ها | +| `products.manage` | محصولات + دسته‌بندی + ویرایش دسته‌جمعی | +| `orders.view` | سفارشات | +| `inventory.manage` | انبارداری (4 صفحه) | +| `discountshop.manage` | فروشگاه تخفیفی | +| `users.view` | کاربران | +| `roles.manage` | نقش‌ها | +| `manualpayments.create` | پرداخت دستی | +| `blog.manage` | بلاگ | +| `sitepages.manage` | صفحات سایت | +| `publicmessages.view` | پیام‌های عمومی | +| `settings.manage_configuration` | تنظیمات سیستم | + +--- + +## ۵. مسیرهای (Routing) + +### مسیرهای ثابت (`RouteConstance.cs`) +``` +/ → Dashboard +/PackagePage/ → Packages +/ProductsPage/ → Products +/CategoryPage/ → Categories +/OrdersPage/ → Orders Hub +/OrdersSalesReportsPage/ → Orders Sales Reports +/InventoryPage/ → Inventory +/InventoryLowStockPage/ → Low Stock +/InventoryWarehousesPage/ → Warehouses +/InventoryMovementsPage/ → Stock Movements +/UserPage/ → Users +/RolePage/ → Roles +/ProductsBulkEditPage/ → Bulk Edit +/ProductCategoriesPage/ → Product-Category DragDrop +/CategoryProductsPage/ → Category-Product DragDrop +``` + +### مسیرهای hardcode (فروشگاه تخفیفی + سایر) +``` +/discount-products → Discount Products +/discount-categories → Discount Categories +/discount-shop → Discount Orders Hub +/discount-orders → Discount Orders +/discount-sales-reports → Discount Sales Reports +/commission/* → Commission pages +/network/* → Network pages +/club/* → Club pages +/blog/* → Blog pages +/wallets → Wallets +/contracts → Contracts +/payment/manual-payments → Manual Payments +/system/* → System pages +/settings → Settings +/content/pages → Content Pages +/public-messages → Public Messages +``` + +--- + +## ۶. ارتباط فروشگاه عادی vs تخفیفی + +| جنبه | فروشگاه عادی | فروشگاه تخفیفی | +|------|-------------|---------------| +| **سرویس محصولات** | gRPC `ProductsContractClient` | HTTP `IDiscountProductService` | +| **سرویس دسته‌بندی** | gRPC `CategoryContractClient` | HTTP `IDiscountCategoryService` | +| **سرویس سفارشات** | gRPC `UserOrderContractClient` | HTTP `IDiscountOrderService` | +| **Entity بکند** | `Product` | `DiscountProduct` | +| **پرداخت** | فقط درگاه | ترکیبی (کیف تخفیفی + درگاه) | +| **فیلد اختصاصی** | — | `MaxDiscountPercent` | +| **UI Pattern** | BasePageComponent | BasePageComponent (یکسان) | +| **ستون‌ها** | یکسان | یکسان + ستون تخفیف | + +--- + +## ۷. نقشه NavMenu + +``` +داشبورد +───────────────────── +کمیسیون و شبکه +├── کمیسیون (NavGroup) +│ ├── داشبورد کمیسیون +│ ├── گزارش‌های هفتگی +│ ├── پرداخت کاربران +│ ├── درخواست‌های برداشت [Badge] +│ └── گزارش برداشت‌ها +├── شبکه (NavGroup) +│ ├── درخت شبکه +│ ├── گزارش موجودی‌ها +│ └── آمار شبکه +└── باشگاه مشتریان (NavGroup) + ├── اعضا و آمار + └── فیچرهای باشگاه +───────────────────── +فروشگاه [AuthorizeView: Administrator] +├── پکیج‌ها +├── فروشگاه عادی (NavGroup) +│ ├── محصولات +│ ├── دسته‌بندی‌ها +│ └── سفارشات و گزارش +├── انبارداری (NavGroup) +│ ├── موجودی انبار +│ ├── محصولات کم‌موجود +│ ├── مدیریت انبارها +│ └── تاریخچه تغییرات +└── فروشگاه تخفیفی (NavGroup) + ├── محصولات + ├── دسته‌بندی‌ها + └── سفارشات و گزارش +───────────────────── +مدیریت [AuthorizeView: Administrator] +├── کاربران +├── نقش‌ها +├── پرداخت دستی +├── کیف‌پول +└── قراردادها +───────────────────── +مدیریت محتوا +├── بلاگ +├── صفحات سایت +└── پیام‌های عمومی +───────────────────── +سیستم [AuthorizeView: Administrator] +├── مدیریت سیستم +└── نسخه اپلیکیشن‌ها +───────────────────── +تنظیمات +``` diff --git a/ui-modernization/BACKOFFICE-STORE-UNIFICATION.md b/ui-modernization/BACKOFFICE-STORE-UNIFICATION.md new file mode 100644 index 0000000..5ecc499 --- /dev/null +++ b/ui-modernization/BACKOFFICE-STORE-UNIFICATION.md @@ -0,0 +1,195 @@ +# 🏪 یکسان‌سازی فروشگاه عادی و تخفیفی — 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 + + + + + + + + +``` + +**در 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 (سفارشات + گزارش فروش) | 🟢 یکسان | diff --git a/ui-modernization/PHASE-1-COMPLETE.md b/ui-modernization/PHASE-1-COMPLETE.md new file mode 100644 index 0000000..fc175e3 --- /dev/null +++ b/ui-modernization/PHASE-1-COMPLETE.md @@ -0,0 +1,118 @@ +# فاز ۱ — موجودیت‌های بکند CMS ✅ تکمیل شد + +> **تاریخ تکمیل:** ۱۴۰۴/۰۴/۲۱ (2026-02-11) +> **وضعیت:** ✅ تکمیل — بیلد موفق + Migration ساخته شد + +--- + +## خلاصه کارهای انجام شده + +### 1.1 موجودیت‌های دامین (8 فایل) + +| فایل | مسیر | توضیح | +|------|------|-------| +| `BlogPostStatus.cs` | `Domain/Enums/` | enum: Draft=0, Published=1, Scheduled=2, Archived=3 | +| `BlogPost.cs` | `Domain/Entities/Blog/` | پست بلاگ — عنوان، اسلاگ، خلاصه، محتوای HTML، تصویر، وضعیت، شمارنده بازدید | +| `BlogCategory.cs` | `Domain/Entities/Blog/` | دسته‌بندی بلاگ — عنوان، اسلاگ، آیکون، ترتیب | +| `BlogPostCategory.cs` | `Domain/Entities/Blog/` | جدول واسط پست-دسته‌بندی (Many-to-Many) | +| `BlogPostTag.cs` | `Domain/Entities/Blog/` | جدول واسط پست-تگ (از Tag موجود استفاده شد) | +| `BlogPostImage.cs` | `Domain/Entities/Blog/` | گالری تصاویر پست — مسیر، عنوان جایگزین، ترتیب | +| `SitePage.cs` | `Domain/Entities/Content/` | صفحات سایت (درباره ما، تماس با ما) — با کلید یکتا | +| `SitePageSection.cs` | `Domain/Entities/Content/` | بخش‌های هر صفحه — محتوای HTML، آیکون، تصویر، داده اضافی JSON | + +### 1.2 تنظیمات Entity Framework (7 فایل) + +| فایل | مسیر | ایندکس‌ها | +|------|------|----------| +| `BlogPostConfiguration.cs` | `Configurations/Blog/` | Slug (unique), Status, PublishedAt, IsFeatured, AuthorUserId, Status+PublishedAt | +| `BlogCategoryConfiguration.cs` | `Configurations/Blog/` | Slug (unique), IsActive | +| `BlogPostCategoryConfiguration.cs` | `Configurations/Blog/` | FK: BlogPostId + BlogCategoryId | +| `BlogPostTagConfiguration.cs` | `Configurations/Blog/` | FK: BlogPostId + TagId | +| `BlogPostImageConfiguration.cs` | `Configurations/Blog/` | FK: BlogPostId | +| `SitePageConfiguration.cs` | `Configurations/Content/` | PageKey (unique) | +| `SitePageSectionConfiguration.cs` | `Configurations/Content/` | SitePageId + SectionKey (compound) | + +### 1.3 DbContext (2 فایل ویرایش شده) + +- `IApplicationDbContext.cs` — افزودن 7 DbSet +- `ApplicationDbContext.cs` — افزودن 7 DbSet property + +### 1.4 تعاریف Proto (4 فایل + csproj) + +| فایل | RPCها | csharp_namespace | +|------|-------|-----------------| +| `blogpost.proto` | 11 RPC (CRUD + Publish/Archive/ViewCount + BySlug + Published/Featured) | `BlogPost` | +| `blogcategory.proto` | 6 RPC (CRUD + GetAll + GetActive) | `BlogCategory` | +| `blogpostimage.proto` | 4 RPC (Add/Delete/Get/Reorder) | `BlogPostImage` | +| `sitepage.proto` | 8 RPC (Get/GetByKey/Update/GetAll + Section CRUD + Reorder) | `SitePage` | + +### 1.5 لایه CQRS Application (≈50 فایل) + +#### BlogPost Commands (6 گروه، 14 فایل) +- `CreateBlogPost` — Command + Handler + Validator (با اعتبارسنجی اسلاگ regex) +- `UpdateBlogPost` — Command + Handler + Validator (الگوی delete-recreate برای دسته‌بندی/تگ) +- `DeleteBlogPost` — Command + Handler (soft-delete) +- `PublishBlogPost` — Command + Result + Handler (تنظیم Status و PublishedAt) +- `ArchiveBlogPost` — Command + Result + Handler +- `IncrementViewCount` — Command + Handler + +#### BlogPost Queries (5 گروه، 10 فایل) +- `GetBlogPost` — Query + DTO + Handler (با Include chain) +- `GetBlogPostBySlug` — Query + Handler (بازاستفاده از BlogPostDto) +- `GetAllBlogPosts` — Query + ResponseDto + Handler (فیلتر + مرتب‌سازی + صفحه‌بندی) +- `GetPublishedBlogPosts` — Query + Handler (مشتری‌محور، فقط Published) +- `GetFeaturedBlogPosts` — Query + Handler (برای لندینگ پیج) + +#### BlogCategory CQRS (11 فایل) +- Commands: Create + Update + Delete (با Validator) +- Queries: GetBlogCategory + GetAllBlogCategories + GetActiveBlogCategories + +#### BlogPostImage CQRS (8 فایل) +- Commands: Add + Delete + Reorder (با ImageSortItem) +- Queries: GetBlogPostImages + +#### SitePage CQRS (14 فایل) +- Commands: UpdateSitePage + CreateSection + UpdateSection + DeleteSection + ReorderSections +- Queries: GetSitePage + GetSitePageByKey + GetAllSitePages + +### 1.6 سرویس‌های gRPC WebApi (4 فایل) + +| سرویس | الگو | توضیح | +|-------|------|-------| +| `BlogPostService.cs` | ترکیبی (دستی + dispatcher) | مپینگ دستی برای لیست‌ها و RepeatedField | +| `BlogCategoryService.cs` | ترکیبی | dispatcher برای CRUD ساده، دستی برای لیست‌ها | +| `BlogPostImageService.cs` | ترکیبی | dispatcher + مپینگ دستی Reorder | +| `SitePageService.cs` | ترکیبی | dispatcher + مپینگ دستی Sections | + +### 1.7 Mapping Profiles (2 فایل) + +- `BlogPostProfile.cs` — مپینگ PublishBlogPostResult و ArchiveBlogPostResult +- `BlogCategoryProfile.cs` — مپینگ long → CreateBlogCategoryResponse + +### 1.8 EF Migration + +- `20260210232742_AddBlogAndContentEntities.cs` — ایجاد 7 جدول جدید +- **Build:** ✅ موفق (0 Error, warnings مربوط به کد قدیمی) + +--- + +## آمار فاز ۱ + +| متریک | تعداد | +|-------|-------| +| فایل‌های جدید | ~65 | +| فایل‌های ویرایش شده | ~4 | +| موجودیت‌های دامین | 7 (+1 enum) | +| تنظیمات EF | 7 | +| تعاریف Proto | 4 | +| RPCهای gRPC | 29 | +| Commands CQRS | 16 | +| Queries CQRS | 12 | +| سرویس‌های WebApi | 4 | +| جداول دیتابیس جدید | 7 | + +--- + +## فاز بعدی + +**فاز ۲ — پنل مدیریت بلاگ (BackOffice)** — صفحات Blazor WASM برای مدیریت پست‌ها، دسته‌بندی‌ها، تصاویر و صفحات سایت. diff --git a/ui-modernization/PHASE-3-COMPLETE.md b/ui-modernization/PHASE-3-COMPLETE.md new file mode 100644 index 0000000..acbfd01 --- /dev/null +++ b/ui-modernization/PHASE-3-COMPLETE.md @@ -0,0 +1,104 @@ +# فاز ۳: صفحات محتوای پویا (Dynamic Content Pages) ✅ + +## 📋 خلاصه +تبدیل صفحات **درباره ما** و **تماس با ما** از محتوای هاردکد (hardcoded) به محتوای پویا که از CMS (سرویس SitePage) بارگذاری می‌شود، با پشتیبانی fallback به محتوای پیش‌فرض. + +--- + +## 🏗️ معماری + +``` +FrontOffice (Blazor Server) + ├── About.razor/cs ─── SitePageService ──► gRPC ──► CMS SitePageContract + └── Contact.razor/cs ─── SitePageService ──► gRPC ──► CMS SitePageContract +``` + +### الگوی Fallback: +``` +OnInitializedAsync() → SitePageService.GetByKeyAsync("about") + ├── ✅ Data received → Render dynamic content + └── ❌ Error/null → Render hardcoded fallback content +``` + +--- + +## 📁 فایل‌های ایجاد/تغییر یافته + +### فایل‌های جدید: +| فایل | توضیحات | +|------|---------| +| `FrontOffice/src/FrontOffice.Main/Utilities/SitePageService.cs` | سرویس SitePage + DTOs (SitePageDto, SitePageSectionDto) | +| `dbbkup/SeedSitePages.sql` | اسکریپت Seed Data برای درج محتوای اولیه صفحات | + +### فایل‌های تغییر یافته: +| فایل | تغییرات | +|------|---------| +| `FrontOffice/src/FrontOffice.Main/ConfigureServices.cs` | اضافه شدن SitePageService + SitePageContractClient به DI | +| `FrontOffice/src/FrontOffice.Main/Pages/About.razor` | تبدیل به محتوای پویا با fallback | +| `FrontOffice/src/FrontOffice.Main/Pages/About.razor.cs` | اضافه شدن OnInitializedAsync + بارگذاری sections | +| `FrontOffice/src/FrontOffice.Main/Pages/Contact.razor` | تبدیل hero/info/social به پویا، فرم بدون تغییر | +| `FrontOffice/src/FrontOffice.Main/Pages/Contact.razor.cs` | اضافه شدن OnInitializedAsync + ExtraData DTOs | + +--- + +## 🔧 جزئیات فنی + +### SitePageService +```csharp +public class SitePageService +{ + Task GetByKeyAsync(string pageKey) // "about" | "contact" +} +``` + +### SitePageDto Helpers +```csharp +GetSection(string sectionKey) // e.g. "vision", "mission", "contact-info" +GetSections(string prefix) // e.g. "value-" → value-1, value-2, ... +``` + +### SitePageSectionDto.GetExtraData() +JSON deserializer برای فیلد ExtraData — استفاده شده در Contact: +- `ContactInfoData`: address, phone, email, hours +- `SocialMediaData`: telegram, instagram, linkedin, whatsapp + +--- + +## 📄 SectionKey Mapping + +### صفحه درباره ما (PageKey: `about`) +| SectionKey | کاربرد | فیلدهای اصلی | +|------------|--------|--------------| +| `vision` | کارت چشم‌انداز | Title, HtmlContent, IconName | +| `mission` | کارت مأموریت | Title, HtmlContent, IconName | +| `value-1` ... `value-6` | کارت‌های ارزش‌ها | Title, HtmlContent, IconName | +| `team-1` ... `team-3` | کارت‌های اعضای تیم | Title(نام), Subtitle(سمت), HtmlContent(توضیحات), ImagePath(آواتار) | + +### صفحه تماس با ما (PageKey: `contact`) +| SectionKey | کاربرد | فیلدهای اصلی | +|------------|--------|--------------| +| `contact-info` | اطلاعات تماس | ExtraData → `{address, phone, email, hours}` | +| `social-media` | شبکه‌های اجتماعی | ExtraData → `{telegram, instagram, linkedin, whatsapp}` | + +--- + +## 🗃️ Seed Data +فایل `dbbkup/SeedSitePages.sql` شامل: +- **2 صفحه**: about, contact +- **13 سکشن**: 2 (vision/mission) + 6 (values) + 3 (team) + 2 (contact-info/social-media) +- تمام محتوای فعلی hardcoded به عنوان داده اولیه درج شده + +--- + +## ✅ بیلد +``` +FrontOffice.Main: 0 Error(s), Build succeeded +``` + +--- + +## 📌 نکات مهم +1. **فرم تماس** (Contact Form) بدون تغییر باقی ماند — منطق سمت کلاینت است نه محتوای CMS +2. **Fallback**: اگر CMS در دسترس نباشد، محتوای hardcoded نمایش داده می‌شود +3. **Loading State**: صفحه About دارای حالت loading با spinner +4. آیکون‌ها در CMS به صورت string ذخیره می‌شوند (مثل `@Icons.Material.Filled.Security`) diff --git a/ui-modernization/UI-MODERNIZATION-PLAN.md b/ui-modernization/UI-MODERNIZATION-PLAN.md new file mode 100644 index 0000000..b08b19c --- /dev/null +++ b/ui-modernization/UI-MODERNIZATION-PLAN.md @@ -0,0 +1,1418 @@ +# 🎨 طرح نوسازی رابط کاربری فرانت‌آفیس + سیستم بلاگ + +> **تاریخ شروع:** تیر ۱۴۰۴ +> **آخرین بروزرسانی:** بهمن ۱۴۰۴ +> **وضعیت:** فازهای ۱ تا ۶ تکمیل ✅ — فاز ۷ در حال اجرا +> **اولویت:** بالا + +--- + +## فهرست مطالب + +1. [خلاصه اجرایی](#1-خلاصه-اجرایی) +2. [وضعیت فعلی](#2-وضعیت-فعلی) +3. [اهداف پروژه](#3-اهداف-پروژه) +4. [معماری فنی](#4-معماری-فنی) +5. [فاز ۱ — موجودیت‌های بکند (CMS)](#5-فاز-۱--موجودیتهای-بکند-cms) +6. [فاز ۲ — پنل مدیریت بلاگ (BackOffice)](#6-فاز-۲--پنل-مدیریت-بلاگ-backoffice) +7. [فاز ۳ — صفحات محتوای دینامیک (درباره ما / تماس با ما)](#7-فاز-۳--صفحات-محتوای-دینامیک-درباره-ما--تماس-با-ما) +8. [فاز ۴ — نوسازی لندینگ پیج](#8-فاز-۴--نوسازی-لندینگ-پیج) +9. [فاز ۵ — صفحات بلاگ فرانت‌آفیس](#9-فاز-۵--صفحات-بلاگ-فرانتآفیس) +10. [فاز ۶ — بهبود داشبورد کاربر](#10-فاز-۶--بهبود-داشبورد-کاربر) +11. [فاز ۷ — بهینه‌سازی موبایل](#11-فاز-۷--بهینهسازی-موبایل) +12. [فایل‌های تغییریافته (نقشه فایل‌ها)](#12-فایلهای-تغییریافته-نقشه-فایلها) +13. [جدول زمانی](#13-جدول-زمانی) +14. [ریسک‌ها و وابستگی‌ها](#14-ریسکها-و-وابستگیها) + +--- + +## 1. خلاصه اجرایی + +این سند طرح جامع نوسازی رابط کاربری **فرانت‌آفیس** (سمت مشتری) و ایجاد **سیستم بلاگ/مدیریت محتوا** را شامل می‌شود. اهداف اصلی: + +- ✅ ایجاد **سیستم بلاگ** با قابلیت انتشار مقالات، مجوزها، گواهینامه‌ها با تصاویر +- ✅ **صفحات دینامیک** درباره ما و تماس با ما (قابل مدیریت از پنل ادمین) +- ✅ **نوسازی لندینگ پیج** با طراحی حرفه‌ای و حس زنده بودن سامانه +- ✅ **صفحات بلاگ فرانت‌آفیس** — لیست مقالات با جستجو/فیلتر + صفحه جزئیات مقاله +- ✅ **بهبود داشبورد** کاربر با طراحی مدرن و اطلاعات پویا +- 🔄 **طراحی Mobile-First** (اولویت موبایل) — فاز ۷ +- ⛔ بدون تغییر UI سمت ادمین (فقط افزودن صفحات مدیریت بلاگ/محتوا) + +--- + +## 2. وضعیت فعلی + +### 2.1 پشته فنی + +| لایه | تکنولوژی | +|------|----------| +| FrontOffice | Blazor Server (.NET 9) + MudBlazor 8.14.0 | +| BackOffice | Blazor WebAssembly (.NET 9) + MudBlazor 8.14.0 | +| CMS (Backend) | .NET 9 gRPC + MediatR CQRS + EF Core | +| دیتابیس | SQL Server | +| فایل‌ها | FMS (File Management Service) via gRPC | +| زبان UI | فارسی (RTL) + فونت Vazir | +| تم | Light/Dark toggle | + +### 2.2 صفحات فعلی فرانت‌آفیس + +| صفحه | مسیر | وضعیت | +|------|------|-------| +| لندینگ | `/` | ✅ Hero گرادیان + Features + Timeline + Stats + بلاگ + Testimonials + FAQ + CTA | +| درباره ما | `/about` | ✅ محتوای دینامیک از CMS (با fallback) | +| تماس با ما | `/contact` | ✅ محتوای دینامیک از CMS + فرم تماس | +| بلاگ | `/blog` | ✅ لیست مقالات + جستجو + فیلتر دسته‌بندی + صفحه‌بندی | +| مقاله | `/blog/{slug}` | ✅ صفحه جزئیات مقاله + breadcrumb + تگ‌ها + شمارنده بازدید | +| سوالات متداول | `/faq` | ✅ فعال | +| پروفایل | `/profile/*` | ✅ ۱۰ صفحه (هدر گرادیانی + والت‌کارت‌ها + تایل‌های مدرن) | +| فروشگاه | `/products/*` | ✅ ۸ صفحه (محصولات، سبد، سفارشات، ...) | +| باشگاه | `/club/*` | ✅ ۲ صفحه | +| کمیسیون | `/commission/*` | ✅ ۳ صفحه | +| شبکه | `/network/*` | ✅ ۲ صفحه | +| پکیج | `/packages/*` | ✅ ۴ صفحه | + +### 2.3 مشکلات فعلی (رفع شده ✅) + +1. ~~**احساس ایستا بودن** — لندینگ پیج بدون انیمیشن و محتوای پویا~~ → ✅ فاز ۴ حل شد +2. ~~**بدون بلاگ** — امکان انتشار مقاله/خبر/مجوز وجود ندارد~~ → ✅ فازهای ۱، ۲، ۵ حل شد +3. ~~**صفحات استاتیک** — درباره ما و تماس با ما هاردکد هستند~~ → ✅ فاز ۳ حل شد +4. ~~**طراحی ساده** — فاقد عناصر بصری جذاب~~ → ✅ فازهای ۴ و ۶ حل شد +5. **موبایل‌محور نبودن** — ریسپانسیو هست اما اولویت با دسکتاپ → 🔄 فاز ۷ + +--- + +## 3. اهداف پروژه + +### 3.1 اهداف عملیاتی + +| هدف | شاخص موفقیت | +|-----|-------------| +| سیستم بلاگ | ادمین بتواند مقاله بنویسد، دسته‌بندی کند، تصویر آپلود کند، منتشر/آرشیو کند | +| محتوای دینامیک | ادمین بتواند درباره ما و تماس با ما را از پنل ویرایش کند | +| لندینگ حرفه‌ای | اولین بازدید حس «پلتفرم زنده و فعال» بدهد | +| Mobile-First | تمام صفحات در موبایل عالی نمایش داده شوند | +| عملکرد | زمان بارگذاری صفحه اول < ۳ ثانیه | + +### 3.2 محدوده (Scope) + +``` +✅ در محدوده: + - سیستم بلاگ کامل (CMS → BackOffice → FrontOffice) + - صفحات محتوای دینامیک (About, Contact) + - نوسازی لندینگ پیج + - بهبود داشبورد/پروفایل + - بهینه‌سازی موبایل + - افزودن صفحات مدیریت بلاگ به BackOffice + +⛔ خارج از محدوده: + - تغییرات UI سایر بخش‌های BackOffice + - سیستم کامنت‌گذاری (فاز بعدی) + - سیستم جستجوی پیشرفته (فاز بعدی) + - اپلیکیشن موبایل (PWA یا Native) +``` + +--- + +## 4. معماری فنی + +### 4.1 جریان داده بلاگ + +``` +┌──────────────┐ gRPC ┌───────────┐ EF Core ┌──────────┐ +│ BackOffice │ ──────────► │ CMS │ ────────────► │ SQL Server│ +│ (Admin CRUD) │ ◄────────── │ (gRPC) │ ◄──────────── │ │ +└──────────────┘ └───────────┘ └──────────┘ + │ ▲ + gRPC │ │ + ▼ │ + ┌──────────────┐ + │ FrontOffice │ + │ (مشاهده بلاگ)│ + └──────────────┘ + │ + FMS │ (آپلود تصاویر) + ▼ + ┌──────────┐ + │ FMS │ + │ (فایل‌ها) │ + └──────────┘ +``` + +### 4.2 موجودیت‌های جدید (Entity Relationship) + +``` +BlogPost (1) ──────── (N) BlogPostCategory ──────── (1) BlogCategory + │ + │ (1:N) + ▼ +BlogPostImage + +BlogPost.Tags → از Tag موجود استفاده می‌شود (BlogPostTag join table) + +SitePage → صفحات دینامیک (About, Contact, Custom) + │ + │ (1:N) + ▼ +SitePageSection → بخش‌های هر صفحه +``` + +### 4.3 الگوی CQRS موجود (برای مرجع) + +``` +Proto (gRPC Definition) + ↓ +WebApi/Services/{Feature}Service.cs (extends generated gRPC base) + ↓ Mapster (Proto → Command/Query) +Application/{Feature}CQ/Commands/ or Queries/ + ↓ MediatR +Handler → Repository (DbContext) + ↓ +Domain/Entities/{Entity}.cs +``` + +--- + +## 5. فاز ۱ — موجودیت‌های بکند (CMS) ✅ تکمیل شد + +> **وضعیت:** ✅ تکمیل | **اولویت:** بالا | **پیش‌نیاز:** ندارد + +### 5.1 موجودیت‌های جدید Domain + +#### `BlogPost.cs` +``` +Location: CMS/src/CMSMicroservice.Domain/Entities/Blog/BlogPost.cs +Base: BaseAuditableEntity + +Properties: + - string Title (required, max 200) + - string Slug (required, unique, max 200) + - string Summary (max 500) — خلاصه برای کارت‌ها + - string HtmlContent (required) — محتوای HTML کامل + - string? FeaturedImagePath — تصویر شاخص + - string? FeaturedImageThumbnailPath — تامبنیل تصویر شاخص + - BlogPostStatus Status (enum: Draft, Published, Scheduled, Archived) + - DateTime? PublishedAt — زمان انتشار + - DateTime? ScheduledPublishAt — زمانبندی انتشار + - int ViewCount — تعداد بازدید + - long AuthorUserId — نویسنده + - bool IsFeatured — نمایش در صفحه اول + - int SortOrder — ترتیب نمایش + +Relations: + - ICollection BlogPostCategories + - ICollection BlogPostTags + - ICollection BlogPostImages +``` + +#### `BlogCategory.cs` +``` +Location: CMS/src/CMSMicroservice.Domain/Entities/Blog/BlogCategory.cs +Base: BaseAuditableEntity + +Properties: + - string Title (required, max 100) + - string Slug (required, unique, max 100) + - string? Description (max 500) + - string? IconName — آیکون Material + - int SortOrder + - bool IsActive + +Relations: + - ICollection BlogPostCategories +``` + +#### `BlogPostCategory.cs` (Join Table) +``` +Location: CMS/src/CMSMicroservice.Domain/Entities/Blog/BlogPostCategory.cs +Base: BaseEntity + +Properties: + - long BlogPostId + - long BlogCategoryId + +Relations: + - BlogPost BlogPost + - BlogCategory BlogCategory +``` + +#### `BlogPostTag.cs` (Join Table) +``` +Location: CMS/src/CMSMicroservice.Domain/Entities/Blog/BlogPostTag.cs +Base: BaseEntity + +Properties: + - long BlogPostId + - long TagId + +Relations: + - BlogPost BlogPost + - Tag Tag (موجود — استفاده مجدد) +``` + +#### `BlogPostImage.cs` +``` +Location: CMS/src/CMSMicroservice.Domain/Entities/Blog/BlogPostImage.cs +Base: BaseAuditableEntity + +Properties: + - long BlogPostId + - string ImagePath + - string ThumbnailPath + - string? AltText (max 200) + - string? Caption (max 300) + - int SortOrder + +Relations: + - BlogPost BlogPost +``` + +#### `SitePage.cs` (صفحات دینامیک) +``` +Location: CMS/src/CMSMicroservice.Domain/Entities/Content/SitePage.cs +Base: BaseAuditableEntity + +Properties: + - string PageKey (unique, e.g. "about", "contact") — کلید شناسایی + - string Title (max 200) + - string? MetaDescription (max 300) — SEO + - string? HeroTitle — عنوان Hero + - string? HeroSubtitle — زیرعنوان Hero + - string? HeroImagePath — تصویر Hero + - bool IsActive + +Relations: + - ICollection Sections +``` + +#### `SitePageSection.cs` (بخش‌های صفحه) +``` +Location: CMS/src/CMSMicroservice.Domain/Entities/Content/SitePageSection.cs +Base: BaseAuditableEntity + +Properties: + - long SitePageId + - string SectionKey (e.g. "mission", "vision", "team-member-1") + - string Title (max 200) + - string? Subtitle (max 300) + - string? HtmlContent — محتوای HTML + - string? IconName — آیکون Material + - string? ImagePath + - string? ImageThumbnailPath + - int SortOrder + - bool IsActive + - string? ExtraData — JSON blob for flexible data + +Relations: + - SitePage SitePage +``` + +#### Enum: `BlogPostStatus.cs` +``` +Location: CMS/src/CMSMicroservice.Domain/Enums/BlogPostStatus.cs + +Values: + Draft = 0 + Published = 1 + Scheduled = 2 + Archived = 3 +``` + +### 5.2 Entity Configurations (Infrastructure) + +``` +فایل‌های جدید: + CMS/src/CMSMicroservice.Infrastructure/Configurations/Blog/ + ├── BlogPostConfiguration.cs + ├── BlogCategoryConfiguration.cs + ├── BlogPostCategoryConfiguration.cs + ├── BlogPostTagConfiguration.cs + └── BlogPostImageConfiguration.cs + CMS/src/CMSMicroservice.Infrastructure/Configurations/Content/ + ├── SitePageConfiguration.cs + └── SitePageSectionConfiguration.cs + +فایل‌های ویرایشی: + CMS/src/CMSMicroservice.Infrastructure/ApplicationDbContext.cs + + DbSet BlogPosts + + DbSet BlogCategories + + DbSet BlogPostCategories + + DbSet BlogPostTags + + DbSet BlogPostImages + + DbSet SitePages + + DbSet SitePageSections +``` + +### 5.3 Proto Definitions + +#### `blogpost.proto` +``` +Location: CMS/src/CMSMicroservice.Protobuf/Protos/blogpost.proto + +service BlogPostContract { + rpc CreateBlogPost (CreateBlogPostRequest) returns (BlogPostResponse); + rpc UpdateBlogPost (UpdateBlogPostRequest) returns (BlogPostResponse); + rpc DeleteBlogPost (DeleteBlogPostRequest) returns (BoolResponse); + rpc GetBlogPost (GetBlogPostRequest) returns (BlogPostResponse); + rpc GetBlogPostBySlug (GetBlogPostBySlugRequest) returns (BlogPostResponse); + rpc GetAllBlogPosts (GetAllBlogPostsRequest) returns (BlogPostListResponse); + rpc GetPublishedBlogPosts (GetPublishedBlogPostsRequest) returns (BlogPostListResponse); + rpc GetFeaturedBlogPosts (GetFeaturedBlogPostsRequest) returns (BlogPostListResponse); + rpc PublishBlogPost (PublishBlogPostRequest) returns (BoolResponse); + rpc ArchiveBlogPost (ArchiveBlogPostRequest) returns (BoolResponse); + rpc IncrementViewCount (IncrementViewCountRequest) returns (BoolResponse); +} +``` + +#### `blogcategory.proto` +``` +Location: CMS/src/CMSMicroservice.Protobuf/Protos/blogcategory.proto + +service BlogCategoryContract { + rpc CreateBlogCategory (CreateBlogCategoryRequest) returns (BlogCategoryResponse); + rpc UpdateBlogCategory (UpdateBlogCategoryRequest) returns (BlogCategoryResponse); + rpc DeleteBlogCategory (DeleteBlogCategoryRequest) returns (BoolResponse); + rpc GetBlogCategory (GetBlogCategoryRequest) returns (BlogCategoryResponse); + rpc GetAllBlogCategories (GetAllBlogCategoriesRequest) returns (BlogCategoryListResponse); +} +``` + +#### `blogpostimage.proto` +``` +Location: CMS/src/CMSMicroservice.Protobuf/Protos/blogpostimage.proto + +service BlogPostImageContract { + rpc AddBlogPostImage (AddBlogPostImageRequest) returns (BlogPostImageResponse); + rpc DeleteBlogPostImage (DeleteBlogPostImageRequest) returns (BoolResponse); + rpc GetBlogPostImages (GetBlogPostImagesRequest) returns (BlogPostImageListResponse); + rpc ReorderBlogPostImages (ReorderBlogPostImagesRequest) returns (BoolResponse); +} +``` + +#### `sitepage.proto` +``` +Location: CMS/src/CMSMicroservice.Protobuf/Protos/sitepage.proto + +service SitePageContract { + rpc GetSitePage (GetSitePageRequest) returns (SitePageResponse); + rpc GetSitePageByKey (GetSitePageByKeyRequest) returns (SitePageResponse); + rpc UpdateSitePage (UpdateSitePageRequest) returns (SitePageResponse); + rpc GetAllSitePages (GetAllSitePagesRequest) returns (SitePageListResponse); + rpc CreateSitePageSection (CreateSitePageSectionRequest) returns (SitePageSectionResponse); + rpc UpdateSitePageSection (UpdateSitePageSectionRequest) returns (SitePageSectionResponse); + rpc DeleteSitePageSection (DeleteSitePageSectionRequest) returns (BoolResponse); + rpc ReorderSitePageSections (ReorderSitePageSectionsRequest) returns (BoolResponse); +} +``` + +### 5.4 Application Layer (CQRS) + +``` +CMS/src/CMSMicroservice.Application/ + BlogPostCQ/ + Commands/ + CreateBlogPost/ + CreateBlogPostCommand.cs + CreateBlogPostCommandHandler.cs + CreateBlogPostCommandValidator.cs + UpdateBlogPost/ + UpdateBlogPostCommand.cs + UpdateBlogPostCommandHandler.cs + UpdateBlogPostCommandValidator.cs + DeleteBlogPost/ + DeleteBlogPostCommand.cs + DeleteBlogPostCommandHandler.cs + PublishBlogPost/ + PublishBlogPostCommand.cs + PublishBlogPostCommandHandler.cs + ArchiveBlogPost/ + ArchiveBlogPostCommand.cs + ArchiveBlogPostCommandHandler.cs + Queries/ + GetBlogPost/ + GetBlogPostQuery.cs + GetBlogPostQueryHandler.cs + GetBlogPostBySlug/ + GetBlogPostBySlugQuery.cs + GetBlogPostBySlugQueryHandler.cs + GetAllBlogPosts/ + GetAllBlogPostsQuery.cs + GetAllBlogPostsQueryHandler.cs + GetPublishedBlogPosts/ + GetPublishedBlogPostsQuery.cs + GetPublishedBlogPostsQueryHandler.cs + GetFeaturedBlogPosts/ + GetFeaturedBlogPostsQuery.cs + GetFeaturedBlogPostsQueryHandler.cs + + BlogCategoryCQ/ + Commands/ + CreateBlogCategory/ (3 files) + UpdateBlogCategory/ (3 files) + DeleteBlogCategory/ (2 files) + Queries/ + GetBlogCategory/ (2 files) + GetAllBlogCategories/ (2 files) + + BlogPostImageCQ/ + Commands/ + AddBlogPostImage/ (3 files) + DeleteBlogPostImage/ (2 files) + ReorderBlogPostImages/ (2 files) + Queries/ + GetBlogPostImages/ (2 files) + + SitePageCQ/ + Commands/ + UpdateSitePage/ (3 files) + CreateSitePageSection/ (3 files) + UpdateSitePageSection/ (3 files) + DeleteSitePageSection/ (2 files) + ReorderSitePageSections/ (2 files) + Queries/ + GetSitePage/ (2 files) + GetSitePageByKey/ (2 files) + GetAllSitePages/ (2 files) +``` + +### 5.5 WebApi Services & Mappings + +``` +CMS/src/CMSMicroservice.WebApi/ + Services/ + BlogPostService.cs — extends BlogPostContractBase + BlogCategoryService.cs — extends BlogCategoryContractBase + BlogPostImageService.cs — extends BlogPostImageContractBase + SitePageService.cs — extends SitePageContractBase + Common/Mappings/ + BlogPostProfile.cs + BlogCategoryProfile.cs + BlogPostImageProfile.cs + SitePageProfile.cs +``` + +### 5.6 Migration + +```bash +# ساخت Migration جدید +cd CMS/src/CMSMicroservice.Infrastructure +dotnet ef migrations add AddBlogAndSitePages \ + --startup-project ../CMSMicroservice.WebApi \ + --context ApplicationDbContext +``` + +--- + +## 6. فاز ۲ — پنل مدیریت بلاگ (BackOffice) ✅ تکمیل شد + +> **تخمین:** ۲-۳ روز | **اولویت:** بالا | **پیش‌نیاز:** فاز ۱ | **وضعیت: تکمیل ✅** + +### 6.1 صفحات ایجاد شده BackOffice + +``` +BackOffice/src/BackOffice/Pages/ + Blog/ + BlogPostManagementPage.razor — لیست پست‌ها + فیلتر وضعیت/دسته‌بندی + جستجو + BlogPostManagementPage.razor.cs — کدبیهایند: LoadServerData، CRUD، Publish، Archive + BlogCategoryManagementPage.razor — مدیریت دسته‌بندی‌ها (MudDataGrid) + BlogCategoryManagementPage.razor.cs + Components/ + BlogPostEditDialog.razor — ایجاد/ویرایش پست (عنوان، اسلاگ، خلاصه، HTML، دسته‌بندی‌ها) + BlogPostEditDialog.razor.cs — Multi-select دسته‌بندی + ویژه/ترتیب + BlogCategoryEditDialog.razor — ایجاد/ویرایش دسته‌بندی + BlogCategoryEditDialog.razor.cs + + Content/ + SitePageManagementPage.razor — لیست صفحات سایت + SitePageManagementPage.razor.cs — ویرایش صفحه + مدیریت بخش‌ها + Components/ + SitePageEditDialog.razor — ویرایش اطلاعات صفحه (Hero, Meta, وضعیت) + SitePageEditDialog.razor.cs + SitePageSectionsDialog.razor — مدیریت بخش‌های صفحه (CRUD + لیست) + SitePageSectionsDialog.razor.cs + SitePageSectionEditDialog.razor — ایجاد/ویرایش بخش (کلید، عنوان، HTML، تصاویر) + SitePageSectionEditDialog.razor.cs +``` + +### 6.2 ناوبری BackOffice (NavMenu) ✅ + +بخش «مدیریت محتوا» به `NavMenu.razor` اضافه شد (قبل از بخش سیستم): + +```razor +مدیریت محتوا + + + + @if (CanManageBlog) + { + + پست‌ها + دسته‌بندی‌ها + + } + + @if (CanManageSitePages) + { + صفحات سایت + } + + +``` + +مجوزهای جدید: `blog.manage`، `sitepages.manage` + +### 6.3 سرویس‌های BackOffice ✅ + +``` +BackOffice/src/BackOffice/Services/ + Blog/ + IBlogCategoryService.cs — اینترفیس + DTOs (Filter, ListResult, Item, Edit) + BlogCategoryService.cs — پیاده‌سازی با gRPC client (GetAll, GetActive, GetById, Create, Update, Delete) + IBlogPostService.cs — اینترفیس + DTOs (Filter, ListResult, ListItem, Details, Edit, PublishResult, ArchiveResult) + BlogPostService.cs — پیاده‌سازی (CRUD + Publish + Archive) + IBlogPostImageService.cs — اینترفیس + DTOs (ImageItem, ImageAdd, ImageSort) + BlogPostImageService.cs — پیاده‌سازی (GetByPostId, Add, Delete, Reorder) + Content/ + ISitePageService.cs — اینترفیس + DTOs (Summary, Details, SectionItem, Edit, SectionEdit, SectionSort) + SitePageService.cs — پیاده‌سازی (GetAll, GetById, Update, CRUD Section, Reorder) +``` + +ثبت DI در `ConfigureService.cs`: +- ۴ gRPC Client: `BlogPostContractClient`، `BlogCategoryContractClient`، `BlogPostImageContractClient`، `SitePageContractClient` +- ۴ Application Service: `IBlogPostService`، `IBlogCategoryService`، `IBlogPostImageService`، `ISitePageService` + +### 6.4 ویرایشگر محتوا + +از پکیج موجود `Tizzani.MudBlazor.HtmlEditor` (قبلاً نصب شده) استفاده می‌شود: + +```razor + + +``` + +### 6.5 آپلود تصویر شاخص + +از سرویس موجود `IFileManagerService` استفاده: + +```csharp +// الگوی موجود از ProductService +var imagePath = await _fileManagerService.UploadFileAsync(imageBytes, fileName); +``` + +### 6.6 مشخصات صفحه مدیریت مقالات + +| ویژگی | شرح | +|-------|------| +| جدول مقالات | MudDataGrid با ستون‌های: عنوان، دسته‌بندی، وضعیت، تاریخ انتشار، بازدید | +| فیلتر | وضعیت (همه/پیش‌نویس/منتشر/آرشیو) + جستجوی عنوان | +| عملیات | ایجاد، ویرایش، حذف، انتشار، آرشیو، پیش‌نمایش | +| تصویر شاخص | Drag & Drop آپلود + پیش‌نمایش | +| دسته‌بندی | Multi-select از دسته‌بندی‌های موجود | +| تگ | Multi-select با autocomplete از تگ‌های موجود | +| ویرایشگر | WYSIWYG HTML editor (MudBlazor.HtmlEditor) | + +--- + +## 7. فاز ۳ — صفحات محتوای دینامیک (درباره ما / تماس با ما) ✅ تکمیل شد + +> **وضعیت:** ✅ تکمیل | **اولویت:** متوسط | **پیش‌نیاز:** فاز ۱ + +### 7.1 تغییرات About.razor + +**قبل (فعلی):** محتوا هاردکد — عنوان‌ها، تصاویر، متون همه در Razor مستقیم نوشته شده + +**بعد (جدید):** +``` +OnInitializedAsync: + 1. صدا زدن SitePageContract.GetSitePageByKey("about") + 2. دریافت SitePage + Sections + 3. نمایش دینامیک با loop روی Sections + +Fallback: + - اگر سرویس خطا داد → نمایش محتوای پیش‌فرض (هاردکد فعلی به عنوان fallback) +``` + +ساختار بخش‌های صفحه درباره ما: + +| SectionKey | نوع | داده | +|-----------|-----|------| +| `hero` | Hero | HeroTitle, HeroSubtitle, HeroImagePath | +| `mission` | Card | Title, HtmlContent, IconName | +| `vision` | Card | Title, HtmlContent, IconName | +| `value-1` ... `value-N` | Cards Grid | Title, Subtitle, IconName, HtmlContent | +| `team-1` ... `team-N` | Team Cards | Title (نام), Subtitle (سمت), HtmlContent (توضیح), ImagePath | + +### 7.2 تغییرات Contact.razor + +| SectionKey | نوع | داده | +|-----------|-----|------| +| `hero` | Hero | HeroTitle, HeroSubtitle | +| `contact-info` | Info | ExtraData (JSON: address, phone, email, hours) | +| `social-media` | Links | ExtraData (JSON: telegram, instagram, linkedin, whatsapp urls) | +| `map` | Map | ExtraData (JSON: lat, lng, address text) | + +### 7.3 Seed Data + +اجرای اولیه Migration با داده پیش‌فرض (محتوای هاردکد فعلی): + +```csharp +// در SitePageConfiguration یا Seed Migration +var aboutPage = new SitePage { + PageKey = "about", + Title = "درباره ما", + HeroTitle = "پلتفرم هوشمند تیم‌سازی و مدیریت فروش", + HeroSubtitle = "ما با ارائه ابزارهای نوآورانه...", + IsActive = true +}; +// + Sections با محتوای فعلی +``` + +--- + +## 8. فاز ۴ — نوسازی لندینگ پیج ✅ تکمیل شد + +> **وضعیت:** ✅ تکمیل | **اولویت:** بالا | **پیش‌نیاز:** فاز ۱ (بخش بلاگ برای "آخرین مقالات") + +### 8.1 ساختار جدید لندینگ پیج + +``` +┌─────────────────────────────────────────┐ +│ 🔝 HERO SECTION │ +│ • Gradient animated background │ +│ • عنوان بزرگ + زیرعنوان │ +│ • Badge‌های انیمیشنی │ +│ • CTA buttons با hover effects │ +│ • تصویر/ایلاستریشن سمت چپ │ +│ • اعداد زنده (تعداد کاربران، ...) │ +└─────────────────────────────────────────┘ + ↓ Scroll indicator ↓ +┌─────────────────────────────────────────┐ +│ ✨ FEATURES (چرا ما؟) │ +│ • 6 کارت با آیکون + عنوان + توضیح │ +│ • Hover effect (scale + shadow) │ +│ • انیمیشن fade-in on scroll │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ 📊 LIVE STATS (آمار زنده) │ +│ • Counter animation (شمارش تعداد) │ +│ • تعداد کاربران فعال │ +│ • تعداد محصولات │ +│ • حجم معاملات │ +│ • Intersection Observer trigger │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ 🔄 HOW IT WORKS (چطور کار می‌کند) │ +│ • Timeline بهبودیافته │ +│ • آیکون‌های مرحله‌ای بزرگ‌تر │ +│ • انیمیشن step-by-step │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ 📝 LATEST BLOG (آخرین مقالات) │ ← جدید! +│ • ۳ کارت آخرین مقالات منتشرشده │ +│ • تصویر شاخص + عنوان + خلاصه │ +│ • دکمه "مشاهده همه مقالات" │ +│ • Loading skeleton │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ 🏆 TESTIMONIALS (اعتماد مشتریان) │ +│ • Carousel/Swiper اسلایدر │ +│ • Avatar + نام + سمت + نقل قول │ +│ • Auto-play + دکمه‌های ناوبری │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ ❓ FAQ (سوالات متداول) │ +│ • بدون تغییر اساسی (فقط پولیش) │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ 📞 CTA BANNER (دعوت نهایی) │ ← جدید! +│ • Gradient background │ +│ • عنوان جذاب + دکمه ثبت‌نام │ +│ • طرح ساده و تأثیرگذار │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ 🦶 FOOTER │ +│ • بدون تغییر اساسی + لینک بلاگ │ +└─────────────────────────────────────────┘ +``` + +### 8.2 تغییرات فنی لندینگ + +#### الف) Hero Section بهبودیافته + +``` +تغییرات: + 1. Animated gradient background (CSS keyframes) + 2. اعداد واقعی از API (تعداد کاربران، محصولات، ...) + 3. Badge‌ها با انیمیشن pulse + 4. Scroll-down indicator (chevron انیمیشنی) + 5. تصویر/SVG illustration سمت چپ +``` + +#### ب) آمار زنده (Live Stats) + +``` +سرویس جدید — یک Query ساده در CMS: + GetPlatformStats → { UserCount, ProductCount, OrderCount, ... } + +فرانت: + - Counter animation (JS interop یا CSS counter) + - Intersection Observer: فقط وقتی اسکرول به این بخش رسید شمارش شروع شود +``` + +#### ج) بخش آخرین مقالات (جدید) + +```razor + +
+ + آخرین مقالات + + @foreach (var post in _latestPosts.Take(3)) + { + + + + } + + مشاهده همه + +
+``` + +#### د) CTA Banner نهایی (جدید) + +```razor + +
+ + + آماده شروع هستید؟ + همین الان ثبت‌نام کنید و از مزایای کارا بازار سلامت بهره‌مند شوید. + + شروع رایگان + + + +
+``` + +### 8.3 CSS جدید / بهبودیافته + +```css +/* فایل: wwwroot/css/app.css — بخش‌های جدید */ + +/* Animated gradient hero */ +.hero-section { + background: linear-gradient(-45deg, #6366f1, #8b5cf6, #a855f7, #6366f1); + background-size: 400% 400%; + animation: gradientShift 8s ease infinite; +} + +@keyframes gradientShift { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +/* Scroll-triggered fade-in */ +.fade-in-up { + opacity: 0; + transform: translateY(30px); + transition: opacity 0.6s ease, transform 0.6s ease; +} +.fade-in-up.visible { + opacity: 1; + transform: translateY(0); +} + +/* Counter animation */ +@property --num { + syntax: ''; + initial-value: 0; + inherits: false; +} +.counter { + animation: counter 2s ease-out forwards; + counter-reset: num var(--num); +} + +/* Pulse badge */ +.pulse-badge { + animation: pulse 2s infinite; +} +@keyframes pulse { + 0% { box-shadow: 0 0 0 0 rgba(99,102,241,0.4); } + 70% { box-shadow: 0 0 0 10px rgba(99,102,241,0); } + 100% { box-shadow: 0 0 0 0 rgba(99,102,241,0); } +} + +/* CTA Banner */ +.cta-banner { + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); + color: white; + border-radius: 24px; + margin: 2rem; +} + +/* Card hover effects */ +.feature-card-v2 { + transition: transform 0.3s, box-shadow 0.3s; +} +.feature-card-v2:hover { + transform: translateY(-8px); + box-shadow: 0 12px 40px rgba(0,0,0,0.12); +} + +/* Blog post card */ +.blog-card { + transition: transform 0.3s ease; + overflow: hidden; +} +.blog-card:hover { + transform: translateY(-4px); +} +.blog-card .blog-card-image { + transition: transform 0.5s ease; +} +.blog-card:hover .blog-card-image { + transform: scale(1.05); +} +``` + +### 8.4 JS Interop (حداقل) + +```javascript +// فایل: wwwroot/js/landing.js + +// Intersection Observer for fade-in animations +window.initScrollAnimations = () => { + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('visible'); + observer.unobserve(entry.target); + } + }); + }, { threshold: 0.1 }); + + document.querySelectorAll('.fade-in-up').forEach(el => observer.observe(el)); +}; + +// Counter animation +window.animateCounter = (elementId, target, duration) => { + const el = document.getElementById(elementId); + if (!el) return; + let start = 0; + const step = target / (duration / 16); + const timer = setInterval(() => { + start += step; + if (start >= target) { start = target; clearInterval(timer); } + el.textContent = Math.floor(start).toLocaleString('fa-IR'); + }, 16); +}; +``` + +--- + +## 9. فاز ۵ — صفحات بلاگ فرانت‌آفیس ✅ تکمیل شد + +> **وضعیت:** ✅ تکمیل | **اولویت:** بالا | **پیش‌نیاز:** فاز ۱ + +### 9.1 صفحات ایجاد شده (واقعی) + +``` +FrontOffice/src/FrontOffice.Main/Pages/Blog/ + Index.razor — لیست مقالات با جستجو + فیلتر دسته‌بندی + صفحه‌بندی + Index.razor.cs — LoadPostsAsync، OnCategoryChanged، OnPageChanged، NavigateToPost + Post.razor — صفحه جزئیات مقاله (تصویر شاخص، breadcrumb، HTML، تگ‌ها) + Post.razor.cs — OnParametersSetAsync (لود slug)، IncrementViewCount خودکار +``` + +### 9.2 سرویس‌های جدید فرانت‌آفیس + +``` +FrontOffice/src/FrontOffice.Main/Utilities/ + BlogPostService.cs — (قبلاً فاز ۴ ایجاد شد) GetFeatured, GetPublished, GetBySlug, IncrementView + BlogCategoryService.cs — GetActiveCategoriesAsync (فیلتر سایدبار بلاگ) +``` + +**DTOs تعریف شده در `BlogPostService.cs`:** +- `BlogPostCardDto` — Id, Title, Slug, Summary, ThumbnailUrl, PublishedAt, ViewCount, IsFeatured, Categories +- `BlogPostDetailDto` — همه فیلدهای کارت + HtmlContent, FeaturedImagePath, Tags +- `BlogPostListResult` — Posts, TotalCount, TotalPages, CurrentPage +- `BlogCategoryInfo` — Id, Title, Slug +- `BlogTagInfo` — Id, Title, Name + +**DTO تعریف شده در `BlogCategoryService.cs`:** +- `BlogCategoryDto` — Id, Title, Slug, Description, IconName, PostCount + +### 9.3 مسیرها (Routes) — پیاده‌سازی شده + +```csharp +// RouteConstants.cs +public static class Blog +{ + public const string Index = "/blog"; + public const string Post = "/blog/"; // usage: /blog/{slug} +} +``` + +### 9.4 طراحی صفحه لیست بلاگ — پیاده‌سازی شده + +``` +┌─────────────────────────────────────────┐ +│ Blog Hero (گرادیان بنفش) │ +│ "بلاگ" │ +│ "آخرین مطالب، آموزش‌ها و اخبار" │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ [جستجو...] [دسته‌بندی ▼] [جستجو 🔍] │ ← فیلتر + MudSelect +└─────────────────────────────────────────┘ +┌────────┐ ┌────────┐ ┌────────┐ +│ Card 1 │ │ Card 2 │ │ Card 3 │ ← blog-card-v2 +│ thumb │ │ thumb │ │ thumb │ ← تصویر شاخص +│ cat │ │ cat │ │ cat │ ← chip دسته‌بندی +│ title │ │ title │ │ title │ ← ۲ خط clamp +│ desc │ │ desc │ │ desc │ ← ۲ خط clamp +│ 📅 👁 │ │ 📅 👁 │ │ 📅 👁 │ ← تاریخ + بازدید +└────────┘ └────────┘ └────────┘ + ← ← ← [1] [2] [3] → → → ← MudPagination +``` + +### 9.5 طراحی صفحه مقاله — پیاده‌سازی شده + +``` +┌─────────────────────────────────────────┐ +│ [تصویر شاخص full-width] + overlay │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ خانه > بلاگ > عنوان مقاله │ ← Breadcrumb +│ │ +│ [دسته‌بندی ۱] [دسته‌بندی ۲] │ ← Chips (کلیک → فیلتر) +│ │ +│ عنوان مقاله (H4, bold) │ +│ خلاصه مقاله (subtitle) │ +│ │ +│ 📅 تاریخ 👁 ۱,۲۳۴ بازدید │ +│ ───────────────────────── │ +│ │ +│ [محتوای HTML مقاله] │ ← blog-content (styled) +│ ... │ +│ │ +│ ───────────────────────── │ +│ 🏷️ [تگ ۱] [تگ ۲] [تگ ۳] │ ← Outlined chips +│ │ +│ [← بازگشت به بلاگ] │ +└─────────────────────────────────────────┘ +``` + +### 9.6 CSS اضافه شده (بخش Blog Pages در site.css) + +```css +/* Blog Pages region — اضافه شده به site.css */ +.blog-hero — گرادیان بنفش (مشابه CTA) +.blog-title-clamp — -webkit-line-clamp: 2 +.blog-summary-clamp — -webkit-line-clamp: 2 +.cursor-pointer — cursor: pointer +.blog-detail-hero — تصویر شاخص 340px + overlay +.blog-content — تایپوگرافی مقاله (h1-h3, p, img, blockquote, code, pre, ul/ol, a) +/* + mobile overrides + dark mode */ +``` + +### 9.7 ناوبری — لینک بلاگ اضافه شد ✅ + +``` +تغییرات واقعی: + - MainLayout.razor → لینک "بلاگ" در navbar دسکتاپ (بین FAQ و Contact) + - MainLayout.razor → لینک "بلاگ" در Mobile Drawer + - Index.razor (لندینگ) → کارت‌های بلاگ کلیک‌پذیر شدند (onclick) + - Index.razor (لندینگ) → دکمه "مشاهده همه مقالات" فعال شد (uncomment) +``` + +### 9.8 ثبت DI (ConfigureServices.cs) + +```csharp +// اضافه شده: +using CMSMicroservice.Protobuf.Protos.BlogCategory; + +// Common services: +services.AddScoped(); + +// gRPC clients: +services.AddScoped(CreateAuthenticatedClient); +``` + +--- + +## 10. فاز ۶ — بهبود داشبورد کاربر ✅ تکمیل شد + +> **وضعیت:** ✅ تکمیل | **اولویت:** متوسط | **پیش‌نیاز:** ندارد + +### 10.1 بازطراحی کامل Profile/Index.razor + +صفحه `/profile` به طور کامل با طراحی مدرن بازنویسی شد: + +``` +┌─────────────────────────────────────────┐ +│ ████ GRADIENT HEADER CARD ████ │ ← .dash-header-card +│ 🧑 آواتار شیشه‌ای | نام + موبایل │ ← .dash-avatar +│ عضو از ... | [💎 سامانه دایا] │ ← دکمه glass-morphism +└─────────────────────────────────────────┘ +┌──────────┐ ┌──────────┐ ┌──────────┐ +│ 💳 اعتباری│ │ 🏷 تخفیف │ │ 👥 تیمی │ ← .dash-wallet-card +│ ۲.۵M تومان│ │ ۵۰۰K │ │ ۸۵۰K │ (hover lift) +└──────────┘ └──────────┘ └──────────┘ +┌─────────────────────────────────────────┐ +│ 📎 کد دعوت شما | [اشتراک‌گذاری] │ ← فعال اگر پکیج + باشگاه +│ یا │ +│ 🔒 لینک دعوت فعال نشده (dashed border)│ ← حالت قفل +│ [شروع فرآیند تامین اعتبار] │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ دسترسی سریع │ ← .dash-tile (10 تایل) +│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ +│ │شخصی│ │آدرس│ │شجره│ │تنظیم│ │ هر تایل: +│ └────┘ └────┘ └────┘ └────┘ │ - آواتار رنگی +│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ - عنوان + زیرعنوان +│ │فروش│ │کیف │ │برداشت│ │باشگا│ │ - hover: lift + primary border +│ └────┘ └────┘ └────┘ └────┘ │ +│ ┌────┐ ┌────┐ │ +│ │آمار│ │پاداش│ │ +│ └────┘ └────┘ │ +└─────────────────────────────────────────┘ +``` + +### 10.2 تغییرات فایل‌ها + +**`Profile/Index.razor`** — بازنویسی کامل: +- هدر گرادیانی (`dash-header-card`) با آواتار شیشه‌ای +- ۳ کارت والت جداگانه (اعتباری/تخفیف/تیمی) با رنگ‌های مختلف +- بخش رفرال تمیزتر (فعال با TextField + دکمه | قفل با dashed border) +- ۱۰ تایل مدرن با `DashTile` record + آواتار رنگی + +**`Profile/Index.razor.cs`** — اضافه شده: +```csharp +private readonly List _dashTiles = new() +{ + new(RouteConstants.Profile.Personal, Icons.Material.Filled.Person, "اطلاعات شخصی", ...), + new(RouteConstants.Profile.Addresses, Icons.Material.Filled.LocationOn, "آدرس‌ها", ...), + // ... 10 تایل با رنگ‌بندی اختصاصی +}; +private record DashTile(string Href, string Icon, string Title, string Subtitle, string AvatarStyle); +``` + +### 10.3 CSS اضافه شده (بخش Dashboard v2 در site.css) + +```css +/* Dashboard v2 region */ +.dash-header-card — گرادیان بنفش + دایره تزئینی +.dash-avatar — شیشه‌ای (rgba + border سفید) +.dash-wallet-card — border + hover lift +.dash-tile — border + hover lift + primary border on hover +/* + dark mode + mobile overrides */ +``` + +--- + +## 11. فاز ۷ — بهینه‌سازی موبایل ✅ تکمیل شد + +> **وضعیت:** ✅ تکمیل | **اولویت:** بالا | **پیش‌نیاز:** فازهای ۴-۶ + +### 11.1 مشکلات شناسایی و رفع شده + +| مشکل | وضعیت قبل | راه‌حل | +|------|----------|--------| +| **Dead Zone ناوبری (600-959px)** | همبرگر فقط زیر 600px، لینک‌ها فقط بالای 960px | تغییر Breakpoint از `SmAndUp` به `MdAndUp` | +| **فوتر پنهان در موبایل** | `DeviceDetector.IsDesktop()` فوتر را حذف می‌کرد | حذف شرط — فوتر همیشه نمایش داده شود | +| **عدم وجود Bottom Nav** | فقط Drawer برای ناوبری موبایل | اضافه شدن bottom-nav ثابت (5 لینک) | +| **تایپوگرافی بزرگ** | h1=2rem, h2=1.875rem ثابت | responsive: h1→1.5rem, h2→1.35rem در موبایل | +| **Touch targets کوچک** | Dense AppBar، لینک‌های Drawer بدون padding | min-height: 44px برای لینک‌ها، 40px برای آیکون‌ها | +| **نقشه تماس بلند** | 400px ثابت | 240px در موبایل با کلاس `contact-map-placeholder` | +| **دکمه‌های About تنگ** | Row بدون flex-wrap | اضافه flex-wrap + full-width در موبایل | +| **محتوای بلاگ overflow** | بدون guard | `overflow-x: auto` + table/iframe responsive | +| **هاور در تاچ** | جلوه‌های هاور در صفحه لمسی | `@media (hover: none)` — غیرفعال | +| **Safe area** | بدون padding | `env(safe-area-inset-*)` برای notched phones | +| **theme-color** | نداشت | `` | + +### 11.2 Bottom Navigation — پیاده‌سازی + +``` +┌──────────────────────────────────────┐ +│ 🏠 🏬 📰 🛒 👤 │ ← .bottom-nav (fixed) +│ خانه فروش بلاگ سبد پروفایل │ backdrop-filter blur +└──────────────────────────────────────┘ +``` + +- فقط در زیر `Breakpoint.MdAndUp` نمایش داده شود (`MudHidden`) +- اگر لاگین نباشد: دکمه "ورود" بجای "سبد خرید" و "پروفایل" +- `padding-bottom: 68px` روی `.main-content-wrapper` برای جلوگیری از overlap +- `env(safe-area-inset-bottom)` برای گوشی‌های notched + +### 11.3 CSS اضافه شده (بخش Phase 7 در site.css ~120 خط) + +``` +.bottom-nav — fixed, z-1100, rounded top, glass blur +.bottom-nav-item — flex column, min 48px touch, transition color +.main-content-wrapper — padding-bottom 68px (≤960px) +responsive typography — h1-h5 smaller ≤600px +touch targets — drawer links 44px, appbar icons 40px +footer mobile — compact padding, smaller text +landing mobile — smaller chips, timeline text +blog mobile — shorter thumbnails, scaled pagination, overflow guard +dashboard mobile — compact wallets + tiles +about mobile — hero button wrap, reduced padding +contact mobile — shorter map, full-width submit +global mobile — tighter container padding, safe-area +@media (hover: none) — disable hover transforms on touch +``` + +### 11.4 فایل‌های تغییریافته + +``` +MainLayout.razor — Breakpoint fix + Bottom Nav + Footer gate removed +site.css — +120 خط CSS موبایل (Phase 7 region) +_Host.cshtml — +theme-color + apple-mobile-web-app meta tags +Contact.razor — +class contact-map-placeholder +About.razor — +flex-wrap روی دکمه‌های hero +``` + +--- + +## 12. فایل‌های تغییریافته (نقشه فایل‌ها) + +### فایل‌های جدید + +| پروژه | مسیر | تعداد فایل | +|--------|------|-----------| +| **CMS Domain** | `Entities/Blog/` (BlogPost, BlogCategory, BlogTag, BlogPostCategory, BlogPostTag, BlogPostImage) + `Entities/Content/` (SitePage, SitePageAttachment) + `Enums/` (BlogPostStatus) | **9** | +| **CMS Infrastructure** | `Configurations/Blog/` (5) + `Configurations/Content/` (2) + EF Migration | **8** | +| **CMS Application** | `BlogPostCQ/` (~25) + `BlogCategoryCQ/` (~12) + `BlogPostImageCQ/` (~9) + `SitePageCQ/` (~18) | **~64** | +| **CMS Protobuf** | `public_messages.proto` + `blogpost.proto` + `blogcategory.proto` + `sitepage.proto` | **4** | +| **CMS WebApi** | `Services/` (BlogPostService, BlogCategoryService, SitePageService, BlogPostImageService) + `Mappings/` (4 profiles) | **8** | +| **BackOffice Pages** | `Pages/Blog/` (BlogPosts.razor, BlogCategories.razor + ۲ dialog) + `Pages/Content/` (SitePages.razor + ۳ dialog) | **10** | +| **BackOffice Services** | `Services/Blog/` (BlogPostService, BlogCategoryService, BlogTagService + ۳ model) + `Services/Content/` (SitePageService + model) | **8** | +| **FrontOffice Pages** | `Pages/Blog/Index.razor` + `Index.razor.cs` + `Post.razor` + `Post.razor.cs` | **4** | +| **FrontOffice Services** | `Utilities/BlogPostService.cs` + `Utilities/BlogCategoryService.cs` + `Utilities/SitePageService.cs` | **3** | +| **FrontOffice Assets** | `wwwroot/js/landing.js` | **1** | +| | | **~119 فایل جدید** | + +### فایل‌های ویرایشی + +| پروژه | فایل | تغییر | +|--------|------|-------| +| CMS Infrastructure | `ApplicationDbContext.cs` | +7 DbSets (BlogPost, BlogCategory, BlogTag, BlogPostCategory, BlogPostTag, BlogPostImage, SitePage) | +| CMS WebApi | `Program.cs` | +4 gRPC service registrations | +| BackOffice | `Shared/NavMenu.razor` | +بخش مدیریت محتوا (بلاگ + صفحات) + ۲ مجوز جدید | +| BackOffice | `Common/Configure/ConfigureService.cs` | +4 gRPC clients + 4 services + using statements | +| FrontOffice | `Pages/Index.razor` | بازنویسی کامل (Hero + Features + Timeline + Stats + Blog + Testimonials + FAQ + CTA) | +| FrontOffice | `Pages/Index.razor.cs` | +LoadFeaturedPosts + NavigateToPost | +| FrontOffice | `Pages/About.razor` | تبدیل به دینامیک (CMS + fallback) | +| FrontOffice | `Pages/Contact.razor` | تبدیل به دینامیک (CMS + fallback) | +| FrontOffice | `Pages/Profile/Index.razor` | بازنویسی کامل (header card + wallets + tiles) | +| FrontOffice | `Pages/Profile/Index.razor.cs` | +DashTile record + _dashTiles لیست | +| FrontOffice | `Shared/MainLayout.razor` | +لینک بلاگ (desktop + mobile drawer) | +| FrontOffice | `Common/Configure/ConfigureServices.cs` | +BlogCategoryService + BlogCategoryContract gRPC | +| FrontOffice | `Utilities/RouteConstants.cs` | +Blog.Index + Blog.Post routes | +| FrontOffice | `wwwroot/css/site.css` | +Landing v2 + Blog Pages + Dashboard v2 (~300 خط CSS) | +| FrontOffice | `Pages/_Host.cshtml` | +landing.js script reference | +| | | **~15 فایل ویرایشی** | + +--- + +## 13. جدول زمانی + +``` +هفته ۱: + ├─ فاز ۱: موجودیت‌های بکند (Entities + Protos + CQRS) [۲-۳ روز] ✅ تکمیل + └─ فاز ۲: پنل مدیریت بلاگ BackOffice [۲-۳ روز] ✅ تکمیل + +هفته ۲: + ├─ فاز ۳: صفحات دینامیک (About + Contact) [۱-۲ روز] ✅ تکمیل + ├─ فاز ۴: نوسازی لندینگ پیج [۲-۳ روز] ✅ تکمیل + └─ فاز ۵: صفحات بلاگ FrontOffice [۲-۳ روز] ✅ تکمیل + +هفته ۳: + ├─ فاز ۶: بهبود داشبورد کاربر [۱-۲ روز] ✅ تکمیل + ├─ فاز ۷: بهینه‌سازی موبایل [۱-۲ روز] ✅ تکمیل + └─ تست + رفع باگ + دیپلوی [۱-۲ روز] +``` + +**مجموع تخمین: ۱۲-۲۰ روز کاری (۲.۵ تا ۴ هفته)** + +### ترتیب وابستگی + +``` +فاز ۱ (Backend) ──► فاز ۲ (Admin Blog) + │ + ├──────────► فاز ۳ (Dynamic Pages) + │ + ├──────────► فاز ۵ (FrontOffice Blog) + │ │ + │ ▼ + └──────────► فاز ۴ (Landing) ──► فاز ۷ (Mobile) + ▲ + فاز ۶ (Dashboard) ───────┘ +``` + +--- + +## 14. ریسک‌ها و وابستگی‌ها + +| ریسک | احتمال | تأثیر | راه‌حل | +|------|--------|-------|--------| +| WYSIWYG Editor محدودیت RTL | متوسط | متوسط | تست اولیه MudBlazor.HtmlEditor با فارسی، fallback به textarea + markdown | +| حجم Migration بزرگ | کم | بالا | Migration را تکه‌تکه اجرا کن (Blog اول، SitePage بعد) | +| عملکرد لندینگ با انیمیشن‌ها | متوسط | متوسط | Lazy load + Intersection Observer + حداقل JS | +| FMS قطعی هنگام آپلود تصویر | کم | بالا | Retry policy + پیام خطای مناسب | +| Slug فارسی در URL | متوسط | کم | استفاده از ID-based URLs با slug فقط برای SEO | +| سازگاری Dark Mode | متوسط | کم | تست هر بخش در هر دو حالت | + +--- + +## ضمیمه: نمونه کد الگوهای کلیدی + +### A. الگوی Entity (مطابق پروژه) + +```csharp +// Domain/Entities/Blog/BlogPost.cs +public class BlogPost : BaseAuditableEntity +{ + public string Title { get; set; } = default!; + public string Slug { get; set; } = default!; + public string? Summary { get; set; } + public string HtmlContent { get; set; } = default!; + public string? FeaturedImagePath { get; set; } + public string? FeaturedImageThumbnailPath { get; set; } + public BlogPostStatus Status { get; set; } = BlogPostStatus.Draft; + public DateTime? PublishedAt { get; set; } + public int ViewCount { get; set; } + public long AuthorUserId { get; set; } + public bool IsFeatured { get; set; } + public int SortOrder { get; set; } + + public ICollection BlogPostCategories { get; set; } = new List(); + public ICollection BlogPostTags { get; set; } = new List(); + public ICollection BlogPostImages { get; set; } = new List(); +} +``` + +### B. الگوی Proto (مطابق پروژه) + +```protobuf +// blogpost.proto +syntax = "proto3"; +option csharp_namespace = "CMSMicroservice.Protobuf"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +service BlogPostContract { + rpc CreateBlogPost (CreateBlogPostRequest) returns (BlogPostResponse); + rpc GetPublishedBlogPosts (GetPublishedBlogPostsRequest) returns (BlogPostListResponse); + // ... +} + +message CreateBlogPostRequest { + string title = 1; + string slug = 2; + string summary = 3; + string html_content = 4; + bytes featured_image = 5; + string featured_image_file_name = 6; + repeated int64 category_ids = 7; + repeated int64 tag_ids = 8; + bool is_featured = 9; +} + +message BlogPostResponse { + int64 id = 1; + string title = 2; + string slug = 3; + string summary = 4; + string html_content = 5; + string featured_image_path = 6; + string featured_image_thumbnail_path = 7; + int32 status = 8; + google.protobuf.Timestamp published_at = 9; + int32 view_count = 10; + bool is_featured = 11; + repeated BlogCategoryInfo categories = 12; + repeated TagInfo tags = 13; +} +``` + +### C. الگوی CQRS Handler (مطابق پروژه) + +```csharp +// Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandHandler.cs +public class CreateBlogPostCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IFileManagerService _fileManager; + + public CreateBlogPostCommandHandler( + IApplicationDbContext context, + IFileManagerService fileManager) + { + _context = context; + _fileManager = fileManager; + } + + public async Task Handle(CreateBlogPostCommand request, CancellationToken ct) + { + var post = new BlogPost + { + Title = request.Title, + Slug = request.Slug, + Summary = request.Summary, + HtmlContent = request.HtmlContent, + Status = BlogPostStatus.Draft, + AuthorUserId = request.AuthorUserId, + }; + + if (request.FeaturedImage?.Length > 0) + { + var (path, thumb) = await _fileManager.UploadFileAsync( + request.FeaturedImage, request.FeaturedImageFileName); + post.FeaturedImagePath = path; + post.FeaturedImageThumbnailPath = thumb; + } + + _context.BlogPosts.Add(post); + await _context.SaveChangesAsync(ct); + + // Add categories & tags... + + return post.Id; + } +} +``` + +### D. الگوی gRPC Service (مطابق پروژه) + +```csharp +// WebApi/Services/BlogPostService.cs +public class BlogPostService : BlogPostContract.BlogPostContractBase +{ + private readonly IMediator _mediator; + private readonly IMapper _mapper; + + public BlogPostService(IMediator mediator, IMapper mapper) + { + _mediator = mediator; + _mapper = mapper; + } + + public override async Task CreateBlogPost( + CreateBlogPostRequest request, ServerCallContext context) + { + var command = _mapper.Map(request); + var id = await _mediator.Send(command); + // Get and return the created post... + } +} +``` + +--- + +> **یادداشت:** این سند یک نقشه‌راه جامع است. هر فاز به صورت مستقل قابل پیاده‌سازی است و وابستگی‌ها در بخش ۱۳ مشخص شده‌اند. بعد از تأیید این طرح، پیاده‌سازی فاز به فاز شروع می‌شود.