$(document).ready(function () { // =================== // GLOBAL STATE // =================== let partMatrice = {}; let unsavedChanges = false; let matrici = []; // =================== // VOICE RECOGNITION SETUP // =================== const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; let recognition = null; let isVoiceActive = false; const magicWord = "salva"; if (SpeechRecognition) { recognition = new SpeechRecognition(); recognition.lang = "it-IT"; recognition.continuous = true; recognition.interimResults = false; recognition.onresult = function (event) { const transcript = event.results[ event.results.length - 1 ][0].transcript .trim() .toLowerCase(); const $currentRow = $("#partsTableBody tr:last"); const $descriptionInput = $currentRow.find(".part-description"); if (transcript.includes(magicWord)) { const cleanedTranscript = transcript .replace(magicWord, "") .trim(); if (cleanedTranscript) { $descriptionInput.val( ( $descriptionInput.val() + " " + cleanedTranscript ).trim(), ); $descriptionInput.trigger("blur"); } const maxPartNumber = Math.max( ...$("#partsTableBody tr") .map(function () { return ( parseInt($(this).find(".part-number").val()) || 0 ); }) .get(), ); addNewRow(maxPartNumber + 1); const $newRow = $("#partsTableBody tr:last"); $newRow.find(".part-description").focus(); } else { $descriptionInput.val( ($descriptionInput.val() + " " + transcript).trim(), ); $descriptionInput.trigger("blur"); } }; recognition.onerror = function (event) { if (event.error === "no-speech" || event.error === "aborted") { if (isVoiceActive) recognition.start(); } else { alert("Errore nel riconoscimento vocale: " + event.error); toggleVoiceRecognition(); } }; recognition.onend = function () { if (isVoiceActive) recognition.start(); }; } else { $("#toggleVoiceBtn").hide(); } function toggleVoiceRecognition() { if (!recognition) return; isVoiceActive = !isVoiceActive; const $btn = $("#toggleVoiceBtn"); if (isVoiceActive) { $btn.addClass("btn-danger").html( ' Stop Voce', ); recognition.start(); $("#partsTableBody tr:last").find(".part-description").focus(); } else { $btn.removeClass("btn-danger") .addClass("btn-secondary") .html(' Voce'); recognition.stop(); } } $("#toggleVoiceBtn").on("click", toggleVoiceRecognition); // =================== // MODAL HANDLING // =================== function loadParts(iddatadb, idquotations) { if (iddatadb) { if (matrici.length === 0) { $.ajax({ url: "get_matrici_db.php", method: "GET", dataType: "json", success: function (data) { matrici = data.value || []; initializeGlobalSelect2(); loadPhoto(iddatadb, idquotations); loadExistingParts(iddatadb, idquotations); }, error: function (xhr, status, error) { matrici = []; initializeGlobalSelect2(); loadPhoto(iddatadb, idquotations); loadExistingParts(iddatadb, idquotations); const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }, }); } else { initializeGlobalSelect2(); loadPhoto(iddatadb, idquotations); loadExistingParts(iddatadb, idquotations); } } else { loadPhoto(iddatadb, idquotations); loadExistingParts(iddatadb, idquotations); } } // EVENTO PER APRIRE IL SECONDO MODALE $(document).on("click", "#openAnnotationsBtn", function () { console.log("Clic su Apri Annotazioni..."); const iddatadb = $("#partsModal").data("iddatadb"); const idquotations = $("#partsModal").data("idquotations"); const trfHeader = $("#trfHeader").text(); console.log("Dati recuperati da partsModal:", { iddatadb, idquotations, trfHeader, }); const partsModal = bootstrap.Modal.getInstance( document.getElementById("partsModal"), ); if (partsModal) { partsModal.hide(); } // Verifica se annotationsModal esiste nel DOM const annotationsModalElement = document.getElementById("annotationsModal"); if (annotationsModalElement) { console.log("Elemento #annotationsModal trovato nel DOM."); openAnnotationsModal(iddatadb, idquotations, trfHeader); return; } // Carica dinamicamente modal_annotations.php se non è già presente console.log("Caricamento dinamico di modal_annotations.php..."); $.ajax({ url: "modal_annotations.php", method: "GET", cache: false, beforeSend: function () { console.log("Inizio richiesta AJAX per modal_annotations.php"); }, success: function (response) { console.log( "modal_annotations.php caricato con successo:", response.substring(0, 100) + "...", ); $("#annotationsModalContainer").html(response); // Verifica nuovamente la presenza di #annotationsModal const annotationsModalElementAfterLoad = document.getElementById("annotationsModal"); if (!annotationsModalElementAfterLoad) { console.error( "Errore: #annotationsModal non trovato nel DOM dopo il caricamento.", ); alert( "Errore: Il modale delle annotazioni non è stato caricato correttamente. Controlla il contenuto di modal_annotations.php.", ); return; } openAnnotationsModal(iddatadb, idquotations, trfHeader); }, error: function (xhr, status, error) { console.error( "Errore nel caricamento di modal_annotations.php:", { status: status, statusCode: xhr.status, error: error, responseText: xhr.responseText, }, ); alert( "Errore nel caricamento del modale delle annotazioni: " + error + " (Codice: " + xhr.status + ")", ); }, }); }); function openAnnotationsModal(iddatadb, idquotations, trfHeader) { console.log("Tentativo di aprire annotationsModal con:", { iddatadb, idquotations, trfHeader, }); const annotationsModalElement = document.getElementById("annotationsModal"); if (!annotationsModalElement) { console.error( "Elemento #annotationsModal non trovato nel DOM durante openAnnotationsModal.", ); alert( "Errore: Modale annotazioni non trovato dopo il caricamento.", ); return; } if (typeof window.initAnnotationsModal === "function") { console.log("Chiamata a window.initAnnotationsModal..."); window.initAnnotationsModal(iddatadb, idquotations, trfHeader); } else { console.error( "initAnnotationsModal non definito. Verifica che annotationsModal.js sia caricato correttamente e definisca window.initAnnotationsModal.", ); alert( "Errore: Funzione initAnnotationsModal non trovata. Controlla che annotationsModal.js sia incluso correttamente.", ); } } $("#partsModal").on("hide.bs.modal", function (e) { if ( unsavedChanges && !confirm("Hai modifiche non salvate. Vuoi davvero uscire?") ) { e.preventDefault(); } }); $("#partsModal").on("hidden.bs.modal", function () { partMatrice = {}; unsavedChanges = false; matrici = []; $("#photoSelectorContainer").empty().hide(); $("#samplePhoto").attr("src", ""); $("#partsTableBody").empty(); $("#global-matrice").empty(); $(".temp-alert").remove(); $(".modal-backdrop").remove(); $("body").removeClass("modal-open").css("padding-right", ""); $(":focus").blur(); }); // =================== // PHOTO LOADERS // =================== function loadPhoto(iddatadb, idquotations) { const currentPhoto = $("#samplePhoto").attr("src"); const endpoint = idquotations ? "load_photo_quotation.php" : "load_photo.php"; const data = idquotations ? { idquotations: idquotations } : { iddatadb: iddatadb }; $.ajax({ url: endpoint, method: "GET", data: data, success: function (response) { if (response.success) { if (response.photos && response.photos.length > 1) { showPhotoSelector(response.photos, currentPhoto); } else if ( response.photos && response.photos.length === 1 ) { loadSinglePhoto(response.photos[0]); } else { $("#samplePhoto").attr("src", ""); const errorMsg = $( '', ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } } else { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }, error: function (xhr, status, error) { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }, }); } function showPhotoSelector(photos, selected = null) { const selectorContainer = $("#photoSelectorContainer"); selectorContainer.empty().show(); const selector = $( '', ); photos.forEach((photo, index) => { const photoName = photo.split("/").pop(); const option = $("") .val(photo) .text(`Photo ${index + 1} - ${photoName}`); selector.append(option); }); selector.on("change", function () { loadSinglePhoto($(this).val()); }); selectorContainer.append(selector); const photoToSelect = selected && photos.includes(selected) ? selected : photos[0]; if (photoToSelect) { selector.val(photoToSelect); loadSinglePhoto(photoToSelect); } } function loadSinglePhoto(photoPath) { const img = $("#samplePhoto"); img.off("load").attr("src", photoPath); } // =================== // DOWNLOAD PHOTO // =================== $("#downloadPhotoBtn").on("click", function () { const photoSrc = $("#samplePhoto").attr("src"); if (!photoSrc) { const errorMsg = $( '', ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } const photoName = photoSrc.split("/").pop(); const link = document.createElement("a"); link.href = photoSrc; link.download = photoName; document.body.appendChild(link); link.click(); document.body.removeChild(link); }); // =================== // PARTS TABLE // =================== $(document).on("click", ".add-row-global", function (e) { e.preventDefault(); const maxPartNumber = Math.max( ...$("#partsTableBody tr") .map(function () { return parseInt($(this).find(".part-number").val()) || 0; }) .get(), ); addNewRow(maxPartNumber + 1); }); $(document).on("click", ".add-mix-global", function (e) { e.preventDefault(); const maxPartNumber = Math.max( ...$("#partsTableBody tr") .map(function () { return parseInt($(this).find(".part-number").val()) || 0; }) .get(), ); addNewRow(maxPartNumber + 1, true); }); function addNewRow(nextPartNumber, isMix = false) { const description = isMix ? "Mix" : ""; const newRow = `
`; $("#partsTableBody").append(newRow); const $select = $("#partsTableBody tr:last .part-matrice"); initializeSelect2($select, nextPartNumber, "", null); updateRowButtons(); markUnsaved(); } function updateRowButtons() { const rowCount = $("#partsTableBody tr").length; $("#partsTableBody tr").each(function () { $(this) .find(".remove-row") .toggle(rowCount > 1); }); } $(document).on("click", ".add-to-mix-row", function (e) { e.preventDefault(); const $row = $(this).closest("tr"); const partDescription = $row.find(".part-description").val().trim(); if (!partDescription) { const errorMsg = $( '', ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } const $mixRow = $("#partsTableBody tr") .filter(function () { return $(this) .find(".part-description") .val() .trim() .startsWith("Mix"); }) .last(); if ($mixRow.length === 0) { const errorMsg = $( '', ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } let mixDescription = $mixRow.find(".part-description").val().trim(); if (mixDescription === "Mix") { mixDescription = `Mix ${partDescription}`; } else if (!mixDescription.includes(partDescription)) { mixDescription += ` + ${partDescription}`; } $mixRow.find(".part-description").val(mixDescription).trigger("blur"); }); $(document).on("click", ".remove-row", function (e) { e.preventDefault(); const $row = $(this).closest("tr"); const partId = $row.data("part-id"); const partNumber = $row.find(".part-number").val(); const iddatadb = $("#partsModal").data("iddatadb"); const idquotations = $("#partsModal").data("idquotations"); const endpoint = idquotations ? "delete_part_quotation.php" : "delete_part.php"; if (partId && partId !== "new") { $.ajax({ url: endpoint, method: "POST", data: JSON.stringify({ part_id: partId }), contentType: "application/json", success: function (response) { if (response.success) { $row.remove(); delete partMatrice[partNumber]; updateRowButtons(); markUnsaved(); } else { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }, error: function (xhr, status, error) { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }, }); } else { $row.remove(); delete partMatrice[partNumber]; updateRowButtons(); markUnsaved(); } }); $(document).on("blur", ".part-description, .part-number", function () { const $input = $(this); const $row = $input.closest("tr"); const partNumber = $row.find(".part-number").val(); const partDescription = $row.find(".part-description").val().trim(); const $saveStatus = $row.find(".save-status"); const $saveLoading = $row.find(".save-loading"); const iddatadb = $("#partsModal").data("iddatadb"); const idquotations = $("#partsModal").data("idquotations"); const isMix = partDescription.startsWith("Mix") ? "Y" : "N"; const partId = $row.data("part-id") || null; const endpoint = idquotations ? "save_parts_quotation.php" : "save_parts.php"; const data = idquotations ? { idquotations: idquotations } : { iddatadb: iddatadb }; if (partDescription && (iddatadb || idquotations)) { $saveLoading.show(); $saveStatus.hide(); $.ajax({ url: endpoint, method: "POST", data: JSON.stringify({ ...data, parts: [ { id: partId, part_number: partNumber, part_description: partDescription, mix: isMix, }, ], }), contentType: "application/json", success: function (response) { $saveLoading.hide(); if (response.success) { $saveStatus.show(); if (response.part_id) { $row.attr("data-part-id", response.part_id).data( "part-id", response.part_id, ); } setTimeout(() => $saveStatus.hide(), 2000); } else { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }, error: function (xhr, status, error) { $saveLoading.hide(); const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }, }); } }); function loadExistingParts(iddatadb, idquotations) { const endpoint = idquotations ? "load_parts_quotation.php" : "load_parts.php"; const data = idquotations ? { idquotations: idquotations } : { iddatadb: iddatadb }; $.ajax({ url: endpoint, method: "GET", data: data, success: function (response) { $("#partsTableBody").empty(); if ( response.success && response.parts && response.parts.length > 0 ) { response.parts.forEach((part) => { const newRow = `
`; $("#partsTableBody").append(newRow); const $select = $("#partsTableBody").find( `tr[data-part-id="${part.id}"] .part-matrice`, ); initializeSelect2( $select, part.part_number, part.id, part.idmatrice, ); if (part.idmatrice) { partMatrice[part.part_number] = part.idmatrice; } }); } else { addNewRow(1); } updateRowButtons(); }, error: function (xhr, status, error) { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); addNewRow(1); }, }); } function initializeSelect2($select, partNumber, partId, idmatrice) { if (typeof $.fn.select2 === "undefined") { $select.replaceWith( '', ); return; } const options = matrici.map(function (matrice) { return { id: matrice.IdMatrice, text: matrice.NomeMatrice }; }); $select.select2({ placeholder: "Seleziona matrice", allowClear: true, data: options, dropdownParent: $("#partsModal"), matcher: function (params, data) { if (!params.term || params.term.length < 3) return data; const term = params.term.toUpperCase(); if (data.text.toUpperCase().indexOf(term) >= 0) return data; return null; }, }); if (partId && partId !== "new" && idmatrice) { const matrice = matrici.find((m) => m.IdMatrice == idmatrice); if (matrice) { const option = new Option( matrice.NomeMatrice, matrice.IdMatrice, true, true, ); $select.append(option).trigger("change"); partMatrice[partNumber] = matrice.IdMatrice; } } $select.on("change", function () { const idmatrice = $(this).val(); const $row = $(this).closest("tr"); const partId = $row.data("part-id"); const partNumber = $row.find(".part-number").val(); const $saveStatus = $row.find(".save-status"); const $saveLoading = $row.find(".save-loading"); partMatrice[partNumber] = idmatrice || null; if (partId && partId !== "new") { $saveLoading.show(); $saveStatus.hide(); const iddatadb = $("#partsModal").data("iddatadb"); const idquotations = $("#partsModal").data("idquotations"); const endpoint = idquotations ? "save_matrice_quotation.php" : "save_matrice.php"; const data = idquotations ? { idquotations: idquotations } : { iddatadb: iddatadb }; $.ajax({ url: endpoint, method: "POST", data: JSON.stringify({ ...data, parts: [{ id: partId, idmatrice: idmatrice || null }], }), contentType: "application/json", success: function (response) { if (response.success) { $saveLoading.hide(); $saveStatus.show(); setTimeout(() => $saveStatus.hide(), 2000); } else { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); $saveLoading.hide(); } }, error: function (xhr, status, error) { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); $saveLoading.hide(); }, }); } }); } function initializeGlobalSelect2() { const $select = $("#global-matrice"); if (typeof $.fn.select2 === "undefined") { $select.replaceWith( '', ); return; } const options = matrici.map(function (matrice) { return { id: matrice.IdMatrice, text: matrice.NomeMatrice }; }); $select.select2({ placeholder: "Seleziona matrice globale", allowClear: true, data: options, dropdownParent: $("#partsModal"), matcher: function (params, data) { if (!params.term || params.term.length < 3) return data; const term = params.term.toUpperCase(); if (data.text.toUpperCase().indexOf(term) >= 0) return data; return null; }, }); } $(document).on("click", ".propagate-matrice-btn", function () { const $row = $(this).closest("tr"); const globalVal = $("#global-matrice").val(); if (globalVal) { $row.find(".part-matrice").val(globalVal).trigger("change"); } else { const errorMsg = $( '', ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }); function renumberParts() { const $rows = $("#partsTableBody tr"); const iddatadb = $("#partsModal").data("iddatadb"); const idquotations = $("#partsModal").data("idquotations"); const endpoint = idquotations ? "renumber_parts_quotation.php" : "renumber_parts.php"; const data = idquotations ? { idquotations: idquotations } : { iddatadb: iddatadb }; let newPartMatrice = {}; let partsData = $rows .map(function (index) { const $row = $(this); return { partNumber: $row.find(".part-number").val(), partDescription: $row.find(".part-description").val(), partId: $row.data("part-id"), }; }) .get(); partsData.forEach((part, index) => { const newNumber = index + 1; newPartMatrice[newNumber] = partMatrice[part.partNumber] || null; part.partNumber = newNumber; }); $rows.each(function (index) { $(this) .find(".part-number") .val(index + 1); }); partMatrice = newPartMatrice; const partsToSave = partsData.map((part) => ({ id: part.partId || null, part_number: part.partNumber, part_description: part.partDescription, mix: part.partDescription.startsWith("Mix") ? "Y" : "N", idmatrice: partMatrice[part.partNumber] || null, })); $.ajax({ url: endpoint, method: "POST", data: JSON.stringify({ ...data, parts: partsToSave }), contentType: "application/json", success: function (response) { if (response.success) { $rows.each(function (index) { const $row = $(this); const newPartId = response.part_ids && response.part_ids[index] ? response.part_ids[index] : $row.data("part-id"); if (newPartId) { $row.attr("data-part-id", newPartId).data( "part-id", newPartId, ); } const $saveStatus = $row.find(".save-status"); const $saveLoading = $row.find(".save-loading"); $saveLoading.hide(); $saveStatus.show(); setTimeout(() => $saveStatus.hide(), 2000); }); markUnsaved(); } else { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }, error: function (xhr, status, error) { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }, }); } $("#renumberPartsBtn").on("click", renumberParts); function markUnsaved() { if (!unsavedChanges) { unsavedChanges = true; } } $(document).on( "input change", "#partsTableBody input, #partsTableBody select", markUnsaved, ); $(document).on( "click", ".add-row-global, .add-mix-global, .add-to-mix-row, .remove-row, .propagate-matrice-btn", markUnsaved, ); // Esporta la funzione loadParts per essere usata da import_Edit2.php window.loadParts = loadParts; });