Faqs { get; set; } = new();
+ public string? CtaTitle { get; set; }
+ public string? CtaDescription { get; set; }
+ public string? CtaButtonText { get; set; }
+ public bool? FeaturedBlogEnabled { get; set; }
+}
+
+public class TrustBadgeModel
+{
+ public string? IconName { get; set; }
+ public string? Text { get; set; }
+}
+
+public class StepModel
+{
+ public string? Title { get; set; }
+ public string? Description { get; set; }
+}
+
+public class FeatureModel
+{
+ public string? IconName { get; set; }
+ public string? Title { get; set; }
+ public string? Description { get; set; }
+}
+
+public class StatModel
+{
+ public string? Label { get; set; }
+ public double Value { get; set; }
+ public string? Suffix { get; set; }
+ public string? Color { get; set; }
+}
+
+public class TestimonialModel
+{
+ public string? Quote { get; set; }
+ public string? Name { get; set; }
+ public string? Role { get; set; }
+}
+
+public class FaqModel
+{
+ public string? Question { get; set; }
+ public string? Answer { get; set; }
+}
+
+// ══════════════════════════════════════════════════════════════
+// ABOUT PAGE
+// ══════════════════════════════════════════════════════════════
+
+public class AboutSettingsModel
+{
+ public string? VisionTitle { get; set; }
+ public string? VisionText { get; set; }
+ public string? VisionIcon { get; set; }
+ public string? MissionTitle { get; set; }
+ public string? MissionText { get; set; }
+ public string? MissionIcon { get; set; }
+ public string? ValuesTitle { get; set; }
+ public string? TeamTitle { get; set; }
+}
+
+// ══════════════════════════════════════════════════════════════
+// CONTACT PAGE
+// ══════════════════════════════════════════════════════════════
+
+public class ContactSettingsModel
+{
+ public string? Address { get; set; }
+ public string? Phone { get; set; }
+ public string? Email { get; set; }
+ public string? WorkingHours { get; set; }
+ public string? TelegramUrl { get; set; }
+ public string? InstagramUrl { get; set; }
+ public string? LinkedinUrl { get; set; }
+ public string? WhatsappUrl { get; set; }
+}
+
+// ══════════════════════════════════════════════════════════════
+// LICENSES PAGE
+// ══════════════════════════════════════════════════════════════
+
+public class LicensesSettingsModel
+{
+ public string? PageDescription { get; set; }
+ public string? DisplayStyle { get; set; }
+}
diff --git a/src/BackOffice/Pages/Content/PageSettingsManagementPage.razor b/src/BackOffice/Pages/Content/PageSettingsManagementPage.razor
new file mode 100644
index 0000000..8a99432
--- /dev/null
+++ b/src/BackOffice/Pages/Content/PageSettingsManagementPage.razor
@@ -0,0 +1,74 @@
+@page "/content/page-settings"
+
+@using BackOffice.Services.Content
+
+@inject ISitePageSettingsService PageSettingsService
+
+
+
+ مدیریت تنظیمات صفحات
+ تنظیمات ۴ صفحه ثابت سایت: صفحه اصلی، درباره ما، تماس با ما و مجوزها
+
+ @if (_loading)
+ {
+
+ }
+ else
+ {
+
+ @foreach (var item in _pages)
+ {
+
+
+
+
+
+
+
+ @item.Title
+ @item.PageKey
+
+
+
+
+
+ @(item.IsActive ? "فعال" : "غیرفعال")
+
+
+
+
+
+
+
+ @item.ImageCount تصویر
+
+
+ آخرین ویرایش: @item.LastModified.MiladiToJalaliWithTime()
+
+
+
+
+
+ ویرایش
+
+
+ تصاویر
+
+
+
+
+ }
+
+ }
+
+
diff --git a/src/BackOffice/Pages/Content/PageSettingsManagementPage.razor.cs b/src/BackOffice/Pages/Content/PageSettingsManagementPage.razor.cs
new file mode 100644
index 0000000..6c77e93
--- /dev/null
+++ b/src/BackOffice/Pages/Content/PageSettingsManagementPage.razor.cs
@@ -0,0 +1,119 @@
+using BackOffice.Services.Content;
+using BackOffice.Pages.Content.Components;
+using Microsoft.AspNetCore.Components;
+using MudBlazor;
+
+namespace BackOffice.Pages.Content;
+
+public partial class PageSettingsManagementPage
+{
+ private List _pages = new();
+ private bool _loading = true;
+
+ protected override async Task OnInitializedAsync()
+ {
+ await LoadPagesAsync();
+ }
+
+ private async Task LoadPagesAsync()
+ {
+ _loading = true;
+ try
+ {
+ _pages = await PageSettingsService.GetAllAsync();
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"خطا در دریافت صفحات: {ex.Message}", Severity.Error);
+ }
+ finally
+ {
+ _loading = false;
+ }
+ }
+
+ private async Task EditPageSettings(PageSettingsSummaryDto summary)
+ {
+ var details = await PageSettingsService.GetByKeyAsync(summary.PageKey);
+ if (details == null)
+ {
+ Snackbar.Add("خطا در دریافت تنظیمات صفحه", Severity.Error);
+ return;
+ }
+
+ var dto = new SavePageSettingsDto
+ {
+ PageKey = details.PageKey,
+ Title = details.Title,
+ MetaDescription = details.MetaDescription,
+ HeroTitle = details.HeroTitle,
+ HeroSubtitle = details.HeroSubtitle,
+ IsActive = details.IsActive,
+ SettingsJson = details.SettingsJson
+ };
+
+ var parameters = new DialogParameters
+ {
+ { nameof(PageSettingsEditDialog.Model), dto },
+ { nameof(PageSettingsEditDialog.ExistingHeroImagePath), details.HeroImagePath },
+ { nameof(PageSettingsEditDialog.PageTitle), GetPageDisplayName(summary.PageKey) }
+ };
+
+ var dialog = await DialogService.ShowAsync(
+ $"ویرایش تنظیمات «{GetPageDisplayName(summary.PageKey)}»", parameters,
+ new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true });
+
+ var result = await dialog.Result;
+ if (result is { Canceled: false })
+ {
+ Snackbar.Add("تنظیمات صفحه با موفقیت ذخیره شد", Severity.Success);
+ await LoadPagesAsync();
+ }
+ }
+
+ private async Task ManageImages(PageSettingsSummaryDto summary)
+ {
+ var details = await PageSettingsService.GetByKeyAsync(summary.PageKey);
+ if (details == null)
+ {
+ Snackbar.Add("خطا در دریافت اطلاعات صفحه", Severity.Error);
+ return;
+ }
+
+ var parameters = new DialogParameters
+ {
+ { nameof(PageImagesDialog.PageId), details.Id },
+ { nameof(PageImagesDialog.PageKey), details.PageKey },
+ { nameof(PageImagesDialog.PageTitle), GetPageDisplayName(summary.PageKey) },
+ { nameof(PageImagesDialog.Images), details.Images }
+ };
+
+ var dialog = await DialogService.ShowAsync(
+ $"مدیریت تصاویر «{GetPageDisplayName(summary.PageKey)}»", parameters,
+ new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Large, FullWidth = true });
+
+ var result = await dialog.Result;
+ if (result is { Canceled: false })
+ {
+ await LoadPagesAsync();
+ }
+ }
+
+ private static string GetPageIcon(string pageKey) => pageKey switch
+ {
+ "landing" => Icons.Material.Filled.Home,
+ "about" => Icons.Material.Filled.Info,
+ "contact" => Icons.Material.Filled.ContactMail,
+ "licenses" => Icons.Material.Filled.VerifiedUser,
+ _ => Icons.Material.Filled.Article
+ };
+
+ private static string GetPageDisplayName(string pageKey) => pageKey switch
+ {
+ "landing" => "صفحه اصلی",
+ "about" => "درباره ما",
+ "contact" => "تماس با ما",
+ "licenses" => "مجوزها",
+ _ => pageKey
+ };
+}
diff --git a/src/BackOffice/Pages/Inventory/Components/AddStockDialog.razor b/src/BackOffice/Pages/Inventory/Components/AddStockDialog.razor
index 08e7f37..c51c21e 100644
--- a/src/BackOffice/Pages/Inventory/Components/AddStockDialog.razor
+++ b/src/BackOffice/Pages/Inventory/Components/AddStockDialog.razor
@@ -1,6 +1,7 @@
@using CMSMicroservice.Protobuf.Protos.Inventory
@using Google.Protobuf.WellKnownTypes
@using InventoryProductType = CMSMicroservice.Protobuf.Protos.Inventory.ProductType
+@using BackOffice.Pages.AutoComplete
@@ -19,12 +20,16 @@
-
+ @if (_productType == InventoryProductType.RegularProduct)
+ {
+
+ }
+ else
+ {
+
+ }
}
else
@@ -103,7 +108,8 @@
private bool _isSubmitting;
private InventoryProductType _productType = InventoryProductType.RegularProduct;
- private long _productId;
+ private long? _productId;
+ private long? _discountProductId;
private long? _warehouseId;
private int _quantity = 1;
private string _referenceNumber = string.Empty;
@@ -111,9 +117,12 @@
protected override void OnInitialized()
{
- if (ProductIdParam.HasValue)
+ if (ProductIdParam.HasValue && ProductTypeParam.HasValue)
{
- _productId = ProductIdParam.Value;
+ if (ProductTypeParam.Value == InventoryProductType.DiscountProduct)
+ _discountProductId = ProductIdParam.Value;
+ else
+ _productId = ProductIdParam.Value;
}
if (ProductTypeParam.HasValue)
{
@@ -144,9 +153,19 @@
_isSubmitting = true;
try
{
+ var selectedProductId = ProductIdParam
+ ?? (_productType == InventoryProductType.DiscountProduct ? _discountProductId : _productId);
+
+ if (!selectedProductId.HasValue || selectedProductId.Value <= 0)
+ {
+ Snackbar.Add("لطفاً یک محصول انتخاب کنید", Severity.Warning);
+ _isSubmitting = false;
+ return;
+ }
+
var request = new AddStockRequest
{
- ProductId = ProductIdParam ?? _productId,
+ ProductId = selectedProductId.Value,
ProductType = ProductTypeParam ?? _productType,
Quantity = _quantity,
ReferenceNumber = _referenceNumber ?? string.Empty,
diff --git a/src/BackOffice/Services/Content/ISitePageSettingsService.cs b/src/BackOffice/Services/Content/ISitePageSettingsService.cs
new file mode 100644
index 0000000..dca372c
--- /dev/null
+++ b/src/BackOffice/Services/Content/ISitePageSettingsService.cs
@@ -0,0 +1,96 @@
+namespace BackOffice.Services.Content;
+
+public interface ISitePageSettingsService
+{
+ Task> GetAllAsync();
+ Task GetByKeyAsync(string pageKey);
+ Task SaveSettingsAsync(SavePageSettingsDto dto);
+ Task SaveImageAsync(SavePageImageDto dto);
+ Task DeleteImageAsync(long id);
+}
+
+// ── Summary DTO (لیست صفحات) ──
+public class PageSettingsSummaryDto
+{
+ public long Id { get; set; }
+ public string PageKey { get; set; } = string.Empty;
+ public string Title { get; set; } = string.Empty;
+ public bool IsActive { get; set; }
+ public int ImageCount { get; set; }
+ public DateTime LastModified { get; set; }
+}
+
+// ── Details DTO (تنظیمات کامل + تصاویر) ──
+public class PageSettingsDetailsDto
+{
+ public long Id { get; set; }
+ public string PageKey { get; set; } = string.Empty;
+ public string Title { get; set; } = string.Empty;
+ public string? MetaDescription { get; set; }
+ public string? HeroTitle { get; set; }
+ public string? HeroSubtitle { get; set; }
+ public string? HeroImagePath { get; set; }
+ public bool IsActive { get; set; }
+ public string? SettingsJson { get; set; }
+ public List Images { get; set; } = new();
+}
+
+// ── Image Item DTO ──
+public class PageImageItemDto
+{
+ public long Id { get; set; }
+ public string ImageGroup { get; set; } = string.Empty;
+ public string? Title { get; set; }
+ public string? Subtitle { get; set; }
+ public string? Description { get; set; }
+ public string ImagePath { get; set; } = string.Empty;
+ public string? ThumbnailPath { get; set; }
+ public string? LinkUrl { get; set; }
+ public string? IconName { get; set; }
+ public int SortOrder { get; set; }
+ public bool IsActive { get; set; }
+}
+
+// ── Save Settings DTO ──
+public class SavePageSettingsDto
+{
+ public string PageKey { get; set; } = string.Empty;
+ public string Title { get; set; } = string.Empty;
+ public string? MetaDescription { get; set; }
+ public string? HeroTitle { get; set; }
+ public string? HeroSubtitle { get; set; }
+ public bool IsActive { get; set; } = true;
+ public string? SettingsJson { get; set; }
+
+ // Hero image upload
+ public byte[]? ImageFile { get; set; }
+ public string? ImageMime { get; set; }
+ public string? ImageFileName { get; set; }
+}
+
+// ── Save Image DTO ──
+public class SavePageImageDto
+{
+ public long Id { get; set; } // 0 = جدید
+ public long SitePageSettingsId { get; set; }
+ public string ImageGroup { get; set; } = string.Empty;
+ public string? Title { get; set; }
+ public string? Subtitle { get; set; }
+ public string? Description { get; set; }
+ public string? LinkUrl { get; set; }
+ public string? IconName { get; set; }
+ public int SortOrder { get; set; }
+ public bool IsActive { get; set; } = true;
+
+ // Image upload
+ public byte[]? ImageFile { get; set; }
+ public string? ImageMime { get; set; }
+ public string? ImageFileName { get; set; }
+}
+
+// ── Save Result ──
+public class SaveResultDto
+{
+ public long Id { get; set; }
+ public bool Success { get; set; }
+}
diff --git a/src/BackOffice/Services/Content/SitePageSettingsService.cs b/src/BackOffice/Services/Content/SitePageSettingsService.cs
new file mode 100644
index 0000000..40f94f4
--- /dev/null
+++ b/src/BackOffice/Services/Content/SitePageSettingsService.cs
@@ -0,0 +1,131 @@
+using CMSMicroservice.Protobuf.Protos.SitePageSettings;
+
+namespace BackOffice.Services.Content;
+
+public class SitePageSettingsService : ISitePageSettingsService
+{
+ private readonly SitePageSettingsContract.SitePageSettingsContractClient _client;
+
+ public SitePageSettingsService(SitePageSettingsContract.SitePageSettingsContractClient client)
+ {
+ _client = client;
+ }
+
+ public async Task> GetAllAsync()
+ {
+ var response = await _client.GetAllPageSettingsAsync(new GetAllPageSettingsRequest());
+
+ return response.Pages.Select(m => new PageSettingsSummaryDto
+ {
+ Id = m.Id,
+ PageKey = m.PageKey,
+ Title = m.Title,
+ IsActive = m.IsActive,
+ ImageCount = m.ImageCount,
+ LastModified = m.LastModified?.ToDateTime() ?? DateTime.MinValue
+ }).ToList();
+ }
+
+ public async Task GetByKeyAsync(string pageKey)
+ {
+ var response = await _client.GetPageSettingsAsync(
+ new GetPageSettingsRequest { PageKey = pageKey });
+
+ if (response == null || response.Id <= 0) return null;
+
+ var dto = new PageSettingsDetailsDto
+ {
+ Id = response.Id,
+ PageKey = response.PageKey,
+ Title = response.Title,
+ MetaDescription = response.MetaDescription,
+ HeroTitle = response.HeroTitle,
+ HeroSubtitle = response.HeroSubtitle,
+ HeroImagePath = response.HeroImagePath,
+ IsActive = response.IsActive,
+ SettingsJson = response.SettingsJson
+ };
+
+ foreach (var img in response.Images)
+ {
+ dto.Images.Add(new PageImageItemDto
+ {
+ Id = img.Id,
+ ImageGroup = img.ImageGroup,
+ Title = img.Title,
+ Subtitle = img.Subtitle,
+ Description = img.Description,
+ ImagePath = img.ImagePath,
+ ThumbnailPath = img.ThumbnailPath,
+ LinkUrl = img.LinkUrl,
+ IconName = img.IconName,
+ SortOrder = img.SortOrder,
+ IsActive = img.IsActive
+ });
+ }
+
+ return dto;
+ }
+
+ public async Task SaveSettingsAsync(SavePageSettingsDto dto)
+ {
+ var request = new SavePageSettingsRequest
+ {
+ PageKey = dto.PageKey,
+ Title = dto.Title,
+ MetaDescription = dto.MetaDescription ?? string.Empty,
+ HeroTitle = dto.HeroTitle ?? string.Empty,
+ HeroSubtitle = dto.HeroSubtitle ?? string.Empty,
+ IsActive = dto.IsActive,
+ SettingsJson = dto.SettingsJson ?? string.Empty
+ };
+
+ if (dto.ImageFile != null && !string.IsNullOrEmpty(dto.ImageMime) && !string.IsNullOrEmpty(dto.ImageFileName))
+ {
+ request.HeroImageFile = new PageSettingsImageFile
+ {
+ File = Google.Protobuf.ByteString.CopyFrom(dto.ImageFile),
+ Mime = dto.ImageMime,
+ FileName = dto.ImageFileName
+ };
+ }
+
+ var response = await _client.SavePageSettingsAsync(request);
+ return new SaveResultDto { Id = response.Id, Success = response.Success };
+ }
+
+ public async Task SaveImageAsync(SavePageImageDto dto)
+ {
+ var request = new SavePageImageRequest
+ {
+ Id = dto.Id,
+ SitePageSettingsId = dto.SitePageSettingsId,
+ ImageGroup = dto.ImageGroup,
+ Title = dto.Title ?? string.Empty,
+ Subtitle = dto.Subtitle ?? string.Empty,
+ Description = dto.Description ?? string.Empty,
+ LinkUrl = dto.LinkUrl ?? string.Empty,
+ IconName = dto.IconName ?? string.Empty,
+ SortOrder = dto.SortOrder,
+ IsActive = dto.IsActive
+ };
+
+ if (dto.ImageFile != null && !string.IsNullOrEmpty(dto.ImageMime) && !string.IsNullOrEmpty(dto.ImageFileName))
+ {
+ request.ImageFile = new PageSettingsImageFile
+ {
+ File = Google.Protobuf.ByteString.CopyFrom(dto.ImageFile),
+ Mime = dto.ImageMime,
+ FileName = dto.ImageFileName
+ };
+ }
+
+ var response = await _client.SavePageImageAsync(request);
+ return new SaveResultDto { Id = response.Id, Success = response.Success };
+ }
+
+ public async Task DeleteImageAsync(long id)
+ {
+ await _client.DeletePageImageAsync(new DeletePageImageRequest { Id = id });
+ }
+}
diff --git a/src/BackOffice/Shared/NavMenu.razor b/src/BackOffice/Shared/NavMenu.razor
index 44f964a..6a61a93 100644
--- a/src/BackOffice/Shared/NavMenu.razor
+++ b/src/BackOffice/Shared/NavMenu.razor
@@ -231,9 +231,9 @@
@if (CanManageSitePages)
{
- صفحات سایت
+ تنظیمات صفحات
}
diff --git a/src/BackOffice/wwwroot/appsettings.json b/src/BackOffice/wwwroot/appsettings.json
index 9e32c79..abfef19 100644
--- a/src/BackOffice/wwwroot/appsettings.json
+++ b/src/BackOffice/wwwroot/appsettings.json
@@ -1,8 +1,5 @@
{
- "GwUrl": "http://localhost:32847",
-// "GwUrl": "https://localhost:32846",
-// "GwUrl": "https://backoffice-bff.se.kbs1.ir", // OLD - via BFF
- // "GwUrl": "https://cms.se.kbs1.ir", // NEW - direct to CMS
+ "GwUrl": "http://localhost:32847",
"Authentication": {
"Authority": "https://ids.afrino.co/",
"ClientId": "client_backoffice_spa"