")
.text(part.part_description || "")
.html();
const escapedNote = part.note
? $("
").text(part.note).html()
: "";
const newRow = `
|
|
|
|
${partsExtraField ? buildExtraFieldCellHtml() : ``}
|
`;
$("#partsTableBody").append(newRow);
const $row = $(
`#partsTableBody tr[data-part-id="${part.id}"]`,
);
setRowMix($row, part.mix === "Y" ? "Y" : "N");
if (
part.extra_value_id !== undefined &&
part.extra_value_id !== null
) {
const vid = String(part.extra_value_id);
$row.data("extra-value-id", vid);
$row.find(".part-extra-value-id").val(vid);
$row.find(".part-extra-select").attr(
"data-selected",
vid,
);
}
if (
part.extra_value_text !== undefined &&
part.extra_value_text !== null
) {
$row.data(
"extra-value-text",
part.extra_value_text,
);
$row.find(".part-extra-field").val(
part.extra_value_text,
);
}
initializeExtraFieldSelect2($row);
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 = $(
'
Errore nel caricamento delle parti: ' +
error +
" (" +
xhr.status +
")
",
);
$("#partsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
addNewRow(1, false); // Riga iniziale normale
},
});
}
function loadMacroMatrici() {
$.ajax({
url: "search_matrici.php?macro_list=1",
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 = $(
'
Errore nel caricamento delle MacroMatrici: ' +
error +
" (" +
xhr.status +
")
",
);
$("#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;
}
// Memorizza il valore corrente
const currentValue = $select.val();
// Distrugge Select2 esistente, se presente
if ($select.hasClass("select2-hidden-accessible")) {
$select.select2("destroy");
}
// Inizializza Select2 con AJAX
$select.empty().select2({
placeholder: "Seleziona matrice globale",
allowClear: true,
dropdownParent: $("#partsModal"),
minimumInputLength: 0,
ajax: {
url: "search_matrici.php",
dataType: "json",
delay: 150,
data: function (params) {
return {
q: params.term || "",
limit: 20,
macro: selectedMacro || "",
};
},
processResults: function (data) {
return { results: data.results || [] };
},
cache: true,
},
});
// Ripristina il valore corrente
if (currentValue) {
$.ajax({
url: "search_matrici.php",
data: { id: currentValue },
dataType: "json",
}).then(function (data) {
const item = (data.results || [])[0];
if (item) {
const option = new Option(item.text, item.id, true, true);
$select.append(option).trigger("change");
}
});
}
}
function initializeSelect2(
$select,
partNumber,
partId,
idmatrice,
selectedMacro = null,
fromFilter = false,
) {
if (typeof $.fn.select2 === "undefined") {
$select.replaceWith(
'
',
);
return;
}
// 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 con AJAX
$select.empty().select2({
placeholder: "Seleziona matrice",
allowClear: true,
dropdownParent: $("#partsModal"),
minimumInputLength: 0,
ajax: {
url: "search_matrici.php",
dataType: "json",
delay: 150,
data: function (params) {
return {
q: params.term || "",
limit: 20,
macro: selectedMacro || "",
};
},
processResults: function (data) {
return { results: data.results || [] };
},
cache: true,
},
});
// Ripristina il valore se valido
if (partId && partId !== "new" && currentValue) {
$.ajax({
url: "search_matrici.php",
data: { id: currentValue },
dataType: "json",
}).then(function (data) {
const item = (data.results || [])[0];
if (item) {
const option = new Option(item.text, item.id, true, true);
if (!fromFilter)
$select
.append(option)
.trigger("change", [{ skipHandler: true }]);
else $select.append(option);
partMatrice[partNumber] = item.id;
} else {
if (!fromFilter)
$select
.val(null)
.trigger("change", [{ skipHandler: true }]);
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 partNumber = $row.find(".part-number").val();
partMatrice[partNumber] = idmatrice || null;
markUnsaved();
});
// Messaggio se macro selezionata ma nessun risultato sarà gestito dal placeholder Select2
// 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();
const globalText = $("#global-matrice").find("option:selected").text();
if (globalVal) {
const $target = $row.find(".part-matrice");
if (!$target.find(`option[value="${globalVal}"]`).length) {
$target.append(new Option(globalText, globalVal, true, true));
}
$target.val(globalVal).trigger("change");
} else {
const errorMsg = $(
'
Seleziona una matrice globale prima di propagare.
',
);
$("#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();
const globalText = $("#global-matrice").find("option:selected").text();
if (globalVal) {
$("#partsTableBody .part-matrice").each(function () {
const $target = $(this);
if (!$target.find(`option[value="${globalVal}"]`).length) {
$target.append(
new Option(globalText, globalVal, true, true),
);
}
$target.val(globalVal).trigger("change");
});
} else {
const errorMsg = $(
'
Seleziona una matrice globale prima di propagare.
',
);
$("#partsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
}
});
function saveAllParts(done = null) {
const $rows = $("#partsTableBody tr");
const iddatadb = $("#partsModal").data("iddatadb");
const idquotations = $("#partsModal").data("idquotations");
const endpoint = idquotations
? "save_parts_quotation.php"
: "save_parts.php";
const ctx = idquotations ? { idquotations } : { iddatadb };
if (!iddatadb && !idquotations) {
if (done) done(false);
return;
}
// Stesso criterio del PHP: una riga nuova viene salvata solo se ha
// descrizione, nota o data. Filtriamo qui per tenere l'allineamento
// per indice tra righe inviate e results[] restituiti.
const rowHasContent = ($row) => {
const hasId = !!getPartId($row);
const desc = $row.find(".part-description").val().trim();
const note = $row.data("note") || "";
const date = $row.find(".part-dateexpiry").val() || "";
return hasId || desc || note || date;
};
const $rowsToSave = $rows.filter(function () {
return rowHasContent($(this));
});
const partsToSave = $rowsToSave
.map(function () {
const $row = $(this);
return {
id: getPartId($row), // null se "new"/""
part_number: $row.find(".part-number").val(),
part_description: $row
.find(".part-description")
.val()
.trim(),
mix: getRowMix($row),
idmatrice: $row.find(".part-matrice").val() || null,
dateexpiry: $row.find(".part-dateexpiry").val() || null,
note: $row.data("note") || null,
extra_field_id:
$row.find(".part-extra-field-id").val() || null,
extra_value_id:
$row.find(".part-extra-value-id").val() || null,
extra_value_text: $row.find(".part-extra-field").length
? ($row.find(".part-extra-field").val() || "").trim()
: null,
};
})
.get();
if (partsToSave.length === 0) {
unsavedChanges = false;
if (done) done(true);
return;
}
const $btn = $("#savePartsBtn");
const originalHtml = $btn.length ? $btn.html() : "";
if ($btn.length) {
$btn.prop("disabled", true).html(
'
Salvataggio...',
);
}
$rowsToSave.find(".save-loading").show();
$rowsToSave.find(".save-status").hide();
$.ajax({
url: endpoint,
method: "POST",
data: JSON.stringify({ ...ctx, parts: partsToSave }),
contentType: "application/json",
success: function (response) {
if ($btn.length)
$btn.prop("disabled", false).html(originalHtml);
$rowsToSave.find(".save-loading").hide();
if (response.success) {
// results[] è nell'ordine delle righe inviate
const results = response.results || [];
$rowsToSave.each(function (i) {
const r = results[i];
const newId = r ? r.part_id || r.id : null;
if (newId) setPartId($(this), newId);
});
$rowsToSave.find(".save-status").show();
setTimeout(
() => $rowsToSave.find(".save-status").hide(),
2000,
);
unsavedChanges = false;
updateSaveBtnState();
if (!$("#quotationeBtn").hasClass("d-none"))
$("#quotationeBtn").addClass("d-none");
if (done) done(true);
} else {
const errorMsg = $(
'
Errore nel salvataggio: ' +
(response.message || "Errore sconosciuto") +
"
",
);
$("#partsModal .modal-body").prepend(errorMsg);
setTimeout(() => {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
if (done) done(false);
}
},
error: function (xhr, status, error) {
if ($btn.length)
$btn.prop("disabled", false).html(originalHtml);
$rowsToSave.find(".save-loading").hide();
const errorMsg = $(
'
Errore nel salvataggio: ' +
error +
" (" +
xhr.status +
")
",
);
$("#partsModal .modal-body").prepend(errorMsg);
setTimeout(() => {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
if (done) done(false);
},
});
}
$(document).on("click", "#savePartsBtn", function (e) {
e.preventDefault();
saveAllParts();
});
window.saveAllParts = saveAllParts;
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: getRowMix($rows.eq(index)),
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 = $(
'
Errore nella rinumerazione delle parti: ' +
(response.message || "Errore sconosciuto") +
"
",
);
$("#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 = $(
'
Errore nella rinumerazione delle parti: ' +
error +
" (" +
xhr.status +
")
",
);
$("#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 (isLoadingPartsRecord) return;
unsavedChanges = true;
updateSaveBtnState();
}
// Aggiorna aspetto del tasto Salva in base a unsavedChanges
function updateSaveBtnState() {
const $btn = $("#savePartsBtn");
if (!$btn.length) return;
if (unsavedChanges) {
$btn.removeClass("btn-success").addClass("btn-danger");
} else {
$btn.removeClass("btn-danger").addClass("btn-success");
}
}
// Catch-all: modifiche manuali negli input della tabella parti segnano "non salvato".
// I