diff --git a/public/userarea/error_log.txt b/public/userarea/error_log.txt index 5ddbde3..4060a41 100644 --- a/public/userarea/error_log.txt +++ b/public/userarea/error_log.txt @@ -12,3 +12,6 @@ 2025-08-26 16:49:24 - Risposta non JSON valida: fetchAll(PDO::FETCH_ASSOC) as $row) { + + + \ No newline at end of file diff --git a/public/userarea/parts.js b/public/userarea/parts.js index c77ec00..1b562b6 100644 --- a/public/userarea/parts.js +++ b/public/userarea/parts.js @@ -16,10 +16,15 @@ $(document).ready(function () { let photoAnnotations = {}; // colors keyed by part number let partColors = {}; + // matrice IDs keyed by part number + let partMatrice = {}; // selection let selectedPartNumber = null; + // lista delle matrici precaricata + let matrici = []; + // =================== // VOICE RECOGNITION SETUP // =================== @@ -27,13 +32,13 @@ $(document).ready(function () { window.SpeechRecognition || window.webkitSpeechRecognition; let recognition = null; let isVoiceActive = false; - const magicWord = "salva"; // Parola magica scelta: "prossima" (fa andare alla riga successiva) + const magicWord = "salva"; // Parola magica scelta if (SpeechRecognition) { recognition = new SpeechRecognition(); - recognition.lang = "it-IT"; // Lingua italiana - recognition.continuous = true; // Ascolto continuo - recognition.interimResults = false; // Solo risultati finali per semplicità + recognition.lang = "it-IT"; + recognition.continuous = true; + recognition.interimResults = false; recognition.onresult = function (event) { const transcript = event.results[ @@ -43,11 +48,10 @@ $(document).ready(function () { .toLowerCase(); console.log("Transcript vocale:", transcript); - const $currentRow = $("#partsTableBody tr:last"); // Ultima riga corrente + const $currentRow = $("#partsTableBody tr:last"); const $descriptionInput = $currentRow.find(".part-description"); if (transcript.includes(magicWord)) { - // Rimuovi la parola magica e aggiungi il resto alla descrizione corrente const cleanedTranscript = transcript .replace(magicWord, "") .trim(); @@ -59,9 +63,8 @@ $(document).ready(function () { cleanedTranscript ).trim(), ); - $descriptionInput.trigger("blur"); // Salva se necessario + $descriptionInput.trigger("blur"); } - // Aggiungi nuova riga (simile a click su +) const maxPartNumber = Math.max( ...$("#partsTableBody tr") .map(function () { @@ -73,22 +76,19 @@ $(document).ready(function () { .get(), ); addNewRow(maxPartNumber + 1); - // Focus sulla nuova descrizione const $newRow = $("#partsTableBody tr:last"); $newRow.find(".part-description").focus(); } else { - // Aggiungi il transcript alla descrizione corrente $descriptionInput.val( ($descriptionInput.val() + " " + transcript).trim(), ); - $descriptionInput.trigger("blur"); // Salva se necessario + $descriptionInput.trigger("blur"); } }; recognition.onerror = function (event) { console.error("Errore riconoscimento vocale:", event.error); if (event.error === "no-speech" || event.error === "aborted") { - // Riavvia se necessario if (isVoiceActive) recognition.start(); } else { alert("Errore nel riconoscimento vocale: " + event.error); @@ -97,18 +97,15 @@ $(document).ready(function () { }; recognition.onend = function () { - if (isVoiceActive) { - recognition.start(); // Riavvia per ascolto continuo - } + if (isVoiceActive) recognition.start(); }; } else { console.warn("Riconoscimento vocale non supportato dal browser."); - $("#toggleVoiceBtn").hide(); // Nascondi pulsante se non supportato + $("#toggleVoiceBtn").hide(); } function toggleVoiceRecognition() { if (!recognition) return; - isVoiceActive = !isVoiceActive; const $btn = $("#toggleVoiceBtn"); if (isVoiceActive) { @@ -116,7 +113,6 @@ $(document).ready(function () { ' Stop Voce', ); recognition.start(); - // Focus iniziale sull'ultima descrizione const $currentRow = $("#partsTableBody tr:last"); $currentRow.find(".part-description").focus(); } else { @@ -154,8 +150,39 @@ $(document).ready(function () { $("#trfHeader").text(`${iddatadb} - ${importRef} - ${description}`); $("#partsModal").data("iddatadb", iddatadb); - loadPhoto(iddatadb); - loadExistingParts(iddatadb); + // Precarica le matrici una volta sola + if (matrici.length === 0) { + $.ajax({ + url: "get_matrice.php", + method: "GET", + dataType: "json", + success: function (data) { + matrici = data.value || []; + console.log( + "Matrici precaricate (una volta sola):", + matrici, + ); + initializeGlobalSelect2(); + loadPhoto(iddatadb); + loadExistingParts(iddatadb); + }, + error: function (xhr, status, error) { + console.error( + "Errore nel precaricamento delle matrici:", + error, + ); + alert("Errore nel caricamento delle matrici: " + error); + matrici = []; + loadPhoto(iddatadb); + loadExistingParts(iddatadb); + }, + }); + } else { + console.log("Matrici già precaricate, riutilizzo."); + initializeGlobalSelect2(); + loadPhoto(iddatadb); + loadExistingParts(iddatadb); + } if (partsModal) { const modal = new bootstrap.Modal(partsModal); @@ -250,7 +277,7 @@ $(document).ready(function () { function loadSinglePhoto(photoPath) { const img = $("#samplePhoto"); - img.off("load"); // avoid stacking multiple handlers + img.off("load"); img.attr("src", photoPath); img.on("load", function () { @@ -281,7 +308,6 @@ $(document).ready(function () { canvas.width = naturalWidth; canvas.height = naturalHeight; - canvas.style.width = `${displayWidth}px`; canvas.style.height = `${displayHeight}px`; @@ -321,7 +347,6 @@ $(document).ready(function () { `; $("#partsTableBody").append(newRow); updateRowButtons(); - // Initialize color for the new part const partNumber = nextPartNumber || 1; partColors[partNumber] = defaultColor; } @@ -377,9 +402,10 @@ $(document).ready(function () { if (response.success) { $row.remove(); delete partColors[partNumber]; + delete partMatrice[partNumber]; updateRowButtons(); updatePartsList(); - clearCanvasMarkers(false); // Preserve descriptions + clearCanvasMarkers(false); } else { alert("Errore nell'eliminazione: " + response.message); } @@ -398,6 +424,7 @@ $(document).ready(function () { } else { $row.remove(); delete partColors[partNumber]; + delete partMatrice[partNumber]; updateRowButtons(); updatePartsList(); } @@ -412,7 +439,6 @@ $(document).ready(function () { const $saveLoading = $row.find(".save-loading"); const iddatadb = $("#partsModal").data("iddatadb"); const isMix = partDescription.startsWith("Mix") ? "Y" : "N"; - const partId = $row.data("part-id") || null; if (partDescription && iddatadb) { @@ -465,6 +491,160 @@ $(document).ready(function () { markUnsaved(); }); + // Funzione per inizializzare Select2 sulle tendine delle matrici + function initializeSelect2($select, partNumber, partId, idmatrice) { + if (typeof $.fn.select2 === "undefined") { + console.error("Select2 non disponibile per parte " + partNumber); + $select.replaceWith( + '', + ); + return; + } + + // Popola con i dati precaricati + const options = matrici.map(function (matrice) { + return { + id: matrice.IdMatrice, + text: matrice.NomeMatriceTraduzione, + }; + }); + + $select.select2({ + placeholder: "Seleziona matrice", + allowClear: true, + data: options, // Carica la lista completa all'apertura + dropdownParent: $("#partsModal"), + matcher: function (params, data) { + // Filtraggio lato client sulla descrizione + if (!params.term || params.term.length < 3) { + return data; // Mostra tutto se meno di 3 caratteri + } + const term = params.term.toUpperCase(); + if (data.text.toUpperCase().indexOf(term) >= 0) { + return data; + } + return null; + }, + }); + + // Carica la matrice esistente, se presente + if (partId && partId !== "new" && idmatrice) { + const matrice = matrici.find((m) => m.IdMatrice == idmatrice); + if (matrice) { + console.log( + "Preselezione matrice per partNumber " + partNumber + ":", + matrice, + ); + const option = new Option( + matrice.NomeMatriceTraduzione, + matrice.IdMatrice, + true, + true, + ); + $select.append(option).trigger("change"); + partMatrice[partNumber] = matrice.IdMatrice; + } else { + console.warn( + "Matrice con ID " + + idmatrice + + " non trovata per partNumber " + + partNumber, + ); + } + } + + // Gestione del cambio della matrice + $select.on("change", function () { + const idmatrice = $(this).val(); + const $listItem = $(this).closest("li"); + const $saveStatus = $listItem.find(".save-status"); + const $saveLoading = $listItem.find(".save-loading"); + + console.log( + "Cambio matrice per partNumber:", + partNumber, + "nuovo idmatrice:", + idmatrice, + ); + partMatrice[partNumber] = idmatrice || null; + + if (partId && partId !== "new") { + $saveLoading.show(); + $saveStatus.hide(); + + $.ajax({ + url: "save_matrice.php", + method: "POST", + data: JSON.stringify({ + iddatadb: $("#partsModal").data("iddatadb"), + parts: [ + { + id: partId, + idmatrice: idmatrice || null, + }, + ], + }), + contentType: "application/json", + success: function (response) { + if (response.success) { + $saveLoading.hide(); + $saveStatus.show(); + setTimeout(() => $saveStatus.hide(), 2000); + } else { + alert( + "Errore nel salvataggio della matrice: " + + response.message, + ); + $saveLoading.hide(); + } + }, + error: function (xhr, status, error) { + alert("Errore nel salvataggio della matrice: " + error); + $saveLoading.hide(); + }, + }); + } + }); + } + + // Funzione per inizializzare Select2 sul dropdown globale + function initializeGlobalSelect2() { + const $select = $("#global-matrice"); + if (typeof $.fn.select2 === "undefined") { + console.error("Select2 non disponibile per il dropdown globale"); + $select.replaceWith( + '', + ); + return; + } + + // Popola con i dati precaricati + const options = matrici.map(function (matrice) { + return { + id: matrice.IdMatrice, + text: matrice.NomeMatriceTraduzione, + }; + }); + + $select.select2({ + placeholder: "Seleziona matrice globale", + allowClear: true, + data: options, // Carica la lista completa all'apertura + dropdownParent: $("#partsModal"), + matcher: function (params, data) { + // Filtraggio lato client sulla descrizione + if (!params.term || params.term.length < 3) { + return data; // Mostra tutto se meno di 3 caratteri + } + const term = params.term.toUpperCase(); + if (data.text.toUpperCase().indexOf(term) >= 0) { + return data; + } + return null; + }, + }); + } + function loadExistingParts(iddatadb) { $.ajax({ url: "load_parts.php", @@ -493,6 +673,9 @@ $(document).ready(function () { `; $("#partsTableBody").append(newRow); partColors[part.part_number] = defaultColor; + if (part.idmatrice) { + partMatrice[part.part_number] = part.idmatrice; + } }); } else { addNewRow(1); @@ -520,6 +703,7 @@ $(document).ready(function () { $("#partsTableBody tr").each(function () { const partNumber = $(this).find(".part-number").val(); const partDescription = $(this).find(".part-description").val(); + const partId = $(this).data("part-id"); const partColor = partColors[partNumber] || (partDescription.startsWith("Mix") ? "#0000ff" : "#ff0000"); @@ -529,14 +713,27 @@ $(document).ready(function () { (showMixParts || !partDescription.startsWith("Mix")) ) { const listItem = ` -
  • - ${partNumber} - ${partDescription} -
    - - -
    -
  • `; +
  • + ${partNumber} - ${partDescription} +
    + + + + + + +
    +
  • `; $("#partsList").append(listItem); + const $select = $("#partsList").find( + `li[data-part-number="${partNumber}"] .part-matrice`, + ); + initializeSelect2( + $select, + partNumber, + partId, + partMatrice[partNumber], + ); } }); updateMarkers(); @@ -546,8 +743,8 @@ $(document).ready(function () { const $rows = $("#partsTableBody tr"); const iddatadb = $("#partsModal").data("iddatadb"); let newPartColors = {}; + let newPartMatrice = {}; - // Raccogli tutte le righe con i loro dati attuali let partsData = $rows .map(function (index) { const $row = $(this); @@ -558,23 +755,21 @@ $(document).ready(function () { }) .get(); - // Rinumera in modo sequenziale partsData.forEach((part, index) => { const newNumber = index + 1; newPartColors[newNumber] = partColors[part.partNumber] || "#ff0000"; + newPartMatrice[newNumber] = partMatrice[part.partNumber] || null; 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; + partMatrice = newPartMatrice; - // Aggiorna i marker nelle annotazioni const currentPhoto = $("#samplePhoto").attr("src"); if (photoAnnotations[currentPhoto]) { photoAnnotations[currentPhoto].markers.forEach((marker) => { @@ -589,12 +784,12 @@ $(document).ready(function () { }); } - // 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", + idmatrice: partMatrice[part.partNumber] || null, })); console.log( @@ -605,10 +800,7 @@ $(document).ready(function () { $.ajax({ url: "renumber_parts.php", method: "POST", - data: JSON.stringify({ - iddatadb: iddatadb, - parts: partsToSave, - }), + data: JSON.stringify({ iddatadb: iddatadb, parts: partsToSave }), contentType: "application/json", success: function (response) { console.log("Risposta da renumber_parts.php:", response); @@ -686,10 +878,23 @@ $(document).ready(function () { updatePartsList(); }); + $(document).on("click", ".propagate-matrice-btn", function () { + const $listItem = $(this).closest("li"); + const globalVal = $("#global-matrice").val(); + if (globalVal) { + $listItem.find(".part-matrice").val(globalVal).trigger("change"); + } else { + alert("Seleziona una matrice globale prima di propagare."); + } + }); + $("#partsList").on("click", "li", function (e) { if ( $(e.target).hasClass("add-to-mix-btn") || - $(e.target).hasClass("part-color") + $(e.target).hasClass("part-color") || + $(e.target).hasClass("part-matrice") || + $(e.target).hasClass("propagate-matrice-btn") || + $(e.target).closest(".select2-container").length ) return; selectedPartNumber = $(this).data("part-number"); @@ -703,6 +908,7 @@ $(document).ready(function () { $("#renumberPartsBtn").on("click", function () { renumberParts(); }); + // =================== // MARKERS & DESCRIPTIONS // =================== @@ -716,7 +922,7 @@ $(document).ready(function () { const clickX = e.clientX - rect.left; const clickY = e.clientY - rect.top; - const x = clickX / photoData.scale; // convert to NATURAL coords + const x = clickX / photoData.scale; const y = clickY / photoData.scale; const currentPhoto = $("#samplePhoto").attr("src"); @@ -957,7 +1163,7 @@ $(document).ready(function () { }); $("#removeAnnotationsBtn").on("click", function () { - clearCanvasMarkers(true); // Remove only descriptions + clearCanvasMarkers(true); }); $("#undoMarkerBtn").on("click", function () { @@ -966,7 +1172,6 @@ $(document).ready(function () { let unsavedChanges = false; - // --- helper functions --- function markUnsaved() { if (!unsavedChanges) { unsavedChanges = true; @@ -979,12 +1184,10 @@ $(document).ready(function () { $("#savePhotoBtn").removeClass("unsaved").text("Salva Foto con Nome"); } - // --- 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?")) { @@ -993,7 +1196,6 @@ $(document).ready(function () { } }); - // --- SAVE BUTTON --- $("#savePhotoBtn").on("click", function () { const canvas = document.getElementById("photoCanvas"); const ctx = canvas.getContext("2d"); @@ -1081,11 +1283,10 @@ $(document).ready(function () { ctx.beginPath(); ctx.arc(x, y, radius, 0, 2 * Math.PI); - ctx.fillStyle = markerColor; // Use the stored color + ctx.fillStyle = markerColor; ctx.fill(); - ctx.lineWidth = 3; - ctx.strokeStyle = markerColor; // Use the same color for the border + ctx.strokeStyle = markerColor; ctx.stroke(); ctx.fillStyle = "#ffffff"; @@ -1122,7 +1323,7 @@ $(document).ready(function () { ); $("#samplePhoto").attr("src", response.file_path); loadPhoto(iddatadb); - clearCanvasMarkers(false); // Preserve descriptions + clearCanvasMarkers(false); clearUnsaved(); } else { alert("Errore: " + response.message); diff --git a/public/userarea/save_matrice.php b/public/userarea/save_matrice.php new file mode 100644 index 0000000..ba130b2 --- /dev/null +++ b/public/userarea/save_matrice.php @@ -0,0 +1,44 @@ +getConnection(); + +$data = json_decode(file_get_contents('php://input'), true); + +$iddatadb = $data['iddatadb'] ?? null; +$parts = $data['parts'] ?? []; + +if (!$iddatadb || empty($parts)) { + echo json_encode(['success' => false, 'message' => 'Dati mancanti']); + exit; +} + +$part = $parts[0]; +$partId = $part['id'] ?? null; +$idmatrice = $part['idmatrice'] ?? null; + +if (!$partId) { + echo json_encode(['success' => false, 'message' => 'ID della parte mancante']); + exit; +} + +try { + $stmt = $pdo->prepare("UPDATE identification_parts + SET idmatrice = :idmatrice, updated_at = NOW() + WHERE id = :id AND iddatadb = :iddatadb"); + $stmt->execute([ + ':idmatrice' => $idmatrice, + ':id' => $partId, + ':iddatadb' => $iddatadb + ]); + + if ($stmt->rowCount() > 0) { + echo json_encode(['success' => true, 'message' => 'Matrice salvata con successo']); + } else { + echo json_encode(['success' => false, 'message' => 'Nessuna riga aggiornata. Verifica l\'ID della parte.']); + } +} catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio della matrice: ' . $e->getMessage()]); +}