Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 6m27s

This commit is contained in:
masoodafar-web
2026-08-09 02:33:27 +03:30
14 changed files with 527 additions and 162 deletions
+1
View File
@@ -1,3 +1,4 @@
# Docs moved to totalDoc # Docs moved to totalDoc
See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation. See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation.
+3
View File
@@ -429,6 +429,8 @@ else
@* ═══════════════════════════════════════════════ @* ═══════════════════════════════════════════════
6. TESTIMONIALS 6. TESTIMONIALS
═══════════════════════════════════════════════ *@ ═══════════════════════════════════════════════ *@
@if (_testimonials.Count > 0)
{
<section class="section-landing"> <section class="section-landing">
<MudContainer MaxWidth="MaxWidth.Large"> <MudContainer MaxWidth="MaxWidth.Large">
<div class="text-center mb-8 fade-in-up"> <div class="text-center mb-8 fade-in-up">
@@ -465,6 +467,7 @@ else
</MudGrid> </MudGrid>
</MudContainer> </MudContainer>
</section> </section>
}
@* ═══════════════════════════════════════════════ @* ═══════════════════════════════════════════════
7. FAQ 7. FAQ
+3 -2
View File
@@ -193,9 +193,10 @@ public partial class Index : IDisposable
} }
// ── Testimonials ── // ── Testimonials ──
if (_settings?.Testimonials?.Any() == true) // اگر تنظیمات از CMS لود شده، لیست خالی یعنی ادمین عمداً حذف کرده — بدون fallback hardcode
if (_settings is not null)
{ {
_testimonials = _settings.Testimonials _testimonials = (_settings.Testimonials ?? [])
.Select((t, i) => new TestimonialItem(t.Quote ?? "", t.Name ?? "", t.Role ?? "", i * 150)) .Select((t, i) => new TestimonialItem(t.Quote ?? "", t.Name ?? "", t.Role ?? "", i * 150))
.ToList(); .ToList();
} }
@@ -35,7 +35,23 @@
</MudAlert> </MudAlert>
} }
@if (_authLoaded && !_isClubMemberActive) @if (_authLoaded && _redirectingToMagic)
{
<MudAlert Severity="MudBlazor.Severity.Info" Class="rounded-lg" Variant="Variant.Outlined">
<MudStack Spacing="2" AlignItems="AlignItems.Center">
<MudProgressCircular Size="Size.Small" Indeterminate="true" Color="Color.Primary" />
<MudText>
کیف پول شما در حالت جادویی است. در حال انتقال به صفحه شارژ کیف پول جادویی...
</MudText>
<MudButton Size="Size.Small" Variant="Variant.Filled" Color="Color.Primary"
Href="@RouteConstants.Profile.MagicWallet"
StartIcon="@Icons.Material.Filled.AutoAwesome">
رفتن به شارژ کیف جادویی
</MudButton>
</MudStack>
</MudAlert>
}
else if (_authLoaded && !_isClubMemberActive)
{ {
<MudAlert Severity="MudBlazor.Severity.Warning" Class="rounded-lg" Variant="Variant.Outlined"> <MudAlert Severity="MudBlazor.Severity.Warning" Class="rounded-lg" Variant="Variant.Outlined">
<MudStack Spacing="2"> <MudStack Spacing="2">
@@ -67,7 +83,35 @@
</MudStack> </MudStack>
</MudAlert> </MudAlert>
} }
else if (_authLoaded) else if (_authLoaded && _needsMagicCeilingConsent && !_allowNormalDespiteMagic && !_redirectingToMagic)
{
<MudAlert Severity="MudBlazor.Severity.Warning" Class="rounded-lg" Variant="Variant.Outlined">
<MudStack Spacing="3">
<MudText Typo="Typo.subtitle1">
سقف شارژ کیف پول جادویی در این دور پر شده است.
</MudText>
<MudText Typo="Typo.body2">
شارژ از این صفحه بدون ضریب (۱:۱) به موجودی اضافه می‌شود و ضریب جادویی اعمال نمی‌شود.
برای ادامه، تأیید کنید.
</MudText>
<MudStack Row="true" Spacing="1" Class="flex-wrap">
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Check"
OnClick="AcceptMagicCeilingConsent">
ادامه شارژ بدون ضریب
</MudButton>
<MudButton Variant="Variant.Outlined"
Color="Color.Secondary"
StartIcon="@Icons.Material.Filled.AutoAwesome"
OnClick="DeclineMagicCeilingConsent">
بازگشت به کیف جادویی
</MudButton>
</MudStack>
</MudStack>
</MudAlert>
}
else if (ShowChargeForm)
{ {
<MudPaper Elevation="2" Class="pa-5 rounded-lg"> <MudPaper Elevation="2" Class="pa-5 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-3"> <MudText Typo="Typo.h6" Class="mb-3">
@@ -75,6 +119,13 @@
شارژ موجودی اصلی شارژ موجودی اصلی
</MudText> </MudText>
@if (_allowNormalDespiteMagic)
{
<MudAlert Severity="MudBlazor.Severity.Warning" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Warning">
این شارژ بدون ضریب جادویی است (۱:۱). سقف واریز جادویی شما در این دور پر شده است.
</MudAlert>
}
<MudAlert Severity="MudBlazor.Severity.Info" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info"> <MudAlert Severity="MudBlazor.Severity.Info" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info">
مبلغ وارد شده (به تومان) از طریق درگاه پرداخت به موجودی اصلی شما اضافه می‌شود. مبلغ وارد شده (به تومان) از طریق درگاه پرداخت به موجودی اصلی شما اضافه می‌شود.
از موجودی اصلی برای خرید از فروشگاه عادی و پرداخت سفارش استفاده می‌شود. از موجودی اصلی برای خرید از فروشگاه عادی و پرداخت سفارش استفاده می‌شود.
@@ -16,6 +16,9 @@ public partial class ChargeCreditWallet : ComponentBase
private bool _isClubMemberActive; private bool _isClubMemberActive;
private bool _hasPurchasedPackage; private bool _hasPurchasedPackage;
private bool _authLoaded; private bool _authLoaded;
private bool _redirectingToMagic;
private bool _allowNormalDespiteMagic;
private bool _needsMagicCeilingConsent;
private readonly long[] _presetAmounts = { 500_000, 1_000_000, 5_000_000, 10_000_000, 20_000_000, 50_000_000 }; private readonly long[] _presetAmounts = { 500_000, 1_000_000, 5_000_000, 10_000_000, 20_000_000, 50_000_000 };
@@ -28,6 +31,12 @@ public partial class ChargeCreditWallet : ComponentBase
[SupplyParameterFromQuery(Name = "returnUrl")] [SupplyParameterFromQuery(Name = "returnUrl")]
public string? ReturnUrlQueryParam { get; set; } public string? ReturnUrlQueryParam { get; set; }
private bool ShowChargeForm =>
_authLoaded
&& _isClubMemberActive
&& !_redirectingToMagic
&& (!_needsMagicCeilingConsent || _allowNormalDespiteMagic);
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_paymentResult = PaymentQueryParam; _paymentResult = PaymentQueryParam;
@@ -53,18 +62,31 @@ public partial class ChargeCreditWallet : ComponentBase
{ {
_authLoaded = true; _authLoaded = true;
} }
if (_isClubMemberActive)
await EvaluateMagicWalletGateAsync();
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender && !string.IsNullOrEmpty(_pendingReturnUrl) && _isClubMemberActive) if (firstRender && !string.IsNullOrEmpty(_pendingReturnUrl) && _isClubMemberActive && !_redirectingToMagic)
{
await CreditChargeNavigation.SaveReturnUrlAsync(JSRuntime, _pendingReturnUrl); await CreditChargeNavigation.SaveReturnUrlAsync(JSRuntime, _pendingReturnUrl);
}
await base.OnAfterRenderAsync(firstRender); await base.OnAfterRenderAsync(firstRender);
} }
private void AcceptMagicCeilingConsent()
{
_allowNormalDespiteMagic = true;
_redirectingToMagic = false;
}
private void DeclineMagicCeilingConsent()
{
_redirectingToMagic = true;
Navigation.NavigateTo(RouteConstants.Profile.MagicWallet);
}
private async Task StartCreditCharge() private async Task StartCreditCharge()
{ {
if (!_isClubMemberActive) if (!_isClubMemberActive)
@@ -75,6 +97,12 @@ public partial class ChargeCreditWallet : ComponentBase
return; return;
} }
if (await EvaluateMagicWalletGateAsync())
return;
if (_needsMagicCeilingConsent && !_allowNormalDespiteMagic)
return;
if (_chargeAmount <= 0 || _isProcessing) return; if (_chargeAmount <= 0 || _isProcessing) return;
_isProcessing = true; _isProcessing = true;
@@ -113,6 +141,50 @@ public partial class ChargeCreditWallet : ComponentBase
} }
} }
/// <summary>
/// Magic با ظرفیت باقی‌مانده → ریدایرکت به MagicWallet.
/// Magic با سقف پر → نمایش تأیید روی صفحه برای شارژ ۱:۱.
/// true یعنی مسیر شارژ فعلی باید متوقف شود.
/// </summary>
private async Task<bool> EvaluateMagicWalletGateAsync()
{
if (_allowNormalDespiteMagic)
return false;
try
{
var status = await WalletService.GetMagicWalletStatusAsync();
if (status.WalletMode != 1)
{
_needsMagicCeilingConsent = false;
return false;
}
if (status.MagicRemainingDeposit > 0)
{
var multiplier = status.MagicMultiplier > 0 ? status.MagicMultiplier : 2.5;
var multiplierText = multiplier.ToString("0.#");
_redirectingToMagic = true;
_needsMagicCeilingConsent = false;
Snackbar.Add(
$"کیف پول شما در حالت جادویی است. شارژ از این صفحه موجودی را چندبرابر نمی‌کند. برای دریافت ضریب جادویی (×{multiplierText}) به صفحه شارژ کیف پول جادویی هدایت می‌شوید.",
Severity.Info);
Navigation.NavigateTo(RouteConstants.Profile.MagicWallet);
return true;
}
_needsMagicCeilingConsent = true;
return !_allowNormalDespiteMagic;
}
catch
{
// در صورت خطا در خواندن وضعیت، مانع شارژ عادی نشو
return false;
}
}
private async Task ContinueShoppingAsync() private async Task ContinueShoppingAsync()
{ {
var returnUrl = await CreditChargeNavigation.GetReturnUrlAsync(JSRuntime); var returnUrl = await CreditChargeNavigation.GetReturnUrlAsync(JSRuntime);
@@ -166,8 +166,23 @@
else else
{ {
<MudAlert Severity="MudBlazor.Severity.Warning" Class="rounded-lg"> <MudAlert Severity="MudBlazor.Severity.Warning" Class="rounded-lg">
سقف شارژ جادویی در این دور پر شده است. <MudStack Spacing="2">
با خرج کردن موجودی و خرید مجدد پکیج، سقف ریست می‌شود. <MudText>
سقف شارژ جادویی در این دور پر شده است.
با خرج کردن موجودی و خرید مجدد پکیج، سقف ریست می‌شود.
</MudText>
<MudText Typo="Typo.body2">
اگر برای تکمیل خرید به موجودی بیشتری نیاز دارید، می‌توانید به‌صورت استثنایی کیف اصلی را بدون ضریب (۱:۱) شارژ کنید.
</MudText>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Size="Size.Small"
StartIcon="@Icons.Material.Filled.AccountBalanceWallet"
Href="@RouteConstants.Profile.ChargeCreditWallet"
Class="rounded-lg align-self-start">
شارژ عادی بدون ضریب
</MudButton>
</MudStack>
</MudAlert> </MudAlert>
} }
+46 -10
View File
@@ -38,18 +38,54 @@
درخواست‌های برداشت درخواست‌های برداشت
</MudButton> </MudButton>
<!-- دکمه شارژ کیف‌پول اصلی --> <!-- دکمه شارژ کیف‌پول اصلی / جادویی -->
@if (_isClubMemberActive) @if (_isClubMemberActive)
{ {
<MudButton Variant="Variant.Filled" @if (_isMagicWallet)
Color="Color.Primary" {
Size="Size.Large" <MudButton Variant="Variant.Filled"
FullWidth="true" Color="Color.Secondary"
StartIcon="@Icons.Material.Filled.AccountBalanceWallet" Size="Size.Large"
Href="@RouteConstants.Profile.ChargeCreditWallet" FullWidth="true"
Class="rounded-lg"> StartIcon="@Icons.Material.Filled.AutoAwesome"
شارژ کیف‌پول اصلی Href="@RouteConstants.Profile.MagicWallet"
</MudButton> Class="rounded-lg">
شارژ کیف‌پول جادویی
</MudButton>
@if (_magicCeilingFull)
{
<MudAlert Severity="Severity.Warning" Dense="true" Variant="Variant.Outlined">
سقف شارژ جادویی این دور پر است. در صورت نیاز می‌توانید به‌صورت استثنایی بدون ضریب شارژ کنید.
</MudAlert>
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
Size="Size.Large"
FullWidth="true"
StartIcon="@Icons.Material.Filled.AccountBalanceWallet"
Href="@RouteConstants.Profile.ChargeCreditWallet"
Class="rounded-lg">
شارژ عادی بدون ضریب
</MudButton>
}
else
{
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined">
کیف پول شما در حالت جادویی است؛ شارژ از مسیر جادویی با ضریب چندبرابر اعمال می‌شود.
</MudAlert>
}
}
else
{
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Size="Size.Large"
FullWidth="true"
StartIcon="@Icons.Material.Filled.AccountBalanceWallet"
Href="@RouteConstants.Profile.ChargeCreditWallet"
Class="rounded-lg">
شارژ کیف‌پول اصلی
</MudButton>
}
} }
else else
{ {
@@ -14,6 +14,8 @@ public partial class Wallet : ComponentBase
private string _filterType = "all"; private string _filterType = "all";
private bool _isClubMemberActive; private bool _isClubMemberActive;
private bool _hasPurchasedPackage; private bool _hasPurchasedPackage;
private bool _isMagicWallet;
private bool _magicCeilingFull;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@@ -29,6 +31,18 @@ public partial class Wallet : ComponentBase
_hasPurchasedPackage = false; _hasPurchasedPackage = false;
} }
try
{
var magicStatus = await WalletService.GetMagicWalletStatusAsync();
_isMagicWallet = magicStatus.WalletMode == 1;
_magicCeilingFull = _isMagicWallet && magicStatus.MagicRemainingDeposit <= 0;
}
catch
{
_isMagicWallet = false;
_magicCeilingFull = false;
}
var b = await WalletService.GetBalancesAsync(); var b = await WalletService.GetBalancesAsync();
_balances = (FormatPrice(b.CreditBalance), FormatPrice(b.DiscountBalance), FormatPrice(b.NetworkBalance)); _balances = (FormatPrice(b.CreditBalance), FormatPrice(b.DiscountBalance), FormatPrice(b.NetworkBalance));
_txs = await WalletService.GetTransactionsAsync(); _txs = await WalletService.GetTransactionsAsync();
@@ -127,6 +127,8 @@ public partial class CheckoutSummary : ComponentBase
var shortfall = totalRequired - walletBalance; var shortfall = totalRequired - walletBalance;
var allowCharge = false; var allowCharge = false;
var hasPurchasedPackage = false; var hasPurchasedPackage = false;
var isMagicWallet = false;
var magicCeilingFull = false;
try try
{ {
var userInfo = await AuthService.GetUserAuthInfo(); var userInfo = await AuthService.GetUserAuthInfo();
@@ -138,13 +140,27 @@ public partial class CheckoutSummary : ComponentBase
allowCharge = false; allowCharge = false;
} }
try
{
var magicStatus = await WalletService.GetMagicWalletStatusAsync();
isMagicWallet = magicStatus.WalletMode == 1;
magicCeilingFull = isMagicWallet && magicStatus.MagicRemainingDeposit <= 0;
}
catch
{
isMagicWallet = false;
magicCeilingFull = false;
}
var parameters = new DialogParameters<InsufficientCreditDialog> var parameters = new DialogParameters<InsufficientCreditDialog>
{ {
{ x => x.CurrentBalance, walletBalance }, { x => x.CurrentBalance, walletBalance },
{ x => x.RequiredAmount, totalRequired }, { x => x.RequiredAmount, totalRequired },
{ x => x.ShortfallAmount, shortfall }, { x => x.ShortfallAmount, shortfall },
{ x => x.AllowChargeCredit, allowCharge }, { x => x.AllowChargeCredit, allowCharge },
{ x => x.HasPurchasedPackage, hasPurchasedPackage } { x => x.HasPurchasedPackage, hasPurchasedPackage },
{ x => x.IsMagicWallet, isMagicWallet },
{ x => x.MagicCeilingFull, magicCeilingFull }
}; };
var dialog = await DialogService.ShowAsync<InsufficientCreditDialog>( var dialog = await DialogService.ShowAsync<InsufficientCreditDialog>(
@@ -46,12 +46,19 @@ public partial class OrderTracking : ComponentBase
private void BuildTrackingSteps() private void BuildTrackingSteps()
{ {
if (_order is null) return; if (_order is null) return;
// Build tracking steps based on PaymentStatus
var isPaid = _order.PaymentStatus == Messages.PaymentStatus.Success; var isPaid = _order.PaymentStatus == Messages.PaymentStatus.Success;
// Domain/public_messages: None=0, Pending=1, InTransit=2, Delivered=3, Returned=4, Cancelled=5, ReadyForOfficePickup=6
_trackingSteps = new List<TrackingStep> var delivery = (int)_order.DeliveryStatus;
{ var isCancelled = delivery == 5;
var isReturned = delivery == 4;
var isOfficePickup = delivery == 6;
var isInTransit = delivery == 2;
var isDelivered = delivery == 3;
var isPendingDelivery = delivery is 0 or 1;
_trackingSteps =
[
new() new()
{ {
Title = "ثبت سفارش", Title = "ثبت سفارش",
@@ -61,45 +68,77 @@ public partial class OrderTracking : ComponentBase
}, },
new() new()
{ {
Title = "در انتظار پرداخت", Title = "پرداخت",
Description = "منتظر تایید پرداخت هستیم", Description = isPaid ? "پرداخت شما تایید شد" : "منتظر تایید پرداخت هستیم",
IsCompleted = isPaid, IsCompleted = isPaid,
IsCurrent = !isPaid, IsCurrent = !isPaid && !isCancelled,
Date = isPaid ? FormatDate(_order.PaymentDate) : "" Date = isPaid ? FormatDate(_order.PaymentDate) : ""
},
new()
{
Title = "تایید پرداخت",
Description = "پرداخت شما تایید شد",
IsCompleted = isPaid,
IsCurrent = false,
Date = isPaid ? FormatDate(_order.PaymentDate) : ""
},
new()
{
Title = "در حال پردازش",
Description = "سفارش شما در حال آماده‌سازی است",
IsCompleted = false,
IsCurrent = isPaid,
Date = ""
},
new()
{
Title = "ارسال شده",
Description = "سفارش شما به پست تحویل داده شد",
IsCompleted = false,
IsCurrent = false,
Date = ""
},
new()
{
Title = "تحویل داده شده",
Description = "سفارش به دست شما رسید",
IsCompleted = false,
IsCurrent = false,
Date = ""
} }
}; ];
if (isCancelled)
{
_trackingSteps.Add(new()
{
Title = "لغو شده",
Description = "ارسال این سفارش لغو شده است",
IsCompleted = true,
IsCurrent = true,
Date = ""
});
return;
}
if (isReturned)
{
_trackingSteps.Add(new()
{
Title = "مرجوع شده",
Description = "سفارش مرجوع شده است",
IsCompleted = true,
IsCurrent = true,
Date = ""
});
return;
}
if (isOfficePickup)
{
_trackingSteps.Add(new()
{
Title = "آماده تحویل در دفتر",
Description = "سفارش برای تحویل حضوری در دفتر آماده است",
IsCompleted = true,
IsCurrent = true,
Date = ""
});
return;
}
_trackingSteps.Add(new()
{
Title = "در حال آماده‌سازی",
Description = "سفارش شما در حال آماده‌سازی است",
IsCompleted = isInTransit || isDelivered,
IsCurrent = isPaid && isPendingDelivery,
Date = ""
});
_trackingSteps.Add(new()
{
Title = "ارسال شده",
Description = "سفارش شما به پست تحویل داده شد",
IsCompleted = isDelivered,
IsCurrent = isInTransit,
Date = ""
});
_trackingSteps.Add(new()
{
Title = "تحویل داده شده",
Description = "سفارش به دست شما رسید",
IsCompleted = isDelivered,
IsCurrent = false,
Date = ""
});
} }
private static string FormatDate(Google.Protobuf.WellKnownTypes.Timestamp? timestamp) private static string FormatDate(Google.Protobuf.WellKnownTypes.Timestamp? timestamp)
+94 -72
View File
@@ -1,80 +1,102 @@
@attribute [Route(RouteConstants.Store.Orders)] @attribute [Route(RouteConstants.Store.Orders)]
@* Injection is handled in code-behind *@
<PageTitle>سفارشات من</PageTitle> <PageTitle>سفارشات من</PageTitle>
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6"> <MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
<PageHeader Title="سفارشات من" BackHref="@RouteConstants.Store.Products" /> <MudStack Spacing="3">
@if (_loading) <PageHeader Title="سفارشات من" BackHref="@RouteConstants.Store.Products" />
{
<LoadingState />
}
else if (_orders.Count == 0)
{
<MudAlert Severity="Severity.Info">هنوز سفارشی ثبت نکرده‌اید.</MudAlert>
}
else
{
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
<MudTable Items="_orders" Dense="true">
<HeaderContent>
<MudTh>شناسه</MudTh>
<MudTh>تاریخ</MudTh>
<MudTh>وضعیت</MudTh>
<MudTh>مبلغ</MudTh>
<MudTh></MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>@context.Id</MudTd>
<MudTd>
@if (context.PaymentDate != null)
{
@context.PaymentDate.ToDateTime().MiladiToJalaliWithTime()
}
else
{
<text>در انتظار پرداخت</text>
}
</MudTd>
<MudTd>@GetStatusText(context.PaymentStatus)</MudTd>
<MudTd>@FormatPrice(context.FactorDetails.Sum(s=>(s.UnitPrice ?? 0) * (s.Count ?? 0)))</MudTd>
<MudTd>
<MudButton Variant="Variant.Text" Href="@($"{RouteConstants.Store.OrderDetail}{context.Id}")" StartIcon="@Icons.Material.Filled.Receipt">جزئیات</MudButton>
</MudTd>
</RowTemplate>
</MudTable>
</MudHidden>
<MudHidden Breakpoint="Breakpoint.MdAndUp"> @if (_loading)
<MudStack Spacing="2"> {
@foreach (var o in _orders) <LoadingState />
{ }
<MudPaper Class="pa-3 rounded-lg" Outlined="true"> else if (_orders.Count == 0)
<MudStack Spacing="1"> {
<MudStack Row="true" Justify="Justify.SpaceBetween"> <MudAlert Severity="Severity.Info">هنوز سفارشی ثبت نشده است.</MudAlert>
<MudText>سفارش #@o.Id</MudText> <MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Store"
<MudText Class="mud-text-secondary"> Href="@RouteConstants.Store.Products">
@if (o.PaymentDate != null) مشاهده فروشگاه
{ </MudButton>
@o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() }
} else
else {
{ <!-- Desktop Table -->
<text>در انتظار پرداخت</text> <MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
} <MudPaper Elevation="1" Class="pa-4 rounded-lg">
</MudText> <MudTable Items="_orders">
<HeaderContent>
<MudTh>شماره سفارش</MudTh>
<MudTh>تعداد اقلام</MudTh>
<MudTh>مبلغ کل</MudTh>
<MudTh>وضعیت پرداخت</MudTh>
<MudTh>وضعیت ارسال</MudTh>
<MudTh>تاریخ</MudTh>
<MudTh></MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>@context.Id</MudTd>
<MudTd>@GetItemsCount(context)</MudTd>
<MudTd>@FormatPrice(GetTotalAmount(context))</MudTd>
<MudTd>
<MudChip T="string" Size="Size.Small"
Color="@GetPaymentStatusColor(context.PaymentStatus)"
Variant="Variant.Filled">
@GetPaymentStatusText(context.PaymentStatus)
</MudChip>
</MudTd>
<MudTd>
<MudChip T="string" Size="Size.Small"
Color="@GetDeliveryStatusColor((int)context.DeliveryStatus)"
Variant="Variant.Outlined">
@GetDeliveryStatusText((int)context.DeliveryStatus)
</MudChip>
</MudTd>
<MudTd>@FormatDate(context)</MudTd>
<MudTd>
<MudButton Size="Size.Small" Variant="Variant.Outlined" Color="Color.Primary"
OnClick="() => ViewOrder(context.Id)">
جزئیات
</MudButton>
</MudTd>
</RowTemplate>
</MudTable>
</MudPaper>
</MudHidden>
<!-- Mobile Cards -->
<MudHidden Breakpoint="Breakpoint.MdAndUp">
<MudStack Spacing="2">
@foreach (var order in _orders)
{
<MudPaper Class="pa-3 rounded-lg cursor-pointer" Outlined="true"
@onclick="() => ViewOrder(order.Id)">
<MudStack Spacing="1">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.subtitle2">سفارش #@order.Id</MudText>
<MudChip T="string" Size="Size.Small"
Color="@GetPaymentStatusColor(order.PaymentStatus)"
Variant="Variant.Filled">
@GetPaymentStatusText(order.PaymentStatus)
</MudChip>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.caption" Class="mud-text-secondary">@FormatDate(order)</MudText>
<MudChip T="string" Size="Size.Small"
Color="@GetDeliveryStatusColor((int)order.DeliveryStatus)"
Variant="Variant.Outlined">
@GetDeliveryStatusText((int)order.DeliveryStatus)
</MudChip>
</MudStack>
<MudDivider />
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2">@GetItemsCount(order) قلم</MudText>
<MudText Typo="Typo.body2" Class="fw-semibold">@FormatPrice(GetTotalAmount(order)) تومان</MudText>
</MudStack>
</MudStack> </MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween"> </MudPaper>
<MudText>وضعیت: @GetStatusText(o.PaymentStatus)</MudText> }
<MudText Color="Color.Primary">@FormatPrice(o.FactorDetails.Sum(s=>(s.UnitPrice ?? 0) * (s.Count ?? 0)))</MudText> </MudStack>
</MudStack> </MudHidden>
<MudStack Row="true" Justify="Justify.FlexEnd"> }
<MudButton Size="Size.Small" Variant="Variant.Outlined" Href="@($"{RouteConstants.Store.OrderDetail}{o.Id}")" StartIcon="@Icons.Material.Filled.Receipt">جزئیات</MudButton> </MudStack>
</MudStack>
</MudStack>
</MudPaper>
}
</MudStack>
</MudHidden>
}
</MudContainer> </MudContainer>
@@ -1,5 +1,5 @@
using System;
using CMSMicroservice.Protobuf.Protos.UserOrder; using CMSMicroservice.Protobuf.Protos.UserOrder;
using DateTimeConverterCL;
using FrontOffice.Main.Utilities; using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Messages = CMSMicroservice.Protobuf.Protos; using Messages = CMSMicroservice.Protobuf.Protos;
@@ -10,7 +10,6 @@ namespace FrontOffice.Main.Pages.Store;
public partial class Orders : ComponentBase public partial class Orders : ComponentBase
{ {
[Inject] private OrderService OrderService { get; set; } = default!; [Inject] private OrderService OrderService { get; set; } = default!;
[Inject] private VATService VAT { get; set; } = default!;
private List<GetUserOrderResponse> _orders = new(); private List<GetUserOrderResponse> _orders = new();
private bool _loading; private bool _loading;
@@ -33,28 +32,81 @@ public partial class Orders : ComponentBase
} }
} }
protected override async Task OnAfterRenderAsync(bool firstRender) private void ViewOrder(long orderId)
{ {
if (firstRender) Navigation.NavigateTo($"{RouteConstants.Store.OrderDetail}{orderId}");
}
private static string FormatPrice(long price) => $"{price:N0}";
private static string FormatDate(GetUserOrderResponse order)
{
try
{ {
// بارگذاری نرخ VAT var timestamp = order.PaymentDate;
await VAT.LoadAsync(); if (timestamp is null)
return "در انتظار پرداخت";
return timestamp.ToDateTime().ToLocalTime().MiladiToJalaliWithTime();
} }
await base.OnAfterRenderAsync(firstRender); catch
}
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
private string GetStatusText(Messages.PaymentStatus contextPaymentStatus)
{
return contextPaymentStatus switch
{ {
Messages.PaymentStatus.Pending => "در انتظار پرداخت", return "—";
Messages.PaymentStatus.Success => "پرداخت شده", }
Messages.PaymentStatus.Reject => "پرداخت ناموفق",
_ => "نامشخص",
};
} }
}
private static int GetItemsCount(GetUserOrderResponse order) =>
order.FactorDetails.Sum(f => f.Count ?? 0);
private static long GetTotalAmount(GetUserOrderResponse order)
{
if (order.VatInfo is not null && order.VatInfo.TotalAmount > 0)
return order.VatInfo.TotalAmount;
if (order.Amount > 0)
return order.Amount;
return order.FactorDetails.Sum(f => (f.UnitPrice ?? 0) * (f.Count ?? 0));
}
private static string GetPaymentStatusText(Messages.PaymentStatus status) => status switch
{
Messages.PaymentStatus.Pending => "در انتظار پرداخت",
Messages.PaymentStatus.Success => "پرداخت شده",
Messages.PaymentStatus.Reject => "پرداخت ناموفق",
_ => "نامشخص"
};
private static Color GetPaymentStatusColor(Messages.PaymentStatus status) => status switch
{
Messages.PaymentStatus.Pending => Color.Warning,
Messages.PaymentStatus.Success => Color.Success,
Messages.PaymentStatus.Reject => Color.Error,
_ => Color.Default
};
// Domain/public_messages: None=0, Pending=1, InTransit=2, Delivered=3, Returned=4, Cancelled=5, ReadyForOfficePickup=6
private static string GetDeliveryStatusText(int status) => status switch
{
0 => "بدون ارسال",
1 => "در انتظار ارسال",
2 => "ارسال شده",
3 => "تحویل داده شده",
4 => "مرجوع شده",
5 => "لغو شده",
6 => "آماده تحویل در دفتر",
_ => "نامشخص"
};
private static Color GetDeliveryStatusColor(int status) => status switch
{
0 => Color.Default,
1 => Color.Warning,
2 => Color.Primary,
3 => Color.Success,
4 => Color.Error,
5 => Color.Dark,
6 => Color.Primary,
_ => Color.Default
};
}
@@ -11,9 +11,24 @@
<MudStack Spacing="2"> <MudStack Spacing="2">
@if (AllowChargeCredit) @if (AllowChargeCredit)
{ {
<MudText Typo="Typo.body2" Class="mud-text-secondary"> @if (IsMagicWallet && !MagicCeilingFull)
برای تکمیل این سفارش، موجودی اصلی شما کافی نیست. می‌توانید کیف پول را شارژ کنید و سپس سفارش را ثبت کنید. {
</MudText> <MudText Typo="Typo.body2" Class="mud-text-secondary">
برای تکمیل این سفارش موجودی شما کافی نیست. کیف پول شما در حالت جادویی است؛ برای دریافت ضریب، از مسیر شارژ جادویی اقدام کنید.
</MudText>
}
else if (IsMagicWallet && MagicCeilingFull)
{
<MudAlert Severity="Severity.Warning" Dense="true" Variant="Variant.Outlined">
سقف شارژ جادویی شما در این دور پر است. می‌توانید به‌صورت استثنایی کیف اصلی را بدون ضریب (۱:۱) شارژ کنید.
</MudAlert>
}
else
{
<MudText Typo="Typo.body2" Class="mud-text-secondary">
برای تکمیل این سفارش، موجودی اصلی شما کافی نیست. می‌توانید کیف پول را شارژ کنید و سپس سفارش را ثبت کنید.
</MudText>
}
} }
else else
{ {
@@ -46,10 +61,10 @@
@if (AllowChargeCredit) @if (AllowChargeCredit)
{ {
<MudButton Variant="Variant.Filled" <MudButton Variant="Variant.Filled"
Color="Color.Primary" Color="@(IsMagicWallet && !MagicCeilingFull ? Color.Secondary : Color.Primary)"
StartIcon="@Icons.Material.Filled.Payment" StartIcon="@(IsMagicWallet && !MagicCeilingFull ? Icons.Material.Filled.AutoAwesome : Icons.Material.Filled.Payment)"
OnClick="GoToCharge"> OnClick="GoToCharge">
شارژ حساب اصلی @ChargeButtonLabel
</MudButton> </MudButton>
} }
else if (!HasPurchasedPackage) else if (!HasPurchasedPackage)
@@ -93,9 +108,33 @@
[Parameter] [Parameter]
public bool HasPurchasedPackage { get; set; } public bool HasPurchasedPackage { get; set; }
[Parameter]
public bool IsMagicWallet { get; set; }
/// <summary>سقف واریز جادویی این دور پر شده است.</summary>
[Parameter]
public bool MagicCeilingFull { get; set; }
private string ChargeButtonLabel =>
IsMagicWallet && !MagicCeilingFull
? "شارژ کیف جادویی"
: IsMagicWallet && MagicCeilingFull
? "شارژ عادی بدون ضریب (سقف جادویی پر است)"
: "شارژ حساب اصلی";
private void Cancel() => MudDialog.Cancel(); private void Cancel() => MudDialog.Cancel();
private void GoToCharge() => MudDialog.Close(DialogResult.Ok(ShortfallAmount)); private void GoToCharge()
{
if (IsMagicWallet && !MagicCeilingFull)
{
MudDialog.Cancel();
Navigation.NavigateTo(RouteConstants.Profile.MagicWallet);
return;
}
MudDialog.Close(DialogResult.Ok(ShortfallAmount));
}
private void GoToPackages() private void GoToPackages()
{ {
@@ -219,6 +219,8 @@ public class DiscountOrderService
2 => "ارسال شده", 2 => "ارسال شده",
3 => "تحویل داده شده", 3 => "تحویل داده شده",
4 => "لغو شده", 4 => "لغو شده",
5 => "آماده تحویل در دفتر",
6 => "مرجوع شده",
_ => "نامشخص" _ => "نامشخص"
}; };
@@ -228,7 +230,9 @@ public class DiscountOrderService
1 => Color.Info, 1 => Color.Info,
2 => Color.Primary, 2 => Color.Primary,
3 => Color.Success, 3 => Color.Success,
4 => Color.Error, 4 => Color.Dark,
5 => Color.Primary,
6 => Color.Error,
_ => Color.Default _ => Color.Default
}; };