Mango.Nop.Plugins/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/VoiceOrder/Create.cshtml

650 lines
24 KiB
Plaintext

@{
Layout = "_AdminLayout";
ViewBag.PageTitle = "Voice Order Creation";
}
<div class="content-header clearfix">
<h1 class="float-left">
<i class="fas fa-microphone"></i> Voice Order Creation
</h1>
</div>
<section class="content">
<div class="container-fluid">
<!-- Progress Steps -->
<div class="card card-default mb-3">
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div id="step1Indicator" class="alert alert-info">
<i class="fas fa-user"></i> <strong>Step 1:</strong> Select Partner
</div>
</div>
<div class="col-md-6">
<div id="step2Indicator" class="alert alert-secondary">
<i class="fas fa-box"></i> <strong>Step 2:</strong> Add Products
</div>
</div>
</div>
</div>
</div>
<!-- Step 1: Partner Selection -->
<div id="step1Card" class="card card-primary">
<div class="card-header">
<h3 class="card-title">
<i class="fas fa-user"></i> Step 1: Select Partner
</h3>
</div>
<div class="card-body">
<div class="text-center mb-4">
<h4 id="step1Prompt">🎤 Tell me the partner name</h4>
</div>
<!-- Voice Recording Button -->
<div class="text-center mb-4">
<button id="recordPartnerBtn" class="btn btn-lg btn-primary">
<i class="fas fa-microphone"></i> Click to Record Partner Name
</button>
<button id="stopPartnerBtn" class="btn btn-lg btn-danger" style="display: none;">
<i class="fas fa-stop"></i> Stop Recording
</button>
</div>
<!-- Recording Status -->
<div id="partnerRecordingStatus" class="alert alert-info text-center" style="display: none;">
<i class="fas fa-spinner fa-spin"></i> <span id="partnerStatusText">Recording...</span>
</div>
<!-- Transcribed Text -->
<div id="partnerTranscribedCard" class="card card-secondary" style="display: none;">
<div class="card-header">
<h5 class="card-title"><i class="fas fa-comment"></i> You said:</h5>
</div>
<div class="card-body">
<p id="partnerTranscribedText" class="lead"></p>
</div>
</div>
<!-- Partner Matches -->
<div id="partnerMatchesCard" class="card card-success" style="display: none;">
<div class="card-header">
<h5 class="card-title"><i class="fas fa-users"></i> Matching Partners:</h5>
</div>
<div class="card-body">
<div id="partnerButtons" class="d-grid gap-2">
<!-- Partner buttons will be inserted here -->
</div>
</div>
</div>
<!-- Selected Partner Display -->
<div id="selectedPartnerCard" class="card card-success" style="display: none;">
<div class="card-header">
<h5 class="card-title"><i class="fas fa-check-circle"></i> Selected Partner:</h5>
</div>
<div class="card-body">
<h4 id="selectedPartnerName"></h4>
<p id="selectedPartnerDetails" class="text-muted"></p>
<button id="changePartnerBtn" class="btn btn-warning">
<i class="fas fa-undo"></i> Change Partner
</button>
<button id="proceedToProductsBtn" class="btn btn-success">
<i class="fas fa-arrow-right"></i> Proceed to Add Products
</button>
</div>
</div>
</div>
</div>
<!-- Step 2: Product Selection -->
<div id="step2Card" class="card card-info" style="display: none;">
<div class="card-header">
<h3 class="card-title">
<i class="fas fa-box"></i> Step 2: Add Products
</h3>
</div>
<div class="card-body">
<!-- Selected Partner Summary -->
<div class="alert alert-success mb-3">
<strong><i class="fas fa-user"></i> Partner:</strong> <span id="partnerSummary"></span>
</div>
<div class="text-center mb-4">
<h4 id="step2Prompt">🎤 Tell me the products and quantities</h4>
<p class="text-muted">Example: "Narancs 100 kilogram, Alma 50 kilogram"</p>
</div>
<!-- Voice Recording Button -->
<div class="text-center mb-4">
<button id="recordProductBtn" class="btn btn-lg btn-primary">
<i class="fas fa-microphone"></i> Click to Record Products
</button>
<button id="stopProductBtn" class="btn btn-lg btn-danger" style="display: none;">
<i class="fas fa-stop"></i> Stop Recording
</button>
</div>
<!-- Recording Status -->
<div id="productRecordingStatus" class="alert alert-info text-center" style="display: none;">
<i class="fas fa-spinner fa-spin"></i> <span id="productStatusText">Recording...</span>
</div>
<!-- Transcribed Text -->
<div id="productTranscribedCard" class="card card-secondary" style="display: none;">
<div class="card-header">
<h5 class="card-title"><i class="fas fa-comment"></i> You said:</h5>
</div>
<div class="card-body">
<p id="productTranscribedText" class="lead"></p>
</div>
</div>
<!-- Product Matches (for confirmation) -->
<div id="productMatchesCard" class="card card-warning" style="display: none;">
<div class="card-header">
<h5 class="card-title"><i class="fas fa-boxes"></i> Found Products - Click to Confirm:</h5>
</div>
<div class="card-body">
<div id="productButtons" class="d-grid gap-2">
<!-- Product buttons will be inserted here -->
</div>
</div>
</div>
<!-- Current Order Items -->
<div id="orderItemsCard" class="card card-success" style="display: none;">
<div class="card-header">
<h5 class="card-title"><i class="fas fa-shopping-cart"></i> Current Order Items:</h5>
</div>
<div class="card-body">
<table class="table table-bordered" id="orderItemsTable">
<thead>
<tr>
<th>Product</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Total</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="orderItemsBody">
<!-- Order items will be inserted here -->
</tbody>
<tfoot>
<tr>
<th colspan="3" class="text-right">Order Total:</th>
<th id="orderTotalDisplay">0.00 Ft</th>
<th></th>
</tr>
</tfoot>
</table>
<div class="text-center mt-3">
<button id="addMoreProductsBtn" class="btn btn-primary">
<i class="fas fa-microphone"></i> Add More Products
</button>
<button id="finishOrderBtn" class="btn btn-success btn-lg">
<i class="fas fa-check"></i> Finish & Create Order
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Order Creation Success -->
<div id="successCard" class="card card-success" style="display: none;">
<div class="card-header">
<h3 class="card-title">
<i class="fas fa-check-circle"></i> Order Created Successfully!
</h3>
</div>
<div class="card-body text-center">
<h3>Order #<span id="createdOrderId"></span> has been created!</h3>
<div class="mt-4">
<a id="viewOrderBtn" href="#" class="btn btn-primary btn-lg">
<i class="fas fa-eye"></i> View Order
</a>
<button id="createAnotherOrderBtn" class="btn btn-secondary btn-lg">
<i class="fas fa-plus"></i> Create Another Order
</button>
</div>
</div>
</div>
</div>
</section>
<script>
// State management
let currentStep = 1;
let selectedPartnerId = null;
let selectedPartnerName = "";
let orderItems = [];
let mediaRecorder = null;
let audioChunks = [];
let orderTotal = 0;
$(document).ready(function() {
setupEventHandlers();
});
function setupEventHandlers() {
// Partner recording
$('#recordPartnerBtn').click(() => startRecording('partner'));
$('#stopPartnerBtn').click(() => stopRecording('partner'));
// Product recording
$('#recordProductBtn').click(() => startRecording('product'));
$('#stopProductBtn').click(() => stopRecording('product'));
// Navigation
$('#changePartnerBtn').click(resetPartnerSelection);
$('#proceedToProductsBtn').click(proceedToProducts);
$('#addMoreProductsBtn').click(addMoreProducts);
$('#finishOrderBtn').click(finishOrder);
$('#createAnotherOrderBtn').click(resetWizard);
}
async function startRecording(type) {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
audioChunks = [];
mediaRecorder.ondataavailable = (event) => {
audioChunks.push(event.data);
};
mediaRecorder.onstop = () => {
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
processAudio(audioBlob, type);
stream.getTracks().forEach(track => track.stop());
};
mediaRecorder.start();
// Update UI
if (type === 'partner') {
$('#recordPartnerBtn').hide();
$('#stopPartnerBtn').show();
showStatus('partnerRecordingStatus', 'Recording... Speak now!');
} else {
$('#recordProductBtn').hide();
$('#stopProductBtn').show();
showStatus('productRecordingStatus', 'Recording... Speak now!');
}
} catch (error) {
console.error('Error accessing microphone:', error);
alert('Could not access microphone. Please check permissions.');
}
}
function stopRecording(type) {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
// Update UI
if (type === 'partner') {
$('#stopPartnerBtn').hide();
showStatus('partnerRecordingStatus', 'Processing audio...');
} else {
$('#stopProductBtn').hide();
showStatus('productRecordingStatus', 'Processing audio...');
}
}
}
async function processAudio(audioBlob, type) {
const formData = new FormData();
formData.append('audioFile', audioBlob, 'recording.webm');
const endpoint = type === 'partner'
? '@Url.Action("TranscribeForPartner", "VoiceOrder")'
: '@Url.Action("TranscribeForProducts", "VoiceOrder")';
try {
const response = await fetch(endpoint, {
method: 'POST',
body: formData,
headers: {
'RequestVerificationToken': $('input[name="__RequestVerificationToken"]').val()
}
});
const result = await response.json();
if (result.success) {
if (type === 'partner') {
handlePartnerTranscription(result);
} else {
handleProductTranscription(result);
}
} else {
alert('Error: ' + result.message);
resetRecordingUI(type);
}
} catch (error) {
console.error('Error processing audio:', error);
alert('Error processing audio: ' + error.message);
resetRecordingUI(type);
}
}
function handlePartnerTranscription(result) {
$('#partnerRecordingStatus').hide();
$('#recordPartnerBtn').show();
// Show transcribed text
$('#partnerTranscribedText').text(result.transcription);
$('#partnerTranscribedCard').show();
// Show matching partners
if (result.partners && result.partners.length > 0) {
displayPartnerMatches(result.partners);
} else {
alert('No matching partners found. Please try again.');
}
}
function displayPartnerMatches(partners) {
const container = $('#partnerButtons');
container.empty();
partners.forEach(partner => {
const btn = $('<button>')
.addClass('btn btn-outline-primary btn-lg mb-2 text-left')
.css('width', '100%')
.html(`
<i class="fas fa-user"></i> <strong>${partner.label}</strong><br>
<small class="text-muted">ID: ${partner.value}</small>
`)
.click(() => selectPartner(partner));
container.append(btn);
});
$('#partnerMatchesCard').show();
}
function selectPartner(partner) {
selectedPartnerId = partner.value;
selectedPartnerName = partner.label;
$('#selectedPartnerName').text(partner.label);
$('#selectedPartnerDetails').text('Customer ID: ' + partner.value);
$('#partnerMatchesCard').hide();
$('#selectedPartnerCard').show();
}
function resetPartnerSelection() {
selectedPartnerId = null;
selectedPartnerName = "";
$('#partnerTranscribedCard').hide();
$('#partnerMatchesCard').hide();
$('#selectedPartnerCard').hide();
$('#recordPartnerBtn').show();
}
function proceedToProducts() {
currentStep = 2;
// Update step indicators
$('#step1Indicator').removeClass('alert-info').addClass('alert-success');
$('#step2Indicator').removeClass('alert-secondary').addClass('alert-info');
// Hide step 1, show step 2
$('#step1Card').hide();
$('#step2Card').show();
// Update partner summary
$('#partnerSummary').text(selectedPartnerName);
}
function handleProductTranscription(result) {
$('#productRecordingStatus').hide();
$('#recordProductBtn').show();
// Show transcribed text
$('#productTranscribedText').text(result.transcription);
$('#productTranscribedCard').show();
// Show matching products for confirmation
if (result.products && result.products.length > 0) {
displayProductMatches(result.products);
} else {
alert('No matching products found. Please try again.');
}
}
function displayProductMatches(products) {
const container = $('#productButtons');
container.empty();
products.forEach(product => {
const btn = $('<button>')
.addClass('btn btn-outline-success btn-lg mb-2 text-left')
.css('width', '100%')
.html(`
<i class="fas fa-box"></i> <strong>${product.name}</strong><br>
<small>Quantity: ${product.quantity} ${product.unit || 'kg'} | Price: ${product.price.toFixed(2)} Ft | Available: ${product.stockQuantity}</small>
`)
.click(() => addProductToOrder(product));
container.append(btn);
});
$('#productMatchesCard').show();
}
function addProductToOrder(product) {
// Check if product already exists in order
const existingIndex = orderItems.findIndex(item => item.id === product.id);
if (existingIndex >= 0) {
// Update quantity
orderItems[existingIndex].quantity += product.quantity;
} else {
// Add new item
orderItems.push({
id: product.id,
name: product.name,
sku: product.sku,
quantity: product.quantity,
price: product.price,
stockQuantity: product.stockQuantity
});
}
updateOrderItemsDisplay();
// Hide product matches after adding
$('#productMatchesCard').hide();
$('#productTranscribedCard').hide();
}
function updateOrderItemsDisplay() {
const tbody = $('#orderItemsBody');
tbody.empty();
orderTotal = 0;
orderItems.forEach((item, index) => {
const itemTotal = item.quantity * item.price;
orderTotal += itemTotal;
const row = $('<tr>').html(`
<td>${item.name}<br><small class="text-muted">SKU: ${item.sku}</small></td>
<td>
<input type="number" class="form-control" value="${item.quantity}"
onchange="updateQuantity(${index}, this.value)" min="1" max="${item.stockQuantity}">
</td>
<td>${item.price.toFixed(2)} Ft</td>
<td>${itemTotal.toFixed(2)} Ft</td>
<td>
<button class="btn btn-sm btn-danger" onclick="removeItem(${index})">
<i class="fas fa-trash"></i>
</button>
</td>
`);
tbody.append(row);
});
$('#orderTotalDisplay').text(orderTotal.toFixed(2) + ' Ft');
$('#orderItemsCard').show();
}
window.updateQuantity = function(index, newQuantity) {
orderItems[index].quantity = parseInt(newQuantity);
updateOrderItemsDisplay();
};
window.removeItem = function(index) {
orderItems.splice(index, 1);
updateOrderItemsDisplay();
if (orderItems.length === 0) {
$('#orderItemsCard').hide();
}
};
function addMoreProducts() {
$('#productTranscribedCard').hide();
$('#productMatchesCard').hide();
$('#recordProductBtn').show();
}
async function finishOrder() {
if (!selectedPartnerId) {
alert('No partner selected!');
return;
}
if (orderItems.length === 0) {
alert('No products in order!');
return;
}
$('#finishOrderBtn').prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Creating order...');
try {
const orderProductsJson = JSON.stringify(orderItems);
const response = await fetch('@Url.Action("Create", "CustomOrder")', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'RequestVerificationToken': $('input[name="__RequestVerificationToken"]').val()
},
body: new URLSearchParams({
customerId: selectedPartnerId,
orderProductsJson: orderProductsJson,
__RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val()
})
});
// Check if redirect happened (order created successfully)
if (response.redirected) {
// Extract order ID from redirect URL
const url = new URL(response.url);
const orderId = url.searchParams.get('id');
if (orderId) {
showSuccess(orderId);
} else {
// Fallback: redirect to the order edit page
window.location.href = response.url;
}
} else {
alert('Error creating order. Please try again.');
$('#finishOrderBtn').prop('disabled', false).html('<i class="fas fa-check"></i> Finish & Create Order');
}
} catch (error) {
console.error('Error creating order:', error);
alert('Error creating order: ' + error.message);
$('#finishOrderBtn').prop('disabled', false).html('<i class="fas fa-check"></i> Finish & Create Order');
}
}
function showSuccess(orderId) {
$('#step2Card').hide();
$('#successCard').show();
$('#createdOrderId').text(orderId);
$('#viewOrderBtn').attr('href', '@Url.Action("Edit", "Order")?id=' + orderId);
}
function resetWizard() {
// Reset all state
currentStep = 1;
selectedPartnerId = null;
selectedPartnerName = "";
orderItems = [];
orderTotal = 0;
// Reset UI
$('#step1Indicator').removeClass('alert-success').addClass('alert-info');
$('#step2Indicator').removeClass('alert-info').addClass('alert-secondary');
$('#successCard').hide();
$('#step2Card').hide();
$('#step1Card').show();
$('#partnerTranscribedCard').hide();
$('#partnerMatchesCard').hide();
$('#selectedPartnerCard').hide();
$('#productTranscribedCard').hide();
$('#productMatchesCard').hide();
$('#orderItemsCard').hide();
$('#recordPartnerBtn').show();
}
function showStatus(elementId, message) {
$(`#${elementId}`).find('span').text(message);
$(`#${elementId}`).show();
}
function resetRecordingUI(type) {
if (type === 'partner') {
$('#partnerRecordingStatus').hide();
$('#recordPartnerBtn').show();
$('#stopPartnerBtn').hide();
} else {
$('#productRecordingStatus').hide();
$('#recordProductBtn').show();
$('#stopProductBtn').hide();
}
}
</script>
<style>
.d-grid {
display: grid;
}
.gap-2 {
gap: 0.5rem;
}
#step1Indicator, #step2Indicator {
font-size: 1.1rem;
text-align: center;
margin-bottom: 0;
}
.btn-lg {
padding: 1rem 2rem;
font-size: 1.25rem;
}
.card-body h4 {
margin-bottom: 1.5rem;
}
</style>
@* Anti-forgery token *@
@Html.AntiForgeryToken()