$(document).ready(function () { // =================== // GLOBAL STATE // =================== let partMatrice = {}; let unsavedChanges = false; let matrici = []; let macroMatrici = []; let quotations = []; // --- ROW ID helpers: niente più cache impazzita di jQuery .data() --- function getPartId($row) { // Legge in ordine: cache jQuery, nostra cache, attributo const d = $row.data("part-id"); const ours = $row.data("__pid"); const attr = $row.attr("data-part-id"); // Scegli il primo definito const id = d !== undefined ? d : ours !== undefined ? ours : attr !== undefined ? attr : null; // Normalizza: "new" o "" NON sono ID validi return id === "new" || id === "" || id === null ? null : id; } function setPartId($row, id) { if (!id) return; // Sincronizza TUTTO: attributo + cache jQuery + nostra cache $row.attr("data-part-id", id); $row.data("part-id", id); // <<< fondamentale per invalidare la cache di jQuery $row.data("__pid", id); } // =================== // 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, false); // Aggiunge riga normale per riconoscimento vocale 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) { console.log("Voce non supportata dal browser"); 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(); } } $(document).on("click", "#toggleVoiceBtn", function (e) { console.log("Clicked Voce - Evento triggerato"); e.preventDefault(); toggleVoiceRecognition(); }); // =================== // MODAL HANDLING // =================== function loadParts(iddatadb, idquotations, callback = null) { if (iddatadb) { if (matrici.length === 0) { $.ajax({ url: "get_matrici_db.php", method: "GET", dataType: "json", success: function (data) { matrici = data.value || []; loadMacroMatrici(); initializeGlobalSelect2(); loadPhoto(iddatadb, idquotations); loadExistingParts(iddatadb, idquotations, callback); }, error: function (xhr, status, error) { matrici = []; loadMacroMatrici(); initializeGlobalSelect2(); loadPhoto(iddatadb, idquotations); loadExistingParts(iddatadb, idquotations, callback); const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }, }); } else { loadMacroMatrici(); initializeGlobalSelect2(); loadPhoto(iddatadb, idquotations); loadExistingParts(iddatadb, idquotations, callback); } } else { loadPhoto(iddatadb, idquotations); loadExistingParts(iddatadb, idquotations, callback); } } // 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(); } const annotationsModalElement = document.getElementById("annotationsModal"); if (annotationsModalElement) { console.log("Elemento #annotationsModal trovato nel DOM."); openAnnotationsModal(iddatadb, idquotations, trfHeader); return; } 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); 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.", ); 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 = []; macroMatrici = []; $("#photoSelectorContainer").empty().hide(); $("#samplePhoto").attr("src", ""); $("#partsTableBody").empty(); $("#global-matrice").empty(); $("#macro-matrice-filter").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 // =================== $(document).on("click", "#downloadPhotoBtn", function (e) { console.log("Clicked Download Photo - Evento triggerato"); e.preventDefault(); const photoSrc = $("#samplePhoto").attr("src"); if (!photoSrc) { console.log("Nessuna foto caricata"); 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); console.log("Download avviato per:", photoSrc); }); // =================== // 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, false); // Riga normale }); $(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(), ); // Crea la riga Mix addNewRow(maxPartNumber + 1, true); const $mixRow = $("#partsTableBody tr:last"); // Consenti SOLO ora la creazione (INSERT) della riga Mix $mixRow.data("allowCreateMix", true); // esegue SUBITO l'INSERT così ottieni part-id saveRow($mixRow); }); function extractPartId(response) { // prova i campi più comuni if (response == null) return null; if (response.part_id) return response.part_id; if (response.id) return response.id; if (response.insert_id) return response.insert_id; if (response.lastId) return response.lastId; // array di id if (Array.isArray(response.part_ids) && response.part_ids[0]) { return response.part_ids[0]; } // oggetti annidati if (response.parts && response.parts[0]) { if (response.parts[0].id) return response.parts[0].id; if (response.parts[0].part_id) return response.parts[0].part_id; if (response.parts[0].insert_id) return response.parts[0].insert_id; } // altri possibili nomi dal backend if (response.new_id) return response.new_id; if (response.partId) return response.partId; return null; } function saveRow($row) { const partNumber = $row.find(".part-number").val(); const partDescription = $row.find(".part-description").val().trim(); const dateexpiry = $row.find(".part-dateexpiry").val(); const note = $row.data("note") || null; 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"; let partId = getPartId($row); if (partId === "new") partId = null; // difesa extra (non dovrebbe più servire, ma sicura) const endpoint = idquotations ? "save_parts_quotation.php" : "save_parts.php"; const data = idquotations ? { idquotations } : { iddatadb }; // Evita salvataggi concorrenti if ($row.data("saving") === true) return; // Blocca INSERT del Mix se non esplicitamente richiesta if (isMix === "Y" && !partId && $row.data("allowCreateMix") !== true) { return; } $row.data("saving", true); 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, dateexpiry: dateexpiry || null, note: note, }, ], }), contentType: "application/json", success: function (response) { // assegna ID appena arriva (robusto) if (response.success) { const newId = extractPartId(response); if (newId) { setPartId($row, newId); } else { console.warn( "Parte salvata ma ID non presente nella risposta. Ricarico parti per sincronizzare gli ID.", ); loadExistingParts(iddatadb, idquotations); // <<< ORA ANCHE PER RIGHE NORMALI } } $row.data("saving", false); $row.removeData("allowCreateMix"); // ora che l'ID c'è di sicuro, sblocco gli update pendenti (es. M+) $row.trigger("row:saved"); $saveLoading.hide(); if (response.success) { $saveStatus.show(); setTimeout(() => $saveStatus.hide(), 2000); if (!$("#quotationeBtn").hasClass('d-none')) $("#quotationeBtn").addClass("d-none"); } else { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(() => { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }, error: function (xhr, status, error) { $row.data("saving", false); $row.removeData("allowCreateMix"); $saveLoading.hide(); const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(() => { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }, }); } } $(document).on("click", ".add-mix-row", function (e) { e.preventDefault(); const $srcRow = $(this).closest("tr"); const partDescription = $srcRow.find(".part-description").val().trim(); if (!partDescription) { const errorMsg = $( '', ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout( () => errorMsg.fadeOut(500, function () { $(this).remove(); }), 5000, ); return; } let $mixRow = $("#partsTableBody tr") .filter(function () { return $(this) .find(".part-description") .val() .trim() .startsWith("Mix"); }) .last(); // Se non esiste una riga Mix, ne creo una e la INSERISCO SUBITO (come fa il bottone in header) if ($mixRow.length === 0) { const maxPartNumber = Math.max( ...$("#partsTableBody tr") .map(function () { return ( parseInt($(this).find(".part-number").val()) || 0 ); }) .get(), ); addNewRow(maxPartNumber + 1, true); $mixRow = $("#partsTableBody tr:last"); $mixRow.find(".part-description").val(`Mix ${partDescription}`); // Consenti la creazione (INSERT) della riga Mix e salvala subito $mixRow.data("allowCreateMix", true); saveRow($mixRow); // -> INSERT return; // la descrizione include già l'elemento appena aggiunto } // Aggiorna la descrizione del Mix esistente const currentMix = $mixRow.find(".part-description").val().trim(); let newDesc = currentMix; if (currentMix === "Mix") newDesc = currentMix + " " + partDescription; else if (!currentMix.includes(partDescription)) newDesc = currentMix + " + " + partDescription; $mixRow.find(".part-description").val(newDesc); // Se il Mix è già in salvataggio (INSERT o UPDATE in corso), accodiamo un solo UPDATE if ($mixRow.data("saving") === true) { // evita più code accumulate if (!$mixRow.data("pendingUpdate")) { $mixRow.data("pendingUpdate", true); $mixRow.one("row:saved", function () { $mixRow.removeData("pendingUpdate"); // ora che saving è false, salviamo l'ultima descrizione impostata saveRow($mixRow); }); } } else { // libero: salva subito saveRow($mixRow); } }); function addNewRow(nextPartNumber, isMix = false) { const description = isMix ? "Mix" : ""; const newRow = `
`; $("#partsTableBody").append(newRow); const $select = $("#partsTableBody tr:last .part-matrice"); const selectedMacro = $("#macro-matrice-filter").val() || ""; initializeSelect2($select, nextPartNumber, "", null, selectedMacro); updateRowButtons(); markUnsaved(); } // =================== // NOTE HANDLING // =================== $(document).on("click", ".note-btn", function () { const $row = $(this).closest("tr"); const partId = getPartId($row); const note = $row.data("note") || ""; const $noteModal = $("#noteModal"); $noteModal.find(".part-note").val(note); $noteModal.data("part-id", partId); $noteModal.data("row", $row); const modalInstance = new bootstrap.Modal($noteModal[0], { backdrop: "static", keyboard: false, }); modalInstance.show(); }); $(document).on("click", ".save-note-btn", function () { const $noteModal = $("#noteModal"); const $row = $noteModal.data("row"); const partId = $noteModal.data("part-id"); const note = $noteModal.find(".part-note").val().trim(); const iddatadb = $("#partsModal").data("iddatadb"); const idquotations = $("#partsModal").data("idquotations"); const endpoint = idquotations ? "save_parts_quotation.php" : "save_parts.php"; const data = idquotations ? { idquotations: idquotations } : { iddatadb: iddatadb }; // Raccogli tutti i dati della riga per evitare sovrascritture const partNumber = $row.find(".part-number").val(); const partDescription = $row.find(".part-description").val().trim(); const mix = partDescription.startsWith("Mix") ? "Y" : "N"; const idmatrice = $row.find(".part-matrice").val() || null; const dateexpiry = $row.find(".part-dateexpiry").val() || null; if (partId && partId !== "new") { const $saveStatus = $row.find(".save-status"); const $saveLoading = $row.find(".save-loading"); $saveLoading.show(); $saveStatus.hide(); $.ajax({ url: endpoint, method: "POST", data: JSON.stringify({ ...data, parts: [ { id: partId, part_number: partNumber, part_description: partDescription, mix: mix, idmatrice: idmatrice, note: note || null, dateexpiry: dateexpiry, }, ], }), contentType: "application/json", success: function (response) { $saveLoading.hide(); if (response.success) { $row.data("note", note); $row.find(".note-btn").toggleClass("has-note", !!note); $saveStatus.show(); setTimeout(() => $saveStatus.hide(), 2000); bootstrap.Modal.getInstance( document.getElementById("noteModal"), ).hide(); } 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); }, }); } else { $row.data("note", note); $row.find(".note-btn").toggleClass("has-note", !!note); bootstrap.Modal.getInstance( document.getElementById("noteModal"), ).hide(); markUnsaved(); } }); // =================== // DATEEXPIRY HANDLING // =================== $(document).on("change", ".part-dateexpiry", function () { const $input = $(this); const $row = $input.closest("tr"); const partId = getPartId($row); const dateexpiry = $input.val(); const iddatadb = $("#partsModal").data("iddatadb"); const idquotations = $("#partsModal").data("idquotations"); const endpoint = idquotations ? "save_parts_quotation.php" : "save_parts.php"; const data = idquotations ? { idquotations: idquotations } : { iddatadb: iddatadb }; // Raccogli tutti i dati della riga per evitare sovrascritture const partNumber = $row.find(".part-number").val(); const partDescription = $row.find(".part-description").val().trim(); const mix = partDescription.startsWith("Mix") ? "Y" : "N"; const idmatrice = $row.find(".part-matrice").val() || null; const note = $row.data("note") || null; if (partId && partId !== "new") { const $saveStatus = $row.find(".save-status"); const $saveLoading = $row.find(".save-loading"); $saveLoading.show(); $saveStatus.hide(); $.ajax({ url: endpoint, method: "POST", data: JSON.stringify({ ...data, parts: [ { id: partId, part_number: partNumber, part_description: partDescription, mix: mix, idmatrice: idmatrice, note: note, dateexpiry: dateexpiry || null, }, ], }), contentType: "application/json", success: function (response) { $saveLoading.hide(); if (response.success) { $saveStatus.show(); 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); }, }); } else { markUnsaved(); } }); // =================== // OTHER TABLE HANDLERS // =================== function updateRowButtons() { const rowCount = $("#partsTableBody tr").length; $("#partsTableBody tr").each(function () { const $row = $(this); $row.find(".remove-row").css( "display", rowCount > 1 ? "inline-block" : "none", ); }); } $(document).on("click", ".remove-row", function (e) { e.preventDefault(); console.log("Clic su .remove-row rilevato"); // Debug: verifica clic 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"; // Verifica se il modale esiste const $modal = $("#confirmDeleteModal"); if ($modal.length) { try { // Inizializza il modale Bootstrap in modo esplicito const modalInstance = new bootstrap.Modal($modal[0], { backdrop: "static", keyboard: false, }); // Associa l'handler al pulsante Elimina $("#confirmDeleteBtn") .off("click") .on("click", function () { 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); } modalInstance.hide(); }, error: function (xhr, status, error) { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend( errorMsg, ); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); modalInstance.hide(); }, }); } else { $row.remove(); delete partMatrice[partNumber]; updateRowButtons(); markUnsaved(); modalInstance.hide(); } }); // Mostra il modale modalInstance.show(); } catch (error) { console.error("Errore nell'apertura del modale:", error); alert( "Errore: Impossibile aprire il modale di conferma. Dettagli: " + error.message, ); // Fallback con confirm nativo if (confirm("Sei sicuro di voler eliminare questa parte?")) { 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(); } } } } else { console.error("Modale #confirmDeleteModal non trovato nel DOM"); alert("Errore: Modale di conferma non trovato. Verifica l'HTML."); // Fallback con confirm nativo if (confirm("Sei sicuro di voler eliminare questa parte?")) { 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 () { saveRow($(this).closest("tr")); }); function loadExistingParts(iddatadb, idquotations, callback = null) { 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) => { console.log( "Caricamento parte:", part.id, "Numero:", part.part_number, "Descrizione:", part.part_description, "idmatrice:", part.idmatrice || "non definito", "note:", part.note || "non definito", "dateexpiry:", part.dateexpiry || "non definito", ); // Escape HTML per evitare problemi con caratteri speciali const escapedDescription = $("
") .text(part.part_description || "") .html(); const escapedNote = part.note ? $("
").text(part.note).html() : ""; const newRow = `
`; $("#partsTableBody").append(newRow); const $select = $("#partsTableBody").find( `tr[data-part-id="${part.id}"] .part-matrice`, ); const selectedMacro = $("#macro-matrice-filter").val() || ""; initializeSelect2( $select, part.part_number, part.id, part.idmatrice || null, selectedMacro, ); if (part.idmatrice) { partMatrice[part.part_number] = part.idmatrice; } }); } else { addNewRow(1, false); // Riga iniziale normale $("#quotationeBtn").removeClass("d-none"); } updateRowButtons(); if (callback) callback(); }, error: function (xhr, status, error) { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); addNewRow(1, false); // Riga iniziale normale }, }); } function loadMacroMatrici() { $.ajax({ url: "get_macro_matrici.php", method: "GET", dataType: "json", success: function (data) { macroMatrici = data.value || []; initializeMacroMatriceFilter(); }, error: function (xhr, status, error) { console.error( "Errore nel caricamento delle MacroMatrici:", error, ); macroMatrici = []; initializeMacroMatriceFilter(); const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }, }); } function initializeMacroMatriceFilter() { const $select = $("#macro-matrice-filter"); if ($select.length === 0) return; // Ignora se il filtro non è presente nell'HTML if (typeof $.fn.select2 === "undefined") { $select.replaceWith( '', ); return; } // Popola il dropdown MacroMatrice const options = [ { id: "", text: "Tutte le MacroMatrici" }, ...macroMatrici.map(function (macro) { return { id: macro, text: macro }; }), ]; // Distrugge Select2 esistente, se presente if ($select.hasClass("select2-hidden-accessible")) { $select.select2("destroy"); } $select.empty().select2({ placeholder: "Seleziona MacroMatrice", allowClear: true, data: options, dropdownParent: $("#partsModal"), }); $select.on("change", function () { const selectedMacro = $(this).val() || ""; console.log("Filtro MacroMatrice cambiato:", selectedMacro); // Aggiorna global-matrice initializeGlobalSelect2(selectedMacro); // Aggiorna tutti i part-matrice $("#partsTableBody .part-matrice").each(function () { const $partSelect = $(this); const partNumber = $partSelect .closest("tr") .find(".part-number") .val(); const partId = $partSelect.closest("tr").data("part-id"); const currentValue = $partSelect.val() || partMatrice[partNumber] || null; initializeSelect2( $partSelect, partNumber, partId, currentValue, selectedMacro, true ); }); }); } function initializeGlobalSelect2(selectedMacro = null) { const $select = $("#global-matrice"); if (typeof $.fn.select2 === "undefined") { $select.replaceWith( '', ); return; } // Filtra le matrici in base alla MacroMatrice selezionata const filteredMatrici = selectedMacro ? matrici.filter( (matrice) => matrice.MacroMatrice === selectedMacro, ) : matrici; // Crea opzioni per Select2 const options = filteredMatrici.map(function (matrice) { return { id: matrice.IdMatrice, text: matrice.NomeMatrice }; }); // Memorizza il valore corrente const currentValue = $select.val(); // Distrugge Select2 esistente, se presente if ($select.hasClass("select2-hidden-accessible")) { $select.select2("destroy"); } // Inizializza Select2 $select.empty().select2({ placeholder: filteredMatrici.length ? "Seleziona matrice globale" : "Nessuna matrice disponibile", allowClear: true, data: options, dropdownParent: $("#partsModal"), minimumResultsForSearch: 0, // Abilita ricerca senza limite minimo di caratteri matcher: function (params, data) { if (!params.term) return data; // Mostra tutte le opzioni se non c'è termine di ricerca const term = params.term.toUpperCase(); if (data.text.toUpperCase().indexOf(term) >= 0) return data; return null; }, }); // Ripristina il valore corrente se valido if ( currentValue && filteredMatrici.some((m) => m.IdMatrice == currentValue) ) { $select.val(currentValue).trigger("change"); } else if (filteredMatrici.length === 0) { const errorMsg = $( '', ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } } function initializeSelect2( $select, partNumber, partId, idmatrice, selectedMacro = null, fromFilter = false ) { if (typeof $.fn.select2 === "undefined") { $select.replaceWith( '', ); return; } // Filtra le matrici in base alla MacroMatrice selezionata const filteredMatrici = selectedMacro ? matrici.filter((m) => m.MacroMatrice === selectedMacro) : matrici; // Crea opzioni per Select2, includendo il valore pre-selezionato se non è nel filtro let options = filteredMatrici.map(function (matrice) { return { id: matrice.IdMatrice, text: matrice.NomeMatrice }; }); // Se idmatrice esiste ed è fuori dal filtro, aggiungilo come opzione if ( idmatrice && !filteredMatrici.some((m) => m.IdMatrice == idmatrice) ) { const selectedMatrice = matrici.find( (m) => m.IdMatrice == idmatrice, ); if (selectedMatrice) { options.push({ id: selectedMatrice.IdMatrice, text: selectedMatrice.NomeMatrice, }); } } // Memorizza il valore corrente const currentValue = idmatrice || $select.val(); // Distrugge Select2 esistente, se presente if ($select.hasClass("select2-hidden-accessible")) { $select.select2("destroy"); } // Inizializza Select2 $select.empty().select2({ placeholder: filteredMatrici.length ? "Seleziona matrice" : "Nessuna matrice disponibile", allowClear: true, data: options, dropdownParent: $("#partsModal"), minimumResultsForSearch: 0, // Abilita ricerca senza limite minimo di caratteri matcher: function (params, data) { if (!params.term) return data; // Mostra tutte le opzioni se non c'è termine di ricerca const term = params.term.toUpperCase(); if (data.text.toUpperCase().indexOf(term) >= 0) return data; return null; }, }); // Ripristina il valore se valido if (partId && partId !== "new" && currentValue) { const matrice = matrici.find((m) => m.IdMatrice == currentValue); if (matrice) { const option = new Option( matrice.NomeMatrice, matrice.IdMatrice, true, true, ); if (!fromFilter) $select.append(option).trigger("change"); else $select.append(option); partMatrice[partNumber] = matrice.IdMatrice; } else { // Aggiusta valore non valido if (!fromFilter) $select.val(null).trigger("change"); partMatrice[partNumber] = null; } } else { $select.val(null).trigger("change", [{ skipHandler: true }]); } $select.on("change", function (event, data) { if (data && data?.skipHandler) return; 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(); }, }); } }); // Mostra messaggio se non ci sono matrici if (filteredMatrici.length === 0 && selectedMacro) { const errorMsg = $( '', ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } // Debug per verificare inizializzazione console.log( "Inizializzo Select2 per partId:", partId, "con idmatrice:", idmatrice, "Macro:", selectedMacro, ); } $(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); } }); $(document).on("click", ".propagate-all-btn", function () { const globalVal = $("#global-matrice").val(); if (globalVal) { $("#partsTableBody .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() { console.log( "Inizio rinumera parts, numero righe:", $("#partsTableBody tr").length, ); 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() .trim(), partId: $row.data("part-id") === "new" || $row.data("part-id") === "" ? null : $row.data("part-id"), note: $row.data("note") || null, dateexpiry: $row.find(".part-dateexpiry").val() || null, }; }) .get(); console.log("Parts to save prima di rinumerazione:", partsData); 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, index) => ({ id: part.partId, part_number: index + 1, part_description: part.partDescription, mix: part.partDescription.startsWith("Mix") ? "Y" : "N", idmatrice: partMatrice[index + 1] || null, note: part.note, dateexpiry: part.dateexpiry, })); console.log("Parts to save inviati al server:", partsToSave); $.ajax({ url: endpoint, method: "POST", data: JSON.stringify({ ...data, parts: partsToSave }), contentType: "application/json", success: function (response) { console.log("Risposta server:", response); if (response.success && response.part_ids) { $rows.each(function (index) { const $row = $(this); const newPartId = 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); }); $rows .find(".part-number, .part-description") .trigger("blur"); unsavedChanges = false; } else { console.error("Errore risposta server:", response.message); const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }, error: function (xhr, status, error) { console.error( "Errore AJAX rinumerazione:", error, xhr.responseText, ); const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }, }); } $(document).on("click", "#renumberPartsBtn", function (e) { console.log("Clicked Rinumera Parti - Evento triggerato"); e.preventDefault(); 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-mix-row, .remove-row, .propagate-matrice-btn, .propagate-all-btn, .note-btn", markUnsaved, ); // Esporta la funzione loadParts per essere usata da import_Edit2.php window.loadParts = loadParts; $(document).on("click", "#quotationeBtn", function () { $("#addQuotationModal").modal("show"); if (quotations.length > 0) { reloadQuotations(); } else { $.ajax({ url: "load_quotations.php", method: "GET", success: function (response) { quotations = response?.quotations || []; reloadQuotations(); }, error: function (xhr, status, error) { console.error("Errore AJAX caricamento quotations:", error, xhr.responseText); let message = ` `; $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { message.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }); } }); $(document).on("click", "#addQuotationBtn", function () { const quotationId = $("#addQuotationSelect").val(); if (quotationId && confirm("Confermo di collegare la quotazione al campione?")) { $("#addQuotationModal").modal("hide"); loadParts(null, quotationId, () => { $("#quotationeBtn").addClass("d-none"); setTimeout(() => { $("#partsTableBody tr").each(function () { $(this).find(".save-loading").show(); }); let photoList = $('#photoSelector option').map(function () { return this.value?.split('/')?.pop(); }).get(); if (!photoList.length) { let path = $('#samplePhoto').attr("src")?.split('/')?.pop(); if (path) photoList = [path]; } $.ajax({ url: "save_parts_photo_iddatadb.php", method: "POST", data: JSON.stringify({ iddatadb: $("#partsModal").data("iddatadb"), photoList, partIds: $('#partsTableBody tr').map(function () { return $(this).data("part-id"); }).get(), }), success: function () { $("#partsTableBody tr").each(function () { let $row = $(this); let $saveStatus = $row.find(".save-status"); let $saveLoading = $row.find(".save-loading"); $saveLoading.hide(); $saveStatus.show(); setTimeout(() => $saveStatus.hide(), 2000); }); }, error: function (xhr, status, error) { let message = ` `; $("#partsModal .modal-body").prepend(message); setTimeout(function () { message.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }); }, 100); }); } }); function reloadQuotations() { if (quotations.length > 0) { $("#addQuotationSelect").empty(); $("#addQuotationSelect").append(""); quotations.forEach(quotation => { if (quotation?.description) { $("#addQuotationSelect").append(``); } }); $("#addQuotationSelect").select2(); } } }); $(document).on("change", ".propagate-date-input", function () { const dateexpiry = $(this).val(); const iddatadb = $("#partsModal").data("iddatadb"); const idquotations = $("#partsModal").data("idquotations"); const endpoint = idquotations ? "save_parts_quotation.php" : "save_parts.php"; const data = idquotations ? { idquotations: idquotations } : { iddatadb: iddatadb }; const partsToSave = []; $("#partsTableBody tr").each(function () { const $row = $(this); const partId = $row.data("part-id"); const partNumber = $row.find(".part-number").val(); const partDescription = $row.find(".part-description").val().trim(); const mix = partDescription.startsWith("Mix") ? "Y" : "N"; const idmatrice = $row.find(".part-matrice").val() || null; const note = $row.data("note") || null; partsToSave.push({ id: partId && partId !== "new" ? partId : null, part_number: partNumber, part_description: partDescription, mix: mix, idmatrice: idmatrice, note: note, dateexpiry: dateexpiry || null, }); if (partId && partId !== "new") { $row.find(".save-loading").show(); $row.find(".save-status").hide(); } }); if (partsToSave.length > 0) { $.ajax({ url: endpoint, method: "POST", data: JSON.stringify({ ...data, parts: partsToSave, }), contentType: "application/json", success: function (response) { $("#partsTableBody tr").each(function () { const $row = $(this); const partId = $row.data("part-id"); if (partId && partId !== "new") { $row.find(".save-loading").hide(); $row.find(".save-status").show(); setTimeout( () => $row.find(".save-status").hide(), 2000, ); } $row.find(".part-dateexpiry").val(dateexpiry); }); if (!response.success) { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }, error: function (xhr, status, error) { $("#partsTableBody tr").each(function () { const $row = $(this); $row.find(".save-loading").hide(); }); const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }, }); } else { $("#partsTableBody tr").find(".part-dateexpiry").val(dateexpiry); markUnsaved(); } }); $(document).on("click", ".propagate-note-btn", function () { const $commonNoteModal = $("#commonNoteModal"); $commonNoteModal.find(".part-note").val(""); const modalInstance = new bootstrap.Modal($commonNoteModal[0], { backdrop: "static", keyboard: false, }); modalInstance.show(); }); $(document).on("click", ".save-common-note-btn", function () { const $commonNoteModal = $("#commonNoteModal"); const note = $commonNoteModal.find(".part-note").val().trim(); const iddatadb = $("#partsModal").data("iddatadb"); const idquotations = $("#partsModal").data("idquotations"); const endpoint = idquotations ? "save_parts_quotation.php" : "save_parts.php"; const data = idquotations ? { idquotations: idquotations } : { iddatadb: iddatadb }; const partsToSave = []; $("#partsTableBody tr").each(function () { const $row = $(this); const partId = $row.data("part-id"); const partNumber = $row.find(".part-number").val(); const partDescription = $row.find(".part-description").val().trim(); const mix = partDescription.startsWith("Mix") ? "Y" : "N"; const idmatrice = $row.find(".part-matrice").val() || null; const dateexpiry = $row.find(".part-dateexpiry").val() || null; partsToSave.push({ id: partId && partId !== "new" ? partId : null, part_number: partNumber, part_description: partDescription, mix: mix, idmatrice: idmatrice, note: note || null, dateexpiry: dateexpiry, }); if (partId && partId !== "new") { $row.find(".save-loading").show(); $row.find(".save-status").hide(); } }); if (partsToSave.length > 0) { $.ajax({ url: endpoint, method: "POST", data: JSON.stringify({ ...data, parts: partsToSave, }), contentType: "application/json", success: function (response) { $("#partsTableBody tr").each(function () { const $row = $(this); const partId = $row.data("part-id"); if (partId && partId !== "new") { $row.find(".save-loading").hide(); $row.find(".save-status").show(); setTimeout( () => $row.find(".save-status").hide(), 2000, ); } $row.data("note", note); $row.find(".note-btn").toggleClass("has-note", !!note); }); bootstrap.Modal.getInstance( document.getElementById("commonNoteModal"), ).hide(); if (!response.success) { const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }, error: function (xhr, status, error) { $("#partsTableBody tr").each(function () { const $row = $(this); $row.find(".save-loading").hide(); }); const errorMsg = $( '", ); $("#partsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }, }); } else { $("#partsTableBody tr").each(function () { const $row = $(this); $row.data("note", note); $row.find(".note-btn").toggleClass("has-note", !!note); }); bootstrap.Modal.getInstance( document.getElementById("commonNoteModal"), ).hide(); markUnsaved(); } }); $(document).on("click", "#showHideImageBtn", function () { let mainRow = $(this).closest(".parts-row"); let photoContainer = mainRow.find(".col-md-3"); let tableContainer = mainRow.find("#partsTable").closest("div[class*='col-md']"); if (photoContainer.hasClass("d-none")) { photoContainer.removeClass("d-none"); tableContainer.removeClass("col-md-12").addClass("col-md-9"); $(this).html(""); } else { photoContainer.addClass("d-none"); tableContainer.removeClass("col-md-9").addClass("col-md-12"); $(this).html(""); } });