Mango.Nop.Plugins/Nop.Plugin.Misc.AIPlugin/Views/Order/Index.cshtml

1069 lines
51 KiB
Plaintext

@using System.Text.Encodings.Web
@{
Layout = "_Root";
ViewBag.Title = "Rendelés";
}
<link rel="stylesheet" href="~/Plugins/Misc.FruitBankPlugin/css/quick-order.css" />
<link rel="stylesheet" href="~/Plugins/Misc.FruitBankPlugin/css/preorder.css" />
<div class="quick-order-page">
<!-- ── STEP 1: Delivery date + time ─────────────────────────────────── -->
<div id="deliveryStep" class="qo-delivery-step">
<div class="ds-header">
<i class="fa fa-calendar"></i>
<div>
<div class="ds-title">Mikor kéred a rendelést?</div>
<div class="ds-subtitle">Válassz szállítási napot — a rendszer megmutatja, mi érhető el</div>
</div>
<a href="@Url.Action("Index", "Help")" class="ds-help-link" title="Hogyan működik?">
<i class="fa fa-question-circle"></i> Segítség
</a>
</div>
<!-- Collapsible how-it-works panel -->
<div class="ds-howto" id="howToPanel">
<button type="button" class="ds-howto-toggle" id="howToToggle">
<i class="fa fa-info-circle"></i> Hogyan működik a rendelés?
<i class="fa fa-chevron-down ds-howto-chevron" id="howToChevron"></i>
</button>
<div class="ds-howto-body" id="howToBody" style="display:none;">
<div class="ds-howto-row">
<div class="ds-howto-item green">
<div class="ds-howto-icon"><i class="fa fa-shopping-basket"></i></div>
<div class="ds-howto-title">Rendelés (azonnali)</div>
<div class="ds-howto-desc">Ha a kiválasztott szállítási nap <strong>csütörtöktől vasárnapig</strong> van ÉS erre a hétre esik, a raktárkészletből azonnal teljesítjük. Keresés vagy hangos bevitel alapján add a termékeket a kosárba.</div>
</div>
<div class="ds-howto-item amber">
<div class="ds-howto-icon"><i class="fa fa-calendar-plus-o"></i></div>
<div class="ds-howto-title">Előrendelés (jövő heti áru)</div>
<div class="ds-howto-desc">Ha a szállítást <strong>jövő hétre</strong> kéred (csütörtöktől), az áru még úton van. Leadhatsz kívánságlistát — amint megérkezik a szállítmány, automatikusan rendelés lesz belőle és értesítünk.</div>
</div>
</div>
<div class="ds-howto-tip">
<i class="fa fa-lightbulb-o"></i>
<strong>Tipp:</strong> Válassz ki egy napot lent — a rendszer azonnal jelzi, melyik típusú rendelésre számíthatsz.
</div>
</div>
</div>
<div class="ds-body">
<div class="ds-section-label">Szállítási nap</div>
<div class="ds-day-buttons" id="dayButtons"></div>
<div id="flowPreviewBadge" class="ds-flow-preview" style="display:none;"></div>
<div class="ds-section-label" style="margin-top:20px;">Szállítási időpont</div>
<div class="ds-time-wrapper">
<input type="time" id="deliveryTimePicker" class="ds-time-input" value="08:00" min="05:00" max="22:00" />
<span class="ds-time-hint">Válassz pontos időpontot</span>
</div>
</div>
<div class="ds-footer">
<button type="button" class="ds-confirm-btn" id="deliveryConfirmBtn" disabled>
<i class="fa fa-arrow-right"></i> Termékek mutatása
</button>
</div>
</div>
<!-- ── Delivery chip ────────────────────────────────────────────────── -->
<div id="deliveryChip" class="qo-delivery-chip" style="display:none;">
<i class="fa fa-calendar-check-o"></i>
<span class="dc-label">Szállítás:</span>
<strong id="deliveryChipText"></strong>
<span id="flowTypeBadge" class="of-flow-badge"></span>
<button type="button" class="dc-change-btn" id="deliveryChangeBtn">
<i class="fa fa-pencil"></i> Módosítás
</button>
</div>
<!-- ── MAIN CONTENT (shown after delivery confirmed) ────────────────── -->
<div id="mainContent" style="display:none;">
<!-- ══ QUICK ORDER SECTION ══════════════════════════════════════════ -->
<div id="sectionQuickOrder" style="display:none;">
<!-- Search bar -->
<div class="qo-search-bar-wrapper">
<div class="search-input-group">
<button id="recordBtn" class="mic-btn" title="Hangfelvétel indítása">
<i class="fa fa-microphone"></i>
</button>
<button id="stopBtn" class="mic-btn mic-btn-recording" style="display:none;" title="Leállítás">
<i class="fa fa-stop"></i>
</button>
<input type="text" id="searchInput" class="qo-input"
placeholder="Keress termékeket (pl. narancs 100, alma 50) vagy használd a mikrofont..."
onkeypress="if(event.key==='Enter') submitTextSearch()">
<button class="qo-search-btn" onclick="submitTextSearch()">
<i class="fa fa-search"></i> Keresés
</button>
<button type="button" class="qo-hint-toggle" id="searchHintToggle">
<i class="fa fa-question-circle"></i> <span class="hint-toggle-label">Hogyan keress?</span>
</button>
</div>
<div class="qo-hint-row" style="display:none;"></div>
<div class="qo-input-hint" id="searchHintBody" style="display:none;">
<div class="qo-hint-item">
<i class="fa fa-keyboard-o"></i>
<div><strong>Szöveg:</strong> írd be a termékeket és mennyiségeket (pl. „narancs 100 alma 50”), majd nyomj Entert</div>
</div>
<div class="qo-hint-item">
<i class="fa fa-microphone"></i>
<div><strong>Hang:</strong> nyomj a mikrofon gombra és mondd be hangosan — a rendszer automatikusan leáll, mikor befejezed</div>
</div>
</div>
<div id="recordingStatus" class="recording-status-bar" style="display:none;">
<span id="statusText">Figyelés...</span>
<div class="volume-bar-container">
<div class="volume-bar volume-bar-silent"></div>
</div>
</div>
</div>
<div class="qo-layout">
<!-- LEFT: products -->
<div class="qo-products-panel">
<div id="transcribedCard" class="result-card" style="display:none;">
<div class="result-label"><i class="fa fa-microphone"></i> Hallottam:</div>
<div id="transcribedText" class="result-text"></div>
</div>
<div id="noResultsCard" class="no-results-card" style="display:none;">
<i class="fa fa-search"></i><p>Nem találtunk termékeket.</p>
</div>
<div id="productsLoadingState" class="products-empty-state">
<i class="fa fa-spinner fa-spin"></i><p>Termékek betöltése...</p>
</div>
<div id="productMatchesCard" style="display:none;">
<div class="matches-label">
<i class="fa fa-cubes"></i>
<span id="matchesLabelText">Összes termék</span>
— állítsd be a mennyiséget, majd add a kosárhoz:
</div>
<div id="productButtons" class="product-grid"></div>
</div>
</div>
<!-- RIGHT: cart -->
<div class="qo-cart-panel">
<div class="qo-section-title">
<i class="fa fa-shopping-basket"></i> Kosár
<span id="cartItemCount" class="cart-count-badge">0</span>
</div>
<div id="cartEmptyState" class="cart-empty">
<i class="fa fa-shopping-basket"></i>
<p>A kosár üres.<br>Keress termékeket és add hozzá őket.</p>
</div>
<div id="cartItemsList" class="cart-items-list" style="display:none;"></div>
<div id="cartTotalRow" class="cart-total-row" style="display:none;">
<div class="cart-total-note">
<i class="fa fa-info-circle"></i>
<small>A súlymérést igénylő tételeknél az ár a mérés után véglegesedik.</small>
</div>
<div class="cart-total">
<span>Becsült összeg:</span>
<strong id="cartTotalAmount">0 Ft</strong>
</div>
</div>
<div id="cartActions" style="display:none;">
<a href="@Url.Action("Cart", "ShoppingCart")" class="btn-checkout">
<i class="fa fa-shopping-cart"></i> Tovább a pénztárhoz
</a>
<a href="@Url.Action("Cart", "ShoppingCart")" class="btn-view-cart">
<i class="fa fa-eye"></i> Kosár megtekintése
</a>
</div>
</div>
</div>
</div><!-- /#sectionQuickOrder -->
<!-- ══ PREORDER SECTION ══════════════════════════════════════════════ -->
<div id="sectionPreorder" style="display:none;">
<div class="po-info-banner">
<i class="fa fa-info-circle"></i>
Az előrendelés egy kívánságlista — az áruk megerősítése a szállítmány beérkezésekor történik, és az esetleges változásokról értesítünk.
</div>
<div class="qo-layout">
<!-- LEFT: products + submit -->
<div class="qo-products-panel">
<div id="poLoadingState" class="products-empty-state">
<i class="fa fa-spinner fa-spin"></i><p>Elérhető termékek betöltése...</p>
</div>
<div id="poNoProducts" class="no-results-card" style="display:none;">
<i class="fa fa-calendar-times-o"></i>
<p>Jelenleg nincs előrendelhető termék. Kérjük, látogass vissza később.</p>
</div>
<div id="poProductSection" style="display:none;">
<div class="matches-label">
<i class="fa fa-cubes"></i>
<span>Előrendelhető termékek — add meg a mennyiségeket:</span>
</div>
<div id="poProductGrid" class="product-grid"></div>
<div class="po-note-section">
<label class="po-note-label" for="customerNote">
<i class="fa fa-comment-o"></i> Megjegyzés (nem kötelező)
</label>
<textarea id="customerNote" class="po-note-input"
placeholder="Esetleges megjegyzések az előrendeléssel kapcsolatban..."
rows="3" maxlength="1000"></textarea>
</div>
<div class="po-submit-row">
<div id="poSelectionSummary" class="po-selection-summary">Még nincs kiválasztott termék</div>
<button type="button" id="submitPreorderBtn" class="po-submit-btn" disabled>
<i class="fa fa-paper-plane"></i> Előrendelés leadása
</button>
</div>
</div>
</div>
<!-- RIGHT: summary -->
<div class="qo-cart-panel">
<div class="qo-section-title">
<i class="fa fa-list-ul"></i> Előrendelésed
<span id="poItemCountBadge" class="cart-count-badge">0</span>
</div>
<div id="poSummaryEmpty" class="cart-empty">
<i class="fa fa-list-ul"></i>
<p>Add meg a mennyiségeket a termékeknél.</p>
</div>
<div id="poSummaryList" class="cart-items-list" style="display:none;"></div>
<div id="poSummaryNote" class="cart-total-row" style="display:none;">
<div class="cart-total-note">
<i class="fa fa-info-circle"></i>
<small>A súlymérést igénylő tételeknél az ár a mérés után véglegesedik. A mennyiségek a tényleges szállítmánytól függően változhatnak.</small>
</div>
</div>
</div>
</div>
</div><!-- /#sectionPreorder -->
</div><!-- /#mainContent -->
<!-- ── Preorder success state ────────────────────────────────────────── -->
<div id="successState" style="display:none;" class="po-success-state">
<div class="po-success-icon"><i class="fa fa-check-circle"></i></div>
<h2>Előrendelés leadva!</h2>
<p id="successMessage"></p>
<a href="@Url.RouteUrl("Homepage")" class="po-back-btn">
<i class="fa fa-home"></i> Vissza a főoldalra
</a>
</div>
</div><!-- /.quick-order-page -->
@Html.AntiForgeryToken()
<style>
/* ── Flow type badge on delivery chip ─────────────────────── */
.of-flow-badge {
font-size: 11px;
font-weight: 700;
border-radius: 4px;
padding: 2px 8px;
margin-left: 6px;
}
.of-flow-badge.quickorder {
background: rgba(140,182,60,0.25);
color: #8cb63c;
}
.of-flow-badge.preorder {
background: rgba(244,162,54,0.2);
color: #f4a236;
}
/* ── Help link in header ─────────────────────────────────── */
.ds-header {
display: flex;
align-items: flex-start;
gap: 14px;
position: relative;
}
.ds-help-link {
margin-left: auto;
font-size: 12px;
font-weight: 600;
color: #6b7c6e;
text-decoration: none;
white-space: nowrap;
display: flex;
align-items: center;
gap: 5px;
padding: 4px 10px;
border: 1px solid #dde8da;
border-radius: 6px;
transition: all 0.15s;
}
.ds-help-link:hover {
background: #f5f7f2;
color: #2d7a3a;
border-color: #2d7a3a;
}
/* ── How-it-works collapsible panel ────────────────────── */
.ds-howto {
margin: 14px 0 0;
border: 1px solid #dde8da;
border-radius: 8px;
overflow: hidden;
}
.ds-howto-toggle {
width: 100%;
background: #f5f7f2;
border: none;
padding: 12px 16px;
text-align: left;
font-family: 'DM Sans', sans-serif;
font-size: 13px;
font-weight: 600;
color: #2d7a3a;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
}
.ds-howto-toggle:hover { background: #eaf3de; }
.ds-howto-chevron {
margin-left: auto;
transition: transform 0.2s;
font-size: 11px;
color: #6b7c6e;
}
.ds-howto-body {
padding: 16px;
background: #fff;
border-top: 1px solid #dde8da;
}
.ds-howto-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 14px;
}
.ds-howto-item {
padding: 14px;
border-radius: 8px;
border: 1px solid;
}
.ds-howto-item.green {
background: #eaf3de;
border-color: #a8d08d;
}
.ds-howto-item.amber {
background: #fff8ee;
border-color: #f4c87a;
}
.ds-howto-icon {
font-size: 20px;
margin-bottom: 6px;
}
.ds-howto-item.green .ds-howto-icon { color: #2d7a3a; }
.ds-howto-item.amber .ds-howto-icon { color: #c87500; }
.ds-howto-title {
font-size: 13px;
font-weight: 700;
margin-bottom: 6px;
}
.ds-howto-item.green .ds-howto-title { color: #1a3c22; }
.ds-howto-item.amber .ds-howto-title { color: #7a4200; }
.ds-howto-desc {
font-size: 12px;
line-height: 1.5;
color: #444;
}
.ds-howto-tip {
background: #f5f7f2;
border-radius: 6px;
padding: 10px 12px;
font-size: 12px;
color: #6b7c6e;
display: flex;
align-items: flex-start;
gap: 8px;
line-height: 1.5;
}
.ds-howto-tip .fa { color: #f4a236; flex-shrink: 0; margin-top: 1px; }
/* ── Flow preview badge below day buttons ──────────────────── */
.ds-flow-preview {
margin-top: 10px;
padding: 8px 14px;
border-radius: 6px;
font-size: 13px;
display: inline-flex;
align-items: center;
gap: 8px;
animation: fadeIn 0.2s ease;
}
.ds-flow-preview.flow-quick {
background: #eaf3de;
color: #1a3c22;
border: 1px solid #a8d08d;
}
.ds-flow-preview.flow-pre {
background: #fff8ee;
color: #7a4200;
border: 1px solid #f4c87a;
}
@@keyframes fadeIn { from { opacity:0; transform:translateY(-4px); } to { opacity:1; transform:translateY(0); } }
/* ── Preorder stock label variant ──────────────────────────── */
.pc-stock.stock-preorder {
color: #c87500;
font-style: italic;
font-size: 12px;
}
/* ── Search input hint strip ─────────────────────────────── */
.qo-search-bar-wrapper { position: relative; }
.qo-hint-row {
display: flex;
justify-content: flex-end;
margin-top: 6px;
}
.qo-hint-toggle {
background: none;
border: none;
border-left: 1px solid #dde8da;
border-radius: 0;
padding: 0 14px;
height: 100%;
font-family: 'DM Sans', sans-serif;
font-size: 12px;
font-weight: 600;
color: #6b7c6e;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 5px;
transition: all 0.15s;
white-space: nowrap;
flex-shrink: 0;
}
.qo-hint-toggle:hover, .qo-hint-toggle.active {
background: #f5f7f2;
color: #2d7a3a;
}
/* On mobile: icon only, no text label */
@@media (max-width: 600px) {
.hint-toggle-label { display: none; }
.qo-hint-toggle { padding: 0 10px; }
}
.qo-input-hint {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 6px;
padding: 10px 14px;
background: #f5f7f2;
border: 1px solid #dde8da;
border-radius: 8px;
font-size: 12px;
color: #4a5e4d;
line-height: 1.5;
}
.qo-hint-item {
display: flex;
align-items: flex-start;
gap: 8px;
}
.qo-hint-item .fa { color: #2d7a3a; flex-shrink: 0; margin-top: 2px; font-size: 13px; }
.qo-hint-item .fa-microphone { color: #f4a236; }
.qo-hint-sep {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .5px;
color: #aab8ac;
align-self: center;
flex-shrink: 0;
}
/* Desktop: pull toggle button into the search input row */
@@media (min-width: 601px) {
.qo-hint-row { display: none; }
}
</style>
<script asp-location="Footer">
// ── State ─────────────────────────────────────────────────────────────────
var selectedDeliveryDate = null;
var selectedDeliveryTime = null;
var selectedDayLabel = null;
var currentFlowType = null; // "quickorder" | "preorder"
// Preorder state
var poProducts = [];
var poQuantities = {};
// Quick Order VAD state
var mediaRecorder = null, audioChunks = [], isRecording = false;
var audioContext = null, analyser = null, volumeCheckInterval = null;
var recordingStartTime = null, baselineNoiseLevel = -60, volumeHistory = [];
var VAD_CONFIG = { silenceDuration:1500, minRecordingTime:800, volumeCheckInterval:100,
calibrationTime:500, noiseGateOffset:15, volumeHistorySize:10 };
var huDayNames = ['vasárnap','hétfő','kedd','szerda','csütörtök','péntek','szombat'];
// ── Init ──────────────────────────────────────────────────────────────────
$(document).ready(function () {
renderDayButtons();
$(document).on('click', '.ds-day-btn', function () {
$('.ds-day-btn').removeClass('selected');
$(this).addClass('selected');
selectedDeliveryDate = $(this).data('date');
selectedDayLabel = $(this).data('label');
checkDeliveryReady();
// Show flow preview badge immediately
var flow = computeFlowType(selectedDeliveryDate);
var badge = $('#flowPreviewBadge');
if (flow === 'quickorder') {
badge.html('<i class="fa fa-shopping-basket"></i> <strong>Rendelés</strong> — ez a nap raktárkészletből teljesíthető').attr('class','ds-flow-preview flow-quick').show();
} else {
badge.html('<i class="fa fa-calendar-plus-o"></i> <strong>Előrendelés</strong> — az áru még úton van, kívánságlistát adhat le').attr('class','ds-flow-preview flow-pre').show();
}
});
$('#deliveryTimePicker').on('input change', function () {
selectedDeliveryTime = $(this).val() || null;
checkDeliveryReady();
});
selectedDeliveryTime = $('#deliveryTimePicker').val() || null;
$('#deliveryConfirmBtn').click(confirmDelivery);
$('#deliveryChangeBtn').click(function () {
$('#deliveryChip, #mainContent').hide();
$('#deliveryStep').show();
});
$('#recordBtn').click(startRecording);
$('#stopBtn').click(function () { stopRecording(false); });
$('#submitPreorderBtn').click(submitPreorder);
// Restore saved datetime
$.ajax({
url: '@Url.Action("GetDeliveryDateTime", "Order")',
type: 'GET',
success: function (r) {
if (!r.success || !r.hasValue) return;
selectedDeliveryDate = r.date;
selectedDeliveryTime = r.time;
var $btn = $('.ds-day-btn[data-date="' + r.date + '"]');
if ($btn.length) { $btn.addClass('selected'); selectedDayLabel = $btn.data('label'); }
else { selectedDayLabel = r.date; }
$('#deliveryTimePicker').val(r.time);
activateFlow(r.flowType);
}
});
loadCart();
// How-to panel toggle
$('#howToToggle').click(function () {
var $body = $('#howToBody');
var open = $body.is(':visible');
$body.slideToggle(200);
$('#howToChevron').css('transform', open ? 'rotate(0deg)' : 'rotate(180deg)');
});
// Search hint toggle with 20-second auto-collapse
var hintTimer = null;
function openHint() {
$('#searchHintBody').slideDown(180);
$('#searchHintToggle').addClass('active');
clearTimeout(hintTimer);
hintTimer = setTimeout(closeHint, 20000);
}
function closeHint() {
$('#searchHintBody').slideUp(180);
$('#searchHintToggle').removeClass('active');
clearTimeout(hintTimer);
}
$('#searchHintToggle').click(function () {
if ($('#searchHintBody').is(':visible')) closeHint();
else openHint();
});
});
// ── Day buttons ───────────────────────────────────────────────────────────
function renderDayButtons() {
var c = $('#dayButtons').empty();
var today = new Date();
for (var i = 0; i < 14; i++) {
var d = new Date(today);
d.setDate(today.getDate() + i);
var iso = d.toISOString().split('T')[0];
var name = i === 0 ? 'Ma' : i === 1 ? 'Holnap' : huDayNames[d.getDay()];
var dateStr = (d.getMonth() + 1) + '. ' + d.getDate() + '.';
c.append($('<button type="button" class="ds-day-btn">')
.attr('data-date', iso).attr('data-label', name + ' ' + dateStr)
.html('<span class="ds-day-name">' + name + '</span><span class="ds-day-date">' + dateStr + '</span>'));
}
}
function checkDeliveryReady() {
$('#deliveryConfirmBtn').prop('disabled', !(selectedDeliveryDate && selectedDeliveryTime));
}
// ── Flow type determination (mirrors server-side logic) ───────────────────
function computeFlowType(isoDate) {
var today = new Date();
var todayDow = today.getDay(); // 0=Sun
var daysSinceMon = todayDow === 0 ? 6 : todayDow - 1;
var weekStart = new Date(today); weekStart.setDate(today.getDate() - daysSinceMon); weekStart.setHours(0,0,0,0);
var thisThursday = new Date(weekStart); thisThursday.setDate(weekStart.getDate() + 3); thisThursday.setHours(0,0,0,0);
var weekEnd = new Date(weekStart); weekEnd.setDate(weekStart.getDate() + 6); weekEnd.setHours(23,59,59,999);
var delivery = new Date(isoDate); delivery.setHours(12,0,0,0);
var deliveryBeforeThursday = delivery < thisThursday;
var isLateWeek = (todayDow === 0 || todayDow >= 4);
var deliveryThisWeek = delivery >= weekStart && delivery <= weekEnd;
return (deliveryBeforeThursday || (isLateWeek && deliveryThisWeek)) ? 'quickorder' : 'preorder';
}
// ── Confirm delivery ──────────────────────────────────────────────────────
function confirmDelivery() {
var btn = $('#deliveryConfirmBtn').prop('disabled', true)
.html('<i class="fa fa-spinner fa-spin"></i> Mentés...');
$.ajax({
url : '@Url.Action("SetDeliveryDateTime", "Order")',
type: 'POST',
data: { deliveryDateTime: selectedDeliveryDate + 'T' + selectedDeliveryTime,
__RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val() },
success: function (r) {
if (!r.success) {
alert('Hiba: ' + (r.message || ''));
btn.prop('disabled', false).html('<i class="fa fa-arrow-right"></i> Termékek mutatása');
return;
}
activateFlow(r.flowType);
},
error: function () {
btn.prop('disabled', false).html('<i class="fa fa-arrow-right"></i> Termékek mutatása');
}
});
}
function activateFlow(flowType) {
currentFlowType = flowType;
var chipText = selectedDayLabel + ' \u2014 ' + selectedDeliveryTime;
$('#deliveryChipText').text(chipText);
var badge = $('#flowTypeBadge');
if (flowType === 'quickorder') {
badge.text('Rendelés').attr('class', 'of-flow-badge quickorder');
} else {
badge.text('Előrendelés').attr('class', 'of-flow-badge preorder');
}
$('#deliveryStep').hide();
$('#deliveryChip, #mainContent').show();
if (flowType === 'quickorder') {
$('#sectionQuickOrder').show();
$('#sectionPreorder').hide();
loadAllProducts();
} else {
$('#sectionPreorder').show();
$('#sectionQuickOrder').hide();
loadPreorderProducts();
}
}
// ══════════════════════════════════════════════════════════════════════════
// QUICK ORDER FLOW
// ══════════════════════════════════════════════════════════════════════════
function loadAllProducts() {
$('#transcribedCard').hide();
$('#noResultsCard').hide();
$('#productMatchesCard').hide();
$('#productsLoadingState').show();
$('#matchesLabelText').text('Összes termék');
$.ajax({
url : '@Url.Action("GetAllProducts", "Order")',
type : 'GET',
data : { deliveryDate: selectedDeliveryDate, deliveryTime: selectedDeliveryTime },
success: function (r) {
$('#productsLoadingState').hide();
if (r.success && r.products && r.products.length > 0) displayQOProducts(r.products);
else $('#noResultsCard').show();
},
error : function () { $('#productsLoadingState').hide(); $('#noResultsCard').show(); }
});
}
function displayQOProducts(products) {
var container = $('#productButtons').empty();
var grouped = {};
for (var i = 0; i < products.length; i++) {
var key = products[i].searchTerm || '';
if (!grouped[key]) grouped[key] = [];
grouped[key].push(products[i]);
}
var keys = Object.keys(grouped);
var multiGroup = keys.length > 1 || (keys.length === 1 && keys[0] !== '');
for (var g = 0; g < keys.length; g++) {
var term = keys[g];
if (multiGroup && term) container.append('<div class="group-label"><i class="fa fa-tag"></i> ' + term + '</div>');
var group = grouped[term];
for (var p = 0; p < group.length; p++) {
(function (product) {
var maxQty = product.stockQuantity;
var defaultQty = product.quantity || 1;
var priceHtml = product.isMeasurable
? '<span class="measurable-badge"><i class="fa fa-balance-scale"></i> Súlymérést igényel</span>'
: '<span class="pm-price">' + fmt(product.unitPrice) + ' Ft/db</span>';
var warningHtml = product.isQuantityReduced
? '<div class="stock-warning-badge"><i class="fa fa-exclamation-triangle"></i> Csak ' + maxQty + ' db elérhető</div>' : '';
var card = $('<div>').addClass('product-card' + (product.isQuantityReduced ? ' has-warning' : ''));
card.html(
'<div class="pc-body">' +
'<div class="pc-name"><i class="fa fa-cube"></i> ' + product.name + '</div>' +
warningHtml +
'<div class="pc-meta">' +
'<span class="pc-stock' + (maxQty < 50 ? ' stock-low' : '') + '">Készlet: ' + maxQty + ' db</span>' +
priceHtml +
'</div>' +
'</div>' +
'<div class="pc-actions">' +
'<div class="qty-stepper">' +
'<button type="button" class="qty-btn qty-minus" tabindex="-1"><i class="fa fa-minus"></i></button>' +
'<input type="number" class="qty-input" value="' + defaultQty + '" min="1" max="' + maxQty + '">' +
'<button type="button" class="qty-btn qty-plus" tabindex="-1"><i class="fa fa-plus"></i></button>' +
'</div>' +
'<button type="button" class="pc-add-btn" title="Kosárba"><i class="fa fa-cart-arrow-down"></i></button>' +
'</div>'
);
card.find('.qty-minus').click(function () {
var inp = $(this).siblings('.qty-input'); var v = parseInt(inp.val()) || 1; if (v > 1) inp.val(v - 1);
});
card.find('.qty-plus').click(function () {
var inp = $(this).siblings('.qty-input'); var v = parseInt(inp.val()) || 1; if (v < maxQty) inp.val(v + 1);
});
card.find('.qty-input').on('change blur', function () {
var v = parseInt($(this).val()) || 1; v = Math.max(1, Math.min(maxQty, v)); $(this).val(v);
});
card.find('.pc-add-btn').click(function () {
addToCart(product.id, parseInt(card.find('.qty-input').val()) || 1, product.name, $(this));
});
container.append(card);
})(group[p]);
}
}
$('#productMatchesCard').show();
}
function addToCart(productId, quantity, name, btnEl) {
btnEl.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i>');
$.ajax({
url : '@Url.Action("AddToCart", "Order")',
type: 'POST',
data: { productId: productId, quantity: quantity,
__RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val() },
success: function (r) {
if (r.success) {
btnEl.html('<i class="fa fa-check"></i>').addClass('added');
renderCart(r.cartItems);
showCartToast(name, quantity);
setTimeout(function () { $('#searchInput').val(''); $('#transcribedCard').hide(); loadAllProducts(); }, 700);
} else {
alert('Hiba: ' + r.message);
btnEl.prop('disabled', false).html('<i class="fa fa-cart-arrow-down"></i>');
}
},
error: function () {
btnEl.prop('disabled', false).html('<i class="fa fa-cart-arrow-down"></i>');
}
});
}
function loadCart() {
$.ajax({ url: '@Url.Action("GetCartItems", "Order")', type: 'GET',
success: function (r) { if (r.success) renderCart(r.cartItems); } });
}
function renderCart(items) {
var list = $('#cartItemsList').empty();
var count = items.length;
$('#cartItemCount').text(count);
if (count === 0) {
$('#cartEmptyState').show(); $('#cartItemsList, #cartTotalRow, #cartActions').hide(); return;
}
$('#cartEmptyState').hide(); $('#cartItemsList, #cartTotalRow, #cartActions').show();
var total = 0, hasMeasurable = false;
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (item.isMeasurable) hasMeasurable = true;
var lineTotal = item.isMeasurable ? null : item.unitPrice * item.quantity;
if (lineTotal) total += lineTotal;
var lineTotalHtml = item.isMeasurable
? '<span class="measurable-badge-sm"><i class="fa fa-balance-scale"></i></span>'
: '<strong class="line-total">' + fmt(lineTotal) + ' Ft</strong>';
var priceHtml = item.isMeasurable ? '' : '<span class="ci-price">' + fmt(item.unitPrice) + ' Ft/db</span>';
list.append('<div class="cart-item"><div class="ci-name">' + item.name + '</div>' +
'<div class="ci-details"><span class="ci-qty">' + item.quantity + ' db</span>' +
priceHtml + lineTotalHtml + '</div></div>');
}
$('#cartTotalAmount').text(fmt(total) + ' Ft');
if (hasMeasurable) $('#cartTotalRow .cart-total-note').show();
else $('#cartTotalRow .cart-total-note').hide();
}
function showCartToast(name, qty) {
var t = $('<div class="qo-toast"><i class="fa fa-check-circle"></i> <strong>' + name + '</strong> (' + qty + ' db) hozzáadva</div>');
$('body').append(t);
setTimeout(function () { t.addClass('show'); }, 10);
setTimeout(function () { t.removeClass('show'); setTimeout(function () { t.remove(); }, 400); }, 2500);
}
// Voice recording (same as QuickOrder page)
function getSupportedMimeType() {
var types = ['audio/webm','audio/webm;codecs=opus','audio/ogg;codecs=opus','audio/mp4'];
for (var i = 0; i < types.length; i++) if (MediaRecorder.isTypeSupported(types[i])) return types[i];
return 'audio/webm';
}
function startRecording() {
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { alert('A böngésző nem támogatja a hangfelvételt.'); return; }
navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioContext.createAnalyser();
audioContext.createMediaStreamSource(stream).connect(analyser);
analyser.fftSize = 512;
var mimeType = getSupportedMimeType();
mediaRecorder = new MediaRecorder(stream, { mimeType: mimeType });
audioChunks = []; recordingStartTime = Date.now(); isRecording = true;
mediaRecorder.addEventListener('dataavailable', function (e) { audioChunks.push(e.data); });
mediaRecorder.addEventListener('stop', function () {
var blob = new Blob(audioChunks, { type: mimeType });
stream.getTracks().forEach(function (t) { t.stop(); });
if (audioContext) { audioContext.close(); audioContext = null; }
analyser = null; isRecording = false;
if (blob.size === 0) { alert('Nem sikerült hangot rögzíteni.'); resetRecordingUI(); return; }
processAudio(blob, mimeType);
});
mediaRecorder.start();
$('#recordBtn').hide(); $('#stopBtn').show();
$('#searchInput').attr('placeholder', 'Figyelés... (kezdj el beszélni)');
showStatus('Figyelés...'); startVAD();
}).catch(function (err) { alert('Nem sikerült a mikrofon elérése: ' + err.message); });
}
function startVAD() {
var bufferLength = analyser.frequencyBinCount, dataArray = new Uint8Array(bufferLength);
if (volumeCheckInterval) clearInterval(volumeCheckInterval);
var silentChecks = 0, silentNeeded = Math.ceil(VAD_CONFIG.silenceDuration / VAD_CONFIG.volumeCheckInterval);
var calibrated = false, calibSamples = []; volumeHistory = [];
volumeCheckInterval = setInterval(function () {
if (!isRecording || !analyser) { clearInterval(volumeCheckInterval); return; }
analyser.getByteFrequencyData(dataArray);
var sum = 0; for (var i = 0; i < bufferLength; i++) sum += dataArray[i];
var volume = 20 * Math.log10((sum / bufferLength) / 255);
var elapsed = Date.now() - recordingStartTime;
if (!calibrated && elapsed < VAD_CONFIG.calibrationTime) { calibSamples.push(volume); return; }
if (!calibrated && calibSamples.length > 0) {
var tot = 0; for (var j = 0; j < calibSamples.length; j++) tot += calibSamples[j];
baselineNoiseLevel = tot / calibSamples.length; calibrated = true;
}
volumeHistory.push(volume);
if (volumeHistory.length > VAD_CONFIG.volumeHistorySize) volumeHistory.shift();
var vs = 0; for (var k = 0; k < volumeHistory.length; k++) vs += volumeHistory[k];
var avgVol = vs / volumeHistory.length;
var threshold = baselineNoiseLevel + VAD_CONFIG.noiseGateOffset;
var norm = Math.max(0, Math.min(100, ((volume - threshold + 10) / 40) * 100));
var text = 'Figyelés...', cls = 'volume-bar-silent';
if (norm > 60) { text = 'Hangos és érthető'; cls = 'volume-bar-high'; }
else if (norm > 30) { text = 'Beszél...'; cls = 'volume-bar-medium'; }
else if (norm > 10) { text = 'Hangosabban!'; cls = 'volume-bar-low'; }
$('#statusText').text(text);
$('#recordingStatus .volume-bar').removeClass('volume-bar-low volume-bar-medium volume-bar-high volume-bar-silent').addClass(cls).css('width', norm + '%');
if (elapsed < VAD_CONFIG.minRecordingTime) return;
if (avgVol < threshold) { silentChecks++; if (silentChecks >= silentNeeded) { clearInterval(volumeCheckInterval); stopRecording(true); } }
else { silentChecks = 0; }
}, VAD_CONFIG.volumeCheckInterval);
}
function stopRecording(auto) {
if (volumeCheckInterval) { clearInterval(volumeCheckInterval); volumeCheckInterval = null; }
if (mediaRecorder && mediaRecorder.state !== 'inactive') { showStatus('Feldolgozás...'); mediaRecorder.stop(); }
}
function processAudio(blob, mimeType) {
var formData = new FormData();
formData.append('audioFile', blob, 'recording.webm');
formData.append('deliveryDate', selectedDeliveryDate || '');
formData.append('deliveryTime', selectedDeliveryTime || '');
formData.append('__RequestVerificationToken', $('input[name="__RequestVerificationToken"]').val());
$.ajax({ url: '@Url.Action("TranscribeAndSearch", "Order")', type: 'POST', data: formData,
processData: false, contentType: false,
success: function (r) { resetRecordingUI(); handleSearchResult(r); },
error: function (e) { resetRecordingUI(); console.error(e); } });
}
function resetRecordingUI() {
$('#recordingStatus').hide(); $('#recordBtn').show(); $('#stopBtn').hide();
$('#searchInput').attr('placeholder', 'Keress termékeket (pl. narancs 100, alma 50) vagy használd a mikrofont...');
}
function showStatus(msg) { $('#statusText').text(msg); $('#recordingStatus').show(); }
function submitTextSearch() {
var text = $('#searchInput').val().trim();
if (!text) { alert('Kérem, add meg a termékeket!'); return; }
showStatus('Keresés...'); $('#recordingStatus').show();
$.ajax({ url: '@Url.Action("SearchProducts", "Order")', type: 'POST',
data: { text: text, deliveryDate: selectedDeliveryDate, deliveryTime: selectedDeliveryTime,
__RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val() },
success: function (r) { $('#recordingStatus').hide(); handleSearchResult(r); },
error: function () { $('#recordingStatus').hide(); alert('Hiba a keresés során.'); } });
}
function handleSearchResult(result) {
$('#noResultsCard,#productMatchesCard,#transcribedCard,#productsLoadingState').hide();
if (!result.success) { alert('Hiba: ' + result.message); return; }
if (result.transcription) { $('#transcribedText').text(result.transcription); $('#transcribedCard').show(); }
if (!result.products || result.products.length === 0) { $('#noResultsCard').show(); return; }
$('#matchesLabelText').text('Találatok');
displayQOProducts(result.products);
}
// ══════════════════════════════════════════════════════════════════════════
// PREORDER FLOW
// ══════════════════════════════════════════════════════════════════════════
function loadPreorderProducts() {
$('#poLoadingState').show(); $('#poNoProducts, #poProductSection').hide();
$.ajax({ url: '@Url.Action("GetPreorderProducts", "Order")', type: 'GET',
success: function (r) {
$('#poLoadingState').hide();
if (!r.success || !r.products || r.products.length === 0) { $('#poNoProducts').show(); return; }
poProducts = r.products; poQuantities = {};
renderPreorderProducts(); $('#poProductSection').show();
},
error: function () { $('#poLoadingState').hide(); $('#poNoProducts').show(); }
});
}
function renderPreorderProducts() {
var grid = $('#poProductGrid').empty();
$.each(poProducts, function (_, p) {
poQuantities[p.id] = 0;
var priceHtml = p.isMeasurable
? '<span class="measurable-badge"><i class="fa fa-balance-scale"></i> Súlymérést igényel</span>'
: (p.unitPrice > 0 ? '<span class="pm-price">' + fmt(p.unitPrice) + ' Ft/db</span>' : '');
var stockLabel = p.stockQuantity > 0
? 'Várható készlet: ' + p.stockQuantity + ' db'
: 'Előrendelési cikk — a mennyiség a szállítmány után véglegesül';
var card = $('<div>').addClass('product-card po-product-card').attr('data-id', p.id);
card.html(
'<div class="pc-body">' +
'<div class="pc-name"><i class="fa fa-cube"></i> ' + p.name + '</div>' +
'<div class="pc-meta"><span class="pc-stock' + (p.stockQuantity === 0 ? ' stock-preorder' : '') + '">' + stockLabel + '</span>' + priceHtml + '</div>' +
'</div>' +
'<div class="pc-actions"><div class="qty-stepper">' +
'<button type="button" class="qty-btn qty-minus" tabindex="-1"><i class="fa fa-minus"></i></button>' +
'<input type="number" class="qty-input po-qty" value="0" min="0">' +
'<button type="button" class="qty-btn qty-plus" tabindex="-1"><i class="fa fa-plus"></i></button>' +
'</div></div>'
);
card.find('.qty-minus').click(function () {
var inp = $(this).siblings('.qty-input'); var v = parseInt(inp.val()) || 0;
if (v > 0) { inp.val(v - 1); onPoQtyChange(p.id, v - 1, card); }
});
card.find('.qty-plus').click(function () {
var inp = $(this).siblings('.qty-input'); var v = parseInt(inp.val()) || 0;
inp.val(v + 1); onPoQtyChange(p.id, v + 1, card);
});
card.find('.qty-input').on('input change blur', function () {
var v = parseInt($(this).val()); if (isNaN(v) || v < 0) v = 0;
$(this).val(v); onPoQtyChange(p.id, v, card);
});
grid.append(card);
});
}
function onPoQtyChange(productId, qty, $card) {
poQuantities[productId] = qty;
$card.toggleClass('po-selected', qty > 0);
updatePoSummary();
}
function updatePoSummary() {
var selected = poProducts.filter(function (p) { return (poQuantities[p.id] || 0) > 0; });
var count = selected.length;
$('#poItemCountBadge').text(count);
$('#submitPreorderBtn').prop('disabled', count === 0);
$('#poSelectionSummary').text(count === 0 ? 'Még nincs kiválasztott termék' : count + ' termék kiválasztva');
if (count === 0) { $('#poSummaryEmpty').show(); $('#poSummaryList, #poSummaryNote').hide(); return; }
$('#poSummaryEmpty').hide(); $('#poSummaryList, #poSummaryNote').show();
var list = $('#poSummaryList').empty(); var hasMeasurable = false;
$.each(selected, function (_, p) {
var qty = poQuantities[p.id]; if (p.isMeasurable) hasMeasurable = true;
var lineTotalHtml = p.isMeasurable
? '<span class="measurable-badge-sm"><i class="fa fa-balance-scale"></i></span>'
: '<strong class="line-total">' + fmt(p.unitPrice * qty) + ' Ft</strong>';
list.append('<div class="cart-item"><div class="ci-name">' + p.name + '</div>' +
'<div class="ci-details"><span class="ci-qty">' + qty + ' db</span>' + lineTotalHtml + '</div></div>');
});
if (hasMeasurable) $('#poSummaryNote').show(); else $('#poSummaryNote').hide();
}
function submitPreorder() {
var selected = poProducts.filter(function (p) { return (poQuantities[p.id] || 0) > 0; });
if (!selected.length) return;
var btn = $('#submitPreorderBtn').prop('disabled', true)
.html('<i class="fa fa-spinner fa-spin"></i> Előrendelés mentése...');
$.ajax({
url : '@Url.Action("PlacePreorder", "Order")',
type : 'POST',
contentType: 'application/json',
data : JSON.stringify({
deliveryDateTime : selectedDeliveryDate + 'T' + selectedDeliveryTime,
customerNote : $('#customerNote').val().trim(),
items : selected.map(function (p) { return { productId: p.id, quantity: poQuantities[p.id] }; })
}),
headers : { 'RequestVerificationToken': $('input[name="__RequestVerificationToken"]').val() },
success : function (r) {
if (r.success) {
$('#mainContent, #deliveryChip').hide();
var msg = '#' + r.preorderId + ' számú előrendelésed beérkezett.';
if (r.orderId) {
msg += ' Az azonnal elérhető tételek alapján #' + r.orderId + ' számon rendelés is készült — a többi tétel a szállítmány beérkezésekor kerül megerősítésre.';
} else {
msg += ' A szállítmány megerősítésekor értesítünk.';
}
$('#successMessage').text(msg);
$('#successState').show();
} else {
alert('Hiba: ' + (r.message || ''));
btn.prop('disabled', false).html('<i class="fa fa-paper-plane"></i> Előrendelés leadása');
}
},
error: function () {
btn.prop('disabled', false).html('<i class="fa fa-paper-plane"></i> Előrendelés leadása');
}
});
}
// ── Shared ───────────────────────────────────────────────────────────────
function fmt(val) {
if (!val && val !== 0) return '—';
return Math.round(val).toLocaleString('hu-HU');
}
</script>