diff --git a/libs/BackOffice.BFF.Common.Protobuf.dll b/libs/BackOffice.BFF.Common.Protobuf.dll index c58dbc9..9be5fc3 100644 Binary files a/libs/BackOffice.BFF.Common.Protobuf.dll and b/libs/BackOffice.BFF.Common.Protobuf.dll differ diff --git a/libs/BackOffice.BFF.DiscountProduct.Protobuf.dll b/libs/BackOffice.BFF.DiscountProduct.Protobuf.dll index e967bd9..717621d 100644 Binary files a/libs/BackOffice.BFF.DiscountProduct.Protobuf.dll and b/libs/BackOffice.BFF.DiscountProduct.Protobuf.dll differ diff --git a/src/BackOffice/Pages/DiscountShop/Components/ProductFormDialog.razor b/src/BackOffice/Pages/DiscountShop/Components/ProductFormDialog.razor index c2e0b54..6dc8770 100644 --- a/src/BackOffice/Pages/DiscountShop/Components/ProductFormDialog.razor +++ b/src/BackOffice/Pages/DiscountShop/Components/ProductFormDialog.razor @@ -1,121 +1,155 @@ @using BackOffice.Services.DiscountProduct @using BackOffice.Services.DiscountCategory +@using Microsoft.AspNetCore.Components.Forms +@using Tizzani.MudBlazor.HtmlEditor +@using BackOffice.Common.BaseComponents +@using BackOffice.Pages.AutoComplete @inject ISnackbar Snackbar - - - - + + + @if (!string.IsNullOrWhiteSpace(_mainImagePreview)) + { + + } + else if (!string.IsNullOrEmpty(Model.ImagePath)) + { + + } + else + { + + تصویری انتخاب نشده است + + } - - - + + + + انتخاب تصویر اصلی + + + + @if (context != null) + { + + + @context.Name + + + } + else + { + فایلی انتخاب نشده + } + + + - - - - - - - - - - - - - - - - - - - @foreach (var category in _categories) - { - @GetCategoryPath(category) - } - - - - - - - - - - - - @if (!string.IsNullOrEmpty(Model.ThumbnailPath)) - { - - + + + - } + + + + - - - - + + + + + + + + + + + + + + + + + دسته‌بندی‌ها + + @if (_selectedCategoryIds?.Count > 0) + { + + تعداد دسته‌بندی‌های انتخاب‌شده: @_selectedCategoryIds.Count + + } + + + + تصویر بندانگشتی + + + + انتخاب تصویر بندانگشتی (اختیاری) + + + + + + + - انصراف + انصراف - @if (_loading) - { - - } - else - { - @(IsEditMode ? "ذخیره تغییرات" : "ایجاد محصول") - } - + Disabled="@(!_isValid || _loading)">ثبت @@ -128,56 +162,35 @@ private MudForm? _form; private bool _isValid; private bool _loading; - private List _categories = new(); - private IEnumerable _selectedCategoryIds = new List(); + private IBrowserFile? _mainImageFile; + private IBrowserFile? _thumbnailImageFile; + private readonly long _maxAllowedSize = (1024 * 1024) * 5; + private string? _mainImagePreview; + private List _selectedCategoryIds = new(); protected override async Task OnInitializedAsync() { - await LoadCategories(); - if (Model.CategoryIds?.Any() == true) { _selectedCategoryIds = Model.CategoryIds; } } - private async Task LoadCategories() + private async Task OnMainImageSelected(IBrowserFile? file) { - try + _mainImageFile = file; + if (file != null) { - var tree = await CategoryService.GetCategoriesAsync(isActive: true); - _categories = FlattenCategories(tree); - } - catch (Exception ex) - { - Snackbar.Add($"خطا در بارگذاری دسته‌بندی‌ها: {ex.Message}", Severity.Error); + var buffer = new byte[file.Size]; + await file.OpenReadStream(_maxAllowedSize).ReadAsync(buffer); + _mainImagePreview = $"data:{file.ContentType};base64," + Convert.ToBase64String(buffer); + StateHasChanged(); } } - private List FlattenCategories(List categories, string prefix = "") + private async Task OnThumbnailSelected(IBrowserFile? file) { - var result = new List(); - foreach (var category in categories) - { - var catCopy = new DiscountCategoryDto - { - Id = category.Id, - Title = prefix + category.Title, - ParentCategoryId = category.ParentCategoryId - }; - result.Add(catCopy); - - if (category.Children.Any()) - { - result.AddRange(FlattenCategories(category.Children.ToList(), prefix + " ")); - } - } - return result; - } - - private string GetCategoryPath(DiscountCategoryDto category) - { - return category.Title; + _thumbnailImageFile = file; } private async Task Submit() @@ -193,6 +206,26 @@ // Update CategoryIds from selected values Model.CategoryIds = _selectedCategoryIds.ToList(); + // Handle main image upload + if (_mainImageFile != null) + { + var buffer = new byte[_mainImageFile.Size]; + await _mainImageFile.OpenReadStream(_maxAllowedSize).ReadAsync(buffer); + Model.ImageFile = buffer; + Model.ImageMime = _mainImageFile.ContentType; + Model.ImageFileName = Path.GetFileNameWithoutExtension(_mainImageFile.Name); + } + + // Handle thumbnail upload + if (_thumbnailImageFile != null) + { + var buffer = new byte[_thumbnailImageFile.Size]; + await _thumbnailImageFile.OpenReadStream(_maxAllowedSize).ReadAsync(buffer); + Model.ThumbnailFile = buffer; + Model.ThumbnailMime = _thumbnailImageFile.ContentType; + Model.ThumbnailFileName = Path.GetFileNameWithoutExtension(_thumbnailImageFile.Name); + } + MudDialog.Close(DialogResult.Ok(Model)); _loading = false; } @@ -211,9 +244,16 @@ public string? ThumbnailPath { get; set; } public long Price { get; set; } public int MaxDiscountPercent { get; set; } - public int InitialCount { get; set; } public int SortOrder { get; set; } public bool IsActive { get; set; } = true; public List? CategoryIds { get; set; } + + // Image upload properties + public byte[]? ImageFile { get; set; } + public string? ImageMime { get; set; } + public string? ImageFileName { get; set; } + public byte[]? ThumbnailFile { get; set; } + public string? ThumbnailMime { get; set; } + public string? ThumbnailFileName { get; set; } } } diff --git a/src/BackOffice/Pages/DiscountShop/DiscountProductsMainPage.razor b/src/BackOffice/Pages/DiscountShop/DiscountProductsMainPage.razor index 9ab4344..f723dcd 100644 --- a/src/BackOffice/Pages/DiscountShop/DiscountProductsMainPage.razor +++ b/src/BackOffice/Pages/DiscountShop/DiscountProductsMainPage.razor @@ -232,10 +232,16 @@ ThumbnailPath = formData.ThumbnailPath, Price = formData.Price, MaxDiscountPercent = formData.MaxDiscountPercent, - InitialCount = formData.InitialCount, + InitialCount = 0, // Always 0 - must use inventory management SortOrder = formData.SortOrder, IsActive = formData.IsActive, - CategoryIds = formData.CategoryIds + CategoryIds = formData.CategoryIds, + ImageFile = formData.ImageFile, + ImageMime = formData.ImageMime, + ImageFileName = formData.ImageFileName, + ThumbnailFile = formData.ThumbnailFile, + ThumbnailMime = formData.ThumbnailMime, + ThumbnailFileName = formData.ThumbnailFileName }; await DiscountProductService.CreateAsync(dto); @@ -269,7 +275,6 @@ ThumbnailPath = product.ThumbnailPath, Price = product.Price, MaxDiscountPercent = product.MaxDiscountPercent, - InitialCount = product.RemainingCount, SortOrder = product.SortOrder, IsActive = product.IsActive, CategoryIds = product.Categories?.Select(c => c.Id).ToList() ?? new() @@ -298,7 +303,13 @@ MaxDiscountPercent = formData.MaxDiscountPercent, SortOrder = formData.SortOrder, IsActive = formData.IsActive, - CategoryIds = formData.CategoryIds + CategoryIds = formData.CategoryIds, + ImageFile = formData.ImageFile, + ImageMime = formData.ImageMime, + ImageFileName = formData.ImageFileName, + ThumbnailFile = formData.ThumbnailFile, + ThumbnailMime = formData.ThumbnailMime, + ThumbnailFileName = formData.ThumbnailFileName }; await DiscountProductService.UpdateAsync(productId, dto); diff --git a/src/BackOffice/Services/DiscountProduct/DiscountProductService.cs b/src/BackOffice/Services/DiscountProduct/DiscountProductService.cs index 06b6a86..7f2b711 100644 --- a/src/BackOffice/Services/DiscountProduct/DiscountProductService.cs +++ b/src/BackOffice/Services/DiscountProduct/DiscountProductService.cs @@ -1,4 +1,5 @@ using BackOffice.BFF.DiscountProduct.Protobuf.Protos.DiscountProduct; +using Google.Protobuf; using Google.Protobuf.WellKnownTypes; namespace BackOffice.Services.DiscountProduct; @@ -115,6 +116,28 @@ public class DiscountProductService : IDiscountProductService request.CategoryIds.AddRange(dto.CategoryIds); } + // Add image file if provided + if (dto.ImageFile != null && !string.IsNullOrEmpty(dto.ImageMime) && !string.IsNullOrEmpty(dto.ImageFileName)) + { + request.ImageFile = new ImageFileModel + { + File = ByteString.CopyFrom(dto.ImageFile), + Mime = dto.ImageMime, + FileName = dto.ImageFileName + }; + } + + // Add thumbnail file if provided + if (dto.ThumbnailFile != null && !string.IsNullOrEmpty(dto.ThumbnailMime) && !string.IsNullOrEmpty(dto.ThumbnailFileName)) + { + request.ThumbnailFile = new ImageFileModel + { + File = ByteString.CopyFrom(dto.ThumbnailFile), + Mime = dto.ThumbnailMime, + FileName = dto.ThumbnailFileName + }; + } + var response = await _client.CreateDiscountProductAsync(request); return response.ProductId; } @@ -140,6 +163,50 @@ public class DiscountProductService : IDiscountProductService request.CategoryIds.AddRange(dto.CategoryIds); } + // Handle image file upload + if (dto.ImageFile != null) + { + request.ImageFile = new ImageFileModel + { + File = ByteString.CopyFrom(dto.ImageFile), + Mime = dto.ImageMime ?? string.Empty, + FileName = dto.ImageFileName ?? string.Empty + }; + } + + // Handle thumbnail file upload + if (dto.ThumbnailFile != null) + { + request.ThumbnailFile = new ImageFileModel + { + File = ByteString.CopyFrom(dto.ThumbnailFile), + Mime = dto.ThumbnailMime ?? string.Empty, + FileName = dto.ThumbnailFileName ?? string.Empty + }; + } + + // Add image file if provided + if (dto.ImageFile != null && !string.IsNullOrEmpty(dto.ImageMime) && !string.IsNullOrEmpty(dto.ImageFileName)) + { + request.ImageFile = new ImageFileModel + { + File = ByteString.CopyFrom(dto.ImageFile), + Mime = dto.ImageMime, + FileName = dto.ImageFileName + }; + } + + // Add thumbnail file if provided + if (dto.ThumbnailFile != null && !string.IsNullOrEmpty(dto.ThumbnailMime) && !string.IsNullOrEmpty(dto.ThumbnailFileName)) + { + request.ThumbnailFile = new ImageFileModel + { + File = ByteString.CopyFrom(dto.ThumbnailFile), + Mime = dto.ThumbnailMime, + FileName = dto.ThumbnailFileName + }; + } + await _client.UpdateDiscountProductAsync(request); } diff --git a/src/BackOffice/Services/DiscountProduct/IDiscountProductService.cs b/src/BackOffice/Services/DiscountProduct/IDiscountProductService.cs index 6600a2b..c9a3b23 100644 --- a/src/BackOffice/Services/DiscountProduct/IDiscountProductService.cs +++ b/src/BackOffice/Services/DiscountProduct/IDiscountProductService.cs @@ -74,6 +74,14 @@ public class CreateDiscountProductDto public int SortOrder { get; set; } public bool IsActive { get; set; } = true; public List? CategoryIds { get; set; } + + // Image upload support + public byte[]? ImageFile { get; set; } + public string? ImageMime { get; set; } + public string? ImageFileName { get; set; } + public byte[]? ThumbnailFile { get; set; } + public string? ThumbnailMime { get; set; } + public string? ThumbnailFileName { get; set; } } public class UpdateDiscountProductDto @@ -88,4 +96,13 @@ public class UpdateDiscountProductDto public int SortOrder { get; set; } public bool IsActive { get; set; } public List? CategoryIds { get; set; } + + // Image upload support + public byte[]? ImageFile { get; set; } + public string? ImageMime { get; set; } + public string? ImageFileName { get; set; } + public byte[]? ThumbnailFile { get; set; } + public string? ThumbnailMime { get; set; } + public string? ThumbnailFileName { get; set; } } +