$(document).ready(function () {
// ===================
// GLOBAL STATE
// ===================
let photoData = {
naturalWidth: 0,
naturalHeight: 0,
displayWidth: 0,
displayHeight: 0,
scale: 1,
};
let photoAnnotations = {};
let partColors = {};
let partSizes = {}; // memorizza la dimensione specifica per parte
let selectedPartNumber = null;
let unsavedChanges = false;
let fabricCanvas = null;
let descriptionTextbox = null;
let markerObjects = {};
let nextMarkerId = 1;
let freeTextMode = false;
let freeTextObjects = {};
let nextFreeTextId = 1;
let partsListData = [];
// DIMENSIONE GLOBALE MARKER
let globalMarkerSize = 16;
// COLORE TESTO LISTA DESCRIZIONI
let globalDescriptionColor = "#000000";
// ===================
// MODAL INITIALIZATION
// ===================
window.initAnnotationsModal = function (iddatadb, idquotations, trfHeader) {
console.log("initAnnotationsModal chiamato con:", {
iddatadb,
idquotations,
trfHeader,
});
$("#annotationsModal").attr("data-iddatadb", iddatadb);
if (!iddatadb && !idquotations) {
const errorMsg = $(
'
Errore: ID TRF mancante. Impossibile inizializzare il modale delle annotazioni.
',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
return;
}
$("#trfHeaderAnnotations").text(trfHeader || "N/D");
$("#annotationsModal")
.data("iddatadb", iddatadb || null)
.data("idquotations", idquotations || null);
// Parti (non legate al canvas) possono partire subito
loadExistingParts(iddatadb, idquotations);
const modalElement = document.getElementById("annotationsModal");
if (!modalElement) {
console.error("Elemento #annotationsModal non trovato nel DOM.");
alert(
"Errore: Il modale delle annotazioni non è presente nel DOM.",
);
return;
}
let modal = bootstrap.Modal.getInstance(modalElement);
if (!modal) {
modal = new bootstrap.Modal(modalElement, {
backdrop: true,
keyboard: true,
focus: true,
});
}
// IMPORTANTE: carica la foto SOLO dopo che il modale è completamente
// visibile (animazione fade terminata). Se l'immagine è già in cache
// del browser, l'evento "load" può scattare quasi subito, PRIMA che
// il contenitore del canvas abbia dimensioni reali (parent ancora
// display:none durante il fade) — questo rompeva il canvas Fabric.js
// per la prima foto mostrata (bug riprodotto solo con foto già in cache,
// tipico del server di test dove le foto erano già state visualizzate).
$(modalElement).one("shown.bs.modal", function () {
loadPhoto(iddatadb, idquotations);
});
modal.show();
// Inizializza slider dimensione marker
$("#markerSizeSlider").val(globalMarkerSize);
$("#markerSizeValue").text(globalMarkerSize + "px");
// Debug: Verifica presenza elementi DOM
console.log(
"Presenza #partsListAnnotations:",
$("#partsListAnnotations").length,
);
console.log(
"Presenza #showMixPartsAnnotations:",
$("#showMixPartsAnnotations").length,
);
console.log(
"Presenza #addDescriptionsBtnAnnotations:",
$("#addDescriptionsBtnAnnotations").length,
);
console.log(
"Presenza #removeAnnotationsBtnAnnotations:",
$("#removeAnnotationsBtnAnnotations").length,
);
console.log(
"Presenza #downloadPhotoBtnAnnotations:",
$("#downloadPhotoBtnAnnotations").length,
);
console.log(
"Presenza #backToPartsBtnAnnotations:",
$("#backToPartsBtnAnnotations").length,
);
console.log(
"Presenza #overlayCanvasAnnotations:",
$("#overlayCanvasAnnotations").length,
);
};
$("#annotationsModal").on("hide.bs.modal", function (e) {
if (
unsavedChanges &&
!confirm("Hai modifiche non salvate. Vuoi davvero uscire?")
) {
e.preventDefault();
}
});
$("#annotationsModal").on("hidden.bs.modal", function () {
photoData = {
naturalWidth: 0,
naturalHeight: 0,
displayWidth: 0,
displayHeight: 0,
scale: 1,
};
photoAnnotations = {};
partColors = {};
selectedPartNumber = null;
unsavedChanges = false;
partsListData = [];
freeTextMode = false; // ← aggiungi
freeTextObjects = {}; // ← aggiungi
$("#addFreeTextBtnAnnotations").removeClass("active"); // ← aggiungi
if (fabricCanvas) {
fabricCanvas.off();
fabricCanvas.dispose();
fabricCanvas = null;
}
descriptionTextbox = null;
markerObjects = {};
globalMarkerSize = 16;
globalDescriptionColor = "#000000";
$("#photoSelectorContainerAnnotations").empty().hide();
$("#samplePhotoAnnotations").attr("src", "");
$("#partsListAnnotations").empty();
$(".temp-alert").remove();
const modalElement = document.getElementById("annotationsModal");
const modal = bootstrap.Modal.getInstance(modalElement);
if (modal) {
modal.dispose();
}
$(".modal-backdrop").remove();
$("body").removeClass("modal-open");
$("body").css("padding-right", "");
$(":focus").blur();
});
// SLIDER DIMENSIONE MARKER
$(document)
.off("input", "#markerSizeSlider")
.on("input", "#markerSizeSlider", function () {
globalMarkerSize = parseInt($(this).val());
$("#markerSizeValue").text(globalMarkerSize + "px");
partsListData.forEach(function (part) {
if (part && part.part_number != null) {
partSizes[part.part_number] = globalMarkerSize;
}
});
$("#partsListAnnotations .marker-size-slider").val(
globalMarkerSize,
);
$("#partsListAnnotations .marker-size-value").text(
globalMarkerSize + "px",
);
updateMarkers();
markUnsaved();
});
// ===================
// TESTO LIBERO — TOGGLE MODALITÀ
// ===================
$(document)
.off("click.freeTextToggle", "#addFreeTextBtnAnnotations")
.on("click.freeTextToggle", "#addFreeTextBtnAnnotations", function (e) {
e.preventDefault();
e.stopPropagation();
freeTextMode = !freeTextMode;
$(this).toggleClass("active", freeTextMode);
// Disattiva la selezione parte/marker mentre si aggiunge testo libero
if (freeTextMode) {
selectedPartNumber = null;
$("#partsListAnnotations .list-group-item").removeClass(
"active",
);
}
console.log("Free text mode:", freeTextMode);
});
// ===================
// COLORE LISTA DESCRIZIONI
// ===================
$(document)
.off("input.descriptionColor", "#descriptionColorPickerAnnotations")
.on(
"input.descriptionColor",
"#descriptionColorPickerAnnotations",
function () {
globalDescriptionColor = $(this).val();
if (descriptionTextbox && fabricCanvas) {
descriptionTextbox.set("fill", globalDescriptionColor);
}
// Aggiorna anche i testi liberi già piazzati sulla foto corrente
const currentPhoto = $("#samplePhotoAnnotations").attr("src");
const annotations = photoAnnotations[currentPhoto];
if (annotations && annotations.freeTexts) {
annotations.freeTexts.forEach((entry) => {
entry.color = globalDescriptionColor;
});
}
Object.values(freeTextObjects).forEach((obj) => {
obj.set("fill", globalDescriptionColor);
});
if (fabricCanvas) fabricCanvas.renderAll();
markUnsaved();
},
);
// ===================
// PHOTO LOADERS
// ===================
function loadPhoto(iddatadb, idquotations) {
const currentPhoto = $("#samplePhotoAnnotations").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) {
console.log("Risposta da load_photo:", 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 {
$("#samplePhotoAnnotations").attr("src", "");
const errorMsg = $(
'Nessuna foto trovata per questo elemento.
',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
}
} else {
const errorMsg = $(
'' +
(response.message ||
"Errore nel caricamento della foto.") +
"
",
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
}
},
error: function (xhr, status, error) {
console.error("Errore AJAX in loadPhoto:", {
status,
error,
responseText: xhr.responseText,
});
const errorMsg = $(
'Errore nel caricamento della foto: ' +
error +
" (" +
xhr.status +
")
",
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
},
});
}
function showPhotoSelector(photos, selected = null) {
const selectorContainer = $("#photoSelectorContainerAnnotations");
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 = $("#samplePhotoAnnotations");
img.off("load").attr("src", photoPath);
img.on("load", function () {
console.log("Foto caricata:", photoPath);
const canvas = document.getElementById("photoCanvasAnnotations");
const ctx = canvas.getContext("2d");
const naturalWidth = img[0].naturalWidth;
const naturalHeight = img[0].naturalHeight;
const parent = $(canvas).parent();
const maxW = parent.width();
const maxH = parent.height();
const scale = Math.min(maxW / naturalWidth, maxH / naturalHeight);
photoData = {
naturalWidth,
naturalHeight,
displayWidth: Math.max(1, Math.round(naturalWidth * scale)),
displayHeight: Math.max(1, Math.round(naturalHeight * scale)),
scale,
};
canvas.width = naturalWidth;
canvas.height = naturalHeight;
canvas.style.width = `${photoData.displayWidth}px`;
canvas.style.height = `${photoData.displayHeight}px`;
ctx.clearRect(0, 0, naturalWidth, naturalHeight);
ctx.drawImage(img[0], 0, 0, naturalWidth, naturalHeight);
if (fabricCanvas) {
fabricCanvas.off();
fabricCanvas.dispose();
fabricCanvas = null;
}
const overlayCanvas = document.getElementById(
"overlayCanvasAnnotations",
);
const canvasContainer = overlayCanvas.parentElement;
const newOverlayCanvas = document.createElement("canvas");
newOverlayCanvas.id = "overlayCanvasAnnotations";
newOverlayCanvas.width = photoData.displayWidth;
newOverlayCanvas.height = photoData.displayHeight;
newOverlayCanvas.style.width = `${photoData.displayWidth}px`;
newOverlayCanvas.style.height = `${photoData.displayHeight}px`;
newOverlayCanvas.style.position = "absolute";
newOverlayCanvas.style.top = "0";
newOverlayCanvas.style.left = "0";
newOverlayCanvas.style.zIndex = "1000";
canvasContainer.removeChild(overlayCanvas);
canvasContainer.appendChild(newOverlayCanvas);
setTimeout(() => {
fabricCanvas = new fabric.Canvas("overlayCanvasAnnotations", {
selection: true,
preserveObjectStacking: true,
width: photoData.displayWidth,
height: photoData.displayHeight,
});
fabricCanvas.setDimensions({
width: photoData.displayWidth,
height: photoData.displayHeight,
});
fabricCanvas.on("mouse:down", function (options) {
console.log(
"Evento mouse:down su canvas, selectedPartNumber:",
selectedPartNumber,
"freeTextMode:",
freeTextMode,
);
// Modalità testo libero: ha priorità, un solo click e si disattiva da sola
if (freeTextMode) {
if (options.target) {
console.log(
"Click su un oggetto esistente, ignoro.",
);
return;
}
const pointer = fabricCanvas.getPointer(options.e);
const x = pointer.x / photoData.scale;
const y = pointer.y / photoData.scale;
addFreeText(x, y);
freeTextMode = false;
$("#addFreeTextBtnAnnotations").removeClass("active");
return;
}
if (selectedPartNumber === null) {
console.log(
"Nessuna parte selezionata, ignoro il click.",
);
return;
}
if (options.target) {
console.log("Click su un oggetto esistente, ignoro.");
return;
}
const pointer = fabricCanvas.getPointer(options.e);
const x = pointer.x / photoData.scale;
const y = pointer.y / photoData.scale;
const currentPhoto = $("#samplePhotoAnnotations").attr(
"src",
);
if (!photoAnnotations[currentPhoto]) {
photoAnnotations[currentPhoto] = {
markers: [],
hasDescriptions: false,
descriptionPosition: { x: 10, y: 10 },
descriptionSize: {
width: photoData.displayWidth * 0.3,
height: photoData.displayHeight * 0.3,
},
};
}
const partColor =
partColors[selectedPartNumber] || "#ff0000";
photoAnnotations[currentPhoto].markers.push({
id: nextMarkerId++,
partNumber: selectedPartNumber,
x,
y,
color: partColor,
});
console.log("Marker aggiunto/spostato:", {
partNumber: selectedPartNumber,
x,
y,
color: partColor,
});
updateMarkers();
markUnsaved();
});
fabricCanvas.upperCanvasEl.focus();
fabricCanvas.renderAll();
updateMarkers();
updateDescriptions();
updateFreeTexts();
}, 10);
});
}
// ===================
// DOWNLOAD PHOTO
// ===================
$(document)
.off("click.downloadPhoto", "#downloadPhotoBtnAnnotations")
.on(
"click.downloadPhoto",
"#downloadPhotoBtnAnnotations",
function (e) {
e.preventDefault();
e.stopPropagation();
console.log(
"Evento click su #downloadPhotoBtnAnnotations, ID elemento:",
$(this).attr("id"),
);
if (!$("#downloadPhotoBtnAnnotations").length) {
console.error(
"Pulsante #downloadPhotoBtnAnnotations non trovato nel DOM.",
);
const errorMsg = $(
'Errore: Pulsante #downloadPhotoBtnAnnotations non trovato nel DOM.
',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
return;
}
const photoSrc = $("#samplePhotoAnnotations").attr("src");
console.log("URL immagine per il download:", photoSrc);
if (!photoSrc) {
console.error("Nessuna foto caricata da scaricare.");
const errorMsg = $(
'Nessuna foto caricata da scaricare.
',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
return;
}
// Verifica se l'URL è valido
const img = new Image();
img.src = photoSrc;
img.onload = function () {
const photoName =
photoSrc.split("/").pop() || "downloaded_photo.png";
console.log("Nome file per il download:", photoName);
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:", photoName);
};
img.onerror = function () {
console.error(
"Errore: Impossibile caricare l'immagine per il download:",
photoSrc,
);
const errorMsg = $(
'Errore: Impossibile caricare l\'immagine per il download.
',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
};
},
);
// ===================
// TORNA AL MODALE PARTI (modal_partsTable.php)
// ===================
$(document)
.off("click.backToParts", "#backToPartsBtnAnnotations")
.on("click.backToParts", "#backToPartsBtnAnnotations", function (e) {
e.preventDefault();
e.stopPropagation();
if (
unsavedChanges &&
!confirm(
"Hai modifiche non salvate. Vuoi davvero tornare al modale delle parti?",
)
) {
return;
}
const iddatadb = $("#annotationsModal").data("iddatadb");
const idquotations = $("#annotationsModal").data("idquotations");
const trfHeader = $("#trfHeaderAnnotations").text();
// CHIUDI MODALE ANNOTAZIONI IN MODO SICURO
const annotationsModalElement =
document.getElementById("annotationsModal");
if (annotationsModalElement) {
const modalInstance = bootstrap.Modal.getInstance(
annotationsModalElement,
);
if (modalInstance) {
modalInstance.hide();
} else {
$(annotationsModalElement).modal("hide"); // fallback jQuery
}
}
console.log("Torno a modal_partsTable.php con:", {
iddatadb,
idquotations,
trfHeader,
});
// CARICA E APRI MODALE PARTI
$.get(
"modal_partsTable.php",
{
iddatadb: iddatadb || "",
idquotations: idquotations || "",
trfHeader: trfHeader,
},
function (data) {
// Rimuovi vecchio modale (con ID corretto)
$("#partsTableModal").remove();
$(".modal-backdrop").remove();
// Aggiungi nuovo modale
$("body").append(data);
// Apri con Bootstrap 5
const partsModalElement =
document.getElementById("partsTableModal");
if (partsModalElement) {
const modal = new bootstrap.Modal(partsModalElement, {
backdrop: true,
keyboard: true,
focus: true,
});
modal.show();
} else {
let iddatadb =
$("#annotationsModal").attr("data-iddatadb");
$(
"button.parts-btn[data-iddatadb='" +
iddatadb +
"']",
).trigger("click");
}
},
).fail(function (xhr) {
console.error("Errore caricamento modale parti:", xhr);
alert(
"Errore 404: modal_partsTable.php non trovato o errore server.",
);
});
});
// ===================
// PARTS LIST
// ===================
function updatePartsList() {
console.log(
"updatePartsList chiamato con partsListData:",
partsListData,
);
const showMixParts = $("#showMixPartsAnnotations").is(":checked");
console.log("Stato showMixPartsAnnotations:", showMixParts);
const partsListElement = $("#partsListAnnotations");
if (!partsListElement.length) {
console.error(
"Elemento #partsListAnnotations non trovato nel DOM.",
);
const errorMsg = $(
'Errore: Elemento #partsListAnnotations non trovato nel DOM.
',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
return;
}
partsListElement.empty();
const predefinedColors = [
"#ff0000", // Rosso
"#0000ff", // Blu
"#00ff00", // Verde
"#ffff00", // Giallo
"#ff00ff", // Magenta
"#00ffff", // Ciano
"#800080", // Viola
"#ffa500", // Arancione
];
partsListData.forEach((part) => {
const partNumber = part.part_number;
const partDescription = part.part_description;
const isMixPart = String(part.mix || "N").toUpperCase() === "Y";
const partColor =
partColors[partNumber] || (isMixPart ? "#0000ff" : "#ff0000");
if (partNumber && partDescription && (showMixParts || !isMixPart)) {
const colorOptions = predefinedColors
.map(
(color) =>
``,
)
.join("");
const listItem = `
${partNumber} - ${partDescription}
${partSizes[partNumber] || globalMarkerSize}px
`;
partsListElement.append(listItem);
}
});
console.log(
"Elementi aggiunti a #partsListAnnotations:",
partsListElement.find("li").length,
);
console.log("HTML di #partsListAnnotations:", partsListElement.html());
// Associa evento di selezione all'intera riga
partsListElement
.off("click.partsList")
.on("click.partsList", ".list-group-item", function (e) {
e.stopPropagation();
e.preventDefault();
// Ignora il click se è sulla paletta dei colori
if ($(e.target).closest(".color-picker-container").length) {
console.log(
"Click sulla paletta dei colori, ignoro selezione parte.",
);
return;
}
const $listItem = $(this);
const partNumber = $listItem.data("part-number");
if (
selectedPartNumber == partNumber &&
$listItem.hasClass("active")
) {
selectedPartNumber = null;
$listItem.removeClass("active");
return;
}
selectedPartNumber = partNumber;
$listItem.addClass("active").siblings().removeClass("active");
console.log(
"Parte selezionata tramite riga:",
selectedPartNumber,
);
});
// Associa eventi alla paletta dei colori
partsListElement
.off("click.selectedColor")
.on("click.selectedColor", ".selected-color", function (e) {
e.stopPropagation();
e.preventDefault();
const $picker = $(this).siblings(".color-picker");
console.log(
"Cliccato .selected-color, mostro/nascondo paletta:",
$picker.is(":visible"),
);
$(".color-picker").not($picker).hide();
$picker.toggle();
});
// === Gestione cambio colore ===
partsListElement
.off("click.colorOption")
.on("click.colorOption", ".color-option", function (e) {
e.stopPropagation();
e.preventDefault();
const $this = $(this);
const color = $this.data("color");
const $listItem = $this.closest("li");
const partNumber = $listItem.data("part-number");
console.log(
"Cliccato .color-option, colore:",
color,
"per parte:",
partNumber,
);
// Salva il nuovo colore
partColors[partNumber] = color;
// Aggiorna il colore visivo nel selettore
$listItem
.find(".selected-color")
.css("background-color", color);
let currentPhoto = $("#samplePhotoAnnotations").attr("src");
let annotations = photoAnnotations[currentPhoto];
if (annotations) {
annotations.markers.forEach(function (m) {
if (m.partNumber == partNumber) {
m.color = color;
let group = markerObjects[m.id];
if (group) {
let circle = group.item(0);
circle.set("fill", color);
circle.set("stroke", color);
}
}
});
fabricCanvas.renderAll();
}
// Chiudi la palette e aggiorna canvas
$this.closest(".color-picker").hide();
updateMarkers();
markUnsaved();
});
// === Slider locale per dimensione marker ===
partsListElement
.off("input.localMarkerSize")
.on("input.localMarkerSize", ".marker-size-slider", function (e) {
e.stopPropagation();
e.preventDefault();
// Riferimenti alla parte e valore scelto
const $slider = $(this);
const partNumber = $slider.closest("li").data("part-number");
const newSize = parseInt($slider.val());
// Aggiorna il valore visivo accanto allo slider
$slider.siblings(".marker-size-value").text(newSize + "px");
// Memorizza la nuova dimensione per quella parte
partSizes[partNumber] = newSize;
// Aggiorna i marker sul canvas
updateMarkers();
// Segnala modifiche non salvate
markUnsaved();
console.log(
`Dimensione marker aggiornata per parte ${partNumber}: ${newSize}px`,
);
});
$(document)
.off("click.colorPicker")
.on("click.colorPicker", function (e) {
if (!$(e.target).closest(".color-picker-container").length) {
console.log("Cliccato fuori, nascondo tutte le palette.");
$(".color-picker").hide();
}
});
}
function enableDragDropPartsList() {
const list = $("#partsListAnnotations");
if (!list.length || typeof $.ui === "undefined" || !$.ui.sortable) {
console.warn("jQuery UI o .sortable non disponibile. Ritento...");
setTimeout(enableDragDropPartsList, 100);
return;
}
if (list.hasClass("ui-sortable")) {
list.sortable("destroy"); // evita duplicati
}
list.sortable({
items: "li.list-group-item",
placeholder: "list-group-item placeholder",
axis: "y",
containment: "parent",
tolerance: "pointer",
start: function (e, ui) {
ui.item.addClass("dragging");
},
stop: function (e, ui) {
ui.item.removeClass("dragging");
const newOrder = [];
list.find("li").each(function () {
const partNumber = $(this).data("part-number");
const part = partsListData.find(
(p) => p.part_number == partNumber,
);
if (part) newOrder.push(part);
});
partsListData = newOrder;
console.log(
"Ordine parti aggiornato:",
partsListData.map((p) => p.part_number),
);
markUnsaved();
updateMarkers();
},
});
}
// Delegazione evento per il checkbox
$(document)
.off("change.showMix", "#showMixPartsAnnotations")
.on("change.showMix", "#showMixPartsAnnotations", function (e) {
e.preventDefault();
e.stopPropagation();
console.log(
"Evento change su #showMixPartsAnnotations, ID elemento:",
$(this).attr("id"),
);
const isChecked = $(this).is(":checked");
console.log(
"Checkbox #showMixPartsAnnotations cambiato:",
isChecked,
);
if (!$("#showMixPartsAnnotations").length) {
console.error(
"Checkbox #showMixPartsAnnotations non trovato nel DOM.",
);
const errorMsg = $(
'Errore: Checkbox #showMixPartsAnnotations non trovato nel DOM.
',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
return;
}
updatePartsList();
updateMarkers();
setTimeout(enableDragDropPartsList, 50);
if (
photoAnnotations[$("#samplePhotoAnnotations").attr("src")]
?.hasDescriptions
) {
updateDescriptions();
}
});
// ===================
// LOAD EXISTING PARTS
// ===================
function loadExistingParts(iddatadb, idquotations) {
console.log("loadExistingParts chiamato con:", {
iddatadb,
idquotations,
});
if (!iddatadb && !idquotations) {
const errorMsg = $(
'Errore: ID TRF mancante per il caricamento delle parti.
',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
return;
}
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) {
console.log("Risposta da load_parts:", response);
partsListData = [];
if (
response.success &&
response.parts &&
response.parts.length > 0
) {
partsListData = response.parts;
response.parts.forEach((part) => {
const isMixPart =
String(part.mix || "N").toUpperCase() === "Y";
const defaultColor = isMixPart ? "#0000ff" : "#ff0000";
partColors[part.part_number] = defaultColor;
});
updatePartsList();
// Forza aggiornamento iniziale del checkbox
setTimeout(() => {
$("#showMixPartsAnnotations").trigger("change.showMix");
}, 100);
} else {
const errorMsg = $(
'' +
(response.message ||
"Nessuna parte trovata per questo elemento.") +
"
",
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
}
},
error: function (xhr, status, error) {
console.error("Errore AJAX in loadExistingParts:", {
status,
error,
responseText: xhr.responseText,
});
const errorMsg = $(
'Errore nel caricamento delle parti: ' +
error +
" (" +
xhr.status +
")
",
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
},
});
}
// ===================
// TESTO LIBERO — CREAZIONE E RENDER
// ===================
function addFreeText(x, y) {
const currentPhoto = $("#samplePhotoAnnotations").attr("src");
if (!photoAnnotations[currentPhoto]) {
photoAnnotations[currentPhoto] = {
markers: [],
freeTexts: [],
hasDescriptions: false,
descriptionPosition: { x: 10, y: 10 },
descriptionSize: {
width: photoData.displayWidth * 0.3,
height: photoData.displayHeight * 0.3,
},
};
}
if (!photoAnnotations[currentPhoto].freeTexts) {
photoAnnotations[currentPhoto].freeTexts = [];
}
const entry = {
id: nextFreeTextId++,
x,
y,
text: "Testo",
color: globalDescriptionColor,
fontSize: Math.max(16, Math.round(globalMarkerSize * 0.8)),
};
photoAnnotations[currentPhoto].freeTexts.push(entry);
renderFreeText(entry, true);
markUnsaved();
}
function renderFreeText(entry, startEditing = false) {
const itext = new fabric.IText(entry.text, {
left: entry.x * photoData.scale,
top: entry.y * photoData.scale,
fontFamily: "Arial",
fontSize: entry.fontSize,
fill: entry.color,
selectable: true,
hasControls: true,
editable: true,
borderColor: "#333",
cornerColor: "#000",
cornerSize: 12,
transparentCorners: false,
cornerStyle: "rect",
borderScaleFactor: 2,
});
itext.freeTextId = entry.id;
// FIX: Fabric.js appende la