Mango.Nop.Plugins/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Preorder/List.cshtml

498 lines
25 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@{
ViewBag.PageTitle = "Előrendelések";
NopHtml.SetActiveMenuItemSystemName("Preorders.List");
Layout = "~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/_FruitBankAdminLayout.cshtml";
}
@Html.AntiForgeryToken()
<div class="content-header clearfix">
<h1 class="float-left">
<i class="fas fa-calendar-plus" style="color:#2d7a3a;"></i>
Előrendelések
</h1>
<div class="float-right">
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#create-preorder-window">
<i class="fas fa-plus"></i> Előrendelés rögzítése
</button>
</div>
</div>
<section class="content">
<div class="container-fluid">
<!-- ── Tabs ─────────────────────────────────────────────────────── -->
<ul class="nav nav-tabs mb-3" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="tab-list-link" data-toggle="tab" href="#tab-list" role="tab">
<i class="fas fa-list"></i> Előrendelések
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="tab-demand-link" data-toggle="tab" href="#tab-demand" role="tab">
<i class="fas fa-chart-bar"></i> Kereslet
<span id="demandBadge" class="badge badge-warning ml-1" style="display:none;"></span>
</a>
</li>
</ul>
<div class="tab-content">
<!-- ══ TAB 1: Preorder list ══════════════════════════════════ -->
<div class="tab-pane fade show active" id="tab-list" role="tabpanel">
<!-- Status filter bar -->
<div class="card card-default mb-3">
<div class="card-body py-2">
<div class="d-flex align-items-center" style="gap:8px; flex-wrap:wrap;">
<span class="text-muted" style="font-size:13px;">Szűrő:</span>
<button class="btn btn-sm btn-outline-secondary po-filter active" data-status="">Összes</button>
<button class="btn btn-sm btn-outline-warning po-filter" data-status="0">Függőben</button>
<button class="btn btn-sm btn-outline-success po-filter" data-status="10">Megerősítve</button>
<button class="btn btn-sm po-filter" style="border-color:#f4a236;color:#f4a236;" data-status="20">Részben teljesítve</button>
<button class="btn btn-sm btn-outline-danger po-filter" data-status="30">Törölve</button>
</div>
</div>
</div>
<!-- Main grid -->
<div class="card card-default">
<div class="card-body p-0">
<table id="po-grid" class="table table-bordered table-hover table-sm m-0" style="width:100%">
<thead>
<tr>
<th width="60">#</th>
<th>Ügyfél</th>
<th width="160" name="DateOfReceipt">Kért szállítás</th>
<th width="160" name="CreatedOnUtc">Leadva</th>
<th width="120" name="Status">Állapot</th>
<th width="100" class="text-center">Tételek</th>
<th width="70" class="text-center"></th>
</tr>
</thead>
</table>
</div>
</div>
</div><!-- /#tab-list -->
<!-- ══ TAB 2: Demand overview ════════════════════════════════ -->
<div class="tab-pane fade" id="tab-demand" role="tabpanel">
<!-- Toggle: open only / all time -->
<div class="card card-default mb-3">
<div class="card-body py-2">
<div class="d-flex align-items-center" style="gap:8px;">
<span class="text-muted" style="font-size:13px;">Nézet:</span>
<button class="btn btn-sm btn-warning demand-scope active" data-open="true">
<i class="fas fa-clock"></i> Nyitott előrendelések
</button>
<button class="btn btn-sm btn-outline-secondary demand-scope" data-open="false">
<i class="fas fa-history"></i> Összes idő
</button>
<small class="text-muted ml-3" id="demandScopeLabel">
Termékek amelyekre még van teljesítetlen igény
</small>
</div>
</div>
</div>
<div class="card card-default">
<div class="card-body p-0">
<table id="demand-grid" class="table table-bordered table-hover table-sm m-0" style="width:100%">
<thead>
<tr>
<th>Termék</th>
<th width="80">SKU</th>
<th width="110" class="text-center">Igényelt</th>
<th width="110" class="text-center">Teljesített</th>
<th width="130" class="text-center">Hiány ▼</th>
<th width="90" class="text-center">Rendelések</th>
<th width="120" class="text-right">Átlagár</th>
</tr>
</thead>
</table>
</div>
</div>
</div><!-- /#tab-demand -->
</div><!-- /.tab-content -->
</div>
</section>
<style>
/* Fix: jQuery UI autocomplete must appear above Bootstrap modals (z-index 1050) */
.ui-autocomplete { z-index: 1060 !important; }
.po-status-pending { background:#fff3cd; color:#856404; border-radius:4px; padding:2px 8px; font-size:12px; font-weight:600; }
.po-status-confirmed { background:#d4edda; color:#155724; border-radius:4px; padding:2px 8px; font-size:12px; font-weight:600; }
.po-status-partial { background:#fff8ee; color:#c87500; border-radius:4px; padding:2px 8px; font-size:12px; font-weight:600; }
.po-status-cancelled { background:#f8d7da; color:#721c24; border-radius:4px; padding:2px 8px; font-size:12px; font-weight:600; }
.po-filter.active { font-weight:700; }
.demand-scope.active { font-weight:700; }
.demand-unfulfilled-high { color:#dc3545; font-weight:700; }
.demand-unfulfilled-mid { color:#c87500; font-weight:600; }
.demand-unfulfilled-ok { color:#6b7c6e; }
</style>
<script>
$(function () {
var _token = $('input[name="__RequestVerificationToken"]').val();
var activeStatus = '';
var demandOpenOnly = true;
// ── Helpers ──────────────────────────────────────────────────────────────
function statusBadge(row) {
switch (row.Status) {
case 0: return '<span class="po-status-pending">' + row.StatusLabel + '</span>';
case 10: return '<span class="po-status-confirmed">' + row.StatusLabel + '</span>';
case 20: return '<span class="po-status-partial">' + row.StatusLabel + '</span>';
case 30: return '<span class="po-status-cancelled">' + row.StatusLabel + '</span>';
default: return row.StatusLabel;
}
}
function itemProgress(row) {
var total = row.ItemCount;
var done = row.FulfilledCount;
if (total === 0) return '—';
var pct = Math.round(done / total * 100);
var cls = pct === 100 ? 'bg-success' : pct > 0 ? 'bg-warning' : 'bg-danger';
return '<div style="min-width:80px">' +
'<div class="progress" style="height:6px;margin-bottom:3px;">' +
'<div class="progress-bar ' + cls + '" style="width:' + pct + '%"></div></div>' +
'<small>' + done + '/' + total + ' tétel</small></div>';
}
function fmtQty(n) { return n.toLocaleString('hu-HU') + ' db'; }
function fmtPrice(n) { return n > 0 ? Math.round(n).toLocaleString('hu-HU') + ' Ft' : '—'; }
function unfulfilledCell(n) {
var cls = n > 100 ? 'demand-unfulfilled-high' : n > 20 ? 'demand-unfulfilled-mid' : 'demand-unfulfilled-ok';
return '<span class="' + cls + '">' + fmtQty(n) + '</span>';
}
// ── TAB 1: Preorder list ─────────────────────────────────────────────────
var poTable = $('#po-grid').DataTable({
serverSide : true,
processing : true,
pageLength : 25,
lengthMenu : [[25, 50, 100], [25, 50, 100]],
order : [[3, 'desc']],
language : {
processing : 'Betöltés...',
search : 'Keresés:',
lengthMenu : '_MENU_ sor/oldal',
info : '_START__END_ / _TOTAL_ előrendelés',
infoEmpty : '0 előrendelés',
infoFiltered : '(szűrve _MAX_-ból)',
paginate : { first: '««', previous: '«', next: '»', last: '»»' },
emptyTable : 'Nincs előrendelés',
zeroRecords : 'Nincs találat'
},
ajax: {
url : '/Admin/Preorders/PreorderList',
type: 'POST',
data: function (d) {
d.__RequestVerificationToken = _token;
d.statusFilter = activeStatus;
}
},
columns: [
{ data: 'PreorderId', name: 'PreorderId',
render: function(d) { return '<strong>#' + d + '</strong>'; } },
{ data: 'CustomerName', name: 'CustomerName',
render: function(d, t, row) {
return '<div>' + d + '</div><small class="text-muted">' + row.CustomerEmail + '</small>';
}},
{ data: 'DateOfReceipt', name: 'DateOfReceipt',
render: function(d) { return '<i class="fas fa-calendar-day text-muted mr-1"></i>' + d; }},
{ data: 'CreatedOnUtc', name: 'CreatedOnUtc',
render: function(d) { return '<small>' + d + '</small>'; }},
{ data: 'Status', name: 'Status', orderable: false,
render: function(d, t, row) { return statusBadge(row); }},
{ data: 'ItemCount', orderable: false, className: 'text-center',
render: function(d, t, row) { return itemProgress(row); }},
{ data: 'PreorderId', orderable: false, searchable: false,
className: 'text-center', width: '60px',
render: function(d) {
return '<a href="/Admin/Preorders/Detail/' + d + '" class="btn btn-xs btn-default" title="Részletek">' +
'<i class="fas fa-eye"></i></a>';
}}
]
});
$(document).on('click', '.po-filter', function () {
$('.po-filter').removeClass('active');
$(this).addClass('active');
activeStatus = $(this).data('status').toString();
poTable.ajax.reload();
});
// ── TAB 2: Demand grid ───────────────────────────────────────────────────
var demandTable = $('#demand-grid').DataTable({
serverSide : true,
processing : true,
pageLength : 50,
lengthMenu : [[25, 50, 100, 250], [25, 50, 100, 250]],
order : [[4, 'desc']],
language : {
processing : 'Betöltés...',
search : 'Keresés:',
lengthMenu : '_MENU_ sor/oldal',
info : '_START__END_ / _TOTAL_ termék',
infoEmpty : 'Nincs adat',
infoFiltered : '(szűrve _MAX_-ból)',
paginate : { first: '««', previous: '«', next: '»', last: '»»' },
emptyTable : 'Nincs előrendelési igény',
zeroRecords : 'Nincs találat'
},
ajax: {
url : '/Admin/Preorders/DemandList',
type: 'POST',
data: function (d) {
d.__RequestVerificationToken = _token;
d.openOnly = demandOpenOnly ? 'true' : 'false';
},
dataSrc: function (json) {
// Update badge with number of products that have unfulfilled demand
var withDemand = (json.data || []).filter(function (r) { return r.TotalUnfulfilled > 0; }).length;
if (withDemand > 0) { $('#demandBadge').text(withDemand).show(); }
else { $('#demandBadge').hide(); }
return json.data;
}
},
columns: [
{ data: 'ProductName', name: 'ProductName',
render: function(d, t, row) {
var badge = row.IsMeasurable ? ' <span class="badge badge-light" title="Súlymérést igényel">⚖️</span>' : '';
return '<a href="/Admin/Product/Edit/' + row.ProductId + '" target="_blank">' + d + '</a>' + badge;
}},
{ data: 'Sku', orderable: false,
render: function(d) { return d ? '<code>' + d + '</code>' : ''; }},
{ data: 'TotalRequested', orderable: false, className: 'text-center',
render: function(d) { return fmtQty(d); }},
{ data: 'TotalFulfilled', orderable: false, className: 'text-center',
render: function(d, t, row) {
var pct = row.TotalRequested > 0 ? Math.round(d / row.TotalRequested * 100) : 0;
var cls = pct === 100 ? 'bg-success' : pct > 0 ? 'bg-warning' : 'bg-secondary';
return '<div>' + fmtQty(d) + '</div>' +
'<div class="progress mt-1" style="height:4px;">' +
'<div class="progress-bar ' + cls + '" style="width:' + pct + '%"></div></div>';
}},
{ data: 'TotalUnfulfilled', orderable: false, className: 'text-center',
render: function(d) { return unfulfilledCell(d); }},
{ data: 'PreorderCount', orderable: false, className: 'text-center',
render: function(d) { return '<span class="badge badge-secondary">' + d + '</span>'; }},
{ data: 'AvgUnitPrice', orderable: false, className: 'text-right',
render: function(d) { return fmtPrice(d); }}
]
});
// Load demand tab lazily on first click, reload on subsequent
var demandLoaded = false;
$('#tab-demand-link').on('shown.bs.tab', function () {
if (!demandLoaded) { demandTable.ajax.reload(); demandLoaded = true; }
else { demandTable.ajax.reload(); }
});
// Scope toggle
$(document).on('click', '.demand-scope', function () {
$('.demand-scope').removeClass('active');
$(this).addClass('active');
demandOpenOnly = $(this).data('open') === true;
$('#demandScopeLabel').text(demandOpenOnly
? 'Termékek amelyekre még van teljesítetlen igény'
: 'Összesített kereslet az összes előrendelésből');
demandTable.ajax.reload();
});
/* ── Create Preorder Modal ───────────────────────────────────── */
var cpProducts = [];
// Customer autocomplete
$('#cp-customer-search').autocomplete({
delay : 400,
minLength: 2,
source : '/Admin/CustomOrder/CustomerSearchAutoComplete',
select : function (e, ui) {
$('#cp-customer-id').val(ui.item.value);
$('#cp-customer-name').html('<strong>' + ui.item.label + '</strong>');
$('#cp-customer-search').val('');
$('#cp-product-search-section').slideDown();
$('#cp-customer-error').hide();
return false;
}
});
// Product autocomplete — filtered by active preorder window (same logic as customer-facing page)
$('#cp-product-search').autocomplete({
delay : 400,
minLength: 2,
source : '/Admin/CustomOrder/PreorderProductSearchAutoComplete',
select : function (e, ui) {
addCpProduct(ui.item);
$('#cp-product-search').val('');
return false;
}
});
function addCpProduct(item) {
if (cpProducts.find(function (p) { return p.id === item.value; })) return;
cpProducts.push({ id: item.value, name: item.label, quantity: 1, price: item.price || 0 });
renderCpProducts();
}
function renderCpProducts() {
var $body = $('#cp-products-body').empty();
if (!cpProducts.length) { $('#cp-products-section').hide(); return; }
$('#cp-products-section').show();
cpProducts.forEach(function (p, i) {
$body.append(
'<tr>' +
'<td><strong>' + p.name + '</strong></td>' +
'<td><input type="number" class="form-control form-control-sm" min="1" value="' + p.quantity + '" data-idx="' + i + '" onchange="window._cpUpdateQty(this)"></td>' +
'<td><input type="text" class="form-control form-control-sm" value="' + p.price + '" data-idx="' + i + '" onchange="window._cpUpdatePrice(this)"></td>' +
'<td class="text-center"><button type="button" class="btn btn-danger btn-xs" onclick="window._cpRemove(' + i + ')"><i class="fas fa-trash"></i></button></td>' +
'</tr>'
);
});
$('#cp-products-json').val(JSON.stringify(cpProducts));
}
window._cpUpdateQty = function (el) { cpProducts[+el.dataset.idx].quantity = +el.value; renderCpProducts(); };
window._cpUpdatePrice = function (el) { cpProducts[+el.dataset.idx].price = +el.value; renderCpProducts(); };
window._cpRemove = function (i) { cpProducts.splice(i, 1); renderCpProducts(); };
$('#cp-form').on('submit', function (e) {
e.preventDefault();
if (!$('#cp-customer-id').val()) {
$('#cp-customer-error').show(); return;
}
if (!cpProducts.length) {
alert('Legalább egy terméket adj hozzá!'); return;
}
if (!$('#cp-delivery').val()) {
alert('Add meg a szállítási időpontot!'); return;
}
var btn = $(this).find('[type=submit]').prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Mentés...');
$.ajax({
url : '/Admin/Preorders/CreatePreorder',
type: 'POST',
data: {
customerId : $('#cp-customer-id').val(),
deliveryDateTime : $('#cp-delivery').val(),
customerNote : $('#cp-note').val().trim(),
productsJson : $('#cp-products-json').val(),
__RequestVerificationToken: _token
},
success: function (r) {
if (r.success) {
$('#create-preorder-window').modal('hide');
poTable.ajax.reload();
demandTable.ajax.reload();
if (r.orderId) {
alert('Előrendelés rögzítve (#' + r.preorderId + '). Az azonnal elérhető tételek alapján rendelés is készült: #' + r.orderId);
}
} else {
alert('Hiba: ' + (r.error || 'Ismeretlen hiba'));
btn.prop('disabled', false).html('<i class="fas fa-save"></i> Mentés');
}
},
error: function () {
btn.prop('disabled', false).html('<i class="fas fa-save"></i> Mentés');
}
});
});
$('#create-preorder-window').on('hidden.bs.modal', function () {
$('#cp-customer-search').val('');
$('#cp-customer-id, #cp-customer-name').val('').html('');
$('#cp-customer-error').hide();
$('#cp-product-search-section').hide();
$('#cp-delivery').val('');
$('#cp-note').val('');
cpProducts = [];
renderCpProducts();
});
});
</script>
@* ── Create Preorder Modal ──────────────────────────────────────────────── *@
<div id="create-preorder-window" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header" style="background:#2d7a3a;color:#fff;">
<h4 class="modal-title"><i class="fas fa-calendar-plus"></i> Előrendelés rögzítése (telefónos)</h4>
<button type="button" class="close" data-dismiss="modal" style="color:#fff;"><span>&times;</span></button>
</div>
<form id="cp-form">
<div class="form-horizontal">
<div class="modal-body">
@* ─ Customer ─ *@
<div class="form-group row">
<div class="col-md-3"><label class="col-form-label">Ugyfél</label></div>
<div class="col-md-9">
<input type="text" id="cp-customer-search" autocomplete="off" class="form-control" placeholder="Ugyfél neve, e-mail vagy cég neve..." />
<span id="cp-customer-name" class="mt-1 d-block text-success"></span>
<input type="hidden" id="cp-customer-id" />
<span class="field-validation-error" id="cp-customer-error" style="display:none;">Kérjük válasszon ügyfelet</span>
</div>
</div>
@* ─ Delivery date+time ─ *@
<div class="form-group row">
<div class="col-md-3"><label class="col-form-label">Szállítási időpont</label></div>
<div class="col-md-9">
<input type="datetime-local" id="cp-delivery" class="form-control" />
<small class="text-muted">Kívánt szállítási nap és időpont</small>
</div>
</div>
@* ─ Product search ─ *@
<div class="form-group row" id="cp-product-search-section" style="display:none;">
<div class="col-md-3"><label class="col-form-label">Termék hozzáadása</label></div>
<div class="col-md-9">
<input type="text" id="cp-product-search" autocomplete="off" class="form-control" placeholder="Termék neve vagy SKU..." />
<small class="text-muted">Csak az előrendelési ablakban szereplő termékek jelennek meg</small>
</div>
</div>
@* ─ Products table ─ *@
<div id="cp-products-section" style="display:none;">
<table class="table table-sm table-bordered" id="cp-products-table">
<thead>
<tr>
<th>Termék</th>
<th style="width:100px">Mennyiség</th>
<th style="width:120px">Egységár</th>
<th style="width:40px"></th>
</tr>
</thead>
<tbody id="cp-products-body"></tbody>
</table>
<input type="hidden" id="cp-products-json" />
</div>
@* ─ Note ─ *@
<div class="form-group row">
<div class="col-md-3"><label class="col-form-label">Megjegyzés <small class="text-muted">(nem köt.)</small></label></div>
<div class="col-md-9">
<textarea id="cp-note" class="form-control" rows="2" maxlength="1000" placeholder="Esetleges megjegyzés az előrendeléssel kapcsolatban..."></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Mégse</button>
<button type="submit" class="btn btn-primary"><i class="fas fa-save"></i> Mentés</button>
</div>
</div>
</form>
</div>
</div>
</div>