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 = $(
+ '
Errore nel salvataggio: ' +
+ response.message +
+ "
",
+ );
+ $("#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 = $(
+ 'Errore nel salvataggio delle parti: ' +
+ error +
+ " (" +
+ xhr.status +
+ ")
",
+ );
+ $("#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 = $(
'Inserisci una descrizione valida prima di aggiungerla al Mix.
',
);
$("#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 = $(
- ' Errore nel salvataggio: ' +
- response.message +
- " ",
- );
- $("#partsModal .modal-body").prepend(errorMsg);
- setTimeout(function () {
- errorMsg.fadeOut(500, function () {
- $(this).remove();
- });
- }, 5000);
- }
- },
- error: function (xhr, status, error) {
- $saveLoading.hide();
- const errorMsg = $(
- 'Errore nel salvataggio delle parti: ' +
- error +
- " (" +
- xhr.status +
- ") ",
- );
- $("#partsModal .modal-body").prepend(errorMsg);
- setTimeout(function () {
- errorMsg.fadeOut(500, function () {
- $(this).remove();
- });
- }, 5000);
- },
- });
- }
+ saveRow($(this).closest("tr"));
});
function loadExistingParts(iddatadb, idquotations) {
|