Mango.Nop.Plugins/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Extras/ImageTextExtraction.cshtml

942 lines
39 KiB
Plaintext

@{
Layout = "../_FruitBankEmptyAdminLayout.cshtml";
}
@await Component.InvokeAsync("StoreScopeConfiguration")
<form asp-action="ExtractTextFromImage" asp-controller="FileManager" method="post">
@Html.AntiForgeryToken()
</form>
<!-- Response Message (full width, top) -->
<div id="responseMessage" class="alert" style="display:none;"></div>
<!-- Duplicate Warning Banner -->
<div id="duplicateWarningBanner" class="duplicate-warning-banner" style="display:none;">
<i class="fas fa-exclamation-triangle fa-lg"></i>
<strong>Duplikált dokumentum</strong> — Ez a dokumentum már fel lett dolgozva. Átnézheted/szerkesztheted az adatokat, vagy tölts fel egy másik dokumentumot.
</div>
<div class="row">
<!-- ══════════════ LEFT COLUMN: Upload & Preview ══════════════ -->
<div class="col-lg-5 col-xl-5">
<div class="left-column-sticky">
<!-- Upload Card -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-cloud-upload-alt mr-2"></i>Dokumentum feltöltés</h5>
</div>
<div class="card-body">
<!-- Drag & Drop Zone -->
<div id="dropZone" class="drop-zone mb-3">
<input type="file" class="drop-zone__input" id="imageFile" accept="image/*,.pdf,application/pdf">
<div class="drop-zone__content">
<i class="fas fa-cloud-upload-alt drop-zone__icon"></i>
<p class="drop-zone__title">Húzd ide a fájlt</p>
<p class="drop-zone__subtitle">vagy <span class="drop-zone__browse">tallózz</span></p>
<p class="drop-zone__formats">JPG, PNG, GIF, WebP, PDF</p>
</div>
<div class="drop-zone__selected" style="display:none;">
<i class="fas fa-file drop-zone__file-icon"></i>
<span class="drop-zone__filename"></span>
<button type="button" class="btn btn-sm btn-outline-danger drop-zone__remove" title="Eltávolítás">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<button type="button" id="uploadButton" class="btn btn-primary btn-block mb-2" disabled>
<i class="fas fa-magic mr-1"></i> Szöveg kinyerése
</button>
<!-- Advanced: Custom Prompt (collapsible) -->
<div>
<a class="small text-muted" data-toggle="collapse" href="#advancedPromptSection" role="button" aria-expanded="false">
<i class="fas fa-cog mr-1"></i>Haladó beállítások
</a>
<div class="collapse mt-2" id="advancedPromptSection">
<div class="form-group mb-0">
<label for="customPrompt" class="small font-weight-bold">Egyéni prompt <span class="text-muted font-weight-normal">(Opcionális)</span></label>
<input type="text" class="form-control form-control-sm" id="customPrompt" placeholder="Alapértelmezett: 'Olvasd ki a szöveget és add vissza szépen strukturálva.'">
<small class="form-text text-muted">Az AI szövegkinyerés testreszabása</small>
</div>
</div>
</div>
</div>
</div>
<!-- File Preview Card -->
<div class="card mb-3" id="filePreviewSection" style="display:none;">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-eye mr-2"></i>Előnézet</h5>
</div>
<div class="card-body p-2">
<div id="imagePreviewContainer" style="display:none;">
<img id="imagePreview" src="" alt="Preview" class="preview-image">
</div>
<div id="pdfPreviewContainer" style="display:none;">
<embed id="pdfPreview" type="application/pdf" class="preview-pdf">
</div>
</div>
</div>
<!-- Raw Extracted Text (Collapsible) -->
<div class="card mb-3" id="extractedTextCard" style="display:none;">
<div class="card-header bg-secondary text-white" data-toggle="collapse" data-target="#extractedTextCollapse" style="cursor: pointer;">
<h5 class="mb-0 d-flex justify-content-between align-items-center">
<span><i class="fas fa-file-alt mr-2"></i>Nyers szöveg</span>
<i class="fas fa-chevron-down"></i>
</h5>
</div>
<div id="extractedTextCollapse" class="collapse">
<div class="card-body p-2">
<pre id="extractedText" class="extracted-text-pre"></pre>
<button type="button" id="copyButton" class="btn btn-outline-secondary btn-sm mt-2">
<i class="fas fa-copy mr-1"></i> Másolás
</button>
</div>
</div>
</div>
</div>
</div>
<!-- ══════════════ RIGHT COLUMN: Extracted Data ══════════════ -->
<div class="col-lg-7 col-xl-7">
<div id="shippingDocumentSection" style="display:none;">
<!-- Document Info -->
<div class="card mb-3" id="documentInfoCard">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="fas fa-file-alt mr-2"></i>Dokumentum adatok</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-sm-4">
<div class="form-group mb-2">
<label for="editDocumentIdNumber" class="small font-weight-bold mb-1">Dokumentum azonosító</label>
<input type="text" class="form-control form-control-sm" id="editDocumentIdNumber">
</div>
</div>
<div class="col-sm-8">
<div class="form-group mb-2">
<label for="editPartnerName" class="small font-weight-bold mb-1">Partner</label>
<input type="text" class="form-control form-control-sm partner-search-input" id="editPartnerName" placeholder="Kezdj el gépelni a kereséshez...">
<small class="form-text text-muted">
<span id="partnerIdDisplay"></span>
<span id="partnerTaxIdDisplay"></span>
</small>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4">
<div class="form-group mb-0">
<label for="editTotalPallets" class="small font-weight-bold mb-1">Összes raklap</label>
<input type="number" class="form-control form-control-sm" id="editTotalPallets" readonly>
</div>
</div>
<div class="col-sm-4">
<div class="form-group mb-0">
<label for="editPdfFileName" class="small font-weight-bold mb-1">PDF fájlnév</label>
<input type="text" class="form-control form-control-sm" id="editPdfFileName" readonly>
</div>
</div>
</div>
</div>
</div>
<!-- Shipping Items Header -->
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">
<i class="fas fa-boxes mr-1 text-info"></i>
Szállítási tételek (<span id="itemCount">0</span>)
</h5>
<button type="button" id="addItemButton" class="btn btn-sm btn-success">
<i class="fas fa-plus mr-1"></i> Új tétel
</button>
</div>
<!-- Shipping Item Cards Container -->
<div id="shippingItemsContainer">
</div>
<!-- Save Button -->
<div class="card mt-3 mb-4">
<div class="card-body p-3">
<button type="button" id="saveShippingDocumentButton" class="btn btn-success btn-lg btn-block">
<i class="fas fa-save mr-1"></i> Szállítási dokumentum mentése
</button>
</div>
</div>
</div>
<!-- Empty state when nothing extracted yet -->
<div id="emptyStateRight" class="text-center text-muted py-5">
<i class="fas fa-arrow-left fa-2x mb-3 d-block" style="opacity:0.2;"></i>
<p>Töltsd fel és dolgozd fel a dokumentumot az eredmények megtekintéséhez.</p>
</div>
</div>
</div>
<style>
/* ─── Layout ─── */
.left-column-sticky {
position: sticky;
top: 15px;
}
/* ─── Drag & Drop Zone ─── */
.drop-zone {
border: 2px dashed #ced4da;
border-radius: 8px;
padding: 30px 15px;
text-align: center;
cursor: pointer;
transition: all 0.2s ease;
background: #fafbfc;
position: relative;
}
.drop-zone:hover,
.drop-zone--dragover {
border-color: #007bff;
background: #f0f7ff;
}
.drop-zone__input {
position: absolute;
inset: 0;
opacity: 0;
cursor: pointer;
}
.drop-zone__icon {
font-size: 2rem;
color: #adb5bd;
margin-bottom: 8px;
display: block;
}
.drop-zone__title {
font-size: 1rem;
font-weight: 600;
color: #495057;
margin-bottom: 2px;
}
.drop-zone__subtitle {
font-size: 0.85rem;
color: #6c757d;
margin-bottom: 2px;
}
.drop-zone__browse {
color: #007bff;
text-decoration: underline;
font-weight: 500;
}
.drop-zone__formats {
font-size: 0.75rem;
color: #adb5bd;
margin-bottom: 0;
}
.drop-zone__selected {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.drop-zone__file-icon {
font-size: 1.2rem;
color: #007bff;
}
.drop-zone__filename {
font-weight: 500;
color: #212529;
font-size: 0.9rem;
word-break: break-all;
}
/* ─── Previews ─── */
.preview-image {
width: 100%;
max-height: 800px;
object-fit: contain;
border-radius: 4px;
}
.preview-pdf {
width: 100%;
height: 500px;
border-radius: 4px;
}
.extracted-text-pre {
white-space: pre-wrap;
word-wrap: break-word;
max-height: 300px;
overflow-y: auto;
background: #f8f9fa;
padding: 12px;
border-radius: 4px;
font-size: 0.8rem;
margin: 0;
}
/* ─── Shipping Item Card ─── */
.shipping-item-card {
border: 1px solid #dee2e6;
border-radius: 6px;
padding: 14px;
margin-bottom: 10px;
background: #fff;
position: relative;
transition: box-shadow 0.15s;
}
.shipping-item-card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.shipping-item-card.unmatched {
border-left: 4px solid #ffc107;
background: #fffdf5;
}
.shipping-item-card.matched {
border-left: 4px solid #28a745;
}
.shipping-item-card__header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 10px;
}
.shipping-item-card__number {
font-weight: 700;
font-size: 0.85rem;
color: #6c757d;
min-width: 28px;
height: 28px;
line-height: 28px;
text-align: center;
background: #e9ecef;
border-radius: 50%;
}
.shipping-item-card__fields {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.shipping-item-card__fields .field-full {
grid-column: 1 / -1;
}
.shipping-item-card__fields label {
font-size: 0.72rem;
font-weight: 600;
color: #6c757d;
text-transform: uppercase;
letter-spacing: 0.03em;
margin-bottom: 2px;
display: block;
}
.shipping-item-card__fields input {
font-size: 0.85rem;
}
/* ─── Autocomplete ─── */
.autocomplete-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #dee2e6;
border-top: none;
max-height: 250px;
overflow-y: auto;
z-index: 1050;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
border-radius: 0 0 6px 6px;
}
.autocomplete-item {
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
transition: background 0.1s;
}
.autocomplete-item:hover {
background-color: #f0f7ff;
}
.autocomplete-item:last-child {
border-bottom: none;
}
/* ─── Match Status Colors ─── */
.product-search-input {
background-color: #fff3cd !important;
}
.product-search-input.matched-product {
background-color: #d4edda !important;
}
.partner-search-input-unmatched {
background-color: #fff3cd !important;
}
.partner-search-input-matched {
background-color: #d1ecf1 !important;
}
/* ─── Duplicate Warning ─── */
.duplicate-warning-banner {
background-color: #fff3cd;
border: 1px solid #ffc107;
border-left: 4px solid #ffc107;
padding: 12px 16px;
border-radius: 4px;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 10px;
}
.duplicate-warning-banner i {
color: #856404;
}
.border-warning {
border: 2px solid #ffc107 !important;
}
/* ─── General Polish ─── */
.card {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}
.card-header h5 {
font-size: 0.95rem;
}
</style>
<script>
const dropZone = document.getElementById('dropZone');
const imageFileInput = document.getElementById('imageFile');
const customPromptInput = document.getElementById('customPrompt');
const uploadButton = document.getElementById('uploadButton');
const responseMessage = document.getElementById('responseMessage');
const filePreviewSection = document.getElementById('filePreviewSection');
const imagePreviewContainer = document.getElementById('imagePreviewContainer');
const pdfPreviewContainer = document.getElementById('pdfPreviewContainer');
const imagePreview = document.getElementById('imagePreview');
const pdfPreview = document.getElementById('pdfPreview');
const shippingDocumentSection = document.getElementById('shippingDocumentSection');
const extractedText = document.getElementById('extractedText');
const copyButton = document.getElementById('copyButton');
const addItemButton = document.getElementById('addItemButton');
const saveShippingDocumentButton = document.getElementById('saveShippingDocumentButton');
const emptyStateRight = document.getElementById('emptyStateRight');
const extractedTextCard = document.getElementById('extractedTextCard');
const dropZoneContent = dropZone.querySelector('.drop-zone__content');
const dropZoneSelected = dropZone.querySelector('.drop-zone__selected');
const dropZoneFilename = dropZone.querySelector('.drop-zone__filename');
const dropZoneRemove = dropZone.querySelector('.drop-zone__remove');
const dropZoneFileIcon = dropZone.querySelector('.drop-zone__file-icon');
let selectedFile = null;
let shippingItems = [];
let currentPartner = { id: null, name: '', taxId: '' };
let originalUploadedFile = null;
let extractedFullText = '';
let isKnownDuplicate = false;
// ─── Drag & Drop ───
['dragenter', 'dragover'].forEach(ev => {
dropZone.addEventListener(ev, e => { e.preventDefault(); dropZone.classList.add('drop-zone--dragover'); });
});
['dragleave', 'drop'].forEach(ev => {
dropZone.addEventListener(ev, e => { e.preventDefault(); dropZone.classList.remove('drop-zone--dragover'); });
});
dropZone.addEventListener('drop', e => {
if (e.dataTransfer.files.length > 0) handleFileSelection(e.dataTransfer.files[0]);
});
imageFileInput.addEventListener('change', e => {
if (e.target.files.length > 0) handleFileSelection(e.target.files[0]);
});
dropZoneRemove.addEventListener('click', e => { e.stopPropagation(); clearFileSelection(); });
function handleFileSelection(file) {
selectedFile = file;
uploadButton.disabled = false;
const isPdf = file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf');
dropZoneFileIcon.className = isPdf ? 'fas fa-file-pdf drop-zone__file-icon' : 'fas fa-file-image drop-zone__file-icon';
dropZoneFilename.textContent = file.name;
dropZoneContent.style.display = 'none';
dropZoneSelected.style.display = 'flex';
if (isPdf) {
imagePreviewContainer.style.display = 'none';
pdfPreviewContainer.style.display = 'block';
pdfPreview.src = URL.createObjectURL(file);
} else {
pdfPreviewContainer.style.display = 'none';
imagePreviewContainer.style.display = 'block';
const reader = new FileReader();
reader.onload = e => { imagePreview.src = e.target.result; };
reader.readAsDataURL(file);
}
filePreviewSection.style.display = 'block';
shippingDocumentSection.style.display = 'none';
emptyStateRight.style.display = 'block';
}
function clearFileSelection() {
selectedFile = null;
imageFileInput.value = '';
uploadButton.disabled = true;
dropZoneContent.style.display = 'block';
dropZoneSelected.style.display = 'none';
filePreviewSection.style.display = 'none';
shippingDocumentSection.style.display = 'none';
emptyStateRight.style.display = 'block';
extractedTextCard.style.display = 'none';
}
// ─── Upload / Extract ───
uploadButton.addEventListener('click', async () => {
if (!selectedFile) { showMessage('Kérlek válassz ki egy fájlt!', 'warning'); return; }
const formData = new FormData();
formData.append('imageFile', selectedFile);
const customPrompt = customPromptInput.value.trim();
if (customPrompt) formData.append('customPrompt', customPrompt);
try {
uploadButton.disabled = true;
const isPdf = selectedFile.type === 'application/pdf' || selectedFile.name.toLowerCase().endsWith('.pdf');
uploadButton.innerHTML = isPdf
? '<i class="fas fa-spinner fa-spin"></i> PDF konvertálás és kinyerés...'
: '<i class="fas fa-spinner fa-spin"></i> Szöveg kinyerése...';
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
const response = await fetch('@Url.Action("ExtractTextFromImage", "FileManager")', {
method: 'POST',
headers: { 'RequestVerificationToken': token },
body: formData
});
const result = await response.json();
if (response.ok && result.success) {
if (result.isDuplicate) {
isKnownDuplicate = true;
showMessage(`⚠️ DUPLIKÁTUM: ${result.message}`, 'warning');
document.getElementById('duplicateWarningBanner').style.display = 'flex';
} else {
isKnownDuplicate = false;
document.getElementById('duplicateWarningBanner').style.display = 'none';
showMessage(result.wasConverted ? 'PDF konvertálva és szöveg sikeresen kinyerve!' : 'Szöveg sikeresen kinyerve!', 'success');
}
originalUploadedFile = selectedFile;
extractedFullText = result.shippingDocument.extractedText || '';
if (result.shippingDocument) {
displayShippingDocument(result.shippingDocument, result.isDuplicate);
shippingDocumentSection.style.display = 'block';
emptyStateRight.style.display = 'none';
extractedTextCard.style.display = 'block';
}
} else {
showMessage('Hiba: ' + (result.message || 'Nem sikerült a szöveg kinyerése'), 'danger');
}
} catch (error) {
console.error('Extract error:', error);
showMessage('Hiba: Nem sikerült kapcsolódni a szerverhez', 'danger');
} finally {
uploadButton.disabled = false;
uploadButton.innerHTML = '<i class="fas fa-magic mr-1"></i> Szöveg kinyerése';
}
});
// ─── Copy ───
copyButton.addEventListener('click', () => {
navigator.clipboard.writeText(extractedText.textContent).then(() => {
const orig = copyButton.innerHTML;
copyButton.innerHTML = '<i class="fas fa-check mr-1"></i> Másolva!';
copyButton.classList.replace('btn-outline-secondary', 'btn-success');
setTimeout(() => { copyButton.innerHTML = orig; copyButton.classList.replace('btn-success', 'btn-outline-secondary'); }, 2000);
});
});
addItemButton.addEventListener('click', () => addNewShippingItem());
saveShippingDocumentButton.addEventListener('click', () => saveShippingDocument());
// ─── Display Shipping Document ───
function displayShippingDocument(doc, isDuplicate = false) {
document.getElementById('editDocumentIdNumber').value = doc.documentIdNumber || '';
document.getElementById('editTotalPallets').value = doc.totalPallets || '0';
document.getElementById('editPdfFileName').value = doc.pdfFileName || '';
const infoCard = document.getElementById('documentInfoCard');
isDuplicate ? infoCard.classList.add('border-warning') : infoCard.classList.remove('border-warning');
currentPartner = { id: doc.partnerId || null, name: doc.partnerName || '', taxId: doc.partnerTaxId || '' };
updatePartnerDisplay();
extractedText.textContent = doc.extractedText || 'Nincs kinyert szöveg';
shippingItems = doc.shippingItems || [];
renderShippingItems();
initializePartnerAutocomplete();
}
function updatePartnerDisplay() {
const input = document.getElementById('editPartnerName');
const idDisp = document.getElementById('partnerIdDisplay');
const taxDisp = document.getElementById('partnerTaxIdDisplay');
if (currentPartner.id) {
input.value = currentPartner.name;
input.classList.remove('partner-search-input-unmatched');
input.classList.add('partner-search-input-matched');
idDisp.textContent = `Partner ID: ${currentPartner.id}`;
taxDisp.textContent = currentPartner.taxId ? ` | Adószám: ${currentPartner.taxId}` : '';
} else {
input.value = '';
input.classList.add('partner-search-input-unmatched');
input.classList.remove('partner-search-input-matched');
idDisp.textContent = 'Partner: Nincs párosítva';
taxDisp.textContent = '';
}
}
// ─── Shipping Item Cards ───
function renderShippingItems() {
const container = document.getElementById('shippingItemsContainer');
container.innerHTML = '';
document.getElementById('itemCount').textContent = shippingItems.length;
if (shippingItems.length === 0) {
container.innerHTML = `
<div class="text-center text-muted py-5">
<i class="fas fa-inbox fa-2x mb-2 d-block" style="opacity:0.3;"></i>
Nincsenek szállítási tételek. Kattints az "Új tétel" gombra a hozzáadáshoz.
</div>`;
} else {
shippingItems.forEach((item, index) => {
container.appendChild(createItemCard(item, index));
});
}
updateTotalPallets();
}
function createItemCard(item, index) {
const isMatched = item.productId && item.productId > 0;
const card = document.createElement('div');
card.className = `shipping-item-card ${isMatched ? 'matched' : 'unmatched'}`;
const nameValue = isMatched ? item.name : (item.nameOnDocument || '');
const nameClass = isMatched ? 'product-search-input matched-product' : 'product-search-input';
card.innerHTML = `
<div class="shipping-item-card__header">
<div class="d-flex align-items-center" style="gap: 10px;">
<span class="shipping-item-card__number">${index + 1}</span>
${isMatched
? `<span class="badge badge-success"><i class="fas fa-check mr-1"></i>ID: ${item.productId}</span>`
: `<span class="badge badge-warning"><i class="fas fa-question mr-1"></i>Nincs párosítva</span>`}
</div>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteShippingItem(${index})" title="Tétel törlése">
<i class="fas fa-trash-alt"></i>
</button>
</div>
<div class="shipping-item-card__fields">
<div class="field-full">
<label>Termék neve</label>
<input type="text" class="form-control form-control-sm ${nameClass}"
value="${escapeHtml(nameValue)}" data-index="${index}" placeholder="Kezdj el gépelni a kereséshez...">
</div>
<div>
<label>Magyar név</label>
<input type="text" class="form-control form-control-sm" value="${escapeHtml(item.hungarianName || '')}" data-field="hungarianName" data-index="${index}" readonly>
</div>
<div>
<label>Név a dokumentumon</label>
<input type="text" class="form-control form-control-sm" value="${escapeHtml(item.nameOnDocument || '')}" data-field="nameOnDocument" data-index="${index}">
</div>
<div>
<label>Raklapok</label>
<input type="number" class="form-control form-control-sm" value="${item.palletsOnDocument || 0}" data-field="palletsOnDocument" data-index="${index}">
</div>
<div>
<label>Mennyiség</label>
<input type="number" class="form-control form-control-sm" value="${item.quantityOnDocument || 0}" data-field="quantityOnDocument" data-index="${index}">
</div>
<div>
<label>Nettó súly (kg)</label>
<input type="number" step="0.01" class="form-control form-control-sm" value="${item.netWeightOnDocument ? item.netWeightOnDocument.toFixed(2) : '0.00'}" data-field="netWeightOnDocument" data-index="${index}">
</div>
<div>
<label>Bruttó súly (kg)</label>
<input type="number" step="0.01" class="form-control form-control-sm" value="${item.grossWeightOnDocument ? item.grossWeightOnDocument.toFixed(2) : '0.00'}" data-field="grossWeightOnDocument" data-index="${index}">
</div>
<div>
<label>Egységár</label>
<input type="number" step="0.01" class="form-control form-control-sm" value="${item.unitPriceOnDocument ? item.unitPriceOnDocument.toFixed(2) : '0.00'}" data-field="unitPriceOnDocument" data-index="${index}">
</div>
</div>
`;
// Field change listeners
card.querySelectorAll('input:not(.product-search-input)').forEach(input => {
input.addEventListener('change', e => {
const field = e.target.dataset.field;
const idx = parseInt(e.target.dataset.index);
let value = e.target.value;
if (['palletsOnDocument', 'quantityOnDocument'].includes(field)) {
value = value ? parseInt(value) : 0;
} else if (['netWeightOnDocument', 'grossWeightOnDocument', 'unitPriceOnDocument'].includes(field)) {
value = value ? parseFloat(value) : 0;
}
shippingItems[idx][field] = value;
if (field === 'palletsOnDocument') updateTotalPallets();
});
});
initializeProductAutocomplete(card.querySelector('.product-search-input'), index);
return card;
}
function addNewShippingItem() {
shippingItems.push({
name: '', hungarianName: '', nameOnDocument: '', productId: null,
palletsOnDocument: 0, quantityOnDocument: 0, netWeightOnDocument: 0,
grossWeightOnDocument: 0, unitPriceOnDocument: 0
});
renderShippingItems();
}
window.deleteShippingItem = function (index) {
if (confirm('Biztosan törölni szeretnéd ezt a tételt?')) {
shippingItems.splice(index, 1);
renderShippingItems();
}
};
function updateTotalPallets() {
document.getElementById('editTotalPallets').value = shippingItems.reduce((sum, i) => sum + (i.palletsOnDocument || 0), 0);
}
// ─── Partner Autocomplete ───
let partnerAutocompleteTimeout = null;
function initializePartnerAutocomplete() {
const input = document.getElementById('editPartnerName');
if (input.parentNode.querySelector('.autocomplete-results')) return;
const wrapper = document.createElement('div');
wrapper.style.position = 'relative';
wrapper.style.width = '100%';
input.parentNode.insertBefore(wrapper, input);
wrapper.appendChild(input);
const results = document.createElement('div');
results.className = 'autocomplete-results';
results.style.display = 'none';
wrapper.appendChild(results);
input.addEventListener('input', e => {
clearTimeout(partnerAutocompleteTimeout);
const term = e.target.value.trim();
if (term.length < 2) { results.style.display = 'none'; return; }
partnerAutocompleteTimeout = setTimeout(() => searchPartners(term, results), 300);
});
document.addEventListener('click', e => {
if (!wrapper.contains(e.target)) results.style.display = 'none';
});
}
async function searchPartners(term, container) {
try {
const resp = await fetch(`@Url.Action("PartnerSearchAutoComplete", "FileManager")?term=${encodeURIComponent(term)}`);
displayPartnerResults(await resp.json(), container);
} catch (err) { console.error('Partner search error:', err); }
}
function displayPartnerResults(partners, container) {
container.innerHTML = '';
if (!partners || !partners.length) {
container.innerHTML = '<div class="autocomplete-item text-muted">Nem található partner</div>';
container.style.display = 'block';
return;
}
partners.forEach(p => {
const item = document.createElement('div');
item.className = 'autocomplete-item';
item.innerHTML = `<div style="font-weight:500;">${escapeHtml(p.name)}</div>
<div style="font-size:0.85em;color:#6c757d;">Adószám: ${escapeHtml(p.taxId || 'N/A')} | ${escapeHtml(p.city || '')}${p.country ? ', ' + escapeHtml(p.country) : ''}</div>`;
item.addEventListener('click', () => { selectPartner(p); container.style.display = 'none'; });
container.appendChild(item);
});
container.style.display = 'block';
}
function selectPartner(partner) {
const prev = currentPartner.id;
currentPartner = { id: partner.value, name: partner.name, taxId: partner.taxId || '' };
updatePartnerDisplay();
showMessage(prev ? `Partner módosítva: ${partner.name}` : `Partner párosítva: ${partner.name}`, prev ? 'info' : 'success');
}
// ─── Product Autocomplete ───
let autocompleteTimeout = null;
function initializeProductAutocomplete(inputElement, itemIndex) {
const wrapper = document.createElement('div');
wrapper.style.position = 'relative';
wrapper.style.width = '100%';
inputElement.parentNode.insertBefore(wrapper, inputElement);
wrapper.appendChild(inputElement);
const results = document.createElement('div');
results.className = 'autocomplete-results';
results.style.display = 'none';
wrapper.appendChild(results);
inputElement.addEventListener('input', e => {
clearTimeout(autocompleteTimeout);
const term = e.target.value.trim();
if (term.length < 2) { results.style.display = 'none'; return; }
autocompleteTimeout = setTimeout(() => searchProducts(term, results, itemIndex), 300);
});
document.addEventListener('click', e => {
if (!wrapper.contains(e.target)) results.style.display = 'none';
});
}
async function searchProducts(term, container, itemIndex) {
try {
const resp = await fetch(`@Url.Action("ProductSearchUnfilteredAutoComplete", "CustomOrder")?term=${encodeURIComponent(term)}`);
displayProductResults(await resp.json(), container, itemIndex);
} catch (err) { console.error('Product search error:', err); }
}
function displayProductResults(products, container, itemIndex) {
container.innerHTML = '';
if (!products || !products.length) {
container.innerHTML = '<div class="autocomplete-item text-muted">Nem található termék</div>';
container.style.display = 'block';
return;
}
products.forEach(p => {
const item = document.createElement('div');
item.className = 'autocomplete-item';
item.innerHTML = `<div style="font-weight:500;">${escapeHtml(p.label)}</div>
<div style="font-size:0.85em;color:#6c757d;">SKU: ${escapeHtml(p.sku || 'N/A')}</div>`;
item.addEventListener('click', () => { selectProduct(p, itemIndex); container.style.display = 'none'; });
container.appendChild(item);
});
container.style.display = 'block';
}
function selectProduct(product, itemIndex) {
const name = product.label.split('[')[0].trim();
const prev = shippingItems[itemIndex].productId;
shippingItems[itemIndex] = {
...shippingItems[itemIndex],
productId: product.value, name, hungarianName: name,
unitPriceOnDocument: product.price || 0
};
renderShippingItems();
showMessage(prev ? `Termék módosítva: ${name}` : `Termék párosítva: ${name}`, prev ? 'info' : 'success');
}
// ─── Save ───
async function saveShippingDocument() {
if (isKnownDuplicate) {
if (!confirm('Ez a dokumentum már fel lett dolgozva. Új bejegyzést szeretnél létrehozni?')) {
showMessage('Mentés megszakítva.', 'info');
return;
}
}
if (!currentPartner.id) { showMessage('Kérlek válassz partnert a mentés előtt.', 'warning'); return; }
const doc = {
documentIdNumber: document.getElementById('editDocumentIdNumber').value,
partnerId: currentPartner.id,
totalPallets: parseInt(document.getElementById('editTotalPallets').value) || 0,
pdfFileName: document.getElementById('editPdfFileName').value,
shippingItems: shippingItems
};
try {
saveShippingDocumentButton.disabled = true;
saveShippingDocumentButton.innerHTML = '<i class="fas fa-spinner fa-spin mr-1"></i> Mentés...';
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
const formData = new FormData();
formData.append('documentData', JSON.stringify(doc));
formData.append('extractedText', extractedFullText);
if (originalUploadedFile) formData.append('originalFile', originalUploadedFile);
const response = await fetch('@Url.Action("SaveShippingDocument", "FileManager")', {
method: 'POST',
headers: { 'RequestVerificationToken': token },
body: formData
});
const result = await response.json();
if (response.ok && result.success) {
showMessage('Szállítási dokumentum mentve! Újratöltés...', 'success');
setTimeout(() => window.location.reload(), 1500);
} else {
showMessage('Hiba: ' + (result.message || 'Nem sikerült a mentés'), 'danger');
}
} catch (error) {
console.error('Save error:', error);
showMessage('Hiba: Nem sikerült kapcsolódni a szerverhez', 'danger');
} finally {
saveShippingDocumentButton.disabled = false;
saveShippingDocumentButton.innerHTML = '<i class="fas fa-save mr-1"></i> Szállítási dokumentum mentése';
}
}
// ─── Helpers ───
function escapeHtml(text) {
if (!text) return '';
const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
return text.toString().replace(/[&<>"']/g, m => map[m]);
}
function showMessage(message, type) {
responseMessage.textContent = message;
responseMessage.className = 'alert alert-' + type;
responseMessage.style.display = 'block';
setTimeout(() => { responseMessage.style.display = 'none'; }, 5000);
}
</script>