Compare commits

..

3 Commits

Author SHA1 Message Date
solocla 598a2cc84c change marker dimension 2025-10-31 10:16:57 +01:00
MGrigoryan 5eb5bd1613 fix: deselect part when clicking selected row 2025-10-31 11:30:18 +04:00
MGrigoryan 03642fdfab feat(annotations): support multiple pins per part 2025-10-30 17:08:57 +04:00
6 changed files with 85 additions and 278 deletions
+68 -32
View File
@@ -18,9 +18,10 @@ $(document).ready(function () {
let fabricCanvas = null; let fabricCanvas = null;
let descriptionTextbox = null; let descriptionTextbox = null;
let markerObjects = {}; let markerObjects = {};
let nextMarkerId = 1;
let partsListData = []; let partsListData = [];
// DIMENSIONE GLOBALE MARKER // DIMENSIONE GLOBALE MARKER
let globalMarkerSize = 24; let globalMarkerSize = 16;
// =================== // ===================
// MODAL INITIALIZATION // MODAL INITIALIZATION
@@ -32,7 +33,7 @@ $(document).ready(function () {
trfHeader, trfHeader,
}); });
$("#annotationsModal").attr('data-iddatadb', iddatadb); $("#annotationsModal").attr("data-iddatadb", iddatadb);
if (!iddatadb && !idquotations) { if (!iddatadb && !idquotations) {
const errorMsg = $( const errorMsg = $(
@@ -136,7 +137,7 @@ $(document).ready(function () {
} }
descriptionTextbox = null; descriptionTextbox = null;
markerObjects = {}; markerObjects = {};
globalMarkerSize = 24; globalMarkerSize = 16;
$("#photoSelectorContainerAnnotations").empty().hide(); $("#photoSelectorContainerAnnotations").empty().hide();
$("#samplePhotoAnnotations").attr("src", ""); $("#samplePhotoAnnotations").attr("src", "");
$("#partsListAnnotations").empty(); $("#partsListAnnotations").empty();
@@ -159,6 +160,20 @@ $(document).ready(function () {
.on("input", "#markerSizeSlider", function () { .on("input", "#markerSizeSlider", function () {
globalMarkerSize = parseInt($(this).val()); globalMarkerSize = parseInt($(this).val());
$("#markerSizeValue").text(globalMarkerSize + "px"); $("#markerSizeValue").text(globalMarkerSize + "px");
partsListData.forEach(function (part) {
if (part && part.part_number != null) {
partSizes[part.part_number] = globalMarkerSize;
}
});
$("#partsListAnnotations .marker-size-slider").val(
globalMarkerSize,
);
$("#partsListAnnotations .marker-size-value").text(
globalMarkerSize + "px",
);
updateMarkers(); updateMarkers();
markUnsaved(); markUnsaved();
}); });
@@ -366,22 +381,13 @@ $(document).ready(function () {
const partColor = const partColor =
partColors[selectedPartNumber] || "#ff0000"; 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({ photoAnnotations[currentPhoto].markers.push({
id: nextMarkerId++,
partNumber: selectedPartNumber, partNumber: selectedPartNumber,
x, x,
y, y,
color: partColor, color: partColor,
}); });
}
console.log("Marker aggiunto/spostato:", { console.log("Marker aggiunto/spostato:", {
partNumber: selectedPartNumber, partNumber: selectedPartNumber,
@@ -391,8 +397,6 @@ $(document).ready(function () {
}); });
updateMarkers(); updateMarkers();
markUnsaved(); markUnsaved();
selectedPartNumber = null;
$("#partsListAnnotations li").removeClass("active");
}); });
fabricCanvas.upperCanvasEl.focus(); fabricCanvas.upperCanvasEl.focus();
@@ -550,9 +554,14 @@ $(document).ready(function () {
}); });
modal.show(); modal.show();
} else { } else {
let iddatadb = $("#annotationsModal").attr('data-iddatadb'); let iddatadb =
$("#annotationsModal").attr("data-iddatadb");
$("button.parts-btn[data-iddatadb='" + iddatadb + "']").trigger('click'); $(
"button.parts-btn[data-iddatadb='" +
iddatadb +
"']",
).trigger("click");
} }
}, },
).fail(function (xhr) { ).fail(function (xhr) {
@@ -660,8 +669,21 @@ $(document).ready(function () {
); );
return; return;
} }
const $listItem = $(this); const $listItem = $(this);
selectedPartNumber = $listItem.data("part-number"); const partNumber = $listItem.data("part-number");
if (
selectedPartNumber == partNumber &&
$listItem.hasClass("active")
) {
selectedPartNumber = null;
$listItem.removeClass("active");
return;
}
selectedPartNumber = partNumber;
$listItem.addClass("active").siblings().removeClass("active"); $listItem.addClass("active").siblings().removeClass("active");
console.log( console.log(
"Parte selezionata tramite riga:", "Parte selezionata tramite riga:",
@@ -711,12 +733,23 @@ $(document).ready(function () {
.find(".selected-color") .find(".selected-color")
.css("background-color", color); .css("background-color", color);
// Se il marker è già presente, aggiorna anche sul canvas let currentPhoto = $("#samplePhotoAnnotations").attr("src");
if (markerObjects[partNumber]) { let annotations = photoAnnotations[currentPhoto];
const group = markerObjects[partNumber];
const circle = group.item(0); // il cerchio if (annotations) {
annotations.markers.forEach(function (m) {
if (m.partNumber == partNumber) {
m.color = color;
let group = markerObjects[m.id];
if (group) {
let circle = group.item(0);
circle.set("fill", color); circle.set("fill", color);
circle.set("stroke", color); circle.set("stroke", color);
}
}
});
fabricCanvas.renderAll(); fabricCanvas.renderAll();
} }
@@ -904,9 +937,9 @@ $(document).ready(function () {
"updateMarkers chiamato, markerObjects:", "updateMarkers chiamato, markerObjects:",
Object.keys(markerObjects), Object.keys(markerObjects),
); );
for (let partNumber in markerObjects) { for (let markerId in markerObjects) {
fabricCanvas.remove(markerObjects[partNumber]); fabricCanvas.remove(markerObjects[markerId]);
delete markerObjects[partNumber]; delete markerObjects[markerId];
} }
markerObjects = {}; markerObjects = {};
@@ -987,6 +1020,9 @@ $(document).ready(function () {
lockRotation: true, lockRotation: true,
}); });
group.markerId = marker.id;
group.partNumber = marker.partNumber;
group.on("moving", function () { group.on("moving", function () {
marker.x = this.left / photoData.scale; marker.x = this.left / photoData.scale;
marker.y = this.top / photoData.scale; marker.y = this.top / photoData.scale;
@@ -999,7 +1035,7 @@ $(document).ready(function () {
}); });
fabricCanvas.add(group); fabricCanvas.add(group);
markerObjects[marker.partNumber] = group; markerObjects[marker.id] = group;
}); });
fabricCanvas.renderAll(); fabricCanvas.renderAll();
@@ -1153,9 +1189,9 @@ $(document).ready(function () {
} }
} }
for (let partNumber in markerObjects) { for (let markerId in markerObjects) {
fabricCanvas.remove(markerObjects[partNumber]); fabricCanvas.remove(markerObjects[markerId]);
delete markerObjects[partNumber]; delete markerObjects[markerId];
} }
markerObjects = {}; markerObjects = {};
@@ -1190,9 +1226,9 @@ $(document).ready(function () {
photoAnnotations[currentPhoto].markers.length > 0 photoAnnotations[currentPhoto].markers.length > 0
) { ) {
const lastMarker = photoAnnotations[currentPhoto].markers.pop(); const lastMarker = photoAnnotations[currentPhoto].markers.pop();
if (markerObjects[lastMarker.partNumber]) { if (markerObjects[lastMarker.id]) {
fabricCanvas.remove(markerObjects[lastMarker.partNumber]); fabricCanvas.remove(markerObjects[lastMarker.id]);
delete markerObjects[lastMarker.partNumber]; delete markerObjects[lastMarker.id];
fabricCanvas.renderAll(); fabricCanvas.renderAll();
} }
console.log("Ultimo marker rimosso:", lastMarker); console.log("Ultimo marker rimosso:", lastMarker);
-32
View File
@@ -1,32 +0,0 @@
<?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()]);
}
+1 -1
View File
@@ -8,7 +8,7 @@
<!-- SLIDER PER DIMENSIONE MARKER --> <!-- SLIDER PER DIMENSIONE MARKER -->
<div style="display: flex; align-items: center; gap: 10px; margin-left: 20px;"> <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> <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;"> <input type="range" id="markerSizeSlider" min="16" max="48" value="16" step="2" style="width: 120px;">
<span id="markerSizeValue" style="font-weight: bold; min-width: 30px;">24px</span> <span id="markerSizeValue" style="font-weight: bold; min-width: 30px;">24px</span>
</div> </div>
-27
View File
@@ -14,7 +14,6 @@
<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;"> <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-eye-slash" style="font-size: 0.8rem;"></i>
<i class="fas fa-image ms-1" style="font-size: 0.8rem;"></i> <i class="fas fa-image ms-1" style="font-size: 0.8rem;"></i>
@@ -146,24 +145,6 @@
</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 --- */
@@ -180,14 +161,6 @@
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
+6 -128
View File
@@ -6,7 +6,6 @@ $(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) {
@@ -142,7 +141,7 @@ $(document).ready(function () {
// =================== // ===================
// MODAL HANDLING // MODAL HANDLING
// =================== // ===================
function loadParts(iddatadb, idquotations, callback = null) { function loadParts(iddatadb, idquotations) {
if (iddatadb) { if (iddatadb) {
if (matrici.length === 0) { if (matrici.length === 0) {
$.ajax({ $.ajax({
@@ -154,14 +153,14 @@ $(document).ready(function () {
loadMacroMatrici(); loadMacroMatrici();
initializeGlobalSelect2(); initializeGlobalSelect2();
loadPhoto(iddatadb, idquotations); loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations, callback); loadExistingParts(iddatadb, idquotations);
}, },
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, callback); loadExistingParts(iddatadb, idquotations);
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 +
@@ -181,11 +180,11 @@ $(document).ready(function () {
loadMacroMatrici(); loadMacroMatrici();
initializeGlobalSelect2(); initializeGlobalSelect2();
loadPhoto(iddatadb, idquotations); loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations, callback); loadExistingParts(iddatadb, idquotations);
} }
} else { } else {
loadPhoto(iddatadb, idquotations); loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations, callback); loadExistingParts(iddatadb, idquotations);
} }
} }
@@ -590,8 +589,6 @@ $(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: ' +
@@ -1158,7 +1155,7 @@ $(document).ready(function () {
saveRow($(this).closest("tr")); saveRow($(this).closest("tr"));
}); });
function loadExistingParts(iddatadb, idquotations, callback = null) { function loadExistingParts(iddatadb, idquotations) {
const endpoint = idquotations const endpoint = idquotations
? "load_parts_quotation.php" ? "load_parts_quotation.php"
: "load_parts.php"; : "load_parts.php";
@@ -1236,11 +1233,8 @@ $(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 = $(
@@ -1793,122 +1787,6 @@ $(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 () {
@@ -1,48 +0,0 @@
<?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']);
}