942 lines
39 KiB
Plaintext
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 = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
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>
|