-
-
-
-
${colorOptions}
+
+ ${partNumber} - ${partDescription}
+
-
- `;
+ `;
$("#partsList").append(listItem);
}
});
- // Gestione del selettore colori personalizzato
$(".selected-color").on("click", function (e) {
e.stopPropagation();
const $picker = $(this).siblings(".color-picker");
- $(".color-picker").not($picker).hide(); // Chiude altri selettori aperti
+ $(".color-picker").not($picker).hide();
$picker.toggle();
});
@@ -571,130 +807,16 @@ $(document).ready(function () {
const partNumber = $listItem.data("part-number");
partColors[partNumber] = color;
$listItem.find(".selected-color").css("background-color", color);
- $this.closest(".color-picker").hide(); // Chiude il selettore dopo la scelta
+ $this.closest(".color-picker").hide();
updateMarkers();
markUnsaved();
});
- // Chiude il selettore se si clicca fuori
$(document).on("click", function (e) {
if (!$(e.target).closest(".color-picker-container").length) {
$(".color-picker").hide();
}
});
-
- updateMarkers();
- }
-
- function renumberParts() {
- const $rows = $("#partsTableBody tr");
- const iddatadb = $("#partsModal").data("iddatadb");
- let newPartColors = {};
-
- // Raccogli tutte le righe con i loro dati attuali
- let partsData = $rows
- .map(function (index) {
- const $row = $(this);
- const partNumber = $row.find(".part-number").val();
- const partDescription = $row.find(".part-description").val();
- const partId = $row.data("part-id");
- return { partNumber, partDescription, partId };
- })
- .get();
-
- // Rinumera in modo sequenziale
- partsData.forEach((part, index) => {
- const newNumber = index + 1;
- newPartColors[newNumber] = partColors[part.partNumber] || "#ff0000";
- part.partNumber = newNumber;
- });
-
- // Aggiorna i valori nella tabella
- $rows.each(function (index) {
- const $row = $(this);
- $row.find(".part-number").val(index + 1);
- });
-
- // Aggiorna partColors
- partColors = newPartColors;
-
- // Aggiorna i marker nelle annotazioni
- const currentPhoto = $("#samplePhoto").attr("src");
- if (photoAnnotations[currentPhoto]) {
- photoAnnotations[currentPhoto].markers.forEach((marker) => {
- const oldPartNumber = marker.partNumber;
- const newPartNumber = partsData.find(
- (p) => p.partNumber == oldPartNumber,
- )?.partNumber;
- if (newPartNumber) {
- marker.partNumber = newPartNumber;
- marker.color = partColors[newPartNumber];
- }
- });
- }
-
- // Salva le modifiche nel database
- const partsToSave = partsData.map((part) => ({
- id: part.partId || null,
- part_number: part.partNumber,
- part_description: part.partDescription,
- mix: part.partDescription.startsWith("Mix") ? "Y" : "N",
- }));
-
- console.log(
- "Dati inviati a renumber_parts.php:",
- JSON.stringify({ iddatadb: iddatadb, parts: partsToSave }),
- );
-
- $.ajax({
- url: "renumber_parts.php",
- method: "POST",
- data: JSON.stringify({
- iddatadb: iddatadb,
- parts: partsToSave,
- }),
- contentType: "application/json",
- success: function (response) {
- console.log("Risposta da renumber_parts.php:", response);
- if (response.success) {
- $rows.each(function (index) {
- const $row = $(this);
- const newPartId =
- response.part_ids && response.part_ids[index]
- ? response.part_ids[index]
- : $row.data("part-id");
- if (newPartId) {
- $row.attr("data-part-id", newPartId);
- $row.data("part-id", newPartId);
- }
- const $saveStatus = $row.find(".save-status");
- const $saveLoading = $row.find(".save-loading");
- $saveLoading.hide();
- $saveStatus.show();
- setTimeout(() => $saveStatus.hide(), 2000);
- });
- updatePartsList();
- updateMarkers();
- updateDescriptions();
- markUnsaved();
- } else {
- console.error("Errore dal server:", response.message);
- alert(
- "Errore nella rinumerazione delle parti: " +
- response.message,
- );
- }
- },
- error: function (xhr, status, error) {
- console.error("Errore AJAX:", status, error, xhr.responseText);
- alert(
- "Errore nella rinumerazione delle parti: " +
- error +
- " - " +
- xhr.responseText,
- );
- },
- });
}
$(document).on("click", ".add-to-mix-btn", function () {
@@ -710,13 +832,20 @@ $(document).ready(function () {
.last();
if ($mixRow.length === 0) {
- alert("Crea prima una riga Mix usando il pulsante 'M'.");
+ const errorMsg = $(
+ '
Crea prima una riga Mix usando il pulsante \'M\'.
',
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
return;
}
const $descriptionInput = $mixRow.find(".part-description");
let currentDescription = $descriptionInput.val().trim();
-
if (currentDescription === "Mix") {
currentDescription = `Mix ${partDescription}`;
} else if (!currentDescription.includes(partDescription)) {
@@ -728,12 +857,16 @@ $(document).ready(function () {
$descriptionInput.val(currentDescription);
$descriptionInput.trigger("blur");
updatePartsList();
+ if (photoAnnotations[$("#samplePhoto").attr("src")]?.hasDescriptions) {
+ updateDescriptions();
+ }
});
$("#partsList").on("click", "li", function (e) {
if (
$(e.target).hasClass("add-to-mix-btn") ||
- $(e.target).hasClass("part-color")
+ $(e.target).hasClass("color-option") ||
+ $(e.target).closest(".color-picker-container").length
)
return;
selectedPartNumber = $(this).data("part-number");
@@ -742,81 +875,163 @@ $(document).ready(function () {
$("#showMixParts").on("change", function () {
updatePartsList();
+ updateMarkers();
+ if (photoAnnotations[$("#samplePhoto").attr("src")]?.hasDescriptions) {
+ updateDescriptions();
+ }
});
- $("#renumberPartsBtn").on("click", function () {
- renumberParts();
- });
- // ===================
- // MARKERS & DESCRIPTIONS
- // ===================
- const canvas = document.getElementById("photoCanvas");
- const ctx = canvas.getContext("2d");
+ function renumberParts() {
+ 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 newPartColors = {};
+ let newMarkerObjects = {};
- $("#markerContainer").on("click", function (e) {
- if (selectedPartNumber === null) return;
+ let partsData = $rows
+ .map(function (index) {
+ const $row = $(this);
+ return {
+ partNumber: $row.find(".part-number").val(),
+ partDescription: $row.find(".part-description").val(),
+ partId: $row.data("part-id"),
+ };
+ })
+ .get();
- const rect = canvas.getBoundingClientRect();
- const clickX = e.clientX - rect.left;
- const clickY = e.clientY - rect.top;
+ partsData.forEach((part, index) => {
+ const newNumber = index + 1;
+ newPartColors[newNumber] = partColors[part.partNumber] || "#ff0000";
+ if (markerObjects[part.partNumber]) {
+ newMarkerObjects[newNumber] = markerObjects[part.partNumber];
+ }
+ part.partNumber = newNumber;
+ });
- const x = clickX / photoData.scale; // convert to NATURAL coords
- const y = clickY / photoData.scale;
+ $rows.each(function (index) {
+ $(this)
+ .find(".part-number")
+ .val(index + 1);
+ });
const currentPhoto = $("#samplePhoto").attr("src");
- if (!photoAnnotations[currentPhoto]) {
- photoAnnotations[currentPhoto] = {
- markers: [],
- hasDescriptions: false,
- descriptionPosition: { x: 10, y: 10 },
- };
- }
-
- const partColor = partColors[selectedPartNumber] || "#ff0000";
-
- const existingMarker = photoAnnotations[currentPhoto].markers.find(
- (m) => m.partNumber == selectedPartNumber,
- );
- if (existingMarker) {
- existingMarker.x = x;
- existingMarker.y = y;
- existingMarker.color = partColor;
- } else {
- photoAnnotations[currentPhoto].markers.push({
- partNumber: selectedPartNumber,
- x,
- y,
- color: partColor,
+ if (photoAnnotations[currentPhoto]) {
+ photoAnnotations[currentPhoto].markers.forEach((marker) => {
+ const newPartNumber = partsData.find(
+ (p) => p.partNumber == marker.partNumber,
+ )?.partNumber;
+ if (newPartNumber) {
+ marker.partNumber = newPartNumber;
+ marker.color = newPartColors[newPartNumber];
+ }
});
}
- updateMarkers();
- updateDescriptions();
- markUnsaved();
+ partColors = newPartColors;
+ markerObjects = newMarkerObjects;
- selectedPartNumber = null;
- $("#partsList li").removeClass("active");
- });
+ const partsToSave = partsData.map((part) => ({
+ id: part.partId || null,
+ part_number: part.partNumber,
+ part_description: part.partDescription,
+ mix: part.partDescription.startsWith("Mix") ? "Y" : "N",
+ }));
- function updateMarkers() {
- const markerContainer = $("#markerContainer");
- markerContainer.empty();
-
- markerContainer.css({
- width: `${photoData.displayWidth}px`,
- height: `${photoData.displayHeight}px`,
+ $.ajax({
+ url: endpoint,
+ method: "POST",
+ data: JSON.stringify({ ...data, parts: partsToSave }),
+ contentType: "application/json",
+ success: function (response) {
+ if (response.success) {
+ $rows.each(function (index) {
+ const $row = $(this);
+ const newPartId =
+ response.part_ids && response.part_ids[index]
+ ? 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);
+ });
+ updatePartsList();
+ updateMarkers();
+ if (photoAnnotations[currentPhoto]?.hasDescriptions) {
+ updateDescriptions();
+ }
+ markUnsaved();
+ } else {
+ const errorMsg = $(
+ '
Errore nella rinumerazione delle parti: ' +
+ response.message +
+ "
",
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ }
+ },
+ error: function (xhr, status, error) {
+ const errorMsg = $(
+ '
Errore nella rinumerazione delle parti: ' +
+ error +
+ " (" +
+ xhr.status +
+ ")
",
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ },
});
+ }
+
+ $("#renumberPartsBtn").on("click", renumberParts);
+
+ // ===================
+ // MARKERS & DESCRIPTIONS
+ // ===================
+ function updateMarkers() {
+ // Rimuovi marker esistenti
+ for (let partNumber in markerObjects) {
+ fabricCanvas.remove(markerObjects[partNumber]);
+ delete markerObjects[partNumber];
+ }
+ markerObjects = {};
const currentPhoto = $("#samplePhoto").attr("src");
const annotations = photoAnnotations[currentPhoto] || {
markers: [],
hasDescriptions: false,
descriptionPosition: { x: 10, y: 10 },
+ descriptionSize: {
+ width: photoData.displayWidth * 0.3,
+ height: photoData.displayHeight * 0.3,
+ },
};
- const markers = annotations.markers;
const showMixParts = $("#showMixParts").is(":checked");
- markers.forEach((marker) => {
+ annotations.markers.forEach((marker) => {
const partRow = $("#partsTableBody tr").filter(function () {
return $(this).find(".part-number").val() == marker.partNumber;
});
@@ -825,78 +1040,72 @@ $(document).ready(function () {
!showMixParts &&
partDescription &&
partDescription.startsWith("Mix")
- ) {
+ )
return;
- }
- const scaledX = marker.x * photoData.scale;
- const scaledY = marker.y * photoData.scale;
+ const radius = 12;
+ const fontSize = 16;
const markerColor =
marker.color || partColors[marker.partNumber] || "#ff0000";
- const $marker = $(
- `
${marker.partNumber}
`,
- ).css({
- left: scaledX - 8 + "px",
- top: scaledY - 8 + "px",
+ // Crea cerchio
+ const circle = new fabric.Circle({
+ radius: radius,
+ fill: markerColor,
+ stroke: markerColor,
+ strokeWidth: 1,
+ left: marker.x * photoData.scale,
+ top: marker.y * photoData.scale,
+ originX: "center",
+ originY: "center",
+ selectable: true,
+ hasControls: false,
+ lockScalingX: true,
+ lockScalingY: true,
+ lockRotation: true,
});
- markerContainer.append($marker);
- makeDraggable($marker, marker);
- });
- }
- function makeDraggable($element, item) {
- let isDragging = false;
- let startLeft = 0;
- let startTop = 0;
- let initialX = 0;
- let initialY = 0;
+ // Crea testo
+ const text = new fabric.Text(marker.partNumber.toString(), {
+ fontFamily: "Arial",
+ fontSize: fontSize,
+ fontWeight: "bold",
+ fill: "#ffffff",
+ left: marker.x * photoData.scale,
+ top: marker.y * photoData.scale,
+ originX: "center",
+ originY: "middle",
+ selectable: true,
+ hasControls: false,
+ lockScalingX: true,
+ lockScalingY: true,
+ lockRotation: true,
+ });
- $element.on("mousedown", function (e) {
- e.preventDefault();
- isDragging = true;
- startLeft = parseFloat($element.css("left")) || 0;
- startTop = parseFloat($element.css("top")) || 0;
- initialX = e.clientX - startLeft;
- initialY = e.clientY - startTop;
- $element.css("z-index", 1001);
- });
+ // Raggruppa cerchio e testo
+ const group = new fabric.Group([circle, text], {
+ left: marker.x * photoData.scale,
+ top: marker.y * photoData.scale,
+ originX: "center",
+ originY: "center",
+ selectable: true,
+ hasControls: false,
+ lockScalingX: true,
+ lockScalingY: true,
+ lockRotation: true,
+ });
- $(document).on("mousemove.dragMarker", function (e) {
- if (!isDragging) return;
- let currentX = e.clientX - initialX;
- let currentY = e.clientY - initialY;
-
- const maxX = photoData.displayWidth - $element.width();
- const maxY = photoData.displayHeight - $element.height();
-
- currentX = Math.max(0, Math.min(currentX, maxX));
- currentY = Math.max(0, Math.min(currentY, maxY));
-
- $element.css({ left: currentX + "px", top: currentY + "px" });
-
- if (item && item.partNumber) {
- item.x = (currentX + 8) / photoData.scale;
- item.y = (currentY + 8) / photoData.scale;
+ group.on("moving", function () {
+ marker.x = this.left / photoData.scale;
+ marker.y = this.top / photoData.scale;
markUnsaved();
- } else {
- const currentPhoto = $("#samplePhoto").attr("src");
- if (photoAnnotations[currentPhoto]) {
- photoAnnotations[currentPhoto].descriptionPosition.x =
- (currentX + 5) / photoData.scale;
- photoAnnotations[currentPhoto].descriptionPosition.y =
- (currentY + 5) / photoData.scale;
- markUnsaved();
- }
- }
+ });
+
+ fabricCanvas.add(group);
+ markerObjects[marker.partNumber] = group;
});
- $(document).on("mouseup.dragMarker", function () {
- if (!isDragging) return;
- isDragging = false;
- $element.css("z-index", 1000);
- $(document).off("mousemove.dragMarker mouseup.dragMarker");
- });
+ fabricCanvas.renderAll();
}
function updateDescriptions() {
@@ -905,14 +1114,19 @@ $(document).ready(function () {
markers: [],
hasDescriptions: false,
descriptionPosition: { x: 10, y: 10 },
+ descriptionSize: {
+ width: photoData.displayWidth * 0.3,
+ height: photoData.displayHeight * 0.3,
+ },
};
const showMixParts = $("#showMixParts").is(":checked");
- const descriptionList = $("#descriptionList");
- descriptionList.empty();
-
if (!annotations.hasDescriptions) {
- descriptionList.css("display", "none");
+ if (descriptionTextbox) {
+ fabricCanvas.remove(descriptionTextbox);
+ descriptionTextbox = null;
+ fabricCanvas.renderAll();
+ }
return;
}
@@ -929,47 +1143,129 @@ $(document).ready(function () {
}
});
- descriptionList.css({
- display: "block",
- top: annotations.descriptionPosition.y * photoData.scale + "px",
- left: annotations.descriptionPosition.x * photoData.scale + "px",
- });
- partsList.forEach((part) =>
- descriptionList.append(`
${part}
`),
- );
+ const text = partsList.join("\n");
- updateMarkers();
+ // Rimuovi il textbox esistente
+ if (descriptionTextbox) {
+ fabricCanvas.remove(descriptionTextbox);
+ descriptionTextbox = null;
+ }
+
+ descriptionTextbox = new fabric.Textbox(text, {
+ left: annotations.descriptionPosition.x * photoData.scale,
+ top: annotations.descriptionPosition.y * photoData.scale,
+ width: annotations.descriptionSize.width,
+ scaleX: 1,
+ scaleY: 1,
+ backgroundColor: "rgba(255, 255, 255, 0.8)",
+ fontFamily: "Arial",
+ fontSize: 24,
+ fill: "#000000",
+ padding: 10,
+ editable: false,
+ selectable: true,
+ hasControls: true,
+ borderColor: "#ccc",
+ cornerColor: "#888",
+ cornerSize: 10,
+ transparentCorners: false,
+ lockRotation: true,
+ lockScalingFlip: true,
+ minScaleLimit: 0.1,
+ });
+
+ descriptionTextbox.on("scaling", function () {
+ fitFontToBox(this);
+ annotations.descriptionSize.width = this.width * this.scaleX;
+ annotations.descriptionSize.height = this.height * this.scaleY;
+ markUnsaved();
+ });
+
+ descriptionTextbox.on("moving", function () {
+ annotations.descriptionPosition.x = this.left / photoData.scale;
+ annotations.descriptionPosition.y = this.top / photoData.scale;
+ markUnsaved();
+ });
+
+ fabricCanvas.add(descriptionTextbox);
+ fitFontToBox(descriptionTextbox);
+ fabricCanvas.renderAll();
+ }
+
+ function fitFontToBox(textbox) {
+ let fontSize = 24;
+ textbox.set("fontSize", fontSize);
+ textbox.setCoords();
+
+ while (
+ (textbox.textLines.length * textbox.fontSize * 1.2 >
+ textbox.height * textbox.scaleY ||
+ textbox._getTransformedDimensions().x >
+ textbox.width * textbox.scaleX) &&
+ fontSize > 8
+ ) {
+ fontSize -= 1;
+ textbox.set("fontSize", fontSize);
+ textbox.setCoords();
+ }
+
+ while (
+ textbox.textLines.length * textbox.fontSize * 1.2 <
+ textbox.height * textbox.scaleY &&
+ textbox._getTransformedDimensions().x <
+ textbox.width * textbox.scaleX &&
+ fontSize < 32
+ ) {
+ fontSize += 1;
+ textbox.set("fontSize", fontSize);
+ textbox.setCoords();
+ }
}
function clearCanvasMarkers(clearDescriptions = true) {
const currentPhoto = $("#samplePhoto").attr("src");
- if (clearDescriptions) {
- if (photoAnnotations[currentPhoto]) {
- photoAnnotations[currentPhoto].hasDescriptions = false;
- photoAnnotations[currentPhoto].descriptionPosition = {
- x: 10,
- y: 10,
- };
+ if (clearDescriptions && photoAnnotations[currentPhoto]) {
+ photoAnnotations[currentPhoto].hasDescriptions = false;
+ photoAnnotations[currentPhoto].descriptionPosition = {
+ x: 10,
+ y: 10,
+ };
+ photoAnnotations[currentPhoto].descriptionSize = {
+ width: photoData.displayWidth * 0.3,
+ height: photoData.displayHeight * 0.3,
+ };
+ if (descriptionTextbox) {
+ fabricCanvas.remove(descriptionTextbox);
+ descriptionTextbox = null;
}
- $("#descriptionList").css("display", "none");
}
- $("#markerContainer").empty();
+
+ for (let partNumber in markerObjects) {
+ fabricCanvas.remove(markerObjects[partNumber]);
+ delete markerObjects[partNumber];
+ }
+ markerObjects = {};
const canvas = document.getElementById("photoCanvas");
const ctx = canvas.getContext("2d");
-
canvas.width = photoData.naturalWidth;
canvas.height = photoData.naturalHeight;
canvas.style.width = `${photoData.displayWidth}px`;
canvas.style.height = `${photoData.displayHeight}px`;
-
- const img = $("#samplePhoto");
ctx.clearRect(0, 0, canvas.width, canvas.height);
- if (img[0].naturalWidth) {
- ctx.drawImage(img.get(0), 0, 0, canvas.width, canvas.height);
+ if ($("#samplePhoto")[0].naturalWidth) {
+ ctx.drawImage(
+ $("#samplePhoto")[0],
+ 0,
+ 0,
+ canvas.width,
+ canvas.height,
+ );
}
- markUnsaved();
updateMarkers();
+ if (photoAnnotations[currentPhoto]?.hasDescriptions) {
+ updateDescriptions();
+ }
}
function undoLastMarker() {
@@ -978,9 +1274,12 @@ $(document).ready(function () {
photoAnnotations[currentPhoto] &&
photoAnnotations[currentPhoto].markers.length > 0
) {
- photoAnnotations[currentPhoto].markers.pop();
- updateMarkers();
- updateDescriptions();
+ const lastMarker = photoAnnotations[currentPhoto].markers.pop();
+ if (markerObjects[lastMarker.partNumber]) {
+ fabricCanvas.remove(markerObjects[lastMarker.partNumber]);
+ delete markerObjects[lastMarker.partNumber];
+ fabricCanvas.renderAll();
+ }
markUnsaved();
}
}
@@ -992,25 +1291,251 @@ $(document).ready(function () {
markers: [],
hasDescriptions: false,
descriptionPosition: { x: 10, y: 10 },
+ descriptionSize: {
+ width: photoData.displayWidth * 0.3,
+ height: photoData.displayHeight * 0.3,
+ },
};
}
photoAnnotations[currentPhoto].hasDescriptions = true;
updateDescriptions();
- makeDraggable($("#descriptionList"));
markUnsaved();
});
$("#removeAnnotationsBtn").on("click", function () {
- clearCanvasMarkers(true); // Remove only descriptions
+ clearCanvasMarkers(true);
});
- $("#undoMarkerBtn").on("click", function () {
- undoLastMarker();
+ $("#undoMarkerBtn").on("click", undoLastMarker);
+
+ // ===================
+ // SAVE PHOTO
+ // ===================
+ $("#savePhotoBtn").on("click", function () {
+ if (!$("#samplePhoto").attr("src")) {
+ const errorMsg = $(
+ '
Nessuna foto caricata da salvare.
',
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ return;
+ }
+
+ const canvas = document.getElementById("photoCanvas");
+ const ctx = canvas.getContext("2d");
+ const img = $("#samplePhoto")[0];
+
+ // Verifica che l'immagine sia caricata
+ if (!img.complete || img.naturalWidth === 0) {
+ const errorMsg = $(
+ '
Errore: l\'immagine non è caricata correttamente.
',
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ return;
+ }
+
+ // Imposta le dimensioni del canvas
+ canvas.width = photoData.naturalWidth;
+ canvas.height = photoData.naturalHeight;
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
+
+ // Renderizza Fabric overlay scalato su photoCanvas
+ try {
+ const fabricDataURL = fabricCanvas.toDataURL({
+ format: "png",
+ multiplier: 1 / photoData.scale,
+ });
+ const tempImg = new Image();
+ tempImg.src = fabricDataURL;
+ tempImg.onload = function () {
+ ctx.drawImage(tempImg, 0, 0, canvas.width, canvas.height);
+ finalizeSave();
+ };
+ tempImg.onerror = function () {
+ const errorMsg = $(
+ '
Errore durante il rendering dell\'overlay Fabric.js.
',
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ };
+ } catch (e) {
+ const errorMsg = $(
+ '
Errore durante la generazione dell\'immagine annotata.
',
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ return;
+ }
+
+ // 1. PRIMA - Modifica la funzione finalizeSave per pulire la foto originale:
+ function finalizeSave() {
+ try {
+ const dataURL = canvas.toDataURL("image/png");
+ const timestamp = new Date()
+ .toISOString()
+ .replace(/[:.]/g, "-");
+ const iddatadb = $("#partsModal").data("iddatadb");
+ const idquotations = $("#partsModal").data("idquotations");
+ const id = iddatadb || idquotations;
+ const endpoint = idquotations
+ ? "save_annotated_photo_quotation.php"
+ : "save_annotated_photo.php";
+ const dataParam = idquotations
+ ? { idquotations: id }
+ : { iddatadb: id };
+ const finalName = `photo_${id}_${timestamp}.png`;
+
+ $.ajax({
+ url: endpoint,
+ method: "POST",
+ data: {
+ dataURL: dataURL,
+ filename: finalName,
+ ...dataParam,
+ },
+ success: function (response) {
+ if (response.success) {
+ // Mostra messaggio non-blocking
+ const successMsg = $(
+ '
Foto salvata con successo: ' +
+ response.file_path +
+ "
",
+ );
+ $("#partsModal .modal-body").prepend(successMsg);
+ setTimeout(function () {
+ successMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+
+ // AGGIORNA IL DROPDOWN E TORNA ALLA PRIMA FOTO
+ const photoSelector = $("#photoSelector");
+ if (photoSelector.length > 0) {
+ const newPhotoPath = response.file_path;
+ const newPhotoName = newPhotoPath
+ .split("/")
+ .pop();
+ const optionCount =
+ photoSelector.find("option").length;
+
+ // Aggiungi la nuova opzione al dropdown
+ const newOption = $("
")
+ .val(newPhotoPath)
+ .text(
+ `Photo ${optionCount + 1} - ${newPhotoName}`,
+ );
+ photoSelector.append(newOption);
+
+ // Seleziona la prima foto del dropdown
+ const firstPhotoPath = photoSelector
+ .find("option:first")
+ .val();
+ photoSelector.val(firstPhotoPath);
+
+ // Carica effettivamente la prima foto
+ loadSinglePhoto(firstPhotoPath);
+ } else {
+ // Se non c'è dropdown, ricarica la foto corrente per vedere la versione pulita
+ const currentPhoto =
+ $("#samplePhoto").attr("src");
+ if (currentPhoto) {
+ loadSinglePhoto(currentPhoto);
+ }
+ }
+
+ // PULISCI LA FOTO ORIGINALE (come richiesto)
+ const currentPhoto = $("#samplePhoto").attr("src");
+
+ // Inizializza annotazioni vuote per la nuova foto salvata
+ const newPhoto = response.file_path;
+ photoAnnotations[newPhoto] = {
+ markers: [],
+ hasDescriptions: false,
+ descriptionPosition: { x: 10, y: 10 },
+ descriptionSize: {
+ width: photoData.displayWidth * 0.3,
+ height: photoData.displayHeight * 0.3,
+ },
+ };
+
+ // SVUOTA LE ANNOTAZIONI DELLA FOTO ORIGINALE
+ photoAnnotations[currentPhoto] = {
+ markers: [],
+ hasDescriptions: false,
+ descriptionPosition: { x: 10, y: 10 },
+ descriptionSize: {
+ width: photoData.displayWidth * 0.3,
+ height: photoData.displayHeight * 0.3,
+ },
+ };
+
+ // PULISCI IL CANVAS COMPLETAMENTE
+ clearCanvasMarkers(true); // Pulisce marker e descrizioni
+ clearUnsaved();
+ } else {
+ const errorMsg = $(
+ '
Errore: ' +
+ (response.message || "Errore sconosciuto") +
+ "
",
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ }
+ },
+ error: function (xhr, status, error) {
+ const errorMsg = $(
+ '
Errore nel salvataggio della foto: ' +
+ error +
+ " (" +
+ xhr.status +
+ ")
",
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ },
+ });
+ } catch (e) {
+ const errorMsg = $(
+ '
Errore durante la creazione dell\'immagine finale.
',
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ }
+ }
});
- let unsavedChanges = false;
-
- // --- helper functions ---
+ // ===================
+ // HELPER FUNCTIONS
+ // ===================
function markUnsaved() {
if (!unsavedChanges) {
unsavedChanges = true;
@@ -1020,173 +1545,9 @@ $(document).ready(function () {
function clearUnsaved() {
unsavedChanges = false;
- $("#savePhotoBtn").removeClass("unsaved").text("Salva Foto con Nome");
+ $("#savePhotoBtn").removeClass("unsaved").text("Salva Foto");
}
- // --- event listeners ---
$(document).on("input change", "#partsTableBody input", markUnsaved);
$(document).on("click", ".add-row, .add-mix-row, .remove-row", markUnsaved);
- $(document).on("markerChanged descriptionChanged", markUnsaved);
-
- // --- modal close protection ---
- $("#partsModal").on("hide.bs.modal", function (e) {
- if (unsavedChanges) {
- if (!confirm("Hai modifiche non salvate. Vuoi davvero uscire?")) {
- e.preventDefault();
- }
- }
- });
-
- // --- SAVE BUTTON ---
- $("#savePhotoBtn").on("click", function () {
- const canvas = document.getElementById("photoCanvas");
- const ctx = canvas.getContext("2d");
- const img = $("#samplePhoto");
-
- const naturalWidth = img.get(0).naturalWidth;
- const naturalHeight = img.get(0).naturalHeight;
- canvas.width = naturalWidth;
- canvas.height = naturalHeight;
-
- ctx.drawImage(img.get(0), 0, 0, naturalWidth, naturalHeight);
-
- const currentPhoto = $("#samplePhoto").attr("src");
- const annotations = photoAnnotations[currentPhoto] || {
- markers: [],
- hasDescriptions: false,
- descriptionPosition: { x: 10, y: 10 },
- };
- const showMixParts = $("#showMixParts").is(":checked");
-
- if (annotations.hasDescriptions) {
- const partsList = [];
- $("#partsTableBody tr").each(function () {
- const partNumber = $(this).find(".part-number").val();
- const partDescription = $(this).find(".part-description").val();
- if (
- partNumber &&
- partDescription &&
- (showMixParts || !partDescription.startsWith("Mix"))
- ) {
- partsList.push(`${partNumber} ${partDescription}`);
- }
- });
-
- if (partsList.length > 0) {
- const fontSize = Math.round(naturalWidth * 0.02);
- ctx.font = fontSize + "px Arial";
- const textHeight = fontSize + 8;
- const boxWidth = Math.round(naturalWidth * 0.28);
- const boxHeight = partsList.length * textHeight + 25;
-
- const x = annotations.descriptionPosition.x;
- const y = annotations.descriptionPosition.y;
-
- ctx.save();
- ctx.shadowColor = "rgba(0,0,0,0.3)";
- ctx.shadowBlur = 8;
- ctx.shadowOffsetX = 3;
- ctx.shadowOffsetY = 3;
-
- ctx.fillStyle = "rgba(255, 255, 255, 0.9)";
- ctx.beginPath();
- ctx.roundRect(x, y, boxWidth, boxHeight, 12);
- ctx.fill();
-
- ctx.restore();
-
- ctx.fillStyle = "#111111";
- partsList.forEach((part, index) => {
- ctx.fillText(part, x + 15, y + 35 + index * textHeight);
- });
- }
- }
-
- const markers = annotations.markers;
- markers.forEach((marker) => {
- const partRow = $("#partsTableBody tr").filter(function () {
- return $(this).find(".part-number").val() == marker.partNumber;
- });
- const partDescription = partRow.find(".part-description").val();
- if (
- !showMixParts &&
- partDescription &&
- partDescription.startsWith("Mix")
- ) {
- return;
- }
-
- const x = marker.x;
- const y = marker.y;
- const radius = Math.max(5, Math.round(naturalWidth * 0.025));
- const fontSize = Math.max(8, Math.round(radius * 0.9));
- const markerColor =
- marker.color || partColors[marker.partNumber] || "#ff0000";
-
- ctx.beginPath();
- ctx.arc(x, y, radius, 0, 2 * Math.PI);
- ctx.fillStyle = markerColor; // Use the stored color
- ctx.fill();
-
- ctx.lineWidth = 3;
- ctx.strokeStyle = markerColor; // Use the same color for the border
- ctx.stroke();
-
- ctx.fillStyle = "#ffffff";
- ctx.font = `bold ${fontSize}px Arial`;
- ctx.textAlign = "center";
- ctx.textBaseline = "middle";
- ctx.fillText(marker.partNumber || "", x, y);
- });
-
- const dataURL = canvas.toDataURL("image/png");
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
- const iddatadb = $("#partsModal").data("iddatadb");
- const defaultName = `photo_${iddatadb}_${timestamp}.png`;
-
- const newName = prompt(
- "Inserisci il nome del file (senza estensione):",
- defaultName.split(".png")[0],
- );
-
- if (newName) {
- const finalName = newName + "_" + timestamp + ".png";
- $.ajax({
- url: "save_annotated_photo.php",
- method: "POST",
- data: {
- dataURL: dataURL,
- filename: finalName,
- iddatadb: iddatadb,
- },
- success: function (response) {
- if (response.success) {
- alert(
- "Foto salvata con successo: " + response.file_path,
- );
- $("#samplePhoto").attr("src", response.file_path);
- loadPhoto(iddatadb);
- clearCanvasMarkers(false); // Preserve descriptions
- clearUnsaved();
- } else {
- alert("Errore: " + response.message);
- }
- },
- error: function (xhr, status, error) {
- alert("Errore Ajax: " + error);
- },
- });
- }
- });
-
- // ===================
- // DEBUG HOVER LOGS
- // ===================
- $(document).on("mouseenter", "tr", function () {
- // console.log("Mouse entrato su riga");
- });
-
- $(document).on("mouseleave", "tr", function () {
- // console.log("Mouse uscito da riga");
- });
});
diff --git a/public/userarea/photos.js b/public/userarea/photos.js
index c499ac3..bf22028 100644
--- a/public/userarea/photos.js
+++ b/public/userarea/photos.js
@@ -1,19 +1,21 @@
document.addEventListener("DOMContentLoaded", function () {
// Funzione per caricare il contenuto del popup
- async function loadPopupContent(iddatadb) {
+ async function loadPopupContent(iddatadb, idquotations) {
const popupContent = document.getElementById("popupContent");
if (!popupContent) {
console.error("Elemento popupContent non trovato");
return;
}
try {
- const response = await fetch(
- `photos_popup.php?iddatadb=${iddatadb}`,
- );
+ const endpoint = idquotations
+ ? `photos_popup.php?idquotations=${idquotations}`
+ : `photos_popup.php?iddatadb=${iddatadb}`;
+ console.log("Caricamento popup da:", endpoint);
+ const response = await fetch(endpoint);
if (!response.ok)
throw new Error("Errore nella risposta del server");
popupContent.innerHTML = await response.text();
- attachPhotoEventListeners(iddatadb);
+ attachPhotoEventListeners(iddatadb, idquotations);
} catch (error) {
popupContent.innerHTML = `
Errore durante il caricamento: ${error.message}
`;
console.error("Errore in loadPopupContent:", error);
@@ -21,7 +23,7 @@ document.addEventListener("DOMContentLoaded", function () {
}
// Funzione per gestire la webcam
- function setupWebcam(iddatadb) {
+ function setupWebcam(iddatadb, idquotations) {
const openWebcamBtn = document.getElementById("openWebcamBtn");
const webcamArea = document.getElementById("webcamArea");
const webcamVideo = document.getElementById("webcamVideo");
@@ -158,7 +160,7 @@ document.addEventListener("DOMContentLoaded", function () {
if (loader) {
loader.style.display = "flex";
}
- await handleFiles([file], iddatadb);
+ await handleFiles([file], iddatadb, idquotations);
if (stream) {
stream.getTracks().forEach((track) => track.stop());
stream = null;
@@ -171,7 +173,7 @@ document.addEventListener("DOMContentLoaded", function () {
});
}
- async function handleFiles(files, iddatadb) {
+ async function handleFiles(files, iddatadb, idquotations) {
const loader = document.getElementById("loader");
if (!loader) {
console.error("Elemento loader non trovato");
@@ -193,7 +195,12 @@ document.addEventListener("DOMContentLoaded", function () {
const formData = new FormData();
formData.append("photo", file);
- formData.append("iddatadb", iddatadb);
+ if (idquotations) {
+ formData.append("idquotations", idquotations);
+ } else {
+ formData.append("iddatadb", iddatadb);
+ }
+
try {
const response = await fetch("upload_photo.php", {
method: "POST",
@@ -201,7 +208,7 @@ document.addEventListener("DOMContentLoaded", function () {
});
const result = await response.json();
if (result.success) {
- loadPopupContent(iddatadb);
+ loadPopupContent(iddatadb, idquotations);
} else {
alert("Errore durante il caricamento: " + result.message);
}
@@ -213,7 +220,7 @@ document.addEventListener("DOMContentLoaded", function () {
}
}
- function attachPhotoEventListeners(iddatadb) {
+ function attachPhotoEventListeners(iddatadb, idquotations) {
const dropArea = document.getElementById("dropArea");
const photoInput = document.getElementById("photoInput");
const photosModal = document.getElementById("photosModal");
@@ -257,7 +264,7 @@ document.addEventListener("DOMContentLoaded", function () {
(e) => {
const files = e.dataTransfer.files;
if (files.length > 0) {
- handleFiles(files, iddatadb);
+ handleFiles(files, iddatadb, idquotations);
}
},
false,
@@ -276,7 +283,7 @@ document.addEventListener("DOMContentLoaded", function () {
(e) => {
const files = e.target.files;
if (files.length > 0) {
- handleFiles(files, iddatadb);
+ handleFiles(files, iddatadb, idquotations);
}
e.target.value = "";
},
@@ -298,7 +305,7 @@ document.addEventListener("DOMContentLoaded", function () {
});
const result = await response.json();
if (result.success) {
- loadPopupContent(iddatadb);
+ loadPopupContent(iddatadb, idquotations);
} else {
alert(
"Errore durante l'eliminazione: " +
@@ -336,7 +343,7 @@ document.addEventListener("DOMContentLoaded", function () {
}
});
- setupWebcam(iddatadb);
+ setupWebcam(iddatadb, idquotations);
const createCollageBtn = document.getElementById("createCollageBtn");
if (createCollageBtn) {
@@ -869,9 +876,9 @@ document.addEventListener("DOMContentLoaded", function () {
type: "image/jpeg",
});
- await handleFiles([file], iddatadb);
+ await handleFiles([file], iddatadb, idquotations);
document.getElementById("collageModal").style.display = "none";
- loadPopupContent(iddatadb);
+ loadPopupContent(iddatadb, idquotations);
});
}
@@ -992,8 +999,14 @@ document.addEventListener("DOMContentLoaded", function () {
if (photosButtons.length && photosModal && closeBtn) {
photosButtons.forEach((button) => {
button.addEventListener("click", function () {
- const iddatadb = this.getAttribute("data-iddatadb");
- loadPopupContent(iddatadb);
+ const iddatadb = this.getAttribute("data-iddatadb") || null;
+ const idquotations =
+ this.getAttribute("data-idquotations") || null;
+ console.log("Apertura modale foto con:", {
+ iddatadb,
+ idquotations,
+ });
+ loadPopupContent(iddatadb, idquotations);
photosModal.style.display = "block";
document.querySelector(".overlay").style.display = "none";
});
diff --git a/public/userarea/photos_popup.php b/public/userarea/photos_popup.php
index fade961..e84019c 100644
--- a/public/userarea/photos_popup.php
+++ b/public/userarea/photos_popup.php
@@ -17,69 +17,131 @@ use Endroid\QrCode\QrCode;
use Endroid\QrCode\RoundBlockSizeMode;
use Endroid\QrCode\Writer\PngWriter;
+// Abilita logging per debug
+ini_set('display_errors', 1);
+ini_set('display_startup_errors', 1);
+error_reporting(E_ALL);
+ini_set('log_errors', 1);
+ini_set('error_log', __DIR__ . '/photos_popup_debug.log');
+
+// Log iniziale
+error_log("Richiesta a photos_popup.php: " . print_r($_GET, true));
+
// Carica le variabili d'ambiente
try {
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../../');
$dotenv->load();
} catch (Exception $e) {
error_log("Errore nel caricamento del file .env: " . $e->getMessage());
- echo json_encode(['error' => 'Errore nel caricamento del file di configurazione']);
+?>
+
+ 'Variabile BASE_URL non definita']);
+?>
+
+getConnection();
-// Verifica che l'iddatadb sia stato passato
-if (!isset($_GET['iddatadb']) || empty($_GET['iddatadb'])) {
- echo json_encode(['error' => 'ID riga non fornito']);
+// Verifica che almeno uno degli ID sia passato
+$iddatadb = isset($_GET['iddatadb']) && !empty($_GET['iddatadb']) ? intval($_GET['iddatadb']) : null;
+$idquotations = isset($_GET['idquotations']) && !empty($_GET['idquotations']) ? intval($_GET['idquotations']) : null;
+
+if (!$iddatadb && !$idquotations) {
+ error_log("Errore: ID riga o ID quotations non fornito");
+?>
+
+prepare("SELECT iddatadb, sample_code FROM datadb WHERE iddatadb = ?");
-$stmt->execute([$iddatadb]);
-$row = $stmt->fetch(PDO::FETCH_ASSOC);
-
-if (!$row) {
- echo json_encode(['error' => 'Riga non trovata']);
+if ($iddatadb && $idquotations) {
+ error_log("Errore: Non è possibile specificare sia iddatadb che idquotations");
+?>
+
+ prepare("SELECT {$paramName}, {$field} FROM {$table} WHERE {$paramName} = ?");
+ $stmt->execute([$paramValue]);
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ if (!$row) {
+ error_log("Errore: Riga non trovata per {$paramName} = {$paramValue}");
+ ?>
+
+ getMessage());
+ ?>
+
+prepare("SELECT id, file_path, file_name, description, uploaded_at FROM datadb_photos WHERE iddatadb = ? ORDER BY uploaded_at DESC");
-$stmt->execute([$iddatadb]);
-$photos = $stmt->fetchAll(PDO::FETCH_ASSOC);
+try {
+ $stmt = $pdo->prepare("SELECT id, file_path, file_name, description, uploaded_at FROM {$photoTable} WHERE {$photoParamName} = ? ORDER BY uploaded_at DESC");
+ $stmt->execute([$paramValue]);
+ $photos = $stmt->fetchAll(PDO::FETCH_ASSOC);
+} catch (Exception $e) {
+ error_log("Errore query foto: " . $e->getMessage());
+ $photos = []; // Imposta array vuoto in caso di errore
+}
// Definisci il percorso base per le foto
$photoBasePath = '../photostrf/';
// Usa la variabile d'ambiente BASE_URL
-$baseUrl = rtrim($_ENV['BASE_URL'], '/'); // Rimuove eventuali slash finali
-$uploadUrl = $baseUrl . "/upload_photos_mobile.php?iddatadb=" . $iddatadb;
+$baseUrl = rtrim($_ENV['BASE_URL'], '/');
+$uploadUrl = $iddatadb
+ ? $baseUrl . "/upload_photos_mobile.php?iddatadb=" . $iddatadb
+ : $baseUrl . "/upload_photos_mobile.php?idquotations=" . $idquotations;
// Genera il QR code con endroid/qr-code 6.0.6
$qrCodeDir = '../photostrf/qrcodes/';
if (!is_dir($qrCodeDir)) {
mkdir($qrCodeDir, 0755, true);
}
-$qrCodeFile = $qrCodeDir . "qrcode_{$iddatadb}.png";
+$qrCodeFile = $qrCodeDir . "qrcode_{$id}.png";
$writer = new PngWriter();
-
-// Crea il QR code usando il costruttore
$qrCode = new QrCode(
data: $uploadUrl,
encoding: new Encoding('UTF-8'),
@@ -103,13 +165,13 @@ $result->saveToFile($qrCodeFile);