Compare commits

..

9 Commits

Author SHA1 Message Date
MGrigoryan 6ec0c2062e fix: Include single sample photo in save operation when no photos selected 2025-10-31 11:45:04 +04:00
MGrigoryan 03771e3ca8 feat: Add quotation modal and iddatadb update for quotation parts/images
- Add quotation modal for selecting quotation
- Load quotation parts and photos
- Update iddatadb with quotation parts and photo references
2025-10-31 11:26:11 +04:00
MGrigoryan f6ea17388c fix(partsTable, annotationsModal): correct table height and restore "Torna alle Parti"
- partsTable: reduce excessive height to prevent oversized rendering
- annotationsModal: fix "Torna alle Parti" button behavior to return to parts view
2025-10-30 10:26:46 +04:00
solocla 1c2b4ab7a6 Merge branch 'bugfix/part-creation-matrice-dropdown-fix' 2025-10-29 18:25:26 +01:00
MGrigoryan 31cb23b00e fix(partsTable): skip change handler when setting Select2 value programmatically
Pass skipHandler flag in change event data to prevent handler execution
during programmatic value updates.
2025-10-29 19:18:33 +04:00
MGrigoryan d29563d20d feat(partsTable): add image icon to show/hide photo button
- Add image icon alongside eye icon in show/hide photo button
- Improve visual indication of photo toggle functionality
2025-10-29 18:27:33 +04:00
solocla 82af925ac1 added size to line markers 2025-10-29 13:45:38 +01:00
solocla 5d8360dd87 marker with size 2025-10-29 12:32:09 +01:00
MGrigoryan 683073c244 fix(partsTable): initialize new item matrice as empty and refresh on global filter change
- Default new items' matrice to empty to prevent stale values leaking from prior state
- Force matrice update when the global filter changes to keep items in sync
2025-10-29 14:17:00 +04:00
7 changed files with 497 additions and 119 deletions
+129 -83
View File
@@ -12,12 +12,15 @@ $(document).ready(function () {
let photoAnnotations = {}; let photoAnnotations = {};
let partColors = {}; let partColors = {};
let partSizes = {}; // memorizza la dimensione specifica per parte
let selectedPartNumber = null; let selectedPartNumber = null;
let unsavedChanges = false; let unsavedChanges = false;
let fabricCanvas = null; let fabricCanvas = null;
let descriptionTextbox = null; let descriptionTextbox = null;
let markerObjects = {}; let markerObjects = {};
let partsListData = []; let partsListData = [];
// DIMENSIONE GLOBALE MARKER
let globalMarkerSize = 24;
// =================== // ===================
// MODAL INITIALIZATION // MODAL INITIALIZATION
@@ -29,6 +32,8 @@ $(document).ready(function () {
trfHeader, trfHeader,
}); });
$("#annotationsModal").attr('data-iddatadb', iddatadb);
if (!iddatadb && !idquotations) { if (!iddatadb && !idquotations) {
const errorMsg = $( const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore: ID TRF mancante. Impossibile inizializzare il modale delle annotazioni.</div>', '<div class="alert alert-danger temp-alert" role="alert">Errore: ID TRF mancante. Impossibile inizializzare il modale delle annotazioni.</div>',
@@ -67,6 +72,9 @@ $(document).ready(function () {
}); });
} }
modal.show(); modal.show();
// Inizializza slider dimensione marker
$("#markerSizeSlider").val(globalMarkerSize);
$("#markerSizeValue").text(globalMarkerSize + "px");
// Debug: Verifica presenza elementi DOM // Debug: Verifica presenza elementi DOM
console.log( console.log(
@@ -128,6 +136,7 @@ $(document).ready(function () {
} }
descriptionTextbox = null; descriptionTextbox = null;
markerObjects = {}; markerObjects = {};
globalMarkerSize = 24;
$("#photoSelectorContainerAnnotations").empty().hide(); $("#photoSelectorContainerAnnotations").empty().hide();
$("#samplePhotoAnnotations").attr("src", ""); $("#samplePhotoAnnotations").attr("src", "");
$("#partsListAnnotations").empty(); $("#partsListAnnotations").empty();
@@ -144,6 +153,16 @@ $(document).ready(function () {
$(":focus").blur(); $(":focus").blur();
}); });
// SLIDER DIMENSIONE MARKER
$(document)
.off("input", "#markerSizeSlider")
.on("input", "#markerSizeSlider", function () {
globalMarkerSize = parseInt($(this).val());
$("#markerSizeValue").text(globalMarkerSize + "px");
updateMarkers();
markUnsaved();
});
// =================== // ===================
// PHOTO LOADERS // PHOTO LOADERS
// =================== // ===================
@@ -465,113 +484,84 @@ $(document).ready(function () {
); );
// =================== // ===================
// BACK TO PARTS MODAL // TORNA AL MODALE PARTI (modal_partsTable.php)
// =================== // ===================
$(document) $(document)
.off("click.backToParts", "#backToPartsBtnAnnotations") .off("click.backToParts", "#backToPartsBtnAnnotations")
.on("click.backToParts", "#backToPartsBtnAnnotations", function (e) { .on("click.backToParts", "#backToPartsBtnAnnotations", function (e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
console.log(
"Evento click su #backToPartsBtnAnnotations, ID elemento:",
$(this).attr("id"),
);
if (!$("#backToPartsBtnAnnotations").length) {
console.error(
"Pulsante #backToPartsBtnAnnotations non trovato nel DOM.",
);
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore: Pulsante #backToPartsBtnAnnotations non trovato nel DOM.</div>',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
return;
}
// Controlla modifiche non salvate
if ( if (
unsavedChanges && unsavedChanges &&
!confirm( !confirm(
"Hai modifiche non salvate. Vuoi davvero tornare al modale delle parti?", "Hai modifiche non salvate. Vuoi davvero tornare al modale delle parti?",
) )
) { ) {
console.log(
"Tornare al modale delle parti annullato a causa di modifiche non salvate.",
);
return; return;
} }
// Chiudi annotationsModal
const annotationsModalElement =
document.getElementById("annotationsModal");
const annotationsModal = bootstrap.Modal.getInstance(
annotationsModalElement,
);
if (annotationsModal) {
annotationsModal.hide();
} else {
console.error(
"Impossibile trovare l'istanza del modale #annotationsModal.",
);
}
// Apri partsModal
const iddatadb = $("#annotationsModal").data("iddatadb"); const iddatadb = $("#annotationsModal").data("iddatadb");
const idquotations = $("#annotationsModal").data("idquotations"); const idquotations = $("#annotationsModal").data("idquotations");
const trfHeader = $("#trfHeaderAnnotations").text(); const trfHeader = $("#trfHeaderAnnotations").text();
console.log("Apertura partsModal con:", {
// 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, iddatadb,
idquotations, idquotations,
trfHeader, 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();
const partsModalElement = document.getElementById("partsModal"); // Aggiungi nuovo modale
if (!partsModalElement) { $("body").append(data);
console.error("Elemento #partsModal non trovato nel DOM.");
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore: Il modale delle parti non è presente nel DOM.</div>',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
return;
}
let partsModal = bootstrap.Modal.getInstance(partsModalElement); // Apri con Bootstrap 5
if (!partsModal) { const partsModalElement =
partsModal = new bootstrap.Modal(partsModalElement, { document.getElementById("partsTableModal");
if (partsModalElement) {
const modal = new bootstrap.Modal(partsModalElement, {
backdrop: true, backdrop: true,
keyboard: true, keyboard: true,
focus: true, focus: true,
}); });
} modal.show();
// Inizializza il modale delle parti
if (typeof window.initPartsModal === "function") {
window.initPartsModal(iddatadb, idquotations, trfHeader);
partsModal.show();
console.log("partsModal aperto con successo.");
} else { } else {
console.error("Funzione initPartsModal non definita."); let iddatadb = $("#annotationsModal").attr('data-iddatadb');
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore: Funzione initPartsModal non trovata.</div>',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
}
});
$("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 // PARTS LIST
// =================== // ===================
@@ -634,11 +624,18 @@ $(document).ready(function () {
.join(""); .join("");
const listItem = ` const listItem = `
<li class="list-group-item" data-part-number="${partNumber}"> <li class="list-group-item" data-part-number="${partNumber}">
<span class="part-info" style="cursor: pointer;">${partNumber} - ${partDescription}</span> <span class="part-info" style="cursor: pointer;">${partNumber} - ${partDescription}</span>
<div class="color-picker-container" style="display: inline-block; position: relative; overflow: visible; z-index: 2000;"> <div class="color-picker-container" style="display:inline-block; position: relative; overflow: visible; z-index: 2000; margin-left:5px;">
<div class="color-option selected-color" style="background-color: ${partColor}; width: 20px; height: 20px; display: inline-block; margin-left: 5px; cursor: pointer; pointer-events: auto;"></div> <div class="color-option selected-color" style="background-color: ${partColor}; width: 20px; height: 20px; display: inline-block; cursor: pointer; pointer-events: auto;"></div>
<div class="color-picker" style="display: none; position: absolute; right: 0; z-index: 2000; background: #fff; border: 1px solid #ccc; padding: 5px;">${colorOptions}</div> <div class="color-picker" style="display: none; position: absolute; right: 0; z-index: 2000; background: #fff; border: 1px solid #ccc; padding: 5px;">${colorOptions}</div>
</div> </div>
<div style="display:inline-flex; align-items:center; gap:4px; margin-left:5px;">
<input type="range" class="marker-size-slider"
min="12" max="48" step="2"
value="${partSizes[partNumber] || globalMarkerSize}"
style="width:70px;">
<span class="marker-size-value" style="font-size:0.8rem;">${partSizes[partNumber] || globalMarkerSize}px</span>
</div>
</li>`; </li>`;
partsListElement.append(listItem); partsListElement.append(listItem);
} }
@@ -687,30 +684,77 @@ $(document).ready(function () {
$picker.toggle(); $picker.toggle();
}); });
// === Gestione cambio colore ===
partsListElement partsListElement
.off("click.colorOption") .off("click.colorOption")
.on("click.colorOption", ".color-option", function (e) { .on("click.colorOption", ".color-option", function (e) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
const $this = $(this); const $this = $(this);
const color = $this.data("color"); const color = $this.data("color");
const $listItem = $this.closest("li"); const $listItem = $this.closest("li");
const partNumber = $listItem.data("part-number"); const partNumber = $listItem.data("part-number");
console.log( console.log(
"Cliccato .color-option, colore:", "Cliccato .color-option, colore:",
color, color,
"per parte:", "per parte:",
partNumber, partNumber,
); );
// Salva il nuovo colore
partColors[partNumber] = color; partColors[partNumber] = color;
// Aggiorna il colore visivo nel selettore
$listItem $listItem
.find(".selected-color") .find(".selected-color")
.css("background-color", color); .css("background-color", color);
// Se il marker è già presente, aggiorna anche sul canvas
if (markerObjects[partNumber]) {
const group = markerObjects[partNumber];
const circle = group.item(0); // il cerchio
circle.set("fill", color);
circle.set("stroke", color);
fabricCanvas.renderAll();
}
// Chiudi la palette e aggiorna canvas
$this.closest(".color-picker").hide(); $this.closest(".color-picker").hide();
updateMarkers(); updateMarkers();
markUnsaved(); 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) $(document)
.off("click.colorPicker") .off("click.colorPicker")
.on("click.colorPicker", function (e) { .on("click.colorPicker", function (e) {
@@ -892,8 +936,10 @@ $(document).ready(function () {
return; return;
} }
const radius = 12; const size = partSizes[marker.partNumber] || globalMarkerSize;
const fontSize = 16; const radius = size / 2;
const fontSize = Math.max(10, Math.round(size * 0.65));
const markerColor = const markerColor =
marker.color || partColors[marker.partNumber] || "#ff0000"; marker.color || partColors[marker.partNumber] || "#ff0000";
@@ -1013,7 +1059,7 @@ $(document).ready(function () {
scaleY: 1, scaleY: 1,
backgroundColor: "transparent", backgroundColor: "transparent",
fontFamily: "Arial", fontFamily: "Arial",
fontSize: 24, fontSize: Math.max(16, Math.round(globalMarkerSize * 0.8)),
fill: "#000000", fill: "#000000",
padding: 10, padding: 10,
editable: false, editable: false,
+32
View File
@@ -0,0 +1,32 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
// Recupera l'ID dell'utente loggato
$user_id = $iduserlogin ?? 1;
if (!$user_id) {
echo json_encode(['success' => false, 'message' => "ID dell'utente autenticato mancante"]);
exit;
}
try {
$stmt = $pdo->prepare(
"SELECT DISTINCT q.*
FROM quotations q
INNER JOIN identification_parts ip
ON ip.idquotations = q.id
AND ip.iddatadb IS NULL
WHERE q.iduser = :iduser"
);
$stmt->execute([':iduser' => $user_id]);
$quotations = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'quotations' => $quotations]);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nel caricamento delle quotations: ' . $e->getMessage()]);
}
+42 -3
View File
@@ -1,13 +1,22 @@
<!-- Modal per la gestione delle annotazioni --> <!-- Modal per la gestione delle annotazioni -->
<div class="modal fade" id="annotationsModal" tabindex="-1" aria-labelledby="annotationsModalLabel" aria-hidden="true"> <div class="modal fade" id="annotationsModal" tabindex="-1" aria-labelledby="annotationsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl" style="max-width: 80% !important; width: 80% !important;"> <div class="modal-dialog modal-xl" style="max-width: 90% !important; width: 90% !important;">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="annotationsModalLabel">Annotazioni per TRF: <span id="trfHeaderAnnotations"></span></h5> <h5 class="modal-title" id="annotationsModalLabel">Annotazioni per TRF: <span id="trfHeaderAnnotations"></span></h5>
<!-- SLIDER PER DIMENSIONE MARKER -->
<div style="display: flex; align-items: center; gap: 10px; margin-left: 20px;">
<label for="markerSizeSlider" style="margin: 0; font-size: 0.9rem; white-space: nowrap;">Dimensione marker:</label>
<input type="range" id="markerSizeSlider" min="16" max="48" value="24" step="2" style="width: 120px;">
<span id="markerSizeValue" style="font-weight: bold; min-width: 30px;">24px</span>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
<!-- COLONNA SINISTRA RIDOTTA -->
<div class="col-md-6"> <div class="col-md-6">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h6 style="margin: 0;">Elenco Parti</h6> <h6 style="margin: 0;">Elenco Parti</h6>
@@ -16,15 +25,17 @@
<label for="showMixPartsAnnotations" style="font-size: 0.9rem;">Mix</label> <label for="showMixPartsAnnotations" style="font-size: 0.9rem;">Mix</label>
</div> </div>
</div> </div>
<ul id="partsListAnnotations" class="list-group"></ul> <ul id="partsListAnnotations" class="list-group" style="max-height: 500px; overflow-y: auto;"></ul>
</div> </div>
<!-- COLONNA DESTRA PIÙ GRANDE -->
<div class="col-md-6"> <div class="col-md-6">
<h6>Foto del Campione</h6> <h6>Foto del Campione</h6>
<div style="display: flex; align-items: center; margin-bottom: 10px;"> <div style="display: flex; align-items: center; margin-bottom: 10px;">
<button type="button" class="btn btn-primary btn-sm" id="downloadPhotoBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem; margin-right: 10px;"><i class="fas fa-download"></i></button> <button type="button" class="btn btn-primary btn-sm" id="downloadPhotoBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem; margin-right: 10px;"><i class="fas fa-download"></i></button>
<div id="photoSelectorContainerAnnotations" style="display: none;"></div> <div id="photoSelectorContainerAnnotations" style="display: none;"></div>
</div> </div>
<div style="position: relative; width: 100%; min-height: 400px;"> <div style="position: relative; width: 100%; min-height: 500px; border: 1px solid #ddd; border-radius: 4px; overflow: hidden;">
<img id="samplePhotoAnnotations" src="" alt="Foto del campione" style="max-width: 100%; max-height: 100%; object-fit: contain; position: absolute; top: 0; left: 0;"> <img id="samplePhotoAnnotations" src="" alt="Foto del campione" style="max-width: 100%; max-height: 100%; object-fit: contain; position: absolute; top: 0; left: 0;">
<canvas id="photoCanvasAnnotations" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></canvas> <canvas id="photoCanvasAnnotations" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></canvas>
<canvas id="overlayCanvasAnnotations" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000;"></canvas> <canvas id="overlayCanvasAnnotations" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000;"></canvas>
@@ -216,4 +227,32 @@
transform: translateX(-50%); transform: translateX(-50%);
z-index: 3000; z-index: 3000;
} }
/* Stile per lo slider */
#markerSizeSlider {
-webkit-appearance: none;
height: 6px;
border-radius: 3px;
background: #ddd;
outline: none;
}
#markerSizeSlider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #007bff;
cursor: pointer;
}
#markerSizeSlider::-moz-range-thumb {
width: 16px;
height: 16px;
border-radius: 50%;
background: #007bff;
cursor: pointer;
border: none;
}
</style> </style>
+32 -1
View File
@@ -6,7 +6,7 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row parts-row">
<div class="col-md-9"> <div class="col-md-9">
<!-- Prima riga: Elenco Parti, Rinumera, Voce --> <!-- Prima riga: Elenco Parti, Rinumera, Voce -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
@@ -14,6 +14,11 @@
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Rinumera Parti</button> <button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Rinumera Parti</button>
<button type="button" class="btn btn-secondary btn-sm ms-2" id="toggleVoiceBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;"><i class="fas fa-microphone"></i> Voce</button> <button type="button" class="btn btn-secondary btn-sm ms-2" id="toggleVoiceBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;"><i class="fas fa-microphone"></i> Voce</button>
<button type="button" class="btn btn-info btn-sm ms-2 d-none" id="quotationeBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Add Quotation</button>
<button type="button" class="btn btn-primary btn-sm ms-2" id="showHideImageBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
<i class="fas fa-eye-slash" style="font-size: 0.8rem;"></i>
<i class="fas fa-image ms-1" style="font-size: 0.8rem;"></i>
</button>
</div> </div>
</div> </div>
<!-- Seconda riga: +, M, MacroMatrice, Matrice globale, Propaga --> <!-- Seconda riga: +, M, MacroMatrice, Matrice globale, Propaga -->
@@ -141,6 +146,24 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Finestra modale "Aggiungi preventivo" -->
<div class="modal fade" id="addQuotationModal" tabindex="-1" aria-labelledby="addQuotationModalLabel" aria-hidden="true">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addQuotationModalLabel">Choose a quotation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<select id="addQuotationSelect" class="form-control form-control-sm" style="width: 100% !important; min-width: 100% !important"></select>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
<button type="button" class="btn btn-primary btn-sm" id="addQuotationBtn">Confirm</button>
</div>
</div>
</div>
</div>
<style> <style>
/* --- Base --- */ /* --- Base --- */
@@ -157,6 +180,14 @@
max-width: 100% !important max-width: 100% !important
} }
#addQuotationModal {
z-index: 1060 !important
}
#addQuotationModal .modal-backdrop {
z-index: 1055 !important
}
/* Tabelle */ /* Tabelle */
#partsTable tr { #partsTable tr {
display: table-row !important display: table-row !important
+159 -15
View File
@@ -6,6 +6,7 @@ $(document).ready(function () {
let unsavedChanges = false; let unsavedChanges = false;
let matrici = []; let matrici = [];
let macroMatrici = []; let macroMatrici = [];
let quotations = [];
// --- ROW ID helpers: niente più cache impazzita di jQuery .data() --- // --- ROW ID helpers: niente più cache impazzita di jQuery .data() ---
function getPartId($row) { function getPartId($row) {
@@ -141,7 +142,7 @@ $(document).ready(function () {
// =================== // ===================
// MODAL HANDLING // MODAL HANDLING
// =================== // ===================
function loadParts(iddatadb, idquotations) { function loadParts(iddatadb, idquotations, callback = null) {
if (iddatadb) { if (iddatadb) {
if (matrici.length === 0) { if (matrici.length === 0) {
$.ajax({ $.ajax({
@@ -153,14 +154,14 @@ $(document).ready(function () {
loadMacroMatrici(); loadMacroMatrici();
initializeGlobalSelect2(); initializeGlobalSelect2();
loadPhoto(iddatadb, idquotations); loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations); loadExistingParts(iddatadb, idquotations, callback);
}, },
error: function (xhr, status, error) { error: function (xhr, status, error) {
matrici = []; matrici = [];
loadMacroMatrici(); loadMacroMatrici();
initializeGlobalSelect2(); initializeGlobalSelect2();
loadPhoto(iddatadb, idquotations); loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations); loadExistingParts(iddatadb, idquotations, callback);
const errorMsg = $( const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore nel caricamento delle matrici: ' + '<div class="alert alert-danger temp-alert" role="alert">Errore nel caricamento delle matrici: ' +
error + error +
@@ -180,11 +181,11 @@ $(document).ready(function () {
loadMacroMatrici(); loadMacroMatrici();
initializeGlobalSelect2(); initializeGlobalSelect2();
loadPhoto(iddatadb, idquotations); loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations); loadExistingParts(iddatadb, idquotations, callback);
} }
} else { } else {
loadPhoto(iddatadb, idquotations); loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations); loadExistingParts(iddatadb, idquotations, callback);
} }
} }
@@ -589,6 +590,8 @@ $(document).ready(function () {
if (response.success) { if (response.success) {
$saveStatus.show(); $saveStatus.show();
setTimeout(() => $saveStatus.hide(), 2000); setTimeout(() => $saveStatus.hide(), 2000);
if (!$("#quotationeBtn").hasClass('d-none')) $("#quotationeBtn").addClass("d-none");
} else { } else {
const errorMsg = $( const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio: ' + '<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio: ' +
@@ -1155,7 +1158,7 @@ $(document).ready(function () {
saveRow($(this).closest("tr")); saveRow($(this).closest("tr"));
}); });
function loadExistingParts(iddatadb, idquotations) { function loadExistingParts(iddatadb, idquotations, callback = null) {
const endpoint = idquotations const endpoint = idquotations
? "load_parts_quotation.php" ? "load_parts_quotation.php"
: "load_parts.php"; : "load_parts.php";
@@ -1233,8 +1236,11 @@ $(document).ready(function () {
}); });
} else { } else {
addNewRow(1, false); // Riga iniziale normale addNewRow(1, false); // Riga iniziale normale
$("#quotationeBtn").removeClass("d-none");
} }
updateRowButtons(); updateRowButtons();
if (callback) callback();
}, },
error: function (xhr, status, error) { error: function (xhr, status, error) {
const errorMsg = $( const errorMsg = $(
@@ -1339,6 +1345,7 @@ $(document).ready(function () {
partId, partId,
currentValue, currentValue,
selectedMacro, selectedMacro,
true
); );
}); });
}); });
@@ -1415,6 +1422,7 @@ $(document).ready(function () {
partId, partId,
idmatrice, idmatrice,
selectedMacro = null, selectedMacro = null,
fromFilter = false
) { ) {
if (typeof $.fn.select2 === "undefined") { if (typeof $.fn.select2 === "undefined") {
$select.replaceWith( $select.replaceWith(
@@ -1474,8 +1482,8 @@ $(document).ready(function () {
}, },
}); });
// === MODIFICA CHIRURGICA: solo qui === // Ripristina il valore se valido
if (currentValue && currentValue !== "new") { if (partId && partId !== "new" && currentValue) {
const matrice = matrici.find((m) => m.IdMatrice == currentValue); const matrice = matrici.find((m) => m.IdMatrice == currentValue);
if (matrice) { if (matrice) {
const option = new Option( const option = new Option(
@@ -1484,20 +1492,24 @@ $(document).ready(function () {
true, true,
true, true,
); );
$select.append(option).trigger("change");
if (!fromFilter) $select.append(option).trigger("change");
else $select.append(option);
partMatrice[partNumber] = matrice.IdMatrice; partMatrice[partNumber] = matrice.IdMatrice;
} else { } else {
$select.val(null).trigger("change"); // Aggiusta valore non valido
if (!fromFilter) $select.val(null).trigger("change");
partMatrice[partNumber] = null; partMatrice[partNumber] = null;
} }
} else { } else {
// Nessun valore: assicurati che sia vuoto $select.val(null).trigger("change", [{ skipHandler: true }]);
$select.val(null).trigger("change");
partMatrice[partNumber] = null;
} }
// === FINE MODIFICA ===
$select.on("change", function () { $select.on("change", function (event, data) {
if (data && data?.skipHandler) return;
const idmatrice = $(this).val(); const idmatrice = $(this).val();
const $row = $(this).closest("tr"); const $row = $(this).closest("tr");
const partId = $row.data("part-id"); const partId = $row.data("part-id");
@@ -1781,6 +1793,122 @@ $(document).ready(function () {
// Esporta la funzione loadParts per essere usata da import_Edit2.php // Esporta la funzione loadParts per essere usata da import_Edit2.php
window.loadParts = loadParts; window.loadParts = loadParts;
$(document).on("click", "#quotationeBtn", function () {
$("#addQuotationModal").modal("show");
if (quotations.length > 0) {
reloadQuotations();
} else {
$.ajax({
url: "load_quotations.php",
method: "GET",
success: function (response) {
quotations = response?.quotations || [];
reloadQuotations();
},
error: function (xhr, status, error) {
console.error("Errore AJAX caricamento quotations:", error, xhr.responseText);
let message = `
<div class="alert alert-danger temp-alert" role="alert">
Errore nel caricamento delle quotations: ${error} (${xhr.status})
</div>
`;
$("#partsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
message.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
}
});
}
});
$(document).on("click", "#addQuotationBtn", function () {
const quotationId = $("#addQuotationSelect").val();
if (quotationId && confirm("Confermo di collegare la quotazione al campione?")) {
$("#addQuotationModal").modal("hide");
loadParts(null, quotationId, () => {
$("#quotationeBtn").addClass("d-none");
setTimeout(() => {
$("#partsTableBody tr").each(function () {
$(this).find(".save-loading").show();
});
let photoList = $('#photoSelector option').map(function () {
return this.value?.split('/')?.pop();
}).get();
if (!photoList.length) {
let path = $('#samplePhoto').attr("src")?.split('/')?.pop();
if (path) photoList = [path];
}
$.ajax({
url: "save_parts_photo_iddatadb.php",
method: "POST",
data: JSON.stringify({
iddatadb: $("#partsModal").data("iddatadb"),
photoList,
partIds: $('#partsTableBody tr').map(function () {
return $(this).data("part-id");
}).get(),
}),
success: function () {
$("#partsTableBody tr").each(function () {
let $row = $(this);
let $saveStatus = $row.find(".save-status");
let $saveLoading = $row.find(".save-loading");
$saveLoading.hide();
$saveStatus.show();
setTimeout(() => $saveStatus.hide(), 2000);
});
}, error: function (xhr, status, error) {
let message = `
<div class="alert alert-danger temp-alert" role="alert">
Errore di salvataggio: ${error} (${xhr.status})
</div>
`;
$("#partsModal .modal-body").prepend(message);
setTimeout(function () {
message.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
}
});
}, 100);
});
}
});
function reloadQuotations() {
if (quotations.length > 0) {
$("#addQuotationSelect").empty();
$("#addQuotationSelect").append("<option value=''>Seleziona Quotation</option>");
quotations.forEach(quotation => {
if (quotation?.description) {
$("#addQuotationSelect").append(`<option value='${quotation?.id}'>${quotation?.description}</option>`);
}
});
$("#addQuotationSelect").select2();
}
}
}); });
$(document).on("change", ".propagate-date-input", function () { $(document).on("change", ".propagate-date-input", function () {
@@ -2004,3 +2132,19 @@ $(document).on("click", ".save-common-note-btn", function () {
markUnsaved(); markUnsaved();
} }
}); });
$(document).on("click", "#showHideImageBtn", function () {
let mainRow = $(this).closest(".parts-row");
let photoContainer = mainRow.find(".col-md-3");
let tableContainer = mainRow.find("#partsTable").closest("div[class*='col-md']");
if (photoContainer.hasClass("d-none")) {
photoContainer.removeClass("d-none");
tableContainer.removeClass("col-md-12").addClass("col-md-9");
$(this).html("<i class='fas fa-eye-slash' style='font-size: 0.8rem;'></i><i class='fas fa-image ms-1' style='font-size: 0.8rem;'></i>");
} else {
photoContainer.addClass("d-none");
tableContainer.removeClass("col-md-9").addClass("col-md-12");
$(this).html("<i class='fas fa-eye' style='font-size: 0.8rem;'></i><i class='fas fa-image ms-1' style='font-size: 0.8rem;'></i>");
}
});
+47 -9
View File
@@ -399,14 +399,18 @@ if (isset($_GET['edit_id'])) {
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a> <a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?> <?php include('include/footer.php'); ?>
</div> </div>
<?php include('modal_parts.php'); ?> <div id="partsModalContainer"></div>
<?php include('photos_functions.php'); ?> <div id="annotationsModalContainer"></div>
<?php include 'photos_functions.php'; ?>
<?php include('jsinclude.php'); ?> <?php include('jsinclude.php'); ?>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script> <script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="photos.js"></script> <script src="photos.js"></script>
<script src="parts.js"></script> <script src="annotationsModal.js"></script>
<script src="partsTable.js"></script>
<script> <script>
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
// Mostra messaggi di stato se presenti // Mostra messaggi di stato se presenti
@@ -423,6 +427,45 @@ if (isset($_GET['edit_id'])) {
}, 5000); }, 5000);
} }
$(document).on('click', '.parts-btn', function() {
const idquotations = $(this).data('idquotations');
$.ajax({
url: 'modal_partsTable.php',
method: 'GET',
data: {
idquotations: idquotations
},
success: function(response) {
$('#partsModalContainer').html(response);
const modalElement = document.getElementById('partsModal');
if (!modalElement) return;
$("#trfHeader").text(`Quotation #${idquotations}`);
$("#partsModal").data("idquotations", idquotations);
let modal = bootstrap.Modal.getInstance(modalElement) || new bootstrap.Modal(modalElement, {
backdrop: true
});
modal.show();
if (typeof window.loadParts === 'function') window.loadParts(null, idquotations);
},
error: function(xhr, status, error) {
alert('Errore nel caricamento del modale: ' + error);
}
});
});
$(document).on('hidden.bs.modal', '#partsModal', function() {
$('#partsModalContainer').empty();
$('.modal-backdrop').remove();
$('body').removeClass('modal-open').css('padding-right', '');
});
$(document).on('hidden.bs.modal', '#annotationsModal', function() {
$('#annotationsModalContainer').empty();
$('.modal-backdrop').remove();
$('body').removeClass('modal-open').css('padding-right', '');
});
// Inizializza DataTables se non siamo in modalità modifica // Inizializza DataTables se non siamo in modalità modifica
if (!document.querySelector('#editForm')) { if (!document.querySelector('#editForm')) {
$('#quotationsTable').DataTable({ $('#quotationsTable').DataTable({
@@ -524,12 +567,7 @@ if (isset($_GET['edit_id'])) {
</script> </script>
<!-- Modale per le foto in quotations.php --> <!-- Modale per le foto in quotations.php -->
<div class="modal" id="photosModal">
<div class="modal-content">
<span class="close-btn">&times;</span>
<div class="popup-content"></div>
</div>
</div>
</body> </body>
@@ -0,0 +1,48 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$data = json_decode(file_get_contents('php://input'), true);
$iddatadb = $data['iddatadb'] ?? null;
$partIds = $data['partIds'] ?? [];
$photoList = $data['photoList'] ?? null;
if ($iddatadb) {
if (count($partIds) != 0) {
$idList = array_values(array_map('intval', $partIds));
$placeholders = [];
$parameters = [':iddatadb' => $iddatadb];
foreach ($idList as $index => $id) {
$paramName = ":id{$index}";
$placeholders[] = $paramName;
$parameters[$paramName] = $id;
}
$sql = 'UPDATE identification_parts SET iddatadb = :iddatadb WHERE id IN (' . implode(',', $placeholders) . ')';
$stmt = $pdo->prepare($sql);
$stmt->execute($parameters);
}
if (count($photoList) != 0) {
$placeholders = [];
$parameters = [':iddatadb' => $iddatadb];
foreach ($photoList as $index => $photo) {
$paramName = ":photo{$index}";
$placeholders[] = $paramName;
$parameters[$paramName] = $photo;
}
$stmt = $pdo->prepare('UPDATE datadb_photos SET iddatadb = :iddatadb WHERE file_path IN (' . implode(',', $placeholders) . ')');
$stmt->execute($parameters);
}
echo json_encode(['success' => true, 'message' => '']);
} else {
echo json_encode(['success' => false, 'message' => 'Dati mancanti']);
}