From 8d6fe924813a5ecddafa873537f3927c15cbcca0 Mon Sep 17 00:00:00 2001 From: solocla Date: Tue, 28 Oct 2025 08:58:39 +0100 Subject: [PATCH] fixed multiple parts mix and single line --- public/userarea/partsTable.js | 356 ++++++++++++++++++++++------------ 1 file changed, 237 insertions(+), 119 deletions(-) diff --git a/public/userarea/partsTable.js b/public/userarea/partsTable.js index 91112f6..74a4e1a 100644 --- a/public/userarea/partsTable.js +++ b/public/userarea/partsTable.js @@ -7,6 +7,35 @@ $(document).ready(function () { let matrici = []; let macroMatrici = []; + // --- 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 // =================== @@ -439,6 +468,7 @@ $(document).ready(function () { $(document).on("click", ".add-mix-global", function (e) { e.preventDefault(); + const maxPartNumber = Math.max( ...$("#partsTableBody tr") .map(function () { @@ -446,36 +476,177 @@ $(document).ready(function () { }) .get(), ); - addNewRow(maxPartNumber + 1, true); // Riga Mix + + // 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); + } 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 $row = $(this).closest("tr"); - const partDescription = $row.find(".part-description").val().trim(); + + const $srcRow = $(this).closest("tr"); + const partDescription = $srcRow.find(".part-description").val().trim(); if (!partDescription) { const errorMsg = $( '', ); $("#partsModal .modal-body").prepend(errorMsg); - setTimeout(function () { - errorMsg.fadeOut(500, function () { - $(this).remove(); - }); - }, 5000); + setTimeout( + () => + errorMsg.fadeOut(500, function () { + $(this).remove(); + }), + 5000, + ); return; } - const maxPartNumber = Math.max( - ...$("#partsTableBody tr") - .map(function () { - return parseInt($(this).find(".part-number").val()) || 0; - }) - .get(), - ); - - let mixDescription = `Mix ${partDescription}`; - const $mixRow = $("#partsTableBody tr") + let $mixRow = $("#partsTableBody tr") .filter(function () { return $(this) .find(".part-description") @@ -485,31 +656,59 @@ $(document).ready(function () { }) .last(); - if ($mixRow.length > 0) { - let currentMix = $mixRow.find(".part-description").val().trim(); - if (currentMix === "Mix") { - mixDescription = currentMix + " " + partDescription; - } else if (!currentMix.includes(partDescription)) { - mixDescription = currentMix + " + " + partDescription; + // 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); + }); } - $mixRow - .find(".part-description") - .val(mixDescription) - .trigger("blur"); } else { - addNewRow(maxPartNumber + 1, true); // Crea nuova riga Mix - const $newMixRow = $("#partsTableBody tr:last"); - $newMixRow - .find(".part-description") - .val(mixDescription) - .trigger("blur"); + // libero: salva subito + saveRow($mixRow); } }); function addNewRow(nextPartNumber, isMix = false) { const description = isMix ? "Mix" : ""; const newRow = ` - + @@ -540,7 +739,7 @@ $(document).ready(function () { // =================== $(document).on("click", ".note-btn", function () { const $row = $(this).closest("tr"); - const partId = $row.data("part-id"); + const partId = getPartId($row); const note = $row.data("note") || ""; const $noteModal = $("#noteModal"); $noteModal.find(".part-note").val(note); @@ -655,7 +854,7 @@ $(document).ready(function () { $(document).on("change", ".part-dateexpiry", function () { const $input = $(this); const $row = $input.closest("tr"); - const partId = $row.data("part-id"); + const partId = getPartId($row); const dateexpiry = $input.val(); const iddatadb = $("#partsModal").data("iddatadb"); const idquotations = $("#partsModal").data("idquotations"); @@ -953,88 +1152,7 @@ $(document).ready(function () { }); $(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 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"; - 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, - dateexpiry: dateexpiry || null, - note: note, - }, - ], - }), - 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); - }, - }); - } + saveRow($(this).closest("tr")); }); function loadExistingParts(iddatadb, idquotations) {