Mango.Nop.Plugins/Nop.Plugin.Misc.AIPlugin/Views/CustomerCreditWidget.cshtml

335 lines
16 KiB
Plaintext

@model Nop.Plugin.Misc.FruitBankPlugin.Models.CustomerCreditWidgetModel
@inject Nop.Services.Common.IGenericAttributeService genericAttributeService
@inject Nop.Core.IWorkContext workContext
@{
var remaining = Model.RemainingCredit;
var statusClass = !Model.HasCreditLimit ? "text-muted"
: remaining <= 0 ? "text-danger"
: remaining < Model.CreditLimit * 0.2m ? "text-warning"
: "text-success";
// Per-admin collapse state, persisted like the core nopCommerce cards
var admin = await workContext.GetCurrentCustomerAsync();
const string hideAttributesCardName = "FruitBank.CustomerAttributesCard.Hide";
var hideAttributesCard = await genericAttributeService.GetAttributeAsync<bool>(admin, hideAttributesCardName);
const string hideCreditCardName = "FruitBank.CustomerCreditCard.Hide";
var hideCreditCard = await genericAttributeService.GetAttributeAsync<bool>(admin, hideCreditCardName);
}
@* FruitBank customer-top section: injected into the CustomerDetailsBlock widget zone
(which renders at the BOTTOM of the customer page) and relocated to the top of
#customer-cards by the script below. Future non-credit elements go inside this
same wrapper so the single relocation keeps them all together at the top. *@
<div id="fruitbank-customer-top">
@* ── Customer attributes (generic attributes block) ───────────────────────── *@
<div class="card card-primary card-outline @(hideAttributesCard ? "collapsed-card" : "")" id="fruitbank-customer-attributes-card">
<div class="card-header with-border">
<h3 class="card-title">
<i class="fas fa-id-card"></i>
@T("Plugins.Misc.FruitBankPlugin.CustomerAttributes.Title")
</h3>
<div class="card-tools float-right">
<button type="button" class="btn btn-tool" data-card-widget="collapse">
<i class="fas @(hideAttributesCard ? "fa-plus" : "fa-minus")"></i>
</button>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<div class="col-md-3">
<label>@T("Plugins.Misc.FruitBankPlugin.CustomerAttributes.IsEkaer")</label>
</div>
<div class="col-md-9">
<span class="form-control-plaintext">
@* No name attribute: this must NOT post with the core Customer Edit form;
it is saved via AJAX on change to CustomerAttributesController.SetEkaer. *@
<input type="checkbox" id="fruitbank-isekaer" data-customer-id="@Model.CustomerId" @(Model.IsEkaer ? "checked" : "") />
<span id="fruitbank-isekaer-status" class="ml-2"></span>
</span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<label>@T("Plugins.Misc.FruitBankPlugin.CustomerSites.Title")</label>
</div>
<div class="col-md-9">
<span class="form-control-plaintext" id="fruitbank-sites-summary">
@if (Model.SiteCount > 0)
{
<text>@Model.SiteCount @T("Plugins.Misc.FruitBankPlugin.CustomerSites.CountWord")</text>
if (!string.IsNullOrWhiteSpace(Model.DefaultSiteDisplay))
{
<text> (@T("Plugins.Misc.FruitBankPlugin.CustomerSites.DefaultLabel"): @Model.DefaultSiteDisplay)</text>
}
}
else
{
<span class="text-muted">@T("Plugins.Misc.FruitBankPlugin.CustomerSites.None")</span>
}
</span>
<button type="button" class="btn btn-default mt-1" data-toggle="modal" data-target="#fruitbank-sites-modal">
<i class="fas fa-map-marker-alt"></i>
@T("Plugins.Misc.FruitBankPlugin.CustomerSites.Edit")
</button>
</div>
</div>
</div>
</div>
@* ── Customer credit ──────────────────────────────────────────────────────── *@
<div class="card card-primary card-outline @(hideCreditCard ? "collapsed-card" : "")" id="fruitbank-customer-credit-card">
<div class="card-header with-border">
<h3 class="card-title">
<i class="fas fa-credit-card"></i>
@T("Plugins.Misc.FruitBankPlugin.CustomerCredit.PageTitle")
</h3>
<div class="card-tools float-right">
<button type="button" class="btn btn-tool" data-card-widget="collapse">
<i class="fas @(hideCreditCard ? "fa-plus" : "fa-minus")"></i>
</button>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<div class="col-md-3">
<label>@T("Plugins.Misc.FruitBankPlugin.CustomerCredit.CreditLimit")</label>
</div>
<div class="col-md-9">
<span class="form-control-plaintext">
@(Model.HasCreditLimit ? Model.CreditLimit.ToString("N0") + " Ft" : T("Plugins.Misc.FruitBankPlugin.CustomerCredit.Unlimited").Text)
</span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<label>@T("Plugins.Misc.FruitBankPlugin.CustomerCredit.OutstandingBalance")</label>
</div>
<div class="col-md-9">
<span class="form-control-plaintext">
@Model.OutstandingBalance.ToString("N0") Ft
</span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<label>@T("Plugins.Misc.FruitBankPlugin.CustomerCredit.RemainingCredit")</label>
</div>
<div class="col-md-9">
<span class="form-control-plaintext @statusClass">
<strong>
@if (!Model.HasCreditLimit)
{
@T("Plugins.Misc.FruitBankPlugin.CustomerCredit.Unlimited")
}
else
{
@(remaining!.Value.ToString("N0"))
<span>Ft</span>
}
</strong>
</span>
</div>
</div>
@if (!string.IsNullOrWhiteSpace(Model.Comment))
{
<div class="form-group row">
<div class="col-md-3">
<label>@T("Plugins.Misc.FruitBankPlugin.CustomerCredit.Comment")</label>
</div>
<div class="col-md-9">
<span class="form-control-plaintext text-muted">@Model.Comment</span>
</div>
</div>
}
<div class="form-group row">
<div class="col-md-9 offset-md-3">
<a href="/Admin/CustomerCredit/Details/@Model.CustomerId" class="btn btn-default">
<i class="fas fa-edit"></i>
@T("Plugins.Misc.FruitBankPlugin.CustomerCredit.EditTitle")
</a>
</div>
</div>
</div>
</div>
</div>
@* ── Sites edit modal ───────────────────────────────────────────────────────── *@
<div id="fruitbank-sites-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="fruitbank-sites-modal-title">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="fruitbank-sites-modal-title">@T("Plugins.Misc.FruitBankPlugin.CustomerSites.ModalTitle")</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
@if (!Model.SiteAddresses.Any())
{
<div class="alert alert-info mb-0">@T("Plugins.Misc.FruitBankPlugin.CustomerSites.NoAddresses")</div>
}
else
{
<table class="table table-hover">
<thead>
<tr>
<th style="width:90px">@T("Plugins.Misc.FruitBankPlugin.CustomerSites.IsSiteLabel")</th>
<th style="width:120px">@T("Plugins.Misc.FruitBankPlugin.CustomerSites.DefaultLabel")</th>
<th>@T("Plugins.Misc.FruitBankPlugin.CustomerSites.AddressLabel")</th>
</tr>
</thead>
<tbody>
@foreach (var a in Model.SiteAddresses)
{
<tr>
<td class="text-center">
<input type="checkbox" class="fb-site-check" data-address-id="@a.AddressId" @(a.IsSite ? "checked" : "") />
</td>
<td class="text-center">
<input type="radio" name="fb-site-default" class="fb-site-default" value="@a.AddressId" @(a.IsDefault ? "checked" : "") @(a.IsSite ? "" : "disabled") />
</td>
<td>@a.Display</td>
</tr>
}
</tbody>
</table>
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">@T("Admin.Common.Cancel")</button>
@if (Model.SiteAddresses.Any())
{
<button type="button" class="btn btn-primary" id="fruitbank-sites-save" data-customer-id="@Model.CustomerId">
@T("Plugins.Misc.FruitBankPlugin.CustomerCredit.Save")
</button>
}
</div>
</div>
</div>
</div>
<script>
(function () {
// The widget is rendered into the CustomerDetailsBlock zone at the bottom of
// #customer-cards; move the whole FruitBank section to the top so it shows first.
function moveFruitBankTopSection() {
var section = document.getElementById('fruitbank-customer-top');
var cards = document.getElementById('customer-cards');
if (section && cards && cards.firstElementChild !== section) {
cards.prepend(section);
}
}
if (document.readyState !== 'loading') {
moveFruitBankTopSection();
} else {
document.addEventListener('DOMContentLoaded', moveFruitBankTopSection);
}
})();
$(function () {
// Persist collapse state per admin, same as the core nopCommerce cards.
// AdminLTE handles the visual collapse via data-card-widget="collapse";
// this only saves the new state (class is toggled after this handler runs).
function bindCollapsePersist(cardId, attributeName) {
$('#' + cardId).on('click', 'button[data-card-widget="collapse"]', function () {
var collapsed = !$('#' + cardId).hasClass('collapsed-card');
saveUserPreferences('@(Url.Action("SavePreference", "Preferences"))', attributeName, collapsed);
});
}
bindCollapsePersist('fruitbank-customer-attributes-card', '@hideAttributesCardName');
bindCollapsePersist('fruitbank-customer-credit-card', '@hideCreditCardName');
// isEkaer: save on toggle via AJAX (the widget can't post through the core
// Customer Edit action, which only binds CustomerModel).
$('#fruitbank-isekaer').on('change', function () {
var $cb = $(this);
var value = $cb.is(':checked');
var $status = $('#fruitbank-isekaer-status');
var token = $('input[name="__RequestVerificationToken"]').first().val();
$cb.prop('disabled', true);
$status.html('<i class="fas fa-spinner fa-spin text-muted"></i>');
$.ajax({
url: '@Url.Action("SetEkaer", "CustomerAttributes")',
type: 'POST',
data: { customerId: $cb.data('customer-id'), value: value, __RequestVerificationToken: token },
success: function (res) {
if (res && res.success) {
$status.html('<i class="fas fa-check text-success"></i>');
} else {
$status.html('<i class="fas fa-times text-danger"></i>');
$cb.prop('checked', !value);
}
},
error: function () {
$status.html('<i class="fas fa-times text-danger"></i>');
$cb.prop('checked', !value);
},
complete: function () {
$cb.prop('disabled', false);
setTimeout(function () { $status.empty(); }, 2000);
}
});
});
// ── Sites edit modal ──────────────────────────────────────────────────
// The default radio is only valid for rows checked as a site.
$('#fruitbank-sites-modal').on('change', '.fb-site-check', function () {
var id = $(this).data('address-id');
var $radio = $('.fb-site-default[value="' + id + '"]');
if ($(this).is(':checked')) {
$radio.prop('disabled', false);
} else {
$radio.prop('disabled', true).prop('checked', false);
}
});
$('#fruitbank-sites-save').on('click', function () {
var $btn = $(this);
var ids = [];
$('#fruitbank-sites-modal .fb-site-check:checked').each(function () {
ids.push($(this).data('address-id'));
});
var def = $('#fruitbank-sites-modal .fb-site-default:checked').val() || 0;
var token = $('input[name="__RequestVerificationToken"]').first().val();
$btn.prop('disabled', true);
$.ajax({
url: '@Url.Action("SaveSites", "CustomerAttributes")',
type: 'POST',
traditional: true, // serialize siteAddressIds as repeated keys for int[] binding
data: {
customerId: $btn.data('customer-id'),
siteAddressIds: ids,
defaultAddressId: def,
__RequestVerificationToken: token
},
success: function (res) {
if (res && res.success) {
// Reload to refresh the summary line with the saved state.
location.reload();
} else {
alert(res && res.error ? res.error : 'Hiba a mentés során.');
$btn.prop('disabled', false);
}
},
error: function () {
alert('Hiba a mentés során.');
$btn.prop('disabled', false);
}
});
});
});
</script>