Add category and product drag-drop management pages
This commit is contained in:
@@ -21,22 +21,20 @@
|
|||||||
|
|
||||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.Category.Protobuf" Version="0.0.1" />
|
<PackageReference Include="Foursat.BackOffice.BFF.Category.Protobuf" Version="0.0.3" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.Otp.Protobuf" Version="0.0.111" />
|
<PackageReference Include="Foursat.BackOffice.BFF.Otp.Protobuf" Version="0.0.111" />
|
||||||
|
|
||||||
<PackageReference Include="FourSat.BackOffice.BFF.Package.Protobuf" Version="0.0.111" />
|
<PackageReference Include="FourSat.BackOffice.BFF.Package.Protobuf" Version="0.0.111" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.Products.Protobuf" Version="0.0.3" />
|
<PackageReference Include="Foursat.BackOffice.BFF.Products.Protobuf" Version="0.0.8" />
|
||||||
|
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.Role.Protobuf" Version="0.0.111" />
|
<PackageReference Include="Foursat.BackOffice.BFF.Role.Protobuf" Version="0.0.111" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.User.Protobuf" Version="0.0.111" />
|
<PackageReference Include="Foursat.BackOffice.BFF.User.Protobuf" Version="0.0.111" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.UserAddress.Protobuf" Version="0.0.111" />
|
<PackageReference Include="Foursat.BackOffice.BFF.UserAddress.Protobuf" Version="0.0.111" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.UserOrder.Protobuf" Version="0.0.111" />
|
<PackageReference Include="Foursat.BackOffice.BFF.UserOrder.Protobuf" Version="0.0.113" />
|
||||||
|
|
||||||
<PackageReference Include="Foursat.BackOffice.BFF.UserRole.Protobuf" Version="0.0.111" />
|
<PackageReference Include="Foursat.BackOffice.BFF.UserRole.Protobuf" Version="0.0.111" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
|
||||||
@@ -59,4 +57,9 @@
|
|||||||
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
|
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\BackOffice.BFF\src\Protobufs\BackOffice.BFF.Products.Protobuf\BackOffice.BFF.Products.Protobuf.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\BackOffice.BFF\src\Protobufs\BackOffice.BFF.UserOrder.Protobuf\BackOffice.BFF.UserOrder.Protobuf.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ public static class RouteConstance
|
|||||||
public const string Role = "/RolePage/";
|
public const string Role = "/RolePage/";
|
||||||
public const string UserPage = "/UserPage/";
|
public const string UserPage = "/UserPage/";
|
||||||
public const string UserOrder = "/UserOrderPage/";
|
public const string UserOrder = "/UserOrderPage/";
|
||||||
|
public const string Orders = "/OrdersPage/";
|
||||||
public const string UserRole = "/UserRolePage/";
|
public const string UserRole = "/UserRolePage/";
|
||||||
public const string UserAddress = "/UserAddressPage/";
|
public const string UserAddress = "/UserAddressPage/";
|
||||||
public const string Products = "/ProductsPage/";
|
public const string Products = "/ProductsPage/";
|
||||||
public const string Category = "/CategoryPage/";
|
public const string Category = "/CategoryPage/";
|
||||||
|
public const string ProductCategories = "/ProductCategoriesPage/";
|
||||||
|
public const string CategoryProducts = "/CategoryProductsPage/";
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/BackOffice/Pages/AutoComplete/CategoryAutoComplete.razor
Normal file
15
src/BackOffice/Pages/AutoComplete/CategoryAutoComplete.razor
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
@using BackOffice.BFF.Category.Protobuf.Protos.Category
|
||||||
|
|
||||||
|
<MudAutocomplete T="GetAllCategoryByFilterResponseModel"
|
||||||
|
Label="@Label"
|
||||||
|
Value="@_item"
|
||||||
|
DebounceInterval="700"
|
||||||
|
ValueChanged="@((e) => OnSelected(e))"
|
||||||
|
ToStringFunc="@(e => e == null ? null : e.Title)"
|
||||||
|
SearchFunc="@Search"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Clearable="true"
|
||||||
|
ShowProgressIndicator="true"
|
||||||
|
CoerceText="false"
|
||||||
|
OnClearButtonClick="()=> OnSelected(null)" />
|
||||||
|
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
using BackOffice.BFF.Category.Protobuf.Protos.Category;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.AutoComplete;
|
||||||
|
|
||||||
|
public partial class CategoryAutoComplete
|
||||||
|
{
|
||||||
|
[Inject] public CategoryContract.CategoryContractClient CategoryService { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter] public long? Value { get; set; }
|
||||||
|
[Parameter] public string? Label { get; set; } = "دستهبندی والد";
|
||||||
|
[Parameter] public EventCallback<long?> ValueChanged { get; set; }
|
||||||
|
|
||||||
|
private List<GetAllCategoryByFilterResponseModel> _items = new();
|
||||||
|
private GetAllCategoryByFilterResponseModel? _item;
|
||||||
|
|
||||||
|
protected override async void OnParametersSet()
|
||||||
|
{
|
||||||
|
await base.OnParametersSetAsync();
|
||||||
|
|
||||||
|
if (Value.HasValue && Value.Value > 0)
|
||||||
|
{
|
||||||
|
var response = await CategoryService.GetCategoryAsync(new GetCategoryRequest
|
||||||
|
{
|
||||||
|
Id = Value.Value
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response != null)
|
||||||
|
{
|
||||||
|
_item = new GetAllCategoryByFilterResponseModel
|
||||||
|
{
|
||||||
|
Id = response.Id,
|
||||||
|
Name = response.Name,
|
||||||
|
Title = response.Title,
|
||||||
|
Description = response.Description,
|
||||||
|
ImagePath = response.ImagePath,
|
||||||
|
ParentId = response.ParentId,
|
||||||
|
IsActive = response.IsActive,
|
||||||
|
SortOrder = response.SortOrder
|
||||||
|
};
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnSelected(GetAllCategoryByFilterResponseModel? model)
|
||||||
|
{
|
||||||
|
if (model == null)
|
||||||
|
{
|
||||||
|
_item = null;
|
||||||
|
await ValueChanged.InvokeAsync(null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_item = model;
|
||||||
|
await ValueChanged.InvokeAsync(model.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<GetAllCategoryByFilterResponseModel>> Search(string value, CancellationToken token)
|
||||||
|
{
|
||||||
|
var request = new GetAllCategoryByFilterRequest
|
||||||
|
{
|
||||||
|
Filter = new GetAllCategoryByFilterFilter
|
||||||
|
{
|
||||||
|
Title = string.IsNullOrWhiteSpace(value) ? null : value
|
||||||
|
},
|
||||||
|
PaginationState = new PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = 1,
|
||||||
|
PageSize = 9
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await CategoryService.GetAllCategoryByFilterAsync(request, cancellationToken: token);
|
||||||
|
_items = response?.Models?.ToList() ?? new List<GetAllCategoryByFilterResponseModel>();
|
||||||
|
|
||||||
|
return _items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
@using BackOffice.BFF.Category.Protobuf.Protos.Category
|
||||||
|
|
||||||
|
<MudAutocomplete T="GetAllCategoryByFilterResponseModel"
|
||||||
|
Label="@Label"
|
||||||
|
MultiSelection="true"
|
||||||
|
SelectedValues="_selectedItems"
|
||||||
|
SelectedValuesChanged="OnSelectedValuesChanged"
|
||||||
|
DebounceInterval="700"
|
||||||
|
ToStringFunc="@(e => e == null ? null : e.Title)"
|
||||||
|
SearchFunc="@Search"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Clearable="true"
|
||||||
|
ShowProgressIndicator="true"
|
||||||
|
CoerceText="false" />
|
||||||
|
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
using BackOffice.BFF.Category.Protobuf.Protos.Category;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.AutoComplete;
|
||||||
|
|
||||||
|
public partial class CategoryMultiSelectAutoComplete
|
||||||
|
{
|
||||||
|
[Inject] public CategoryContract.CategoryContractClient CategoryService { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter] public List<long> SelectedIds { get; set; } = new();
|
||||||
|
[Parameter] public string? Label { get; set; } = "انتخاب دستهبندیها";
|
||||||
|
[Parameter] public EventCallback<List<long>> SelectedIdsChanged { get; set; }
|
||||||
|
|
||||||
|
private List<GetAllCategoryByFilterResponseModel> _items = new();
|
||||||
|
private HashSet<GetAllCategoryByFilterResponseModel> _selectedItems = new();
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
await base.OnParametersSetAsync();
|
||||||
|
|
||||||
|
if (SelectedIds == null || SelectedIds.Count == 0)
|
||||||
|
{
|
||||||
|
_selectedItems.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// اگر تعداد آیدیهای فعلی با انتخابها برابر است، از لود مجدد صرفنظر میکنیم
|
||||||
|
if (_selectedItems.Count == SelectedIds.Count &&
|
||||||
|
_selectedItems.All(x => SelectedIds.Contains(x.Id)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedItems.Clear();
|
||||||
|
|
||||||
|
foreach (var id in SelectedIds.Distinct())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await CategoryService.GetCategoryAsync(new GetCategoryRequest
|
||||||
|
{
|
||||||
|
Id = id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response != null)
|
||||||
|
{
|
||||||
|
_selectedItems.Add(new GetAllCategoryByFilterResponseModel
|
||||||
|
{
|
||||||
|
Id = response.Id,
|
||||||
|
Name = response.Name,
|
||||||
|
Title = response.Title,
|
||||||
|
Description = response.Description,
|
||||||
|
ImagePath = response.ImagePath,
|
||||||
|
ParentId = response.ParentId,
|
||||||
|
IsActive = response.IsActive,
|
||||||
|
SortOrder = response.SortOrder
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// در صورت خطا، از ادامه برای آن آیدی صرفنظر میکنیم
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnSelectedValuesChanged(HashSet<GetAllCategoryByFilterResponseModel> values)
|
||||||
|
{
|
||||||
|
_selectedItems = values ?? new HashSet<GetAllCategoryByFilterResponseModel>();
|
||||||
|
|
||||||
|
var ids = _selectedItems
|
||||||
|
.Select(x => x.Id)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
SelectedIds = ids;
|
||||||
|
|
||||||
|
if (SelectedIdsChanged.HasDelegate)
|
||||||
|
{
|
||||||
|
await SelectedIdsChanged.InvokeAsync(ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<GetAllCategoryByFilterResponseModel>> Search(string value, CancellationToken token)
|
||||||
|
{
|
||||||
|
var request = new GetAllCategoryByFilterRequest
|
||||||
|
{
|
||||||
|
Filter = new GetAllCategoryByFilterFilter
|
||||||
|
{
|
||||||
|
Title = string.IsNullOrWhiteSpace(value) ? null : value
|
||||||
|
},
|
||||||
|
PaginationState = new PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = 1,
|
||||||
|
PageSize = 20
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await CategoryService.GetAllCategoryByFilterAsync(request, cancellationToken: token);
|
||||||
|
_items = response?.Models?.ToList() ?? new List<GetAllCategoryByFilterResponseModel>();
|
||||||
|
|
||||||
|
return _items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
@using BackOffice.BFF.Category.Protobuf.Protos.Category
|
||||||
|
|
||||||
|
<MudSelect T="long"
|
||||||
|
@ref="_selectRef"
|
||||||
|
Label="@Label"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Disabled="@Disabled"
|
||||||
|
MultiSelection="true"
|
||||||
|
SelectedValues="_internalSelectedIds"
|
||||||
|
SelectedValuesChanged="@(_ => OnSelectedValuesChangedAsync())">
|
||||||
|
@foreach (var item in _items)
|
||||||
|
{
|
||||||
|
<MudSelectItem T="long" Value="@item.Id">
|
||||||
|
@item.Title
|
||||||
|
</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BackOffice.BFF.Category.Protobuf.Protos.Category;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.AutoComplete;
|
||||||
|
|
||||||
|
public partial class CategoryMultiSelectCombo
|
||||||
|
{
|
||||||
|
[Inject] public CategoryContract.CategoryContractClient CategoryService { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter] public List<long> SelectedIds { get; set; } = new();
|
||||||
|
[Parameter] public EventCallback<List<long>> SelectedIdsChanged { get; set; }
|
||||||
|
[Parameter] public string? Label { get; set; } = "انتخاب دستهبندیها";
|
||||||
|
[Parameter] public bool Disabled { get; set; }
|
||||||
|
|
||||||
|
private List<GetAllCategoryByFilterResponseModel> _items = new();
|
||||||
|
private HashSet<long> _internalSelectedIds = new();
|
||||||
|
private MudBlazor.MudSelect<long>? _selectRef;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await base.OnInitializedAsync();
|
||||||
|
await LoadCategoriesAsync();
|
||||||
|
SyncInternalFromParameter();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
SyncInternalFromParameter();
|
||||||
|
return base.OnParametersSetAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadCategoriesAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await CategoryService.GetAllCategoryByFilterAsync(new GetAllCategoryByFilterRequest
|
||||||
|
{
|
||||||
|
Filter = new GetAllCategoryByFilterFilter(),
|
||||||
|
PaginationState = new PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = 1,
|
||||||
|
PageSize = 1000
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_items = response?.Models?.ToList() ?? new List<GetAllCategoryByFilterResponseModel>();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_items = new List<GetAllCategoryByFilterResponseModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SyncInternalFromParameter()
|
||||||
|
{
|
||||||
|
_internalSelectedIds = SelectedIds != null
|
||||||
|
? SelectedIds.Where(id => id > 0).Distinct().ToHashSet()
|
||||||
|
: new HashSet<long>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnSelectedValuesChangedAsync()
|
||||||
|
{
|
||||||
|
var current = _selectRef?.SelectedValues ?? System.Array.Empty<long>();
|
||||||
|
|
||||||
|
_internalSelectedIds = current.Where(id => id > 0).Distinct().ToHashSet();
|
||||||
|
SelectedIds = _internalSelectedIds.ToList();
|
||||||
|
|
||||||
|
if (SelectedIdsChanged.HasDelegate)
|
||||||
|
{
|
||||||
|
await SelectedIdsChanged.InvokeAsync(SelectedIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -2,13 +2,44 @@
|
|||||||
|
|
||||||
@using BackOffice.BFF.Category.Protobuf.Protos.Category
|
@using BackOffice.BFF.Category.Protobuf.Protos.Category
|
||||||
@using BackOffice.Common.BaseComponents
|
@using BackOffice.Common.BaseComponents
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
@using DataModel = BackOffice.BFF.Category.Protobuf.Protos.Category.GetAllCategoryByFilterResponseModel
|
@using DataModel = BackOffice.BFF.Category.Protobuf.Protos.Category.GetAllCategoryByFilterResponseModel
|
||||||
|
|
||||||
<BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
|
<BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
|
||||||
<Filters>
|
<Filters>
|
||||||
<MudTextField T="string" Clearable="true" Label="عنوان" @bind-Value="@_request.Filter.Title" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
<MudTextField T="string" Clearable="true" Label="عنوان" @bind-Value="@_request.Filter.Title" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||||
|
<CategoryAutoComplete Value="_parentFilterId"
|
||||||
|
ValueChanged="OnParentFilterChanged"
|
||||||
|
Label="فیلتر بر اساس دستهبندی والد" />
|
||||||
</Filters>
|
</Filters>
|
||||||
<Content>
|
<Content>
|
||||||
|
<MudGrid>
|
||||||
|
<MudItem xs="12" md="3">
|
||||||
|
<MudPaper Class="pa-2" Style="height:72vh; overflow:auto;">
|
||||||
|
<MudText Typo="Typo.subtitle2" Class="mb-2">درخت دستهبندیها</MudText>
|
||||||
|
<MudList T="string" Dense="true">
|
||||||
|
<MudListItem T="string"
|
||||||
|
OnClick="@(() => OnTreeNodeSelected(null))"
|
||||||
|
Selected="@(_parentFilterId == null)"
|
||||||
|
Style="cursor:pointer;">
|
||||||
|
<MudText Typo="Typo.body2">همه دستهبندیها</MudText>
|
||||||
|
</MudListItem>
|
||||||
|
@foreach (var node in _treeNodes)
|
||||||
|
{
|
||||||
|
<MudListItem T="string"
|
||||||
|
OnClick="@(() => OnTreeNodeSelected(node.Id))"
|
||||||
|
Selected="@(_parentFilterId == node.Id)"
|
||||||
|
Style="cursor:pointer;">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||||
|
<div style="width:@(node.Level * 16)px;"></div>
|
||||||
|
<MudText Typo="Typo.body2">@node.Title</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudListItem>
|
||||||
|
}
|
||||||
|
</MudList>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" md="9">
|
||||||
<MudDataGrid T="DataModel"
|
<MudDataGrid T="DataModel"
|
||||||
ServerData="@(new Func<GridState<DataModel>, Task<GridData<DataModel>>>(ServerReload))"
|
ServerData="@(new Func<GridState<DataModel>, Task<GridData<DataModel>>>(ServerReload))"
|
||||||
Hover="true"
|
Hover="true"
|
||||||
@@ -40,7 +71,20 @@
|
|||||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||||
<PropertyColumn Property="x => x.Name" Title="نام لاتین" />
|
<PropertyColumn Property="x => x.Name" Title="نام لاتین" />
|
||||||
<PropertyColumn Property="x => x.Title" Title="عنوان" />
|
<PropertyColumn Property="x => x.Title" Title="عنوان" />
|
||||||
<PropertyColumn Property="x => x.ParentId" Title="شناسه والد" />
|
<TemplateColumn Title="دستهبندی والد">
|
||||||
|
<CellTemplate>
|
||||||
|
@{
|
||||||
|
var parentTitle = "-";
|
||||||
|
if (context.Item.ParentId.HasValue &&
|
||||||
|
context.Item.ParentId.Value > 0 &&
|
||||||
|
_parentTitles.TryGetValue(context.Item.ParentId.Value, out var value))
|
||||||
|
{
|
||||||
|
parentTitle = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<MudText Typo="Typo.body2">@parentTitle</MudText>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
<TemplateColumn Title="فعال؟">
|
<TemplateColumn Title="فعال؟">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudChip Color="@(context.Item.IsActive ? Color.Success : Color.Error)"
|
<MudChip Color="@(context.Item.IsActive ? Color.Success : Color.Error)"
|
||||||
@@ -68,6 +112,14 @@
|
|||||||
OnClick="@(() => OnDelete(context.Item))"
|
OnClick="@(() => OnDelete(context.Item))"
|
||||||
Style="cursor:pointer;" />
|
Style="cursor:pointer;" />
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
|
|
||||||
|
<MudTooltip Text="مدیریت محصولات این دسته (درگ و دراپ)">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.ShoppingCart"
|
||||||
|
Size="Size.Small"
|
||||||
|
ButtonType="ButtonType.Button"
|
||||||
|
OnClick="@(() => OpenCategoryProducts(context.Item))"
|
||||||
|
Style="cursor:pointer;" />
|
||||||
|
</MudTooltip>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
@@ -79,5 +131,7 @@
|
|||||||
RowsPerPageString="تعداد سطرهای صفحه" />
|
RowsPerPageString="تعداد سطرهای صفحه" />
|
||||||
</PagerContent>
|
</PagerContent>
|
||||||
</MudDataGrid>
|
</MudDataGrid>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
</Content>
|
</Content>
|
||||||
</BasePageComponent>
|
</BasePageComponent>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using BackOffice.BFF.Category.Protobuf.Protos.Category;
|
using BackOffice.BFF.Category.Protobuf.Protos.Category;
|
||||||
using BackOffice.Common.BaseComponents;
|
using BackOffice.Common.BaseComponents;
|
||||||
|
using BackOffice.Common.Utilities;
|
||||||
using Mapster;
|
using Mapster;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
@@ -14,6 +15,22 @@ public partial class CategoryMainPage
|
|||||||
private BasePageComponent _basePage = default!;
|
private BasePageComponent _basePage = default!;
|
||||||
private MudDataGrid<DataModel> _gridData = default!;
|
private MudDataGrid<DataModel> _gridData = default!;
|
||||||
private GetAllCategoryByFilterRequest _request = new() { Filter = new() };
|
private GetAllCategoryByFilterRequest _request = new() { Filter = new() };
|
||||||
|
private long? _parentFilterId;
|
||||||
|
private readonly Dictionary<long, string> _parentTitles = new();
|
||||||
|
private readonly List<CategoryTreeNode> _treeNodes = new();
|
||||||
|
|
||||||
|
private sealed class CategoryTreeNode
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public long? ParentId { get; set; }
|
||||||
|
public int Level { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadTreeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<GridData<DataModel>> ServerReload(GridState<DataModel> state)
|
private async Task<GridData<DataModel>> ServerReload(GridState<DataModel> state)
|
||||||
{
|
{
|
||||||
@@ -25,6 +42,8 @@ public partial class CategoryMainPage
|
|||||||
var result = await CategoryContract.GetAllCategoryByFilterAsync(_request);
|
var result = await CategoryContract.GetAllCategoryByFilterAsync(_request);
|
||||||
if (result != null && result.Models != null && result.Models.Any())
|
if (result != null && result.Models != null && result.Models.Any())
|
||||||
{
|
{
|
||||||
|
await EnsureParentTitlesAsync(result.Models);
|
||||||
|
|
||||||
return new GridData<DataModel>
|
return new GridData<DataModel>
|
||||||
{
|
{
|
||||||
Items = result.Models.ToList(),
|
Items = result.Models.ToList(),
|
||||||
@@ -35,6 +54,83 @@ public partial class CategoryMainPage
|
|||||||
return new GridData<DataModel>();
|
return new GridData<DataModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LoadTreeAsync()
|
||||||
|
{
|
||||||
|
var request = new GetAllCategoryByFilterRequest
|
||||||
|
{
|
||||||
|
Filter = new GetAllCategoryByFilterFilter(),
|
||||||
|
PaginationState = new PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = 1,
|
||||||
|
PageSize = 1000
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await CategoryContract.GetAllCategoryByFilterAsync(request);
|
||||||
|
var models = response?.Models?.ToList() ?? new List<DataModel>();
|
||||||
|
|
||||||
|
var lookup = models
|
||||||
|
.GroupBy(x => x.ParentId ?? 0)
|
||||||
|
.ToDictionary(g => g.Key,
|
||||||
|
g => g.OrderBy(c => c.SortOrder).ThenBy(c => c.Title).ToList());
|
||||||
|
|
||||||
|
_treeNodes.Clear();
|
||||||
|
|
||||||
|
void AddChildren(long? parentId, int level)
|
||||||
|
{
|
||||||
|
var key = parentId ?? 0;
|
||||||
|
if (!lookup.TryGetValue(key, out var children))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var child in children)
|
||||||
|
{
|
||||||
|
_treeNodes.Add(new CategoryTreeNode
|
||||||
|
{
|
||||||
|
Id = child.Id,
|
||||||
|
Title = child.Title,
|
||||||
|
ParentId = parentId,
|
||||||
|
Level = level
|
||||||
|
});
|
||||||
|
|
||||||
|
AddChildren(child.Id, level + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddChildren(null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnsureParentTitlesAsync(IEnumerable<DataModel> items)
|
||||||
|
{
|
||||||
|
var parentIds = items
|
||||||
|
.Where(x => x.ParentId.HasValue && x.ParentId.Value > 0 && !_parentTitles.ContainsKey(x.ParentId.Value))
|
||||||
|
.Select(x => x.ParentId.Value)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (!parentIds.Any())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var id in parentIds)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await CategoryContract.GetCategoryAsync(new GetCategoryRequest { Id = id });
|
||||||
|
if (response != null)
|
||||||
|
{
|
||||||
|
_parentTitles[id] = response.Title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore individual parent load failures
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task CreateNew()
|
public async Task CreateNew()
|
||||||
{
|
{
|
||||||
var dialog = await DialogService.ShowAsync<CreateOrUpdateCategoryDialog>(
|
var dialog = await DialogService.ShowAsync<CreateOrUpdateCategoryDialog>(
|
||||||
@@ -49,6 +145,7 @@ public partial class CategoryMainPage
|
|||||||
if (!result.Canceled)
|
if (!result.Canceled)
|
||||||
{
|
{
|
||||||
await ReloadData();
|
await ReloadData();
|
||||||
|
await LoadTreeAsync();
|
||||||
Snackbar.Add("دستهبندی با موفقیت ایجاد شد.", Severity.Success);
|
Snackbar.Add("دستهبندی با موفقیت ایجاد شد.", Severity.Success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,6 +168,7 @@ public partial class CategoryMainPage
|
|||||||
if (!result.Canceled)
|
if (!result.Canceled)
|
||||||
{
|
{
|
||||||
await ReloadData();
|
await ReloadData();
|
||||||
|
await LoadTreeAsync();
|
||||||
Snackbar.Add("دستهبندی با موفقیت ویرایش شد.", Severity.Success);
|
Snackbar.Add("دستهبندی با موفقیت ویرایش شد.", Severity.Success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,6 +187,7 @@ public partial class CategoryMainPage
|
|||||||
{
|
{
|
||||||
await CategoryContract.DeleteCategoryAsync(new DeleteCategoryRequest { Id = model.Id });
|
await CategoryContract.DeleteCategoryAsync(new DeleteCategoryRequest { Id = model.Id });
|
||||||
await ReloadData();
|
await ReloadData();
|
||||||
|
await LoadTreeAsync();
|
||||||
Snackbar.Add("دستهبندی با موفقیت حذف شد.", Severity.Success);
|
Snackbar.Add("دستهبندی با موفقیت حذف شد.", Severity.Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +214,26 @@ public partial class CategoryMainPage
|
|||||||
_basePage.IsFiltered = false;
|
_basePage.IsFiltered = false;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
_request = new GetAllCategoryByFilterRequest { Filter = new GetAllCategoryByFilterFilter() };
|
_request = new GetAllCategoryByFilterRequest { Filter = new GetAllCategoryByFilterFilter() };
|
||||||
|
_parentFilterId = null;
|
||||||
await ReloadData();
|
await ReloadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task OnParentFilterChanged(long? parentId)
|
||||||
|
{
|
||||||
|
_parentFilterId = parentId;
|
||||||
|
_request.Filter ??= new GetAllCategoryByFilterFilter();
|
||||||
|
_request.Filter.ParentId = parentId;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnTreeNodeSelected(long? id)
|
||||||
|
{
|
||||||
|
await OnParentFilterChanged(id);
|
||||||
|
await ReloadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenCategoryProducts(DataModel model)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"{RouteConstance.CategoryProducts}{model.Id}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
132
src/BackOffice/Pages/Category/CategoryProductsDragDropPage.razor
Normal file
132
src/BackOffice/Pages/Category/CategoryProductsDragDropPage.razor
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
@attribute [Route(RouteConstance.CategoryProducts + "{CategoryId:long?}")]
|
||||||
|
|
||||||
|
@using BackOffice.BFF.Products.Protobuf.Protos.Products
|
||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
|
||||||
|
<BasePageComponent>
|
||||||
|
<Content>
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<MudText Typo="Typo.h5">مدیریت محصولات دستهبندی (درگ و دراپ)</MudText>
|
||||||
|
|
||||||
|
<MudPaper Class="pa-3">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
|
<MudText Typo="Typo.subtitle2">انتخاب دستهبندی:</MudText>
|
||||||
|
<CategoryAutoComplete Value="_selectedCategoryId"
|
||||||
|
ValueChanged="OnCategoryChanged"
|
||||||
|
Label="دستهبندی" />
|
||||||
|
</MudStack>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_categoryTitle))
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
دستهبندی انتخابشده: <b>@_categoryTitle</b>
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
@if (_isLoading)
|
||||||
|
{
|
||||||
|
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
|
||||||
|
}
|
||||||
|
else if (_selectedCategoryId.HasValue)
|
||||||
|
{
|
||||||
|
<MudGrid>
|
||||||
|
<MudItem xs="12" md="5">
|
||||||
|
<MudPaper Class="pa-3" Style="height:60vh; overflow:auto;">
|
||||||
|
<div class="drag-drop-zone"
|
||||||
|
@ondrop="@OnDropOnUnselected"
|
||||||
|
@ondrop:preventDefault
|
||||||
|
@ondragover="@OnDragOver"
|
||||||
|
@ondragover:preventDefault>
|
||||||
|
<MudText Typo="Typo.subtitle2" Class="mb-2">
|
||||||
|
محصولات خارج از این دسته (@UnselectedProducts.Count)
|
||||||
|
</MudText>
|
||||||
|
<MudDivider Class="mb-2" />
|
||||||
|
@if (UnselectedProducts.Count > 0)
|
||||||
|
{
|
||||||
|
@foreach (var item in UnselectedProducts)
|
||||||
|
{
|
||||||
|
<div class="product-item drag-item"
|
||||||
|
draggable="true"
|
||||||
|
@ondragstart="@(() => OnDragStart(item.Id))">
|
||||||
|
<MudChip T="string"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Color="Color.Default"
|
||||||
|
OnClick="@(() => ToggleProduct(item.Id))">
|
||||||
|
@item.Title
|
||||||
|
</MudChip>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption">همه محصولات در این دسته قرار دارند.</MudText>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12" md="2">
|
||||||
|
<MudStack Class="h-100" Justify="Justify.Center" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
|
<MudText Typo="Typo.body2" Align="Align.Center">
|
||||||
|
برای افزودن/حذف محصول، آیتمها را درگ و دراپ کنید.
|
||||||
|
</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12" md="5">
|
||||||
|
<MudPaper Class="pa-3" Style="height:60vh; overflow:auto;">
|
||||||
|
<div class="drag-drop-zone"
|
||||||
|
@ondrop="@OnDropOnSelected"
|
||||||
|
@ondrop:preventDefault
|
||||||
|
@ondragover="@OnDragOver"
|
||||||
|
@ondragover:preventDefault>
|
||||||
|
<MudText Typo="Typo.subtitle2" Class="mb-2">
|
||||||
|
محصولات داخل این دسته (@SelectedProducts.Count)
|
||||||
|
</MudText>
|
||||||
|
<MudDivider Class="mb-2" />
|
||||||
|
@if (SelectedProducts.Count > 0)
|
||||||
|
{
|
||||||
|
@foreach (var item in SelectedProducts)
|
||||||
|
{
|
||||||
|
<div class="product-item drag-item"
|
||||||
|
draggable="true"
|
||||||
|
@ondragstart="@(() => OnDragStart(item.Id))">
|
||||||
|
<MudChip T="string"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
OnClick="@(() => ToggleProduct(item.Id))">
|
||||||
|
@item.Title
|
||||||
|
</MudChip>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption">هنوز محصولی به این دسته اختصاص داده نشده است.</MudText>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
<MudStack Row="true" Justify="Justify.FlexEnd" Class="mt-4">
|
||||||
|
<MudButton Color="Color.Primary"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Disabled="_isSaving"
|
||||||
|
OnClick="SaveProducts">
|
||||||
|
ذخیره محصولات دسته
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
برای شروع، یک دستهبندی انتخاب کنید.
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</Content>
|
||||||
|
</BasePageComponent>
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
using BackOffice.BFF.Products.Protobuf.Protos.Products;
|
||||||
|
using BackOffice.BFF.Category.Protobuf.Protos.Category;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.Category;
|
||||||
|
|
||||||
|
public partial class CategoryProductsDragDropPage
|
||||||
|
{
|
||||||
|
[Parameter] public long? CategoryId { get; set; }
|
||||||
|
|
||||||
|
[Inject] public ProductsContract.ProductsContractClient ProductsContract { get; set; } = default!;
|
||||||
|
[Inject] public CategoryContract.CategoryContractClient CategoryContract { get; set; } = default!;
|
||||||
|
|
||||||
|
private long? _selectedCategoryId;
|
||||||
|
private bool _isLoading;
|
||||||
|
private bool _isSaving;
|
||||||
|
private string? _categoryTitle;
|
||||||
|
private List<CategoryProductItem> _products = new();
|
||||||
|
private long? _draggingProductId;
|
||||||
|
|
||||||
|
private List<CategoryProductItem> SelectedProducts => _products.Where(p => p.Selected).ToList();
|
||||||
|
private List<CategoryProductItem> UnselectedProducts => _products.Where(p => !p.Selected).ToList();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (CategoryId.HasValue && CategoryId.Value > 0)
|
||||||
|
{
|
||||||
|
_selectedCategoryId = CategoryId.Value;
|
||||||
|
await LoadCategoryAndProducts(CategoryId.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnCategoryChanged(long? categoryId)
|
||||||
|
{
|
||||||
|
_selectedCategoryId = categoryId;
|
||||||
|
_products.Clear();
|
||||||
|
|
||||||
|
if (categoryId.HasValue)
|
||||||
|
{
|
||||||
|
await LoadCategoryAndProducts(categoryId.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadCategoryAndProducts(long categoryId)
|
||||||
|
{
|
||||||
|
await LoadCategoryTitle(categoryId);
|
||||||
|
await LoadProducts(categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadCategoryTitle(long categoryId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await CategoryContract.GetCategoryAsync(new GetCategoryRequest
|
||||||
|
{
|
||||||
|
Id = categoryId
|
||||||
|
});
|
||||||
|
|
||||||
|
_categoryTitle = response?.Title ?? $"شناسه {categoryId}";
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_categoryTitle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadProducts(long categoryId)
|
||||||
|
{
|
||||||
|
_isLoading = true;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await ProductsContract.GetProductsForCategoryAsync(new GetProductsForCategoryRequest
|
||||||
|
{
|
||||||
|
CategoryId = categoryId
|
||||||
|
});
|
||||||
|
|
||||||
|
_products = response?.Items?.ToList() ?? new List<CategoryProductItem>();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Snackbar.Add("در بازیابی محصولات دسته خطایی رخ داد.", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isLoading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragStart(long productId)
|
||||||
|
{
|
||||||
|
_draggingProductId = productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragOver(DragEventArgs args)
|
||||||
|
{
|
||||||
|
// allow drop
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleProduct(long productId)
|
||||||
|
{
|
||||||
|
var item = _products.FirstOrDefault(p => p.Id == productId);
|
||||||
|
if (item == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
item.Selected = !item.Selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDrop(bool targetSelected)
|
||||||
|
{
|
||||||
|
if (_draggingProductId == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var item = _products.FirstOrDefault(p => p.Id == _draggingProductId.Value);
|
||||||
|
if (item == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
item.Selected = targetSelected;
|
||||||
|
|
||||||
|
_draggingProductId = null;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDropOnSelected(DragEventArgs args) => HandleDrop(true);
|
||||||
|
|
||||||
|
private void OnDropOnUnselected(DragEventArgs args) => HandleDrop(false);
|
||||||
|
|
||||||
|
private async Task SaveProducts()
|
||||||
|
{
|
||||||
|
if (!_selectedCategoryId.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isSaving = true;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new UpdateCategoryProductsRequest
|
||||||
|
{
|
||||||
|
CategoryId = _selectedCategoryId.Value
|
||||||
|
};
|
||||||
|
|
||||||
|
request.ProductIds.AddRange(
|
||||||
|
_products.Where(p => p.Selected).Select(p => p.Id));
|
||||||
|
|
||||||
|
await ProductsContract.UpdateCategoryProductsAsync(request);
|
||||||
|
|
||||||
|
Snackbar.Add("محصولات دسته با موفقیت بهروزرسانی شد.", Severity.Success);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Snackbar.Add("در ذخیرهسازی محصولات دسته خطایی رخ داد.", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isSaving = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
@using BackOffice.BFF.Category.Protobuf.Protos.Category
|
@using BackOffice.BFF.Category.Protobuf.Protos.Category
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@@ -24,12 +25,9 @@
|
|||||||
Lines="3"
|
Lines="3"
|
||||||
TextArea="true" />
|
TextArea="true" />
|
||||||
|
|
||||||
<MudNumericField T="long?"
|
<CategoryAutoComplete Value="ParentId"
|
||||||
@bind-Value="ParentId"
|
ValueChanged="OnParentChanged"
|
||||||
HideSpinButtons="true"
|
Label="دستهبندی والد (اختیاری)" />
|
||||||
Label="شناسه والد (اختیاری)"
|
|
||||||
Variant="Variant.Outlined"
|
|
||||||
Margin="Margin.Dense" />
|
|
||||||
|
|
||||||
<MudNumericField T="int"
|
<MudNumericField T="int"
|
||||||
@bind-Value="SortOrder"
|
@bind-Value="SortOrder"
|
||||||
|
|||||||
@@ -78,9 +78,14 @@ public partial class CreateOrUpdateCategoryDialog
|
|||||||
MudDialog.Close(DialogResult.Ok(true));
|
MudDialog.Close(DialogResult.Ok(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task OnParentChanged(long? parentId)
|
||||||
|
{
|
||||||
|
ParentId = parentId;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
private void Cancel()
|
private void Cancel()
|
||||||
{
|
{
|
||||||
MudDialog.Cancel();
|
MudDialog.Cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,179 @@
|
|||||||
@attribute [Route(RouteConstance.HomePage)]
|
@attribute [Route(RouteConstance.HomePage)]
|
||||||
<h1>Hello, world!</h1>
|
|
||||||
|
@using BackOffice.BFF.User.Protobuf.Protos.User
|
||||||
|
@using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder
|
||||||
|
@using BackOffice.BFF.Products.Protobuf.Protos.Products
|
||||||
|
@using BackOffice.BFF.Package.Protobuf.Protos.Package
|
||||||
|
@using BackOffice.BFF.Category.Protobuf.Protos.Category
|
||||||
|
@inject UserContract.UserContractClient UserContract
|
||||||
|
@inject UserOrderContract.UserOrderContractClient UserOrderContract
|
||||||
|
@inject ProductsContract.ProductsContractClient ProductsContract
|
||||||
|
@inject PackageContract.PackageContractClient PackageContract
|
||||||
|
@inject CategoryContract.CategoryContractClient CategoryContract
|
||||||
|
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<MudText Typo="Typo.h5">داشبورد مدیریت</MudText>
|
||||||
|
|
||||||
|
<MudGrid GutterSize="3">
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudPaper Class="pa-4">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.subtitle2">تعداد کاربران</MudText>
|
||||||
|
<MudText Typo="Typo.h5">@_totalUsers.ToString("N0")</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudPaper Class="pa-4">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.subtitle2">تعداد محصولات</MudText>
|
||||||
|
<MudText Typo="Typo.h5">@_totalProducts.ToString("N0")</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudPaper Class="pa-4">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.subtitle2">تعداد پکیجها</MudText>
|
||||||
|
<MudText Typo="Typo.h5">@_totalPackages.ToString("N0")</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudPaper Class="pa-4">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.subtitle2">تعداد دستهبندیها</MudText>
|
||||||
|
<MudText Typo="Typo.h5">@_totalCategories.ToString("N0")</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudPaper Class="pa-4">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.subtitle2">تعداد سفارشها</MudText>
|
||||||
|
<MudText Typo="Typo.h5">@_totalOrders.ToString("N0")</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudPaper Class="pa-4">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.subtitle2">سفارشهای پرداختشده</MudText>
|
||||||
|
<MudText Typo="Typo.h5">@_paidOrders.ToString("N0")</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudPaper Class="pa-4">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.subtitle2">جمع مبلغ سفارشهای پرداختشده</MudText>
|
||||||
|
<MudText Typo="Typo.h6">@_totalPaidAmount.ToString("N0") تومان</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
<MudPaper Class="pa-4">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudText Typo="Typo.subtitle2">آخرین سفارشهای پرداختشده</MudText>
|
||||||
|
@if (_lastOrders.Count == 0)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2">سفارشی ثبت نشده است.</MudText>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudTable Items="_lastOrders" Dense="true">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>شناسه سفارش</MudTh>
|
||||||
|
<MudTh>کاربر</MudTh>
|
||||||
|
<MudTh>مبلغ</MudTh>
|
||||||
|
<MudTh>تاریخ پرداخت</MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd>@context.Id</MudTd>
|
||||||
|
<MudTd>@context.UserFullName (@context.UserNationalCode)</MudTd>
|
||||||
|
<MudTd>@context.Price.ToString("N0")</MudTd>
|
||||||
|
<MudTd>@context.PaymentDate.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm")</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private long _totalUsers;
|
||||||
|
private long _totalOrders;
|
||||||
|
private long _paidOrders;
|
||||||
|
private long _totalPaidAmount;
|
||||||
|
private long _totalProducts;
|
||||||
|
private long _totalPackages;
|
||||||
|
private long _totalCategories;
|
||||||
|
private readonly List<GetUserOrderResponse> _lastOrders = new();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
// کاربران
|
||||||
|
var userRequest = new GetAllUserByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new() { PageNumber = 1, PageSize = 1 },
|
||||||
|
Filter = new()
|
||||||
|
};
|
||||||
|
var userResult = await UserContract.GetAllUserByFilterAsync(userRequest);
|
||||||
|
_totalUsers = userResult?.MetaData?.TotalCount ?? 0;
|
||||||
|
|
||||||
|
// سفارشها
|
||||||
|
var orderRequest = new GetAllUserOrderByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new() { PageNumber = 1, PageSize = 10 },
|
||||||
|
};
|
||||||
|
var orderResult = await UserOrderContract.GetAllUserOrderByFilterAsync(orderRequest);
|
||||||
|
if (orderResult != null && orderResult.Models != null)
|
||||||
|
{
|
||||||
|
_totalOrders = orderResult.MetaData?.TotalCount ?? 0;
|
||||||
|
_paidOrders = orderResult.Models.Count(m => m.PaymentStatus);
|
||||||
|
_totalPaidAmount = orderResult.Models
|
||||||
|
.Where(m => m.PaymentStatus)
|
||||||
|
.Aggregate(0L, (sum, m) => sum + m.Price);
|
||||||
|
|
||||||
|
foreach (var item in orderResult.Models.OrderByDescending(m => m.PaymentDate).Take(5))
|
||||||
|
{
|
||||||
|
var full = await UserOrderContract.GetUserOrderAsync(new GetUserOrderRequest { Id = item.Id });
|
||||||
|
_lastOrders.Add(full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// محصولات
|
||||||
|
var productRequest = new GetAllProductsByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new() { PageNumber = 1, PageSize = 1 },
|
||||||
|
Filter = new()
|
||||||
|
};
|
||||||
|
var productResult = await ProductsContract.GetAllProductsByFilterAsync(productRequest);
|
||||||
|
_totalProducts = productResult?.MetaData?.TotalCount ?? 0;
|
||||||
|
|
||||||
|
// پکیجها
|
||||||
|
var packageRequest = new GetAllPackageByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new() { PageNumber = 1, PageSize = 1 },
|
||||||
|
Filter = new()
|
||||||
|
};
|
||||||
|
var packageResult = await PackageContract.GetAllPackageByFilterAsync(packageRequest);
|
||||||
|
_totalPackages = packageResult?.MetaData?.TotalCount ?? 0;
|
||||||
|
|
||||||
|
// دستهبندیها
|
||||||
|
var categoryRequest = new GetAllCategoryByFilterRequest
|
||||||
|
{
|
||||||
|
PaginationState = new() { PageNumber = 1, PageSize = 1 },
|
||||||
|
Filter = new()
|
||||||
|
};
|
||||||
|
var categoryResult = await CategoryContract.GetAllCategoryByFilterAsync(categoryRequest);
|
||||||
|
_totalCategories = categoryResult?.MetaData?.TotalCount ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Tizzani.MudBlazor.HtmlEditor
|
@using Tizzani.MudBlazor.HtmlEditor
|
||||||
@using BackOffice.Common.BaseComponents
|
@using BackOffice.Common.BaseComponents
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@@ -63,6 +64,19 @@
|
|||||||
<MudHtmlToolbarOptions InsertImage="false" />
|
<MudHtmlToolbarOptions InsertImage="false" />
|
||||||
</MudHtmlEditor>
|
</MudHtmlEditor>
|
||||||
|
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.subtitle2">دستهبندیها</MudText>
|
||||||
|
<CategoryMultiSelectCombo @bind-SelectedIds="_selectedCategoryIds"
|
||||||
|
Label="انتخاب دستهبندیها"
|
||||||
|
Disabled="_isLoading" />
|
||||||
|
@if (_selectedCategoryIds?.Count > 0)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption">
|
||||||
|
تعداد دستهبندیهای انتخابشده: @_selectedCategoryIds.Count
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
<MudStack Spacing="1">
|
<MudStack Spacing="1">
|
||||||
<MudText Typo="Typo.subtitle2">تصویر بندانگشتی</MudText>
|
<MudText Typo="Typo.subtitle2">تصویر بندانگشتی</MudText>
|
||||||
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnThumbnailSelected">
|
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnThumbnailSelected">
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public partial class CreateDialog
|
|||||||
private readonly long _maxAllowedSize = (1024 * 1024) * 5;
|
private readonly long _maxAllowedSize = (1024 * 1024) * 5;
|
||||||
private bool _isLoading;
|
private bool _isLoading;
|
||||||
private string? _mainImagePreview;
|
private string? _mainImagePreview;
|
||||||
|
private List<long> _selectedCategoryIds = new();
|
||||||
|
|
||||||
private async Task OnMainImageSelected(IBrowserFile? file)
|
private async Task OnMainImageSelected(IBrowserFile? file)
|
||||||
{
|
{
|
||||||
@@ -74,7 +75,11 @@ public partial class CreateDialog
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ProductsContract.CreateNewProductsAsync(Model);
|
Model.CategoryIds.Clear();
|
||||||
|
Model.CategoryIds.AddRange(_selectedCategoryIds);
|
||||||
|
|
||||||
|
var createResponse = await ProductsContract.CreateNewProductsAsync(Model);
|
||||||
|
|
||||||
Submit();
|
Submit();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
@@ -4,11 +4,19 @@
|
|||||||
<MudDialog>
|
<MudDialog>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||||
<MudText Typo="Typo.h6">گالری تصاویر - @ProductTitle</MudText>
|
<MudText Typo="Typo.h6">گالری تصاویر - @ProductTitle</MudText>
|
||||||
|
<MudChip T="string" Color="Color.Primary" Variant="Variant.Outlined" Size="Size.Small">
|
||||||
|
تعداد تصاویر: @(Items?.Count ?? 0)
|
||||||
|
</MudChip>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
<MudGrid>
|
<MudGrid>
|
||||||
<MudItem xs="12" md="4">
|
<MudItem xs="12" md="4">
|
||||||
|
<MudPaper Class="pa-3">
|
||||||
<MudStack Spacing="2">
|
<MudStack Spacing="2">
|
||||||
|
<MudText Typo="Typo.subtitle2">افزودن تصویر جدید</MudText>
|
||||||
|
|
||||||
<MudTextField @bind-Value="_title"
|
<MudTextField @bind-Value="_title"
|
||||||
Label="عنوان تصویر"
|
Label="عنوان تصویر"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
@@ -40,30 +48,51 @@
|
|||||||
@if (!string.IsNullOrWhiteSpace(_previewImage))
|
@if (!string.IsNullOrWhiteSpace(_previewImage))
|
||||||
{
|
{
|
||||||
<MudPaper Class="pa-1">
|
<MudPaper Class="pa-1">
|
||||||
<img src="@_previewImage" alt="پیشنمایش" style="width:100%; height:160px; object-fit:cover;" />
|
<img src="@_previewImage" alt="پیشنمایش" style="width:100%; height:180px; object-fit:cover;" />
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
}
|
}
|
||||||
|
|
||||||
<MudButton Color="Color.Primary" OnClick="AddImage" Disabled="_isUploading">افزودن به گالری</MudButton>
|
@if (_isUploading)
|
||||||
|
{
|
||||||
|
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudButton Color="Color.Primary"
|
||||||
|
OnClick="AddImage"
|
||||||
|
Disabled="_isUploading || _file is null">
|
||||||
|
افزودن به گالری
|
||||||
|
</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
<MudItem xs="12" md="8">
|
<MudItem xs="12" md="8">
|
||||||
|
<MudPaper Class="pa-3">
|
||||||
|
<MudText Typo="Typo.subtitle2" Class="mb-2">لیست تصاویر</MudText>
|
||||||
@if (Items?.Count > 0)
|
@if (Items?.Count > 0)
|
||||||
{
|
{
|
||||||
<MudGrid>
|
<MudGrid GutterSize="2">
|
||||||
@foreach (var item in Items)
|
@foreach (var item in Items)
|
||||||
{
|
{
|
||||||
<MudItem xs="6" sm="4" md="3">
|
<MudItem xs="6" sm="4" md="3">
|
||||||
<MudPaper Class="pa-2">
|
<MudPaper Class="pa-2">
|
||||||
<img src="@item.ImageThumbnailPath" alt="@item.Title" style="width:100%; height:120px; object-fit:cover;" />
|
<div style="position:relative; cursor:pointer;"
|
||||||
<MudText Typo="Typo.caption" Class="mt-1">@item.Title</MudText>
|
@onclick="@(() => OpenPreview(item))">
|
||||||
<MudStack Row="true" Justify="Justify.FlexEnd">
|
<img src="@item.ImageThumbnailPath"
|
||||||
|
alt="@item.Title"
|
||||||
|
style="width:100%; height:120px; object-fit:cover; border-radius:4px;" />
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline"
|
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline"
|
||||||
Color="Color.Error"
|
Color="Color.Error"
|
||||||
Size="Size.Small"
|
Size="Size.Small"
|
||||||
OnClick="@(() => RemoveImage(item))" />
|
Class="mt-1"
|
||||||
</MudStack>
|
Style="position:absolute; top:4px; left:4px; background-color:rgba(255,255,255,0.8);"
|
||||||
|
OnClick="@((MouseEventArgs _) => RemoveImage(item))" />
|
||||||
|
</div>
|
||||||
|
<MudTooltip Text="@item.Title">
|
||||||
|
<MudText Typo="Typo.caption" Class="mt-1 text-truncate">
|
||||||
|
@(!string.IsNullOrWhiteSpace(item.Title) ? item.Title : "بدون عنوان")
|
||||||
|
</MudText>
|
||||||
|
</MudTooltip>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
}
|
}
|
||||||
@@ -73,6 +102,7 @@
|
|||||||
{
|
{
|
||||||
<MudText Typo="Typo.caption">هنوز تصویری برای این محصول ثبت نشده است.</MudText>
|
<MudText Typo="Typo.caption">هنوز تصویری برای این محصول ثبت نشده است.</MudText>
|
||||||
}
|
}
|
||||||
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</MudGrid>
|
</MudGrid>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|||||||
@@ -106,6 +106,21 @@ public partial class GalleryDialog
|
|||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OpenPreview(GalleryItemViewModel item)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(item.ImagePath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var parameters = new DialogParameters<ImagePreviewDialog>
|
||||||
|
{
|
||||||
|
{ x => x.ImageUrl, item.ImagePath },
|
||||||
|
{ x => x.Title, item.Title }
|
||||||
|
};
|
||||||
|
|
||||||
|
await DialogService.ShowAsync<ImagePreviewDialog>("پیشنمایش تصویر", parameters,
|
||||||
|
new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true });
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RemoveImage(GalleryItemViewModel item)
|
private async Task RemoveImage(GalleryItemViewModel item)
|
||||||
{
|
{
|
||||||
await ProductsContract.RemoveProductImageAsync(new RemoveProductImageRequest
|
await ProductsContract.RemoveProductImageAsync(new RemoveProductImageRequest
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Tizzani.MudBlazor.HtmlEditor
|
@using Tizzani.MudBlazor.HtmlEditor
|
||||||
@using BackOffice.Common.BaseComponents
|
@using BackOffice.Common.BaseComponents
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@@ -49,6 +50,19 @@
|
|||||||
<MudHtmlToolbarOptions InsertImage="false" />
|
<MudHtmlToolbarOptions InsertImage="false" />
|
||||||
</MudHtmlEditor>
|
</MudHtmlEditor>
|
||||||
|
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.subtitle2">دستهبندیها</MudText>
|
||||||
|
<CategoryMultiSelectCombo @bind-SelectedIds="_selectedCategoryIds"
|
||||||
|
Label="انتخاب دستهبندیها"
|
||||||
|
Disabled="_isLoading" />
|
||||||
|
@if (_selectedCategoryIds?.Count > 0)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption">
|
||||||
|
تعداد دستهبندیهای انتخابشده: @_selectedCategoryIds.Count
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
<MudStack Spacing="1">
|
<MudStack Spacing="1">
|
||||||
<MudText Typo="Typo.subtitle2">تصویر بندانگشتی</MudText>
|
<MudText Typo="Typo.subtitle2">تصویر بندانگشتی</MudText>
|
||||||
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnThumbnailSelected">
|
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnThumbnailSelected">
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public partial class UpdateDialog
|
|||||||
private readonly long _maxAllowedSize = (1024 * 1024) * 5;
|
private readonly long _maxAllowedSize = (1024 * 1024) * 5;
|
||||||
private bool _isLoading;
|
private bool _isLoading;
|
||||||
private string? _mainImagePreview;
|
private string? _mainImagePreview;
|
||||||
|
private List<long> _selectedCategoryIds = new();
|
||||||
|
|
||||||
private async Task OnMainImageSelected(IBrowserFile? file)
|
private async Task OnMainImageSelected(IBrowserFile? file)
|
||||||
{
|
{
|
||||||
@@ -35,10 +36,28 @@ public partial class UpdateDialog
|
|||||||
_thumbnailImageFile = file;
|
_thumbnailImageFile = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_mainImagePreview = Model.ImagePath;
|
_mainImagePreview = Model.ImagePath;
|
||||||
return base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await ProductsContract.GetCategoriesAsync(new GetCategoriesRequest
|
||||||
|
{
|
||||||
|
ProductId = Model.Id
|
||||||
|
});
|
||||||
|
|
||||||
|
_selectedCategoryIds = response?.Items?
|
||||||
|
.Where(c => c.Selected)
|
||||||
|
.Select(c => c.Id)
|
||||||
|
.ToList()
|
||||||
|
?? new List<long>();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore errors for category loading
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void CallUpdateMethod()
|
public async void CallUpdateMethod()
|
||||||
@@ -80,7 +99,10 @@ public partial class UpdateDialog
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Model.CategoryIds.Clear();
|
||||||
|
Model.CategoryIds.AddRange(_selectedCategoryIds);
|
||||||
await ProductsContract.UpdateProductsAsync(Model);
|
await ProductsContract.UpdateProductsAsync(Model);
|
||||||
|
|
||||||
Submit();
|
Submit();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
@attribute [Route(RouteConstance.ProductCategories + "{ProductId:long?}")]
|
||||||
|
|
||||||
|
@using BackOffice.BFF.Products.Protobuf.Protos.Products
|
||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
@using BackOffice.Pages.AutoComplete
|
||||||
|
|
||||||
|
<BasePageComponent>
|
||||||
|
<Content>
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<MudText Typo="Typo.h5">مدیریت دستهبندیهای محصول (درگ و دراپ)</MudText>
|
||||||
|
|
||||||
|
<MudPaper Class="pa-3">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
|
<MudText Typo="Typo.subtitle2">انتخاب محصول:</MudText>
|
||||||
|
<ProductsAutoComplete Value="_selectedProductId"
|
||||||
|
ValueChanged="OnProductChanged"
|
||||||
|
Label="محصول" />
|
||||||
|
</MudStack>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_productTitle))
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
محصول انتخابشده: <b>@_productTitle</b>
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
@if (_isLoading)
|
||||||
|
{
|
||||||
|
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
|
||||||
|
}
|
||||||
|
else if (_selectedProductId.HasValue)
|
||||||
|
{
|
||||||
|
<MudGrid>
|
||||||
|
<MudItem xs="12" md="5">
|
||||||
|
<MudPaper Class="pa-3" Style="height:60vh; overflow:auto;">
|
||||||
|
<div class="drag-drop-zone"
|
||||||
|
@ondrop="@OnDropOnUnselected"
|
||||||
|
@ondrop:preventDefault
|
||||||
|
@ondragover="@OnDragOver"
|
||||||
|
@ondragover:preventDefault>
|
||||||
|
<MudText Typo="Typo.subtitle2" Class="mb-2">دستهبندیهای موجود</MudText>
|
||||||
|
<MudDivider Class="mb-2" />
|
||||||
|
@if (UnselectedCategories.Count > 0)
|
||||||
|
{
|
||||||
|
@foreach (var item in UnselectedCategories)
|
||||||
|
{
|
||||||
|
<div class="category-item drag-item"
|
||||||
|
draggable="true"
|
||||||
|
@ondragstart="@(() => OnDragStart(item.Id))">
|
||||||
|
<MudChip T="string"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Color="Color.Default"
|
||||||
|
OnClick="@(() => ToggleCategory(item.Id))">
|
||||||
|
@item.Title
|
||||||
|
</MudChip>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption">همه دستهبندیها به این محصول اختصاص داده شدهاند.</MudText>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12" md="2">
|
||||||
|
<MudStack Class="h-100" Justify="Justify.Center" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
|
<MudText Typo="Typo.body2" Align="Align.Center">
|
||||||
|
برای افزودن/حذف دستهبندی، آیتمها را درگ و دراپ کنید.
|
||||||
|
</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12" md="5">
|
||||||
|
<MudPaper Class="pa-3" Style="height:60vh; overflow:auto;">
|
||||||
|
<div class="drag-drop-zone"
|
||||||
|
@ondrop="@OnDropOnSelected"
|
||||||
|
@ondrop:preventDefault
|
||||||
|
@ondragover="@OnDragOver"
|
||||||
|
@ondragover:preventDefault>
|
||||||
|
<MudText Typo="Typo.subtitle2" Class="mb-2">دستهبندیهای اختصاص داده شده</MudText>
|
||||||
|
<MudDivider Class="mb-2" />
|
||||||
|
@if (SelectedCategories.Count > 0)
|
||||||
|
{
|
||||||
|
@foreach (var item in SelectedCategories)
|
||||||
|
{
|
||||||
|
<div class="category-item drag-item"
|
||||||
|
draggable="true"
|
||||||
|
@ondragstart="@(() => OnDragStart(item.Id))">
|
||||||
|
<MudChip T="string"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
OnClick="@(() => ToggleCategory(item.Id))">
|
||||||
|
@item.Title
|
||||||
|
</MudChip>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption">هنوز دستهبندیای به این محصول اختصاص داده نشده است.</MudText>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
<MudStack Row="true" Justify="Justify.FlexEnd" Class="mt-4">
|
||||||
|
<MudButton Color="Color.Primary"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Disabled="_isSaving"
|
||||||
|
OnClick="SaveCategories">
|
||||||
|
ذخیره دستهبندیهای محصول
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
برای شروع، یک محصول انتخاب کنید.
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</Content>
|
||||||
|
</BasePageComponent>
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
using BackOffice.BFF.Products.Protobuf.Protos.Products;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.Products;
|
||||||
|
|
||||||
|
public partial class ProductCategoriesDragDropPage
|
||||||
|
{
|
||||||
|
[Parameter] public long? ProductId { get; set; }
|
||||||
|
|
||||||
|
[Inject] public ProductsContract.ProductsContractClient ProductsContract { get; set; } = default!;
|
||||||
|
|
||||||
|
private long? _selectedProductId;
|
||||||
|
private bool _isLoading;
|
||||||
|
private bool _isSaving;
|
||||||
|
private string? _productTitle;
|
||||||
|
private List<CategoryItem> _categories = new();
|
||||||
|
private long? _draggingCategoryId;
|
||||||
|
|
||||||
|
private List<CategoryItem> SelectedCategories => _categories.Where(c => c.Selected).ToList();
|
||||||
|
private List<CategoryItem> UnselectedCategories => _categories.Where(c => !c.Selected).ToList();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (ProductId.HasValue && ProductId.Value > 0)
|
||||||
|
{
|
||||||
|
_selectedProductId = ProductId.Value;
|
||||||
|
await LoadProductAndCategories(ProductId.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnProductChanged(long? productId)
|
||||||
|
{
|
||||||
|
_selectedProductId = productId;
|
||||||
|
_categories.Clear();
|
||||||
|
|
||||||
|
if (productId.HasValue)
|
||||||
|
{
|
||||||
|
await LoadProductAndCategories(productId.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadProductAndCategories(long productId)
|
||||||
|
{
|
||||||
|
await LoadProductTitle(productId);
|
||||||
|
await LoadCategories(productId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadProductTitle(long productId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await ProductsContract.GetProductsAsync(new GetProductsRequest
|
||||||
|
{
|
||||||
|
Id = productId
|
||||||
|
});
|
||||||
|
|
||||||
|
_productTitle = response?.Title;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_productTitle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadCategories(long productId)
|
||||||
|
{
|
||||||
|
_isLoading = true;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await ProductsContract.GetCategoriesAsync(new GetCategoriesRequest
|
||||||
|
{
|
||||||
|
ProductId = productId
|
||||||
|
});
|
||||||
|
|
||||||
|
_categories = response?.Items?.ToList() ?? new List<CategoryItem>();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Snackbar.Add("در بازیابی دستهبندیهای محصول خطایی رخ داد.", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isLoading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragStart(long categoryId)
|
||||||
|
{
|
||||||
|
_draggingCategoryId = categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragOver(DragEventArgs args)
|
||||||
|
{
|
||||||
|
// Allow dropping by preventing default behavior
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleCategory(long categoryId)
|
||||||
|
{
|
||||||
|
var item = _categories.FirstOrDefault(c => c.Id == categoryId);
|
||||||
|
if (item == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
item.Selected = !item.Selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDrop(bool targetSelected)
|
||||||
|
{
|
||||||
|
if (_draggingCategoryId == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var item = _categories.FirstOrDefault(c => c.Id == _draggingCategoryId.Value);
|
||||||
|
if (item == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
item.Selected = targetSelected;
|
||||||
|
|
||||||
|
_draggingCategoryId = null;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDropOnSelected(DragEventArgs args) => HandleDrop(true);
|
||||||
|
|
||||||
|
private void OnDropOnUnselected(DragEventArgs args) => HandleDrop(false);
|
||||||
|
|
||||||
|
private async Task SaveCategories()
|
||||||
|
{
|
||||||
|
if (!_selectedProductId.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isSaving = true;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new UpdateProductCategoriesRequest
|
||||||
|
{
|
||||||
|
ProductId = _selectedProductId.Value
|
||||||
|
};
|
||||||
|
|
||||||
|
request.CategoryIds.AddRange(
|
||||||
|
_categories.Where(c => c.Selected).Select(c => c.Id));
|
||||||
|
|
||||||
|
await ProductsContract.UpdateProductCategoriesAsync(request);
|
||||||
|
|
||||||
|
Snackbar.Add("دستهبندیهای محصول با موفقیت بهروزرسانی شد.", Severity.Success);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Snackbar.Add("در ذخیرهسازی دستهبندیها خطایی رخ داد.", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isSaving = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,6 +73,14 @@
|
|||||||
<MudTooltip Text="گالری تصاویر">
|
<MudTooltip Text="گالری تصاویر">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Collections" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => OpenGallery(context.Item))" Style="cursor:pointer;" />
|
<MudIconButton Icon="@Icons.Material.Filled.Collections" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => OpenGallery(context.Item))" Style="cursor:pointer;" />
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
|
|
||||||
|
<MudTooltip Text="مدیریت دستهبندی (درگ و دراپ)">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Category"
|
||||||
|
Size="Size.Small"
|
||||||
|
ButtonType="ButtonType.Button"
|
||||||
|
OnClick="@(() => OpenCategoryMapping(context.Item))"
|
||||||
|
Style="cursor:pointer;" />
|
||||||
|
</MudTooltip>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using BackOffice.BFF.Products.Protobuf.Protos.Products;
|
using BackOffice.BFF.Products.Protobuf.Protos.Products;
|
||||||
using BackOffice.Common.BaseComponents;
|
using BackOffice.Common.BaseComponents;
|
||||||
|
using BackOffice.Common.Utilities;
|
||||||
using BackOffice.Pages.Products.Components;
|
using BackOffice.Pages.Products.Components;
|
||||||
using Mapster;
|
using Mapster;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
@@ -106,6 +107,11 @@ public partial class ProductsMainPage
|
|||||||
new DialogOptions { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Medium });
|
new DialogOptions { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Medium });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OpenCategoryMapping(DataModel model)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"{RouteConstance.ProductCategories}{model.Id}");
|
||||||
|
}
|
||||||
|
|
||||||
public async Task OpenImagePreview(string imagePath, string title)
|
public async Task OpenImagePreview(string imagePath, string title)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(imagePath))
|
if (string.IsNullOrWhiteSpace(imagePath))
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||||
<MudMenu Icon="@Icons.Material.Filled.MoreVert"
|
<MudMenu Icon="@Icons.Material.Filled.MoreVert"
|
||||||
AriaLabel="Open user menu">
|
AriaLabel="Open user menu">
|
||||||
<MudMenuItem OnClick="@(() => Navigation.NavigateTo($"{RouteConstance.UserOrder}{context.Item.Id}"))">فاکتور ها</MudMenuItem>
|
<MudMenuItem OnClick="@(() => Navigation.NavigateTo($"{RouteConstance.UserOrder}{context.Item.Id}"))">سفارشها</MudMenuItem>
|
||||||
<MudMenuItem OnClick="@(() => Navigation.NavigateTo($"{RouteConstance.UserAddress}{context.Item.Id}"))">آدرس ها</MudMenuItem>
|
<MudMenuItem OnClick="@(() => Navigation.NavigateTo($"{RouteConstance.UserAddress}{context.Item.Id}"))">آدرس ها</MudMenuItem>
|
||||||
<MudMenuItem OnClick="@(async () => await OnclickUserRoleDialog(context.Item.Id))">مدیریت نقش</MudMenuItem>
|
<MudMenuItem OnClick="@(async () => await OnclickUserRoleDialog(context.Item.Id))">مدیریت نقش</MudMenuItem>
|
||||||
<MudMenuItem OnClick="() => OnDelete(context.Item)">آرشیو</MudMenuItem>
|
<MudMenuItem OnClick="() => OnDelete(context.Item)">آرشیو</MudMenuItem>
|
||||||
@@ -63,4 +63,3 @@
|
|||||||
</BackOffice.Common.BaseComponents.BasePageComponent>
|
</BackOffice.Common.BaseComponents.BasePageComponent>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
@using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder
|
||||||
|
@using BackOffice.Common.BaseComponents
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<DialogContent>
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
@if (_isLoading)
|
||||||
|
{
|
||||||
|
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
|
||||||
|
}
|
||||||
|
else if (_model is null)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body1" Color="Color.Error">
|
||||||
|
خطا در دریافت اطلاعات سفارش.
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudText Typo="Typo.h6">جزئیات سفارش شماره @_model.Id</MudText>
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
کاربر: @_model.UserFullName (@_model.UserNationalCode)
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
<MudPaper Class="pa-3">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.subtitle2">خلاصه سفارش</MudText>
|
||||||
|
<MudDivider />
|
||||||
|
<MudText Typo="Typo.body2">مبلغ: @_model.Price.ToString("N0") تومان</MudText>
|
||||||
|
<MudText Typo="Typo.body2">شناسه پرداخت: @_model.TransactionId</MudText>
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
تاریخ پرداخت:
|
||||||
|
@_model.PaymentDate.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd HH:mm")
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
روش پرداخت:
|
||||||
|
@GetPaymentMethodText(_model.PaymentMethod)
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
وضعیت پرداخت:
|
||||||
|
@if (_model.PaymentStatus)
|
||||||
|
{
|
||||||
|
<MudChip T="string" Color="Color.Success" Size="Size.Small">پرداخت شده</MudChip>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudChip T="string" Color="Color.Error" Size="Size.Small">پرداخت نشده</MudChip>
|
||||||
|
}
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
وضعیت ارسال:
|
||||||
|
<MudSelect T="int"
|
||||||
|
Label="وضعیت ارسال"
|
||||||
|
Dense="true"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
@bind-Value="_deliveryStatusValue">
|
||||||
|
<MudSelectItem T="int" Value="0">بدون ارسال / نامشخص</MudSelectItem>
|
||||||
|
<MudSelectItem T="int" Value="1">در انتظار ارسال</MudSelectItem>
|
||||||
|
<MudSelectItem T="int" Value="2">تحویل پست</MudSelectItem>
|
||||||
|
<MudSelectItem T="int" Value="3">تحویل به مشتری</MudSelectItem>
|
||||||
|
<MudSelectItem T="int" Value="4">مرجوع شده</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
</MudText>
|
||||||
|
<MudTextField T="string"
|
||||||
|
Label="کد رهگیری"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Dense="true"
|
||||||
|
@bind-Value="_trackingCode" />
|
||||||
|
<MudTextField T="string"
|
||||||
|
Label="توضیحات ارسال"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Dense="true"
|
||||||
|
Lines="3"
|
||||||
|
@bind-Value="_deliveryDescription" />
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
<MudPaper Class="pa-3">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.subtitle2">آدرس تحویل</MudText>
|
||||||
|
<MudDivider />
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_model.UserAddressText))
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2">@_model.UserAddressText</MudText>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption">آدرسی ثبت نشده است.</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
<MudPaper Class="pa-3">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudText Typo="Typo.subtitle2">اقلام فاکتور</MudText>
|
||||||
|
<MudDivider />
|
||||||
|
@if (_model.FactorDetails != null && _model.FactorDetails.Count > 0)
|
||||||
|
{
|
||||||
|
<MudTable Items="_model.FactorDetails" Dense="true">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>محصول</MudTh>
|
||||||
|
<MudTh>قیمت واحد</MudTh>
|
||||||
|
<MudTh>تعداد</MudTh>
|
||||||
|
<MudTh>تخفیف واحد</MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd>@context.ProductTitle</MudTd>
|
||||||
|
<MudTd>@context.UnitPrice?.ToString("N0")</MudTd>
|
||||||
|
<MudTd>@context.Count</MudTd>
|
||||||
|
<MudTd>@context.UnitDiscountPrice?.ToString("N0")</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption">آیتمی برای این سفارش ثبت نشده است.</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton Variant="Variant.Outlined" Color="Color.Default" OnClick="Close">
|
||||||
|
بستن
|
||||||
|
</MudButton>
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveAsync">
|
||||||
|
ثبت تغییرات
|
||||||
|
</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace BackOffice.Pages.UserOrder.Components;
|
||||||
|
|
||||||
|
public partial class UserOrderDetailsDialog
|
||||||
|
{
|
||||||
|
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = default!;
|
||||||
|
[Inject] public UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter] public long OrderId { get; set; }
|
||||||
|
|
||||||
|
private GetUserOrderResponse? _model;
|
||||||
|
private bool _isLoading;
|
||||||
|
private int _deliveryStatusValue;
|
||||||
|
private string? _trackingCode;
|
||||||
|
private string? _deliveryDescription;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_isLoading = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_model = await UserOrderContract.GetUserOrderAsync(new GetUserOrderRequest
|
||||||
|
{
|
||||||
|
Id = OrderId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (_model is not null)
|
||||||
|
{
|
||||||
|
_deliveryStatusValue = _model.DeliveryStatus;
|
||||||
|
_trackingCode = _model.TrackingCode;
|
||||||
|
_deliveryDescription = _model.DeliveryDescription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_model = null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color GetDeliveryStatusColor(int status)
|
||||||
|
{
|
||||||
|
return status switch
|
||||||
|
{
|
||||||
|
1 => Color.Warning, // Pending
|
||||||
|
2 => Color.Info, // InTransit
|
||||||
|
3 => Color.Success, // Delivered
|
||||||
|
4 => Color.Error, // Returned
|
||||||
|
_ => Color.Default // None / Unknown
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDeliveryStatusText(int status)
|
||||||
|
{
|
||||||
|
return status switch
|
||||||
|
{
|
||||||
|
1 => "در انتظار ارسال",
|
||||||
|
2 => "تحویل پست",
|
||||||
|
3 => "تحویل به مشتری",
|
||||||
|
4 => "مرجوع شده",
|
||||||
|
_ => "بدون ارسال / نامشخص"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetPaymentMethodText(int method)
|
||||||
|
{
|
||||||
|
return method switch
|
||||||
|
{
|
||||||
|
1 => "کیف پول",
|
||||||
|
_ => "درگاه پرداخت"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveAsync()
|
||||||
|
{
|
||||||
|
if (_model is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await UserOrderContract.UpdateUserOrderAsync(new UpdateUserOrderRequest
|
||||||
|
{
|
||||||
|
Id = _model.Id,
|
||||||
|
DeliveryStatus = _deliveryStatusValue,
|
||||||
|
TrackingCode = _trackingCode ?? string.Empty,
|
||||||
|
DeliveryDescription = _deliveryDescription ?? string.Empty
|
||||||
|
});
|
||||||
|
|
||||||
|
MudDialog.Close(DialogResult.Ok(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Close() => MudDialog.Close();
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@attribute [Route(RouteConstance.UserOrder + "{UserId:long}")]
|
@attribute [Route(RouteConstance.Orders)]
|
||||||
|
@attribute [Route(RouteConstance.UserOrder + "{UserId:long}")]
|
||||||
|
|
||||||
@using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder
|
@using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder
|
||||||
@using BackOffice.Pages.UserOrder.Components
|
@using BackOffice.Pages.UserOrder.Components
|
||||||
@@ -7,7 +8,51 @@
|
|||||||
|
|
||||||
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
|
<BackOffice.Common.BaseComponents.BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
|
||||||
<Filters>
|
<Filters>
|
||||||
<MudNumericField HideSpinButtons="true" T="long?" Clearable="true" Label="قیمت" @bind-Value="@_request.Filter.Price" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
<MudNumericField HideSpinButtons="true"
|
||||||
|
T="long?"
|
||||||
|
Clearable="true"
|
||||||
|
Label="قیمت"
|
||||||
|
@bind-Value="@_request.Filter.Price"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense" />
|
||||||
|
|
||||||
|
<MudDatePicker T="DateTime?"
|
||||||
|
Label="از تاریخ پرداخت"
|
||||||
|
@bind-Date="_paymentDateFrom"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense" />
|
||||||
|
|
||||||
|
<MudSelect T="int?"
|
||||||
|
Clearable="true"
|
||||||
|
Label="روش پرداخت"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
@bind-Value="_paymentMethodFilter">
|
||||||
|
<MudSelectItem T="int?" Value="@((int?)0)">درگاه پرداخت</MudSelectItem>
|
||||||
|
<MudSelectItem T="int?" Value="@((int?)1)">کیف پول</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
|
||||||
|
<MudSelect T="int?"
|
||||||
|
Clearable="true"
|
||||||
|
Label="وضعیت پرداخت"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
@bind-Value="_paymentStatusFilter">
|
||||||
|
<MudSelectItem T="int?" Value="@(1)">پرداخت شده</MudSelectItem>
|
||||||
|
<MudSelectItem T="int?" Value="@(0)">پرداخت نشده</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
|
||||||
|
<MudSelect T="int?"
|
||||||
|
Clearable="true"
|
||||||
|
Label="وضعیت ارسال"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
@bind-Value="_deliveryStatusFilter">
|
||||||
|
<MudSelectItem T="int?" Value="@(1)">در انتظار ارسال</MudSelectItem>
|
||||||
|
<MudSelectItem T="int?" Value="@(2)">تحویل پست</MudSelectItem>
|
||||||
|
<MudSelectItem T="int?" Value="@(3)">تحویل به مشتری</MudSelectItem>
|
||||||
|
<MudSelectItem T="int?" Value="@(4)">مرجوع شده</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
</Filters>
|
</Filters>
|
||||||
<Content>
|
<Content>
|
||||||
<MudDataGrid T="DataModel" ServerData="@(new Func<GridState<DataModel>, Task<GridData<DataModel>>>(ServerReload))"
|
<MudDataGrid T="DataModel" ServerData="@(new Func<GridState<DataModel>, Task<GridData<DataModel>>>(ServerReload))"
|
||||||
@@ -20,12 +65,21 @@
|
|||||||
</ColGroup>
|
</ColGroup>
|
||||||
<ToolBarContent>
|
<ToolBarContent>
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||||
<MudText>فاکتور</MudText>
|
<MudText>سفارشهای کاربر</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</ToolBarContent>
|
</ToolBarContent>
|
||||||
<Columns>
|
<Columns>
|
||||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||||
<PropertyColumn Property="x => x.Price" Title="مبلغ" />
|
<PropertyColumn Property="x => x.Price" Title="مبلغ" />
|
||||||
|
<PropertyColumn Property="x => x.UserFullName" Title="نام کاربر" />
|
||||||
|
<PropertyColumn Property="x => x.UserNationalCode" Title="کدملی" />
|
||||||
|
<TemplateColumn Title="روش پرداخت">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudChip T="string" Size="Size.Small">
|
||||||
|
@GetPaymentMethodText(context.Item.PaymentMethod)
|
||||||
|
</MudChip>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
<PropertyColumn Property="x => x.PaymentStatus" Title="وضعیت پرداخت">
|
<PropertyColumn Property="x => x.PaymentStatus" Title="وضعیت پرداخت">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
@if (context.Item.PaymentStatus)
|
@if (context.Item.PaymentStatus)
|
||||||
@@ -40,6 +94,28 @@
|
|||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="x => x.TransactionId" Title="شناسه پرداخت" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;" />
|
<PropertyColumn Property="x => x.TransactionId" Title="شناسه پرداخت" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;" />
|
||||||
<PropertyColumn Property="x => x.PaymentDate" Title="تاریخ پرداخت" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;" />
|
<PropertyColumn Property="x => x.PaymentDate" Title="تاریخ پرداخت" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;" />
|
||||||
|
<TemplateColumn Title="آدرس">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudTooltip Text="@context.Item.UserAddressText">
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
@(string.IsNullOrWhiteSpace(context.Item.UserAddressText)
|
||||||
|
? "-"
|
||||||
|
: (context.Item.UserAddressText.Length > 30
|
||||||
|
? context.Item.UserAddressText.Substring(0, 30) + "..."
|
||||||
|
: context.Item.UserAddressText))
|
||||||
|
</MudText>
|
||||||
|
</MudTooltip>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="وضعیت ارسال">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudChip T="string"
|
||||||
|
Size="Size.Small"
|
||||||
|
Color="@GetDeliveryStatusColor(context.Item.DeliveryStatus)">
|
||||||
|
@GetDeliveryStatusText(context.Item.DeliveryStatus)
|
||||||
|
</MudChip>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -47,6 +123,13 @@
|
|||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||||
|
|
||||||
|
<MudTooltip Text="جزئیات سفارش">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Info"
|
||||||
|
Size="Size.Small"
|
||||||
|
ButtonType="ButtonType.Button"
|
||||||
|
OnClick="@(() => OpenDetails(context.Item))"
|
||||||
|
Style="cursor:pointer;" />
|
||||||
|
</MudTooltip>
|
||||||
|
|
||||||
<MudTooltip Text="آرشیو">
|
<MudTooltip Text="آرشیو">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => OnDelete(context.Item))" Style="cursor:pointer;" />
|
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => OnDelete(context.Item))" Style="cursor:pointer;" />
|
||||||
@@ -62,6 +145,3 @@
|
|||||||
|
|
||||||
</Content>
|
</Content>
|
||||||
</BackOffice.Common.BaseComponents.BasePageComponent>
|
</BackOffice.Common.BaseComponents.BasePageComponent>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,33 @@
|
|||||||
using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
using System.Text.Json;
|
||||||
|
using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
||||||
using BackOffice.Common.BaseComponents;
|
using BackOffice.Common.BaseComponents;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using DataModel = BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder.GetAllUserOrderByFilterResponseModel;
|
using DataModel = BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder.GetAllUserOrderByFilterResponseModel;
|
||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
|
||||||
namespace BackOffice.Pages.UserOrder;
|
namespace BackOffice.Pages.UserOrder;
|
||||||
|
|
||||||
public partial class UserOrderMainPage
|
public partial class UserOrderMainPage
|
||||||
{
|
{
|
||||||
[Parameter] public long UserId { get; set; }
|
[Parameter] public long? UserId { get; set; }
|
||||||
[Inject] public UserOrderContract.UserOrderContractClient UserOrderContract { get; set; }
|
[Inject] public UserOrderContract.UserOrderContractClient UserOrderContract { get; set; }
|
||||||
private bool _isLoading = true;
|
private bool _isLoading = true;
|
||||||
private MudDataGrid<DataModel> _gridData;
|
private MudDataGrid<DataModel> _gridData;
|
||||||
BasePageComponent _basePage;
|
BasePageComponent _basePage;
|
||||||
|
|
||||||
|
private int? _paymentStatusFilter;
|
||||||
|
private int? _deliveryStatusFilter;
|
||||||
|
private int? _paymentMethodFilter;
|
||||||
|
private DateTime? _paymentDateFrom;
|
||||||
|
|
||||||
private GetAllUserOrderByFilterRequest _request = new() { Filter = new() };
|
private GetAllUserOrderByFilterRequest _request = new() { Filter = new() };
|
||||||
|
protected override Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_request.Filter ??= new();
|
||||||
|
return base.OnInitializedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<GridData<DataModel>> ServerReload(GridState<DataModel> state)
|
private async Task<GridData<DataModel>> ServerReload(GridState<DataModel> state)
|
||||||
{
|
{
|
||||||
_request.Filter ??= new();
|
_request.Filter ??= new();
|
||||||
@@ -21,16 +35,104 @@ public partial class UserOrderMainPage
|
|||||||
_request.PaginationState.PageNumber = state.Page + 1;
|
_request.PaginationState.PageNumber = state.Page + 1;
|
||||||
_request.PaginationState.PageSize = state.PageSize;
|
_request.PaginationState.PageSize = state.PageSize;
|
||||||
|
|
||||||
_request.Filter.UserId = UserId;
|
if (UserId.HasValue && UserId.Value > 0)
|
||||||
|
{
|
||||||
|
_request.Filter.UserId = UserId.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_request.Filter.UserId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_paymentDateFrom.HasValue)
|
||||||
|
{
|
||||||
|
_request.Filter.PaymentDate =
|
||||||
|
Timestamp.FromDateTime(DateTime.SpecifyKind(_paymentDateFrom.Value.Date, DateTimeKind.Utc));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_request.Filter.PaymentDate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_paymentStatusFilter.HasValue)
|
||||||
|
{
|
||||||
|
_request.Filter.PaymentStatus = _paymentStatusFilter.Value == 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_request.Filter.PaymentStatus = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_deliveryStatusFilter.HasValue)
|
||||||
|
{
|
||||||
|
_request.Filter.DeliveryStatus = _deliveryStatusFilter.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_request.Filter.DeliveryStatus = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_paymentMethodFilter.HasValue)
|
||||||
|
{
|
||||||
|
_request.Filter.PaymentMethod = _paymentMethodFilter.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_request.Filter.PaymentMethod = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (IsEmptyFilter(_request.Filter))
|
||||||
|
{
|
||||||
|
_request.Filter = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine(JsonSerializer.Serialize(_request.Filter));
|
||||||
|
}
|
||||||
|
|
||||||
var result = await UserOrderContract.GetAllUserOrderByFilterAsync(_request);
|
var result = await UserOrderContract.GetAllUserOrderByFilterAsync(_request);
|
||||||
if (result != null && result.Models != null && result.Models.Any())
|
if (result != null && result.Models != null && result.Models.Any())
|
||||||
{
|
{
|
||||||
return new GridData<DataModel>() { Items = result.Models.ToList(), TotalItems = (int)result.MetaData.TotalCount };
|
return new GridData<DataModel>()
|
||||||
|
{ Items = result.Models.ToList(), TotalItems = (int)result.MetaData.TotalCount };
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GridData<DataModel>();
|
return new GridData<DataModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsEmptyFilter(GetAllUserOrderByFilterFilter src)
|
||||||
|
{
|
||||||
|
return src.Id == null
|
||||||
|
&& src.Price == null
|
||||||
|
&& src.PackageId == null
|
||||||
|
&& src.TransactionId == null
|
||||||
|
&& src.PaymentStatus == null
|
||||||
|
&& src.PaymentDate == null
|
||||||
|
&& src.UserId == null
|
||||||
|
&& src.DeliveryStatus == null
|
||||||
|
&& src.PaymentMethod == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenDetails(DataModel model)
|
||||||
|
{
|
||||||
|
var parameters = new DialogParameters
|
||||||
|
{
|
||||||
|
{ nameof(BackOffice.Pages.UserOrder.Components.UserOrderDetailsDialog.OrderId), model.Id }
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Large, FullWidth = true };
|
||||||
|
var dialog =
|
||||||
|
await DialogService.ShowAsync<BackOffice.Pages.UserOrder.Components.UserOrderDetailsDialog>("جزئیات سفارش",
|
||||||
|
parameters, options);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
|
||||||
|
if (!result.Canceled)
|
||||||
|
{
|
||||||
|
ReLoadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task OnDelete(DataModel model)
|
private async Task OnDelete(DataModel model)
|
||||||
{
|
{
|
||||||
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small };
|
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small };
|
||||||
@@ -47,13 +149,16 @@ public partial class UserOrderMainPage
|
|||||||
});
|
});
|
||||||
ReLoadData();
|
ReLoadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void ReLoadData()
|
public async void ReLoadData()
|
||||||
{
|
{
|
||||||
if (_gridData != null)
|
if (_gridData != null)
|
||||||
await _gridData.ReloadServerData();
|
await _gridData.ReloadServerData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnFilterSubmit()
|
public async Task OnFilterSubmit()
|
||||||
{
|
{
|
||||||
_basePage.IsFiltered = true;
|
_basePage.IsFiltered = true;
|
||||||
@@ -66,6 +171,43 @@ public partial class UserOrderMainPage
|
|||||||
_basePage.IsFiltered = false;
|
_basePage.IsFiltered = false;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
_request = new() { Filter = new() { } };
|
_request = new() { Filter = new() { } };
|
||||||
|
_paymentStatusFilter = null;
|
||||||
|
_deliveryStatusFilter = null;
|
||||||
|
_paymentDateFrom = null;
|
||||||
|
_paymentMethodFilter = null;
|
||||||
ReLoadData();
|
ReLoadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Color GetDeliveryStatusColor(int status)
|
||||||
|
{
|
||||||
|
return status switch
|
||||||
|
{
|
||||||
|
1 => Color.Warning, // Pending
|
||||||
|
2 => Color.Info, // InTransit
|
||||||
|
3 => Color.Success, // Delivered
|
||||||
|
4 => Color.Error, // Returned
|
||||||
|
_ => Color.Default // None / Unknown
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDeliveryStatusText(int status)
|
||||||
|
{
|
||||||
|
return status switch
|
||||||
|
{
|
||||||
|
1 => "در انتظار ارسال",
|
||||||
|
2 => "تحویل پست",
|
||||||
|
3 => "تحویل به مشتری",
|
||||||
|
4 => "مرجوع شده",
|
||||||
|
_ => "بدون ارسال / نامشخص"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetPaymentMethodText(int method)
|
||||||
|
{
|
||||||
|
return method switch
|
||||||
|
{
|
||||||
|
1 => "کیف پول",
|
||||||
|
_ => "درگاه پرداخت"
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,12 @@
|
|||||||
مدیریت محصول
|
مدیریت محصول
|
||||||
</MudNavLink>
|
</MudNavLink>
|
||||||
|
|
||||||
|
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||||
|
Href="@(RouteConstance.Orders)"
|
||||||
|
Icon="@Icons.Material.Filled.ReceiptLong">
|
||||||
|
سفارشها
|
||||||
|
</MudNavLink>
|
||||||
|
|
||||||
<MudNavLink Match="NavLinkMatch.Prefix"
|
<MudNavLink Match="NavLinkMatch.Prefix"
|
||||||
Href="@(RouteConstance.Category)"
|
Href="@(RouteConstance.Category)"
|
||||||
Icon="@Icons.Material.Filled.Category">
|
Icon="@Icons.Material.Filled.Category">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"GwUrl": "https://bogw.kbs1.ir",
|
"GwUrl": "https://bogw.kbs1.ir",
|
||||||
//"GwUrl": "https://localhost:6468",
|
// "GwUrl": "https://localhost:6468",
|
||||||
"Authentication": {
|
"Authentication": {
|
||||||
//"Authority": "https://localhost:5001",
|
//"Authority": "https://localhost:5001",
|
||||||
"Authority": "https://ids.afrino.co/",
|
"Authority": "https://ids.afrino.co/",
|
||||||
|
|||||||
@@ -20,6 +20,22 @@ body {
|
|||||||
font-family: "Vazir", "IRANSans", Tahoma, "Segoe UI", Arial, sans-serif;
|
font-family: "Vazir", "IRANSans", Tahoma, "Segoe UI", Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.drag-drop-zone {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-item {
|
||||||
|
cursor: grab;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-item:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
.loading-progress {
|
.loading-progress {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
Reference in New Issue
Block a user