diff --git a/app/Http/Controllers/Web/Auth/LoginController.php b/app/Http/Controllers/Web/Auth/LoginController.php
index 7f60e66..0f1071b 100644
--- a/app/Http/Controllers/Web/Auth/LoginController.php
+++ b/app/Http/Controllers/Web/Auth/LoginController.php
@@ -113,10 +113,16 @@ class LoginController extends Controller
return redirect()->to('userarea/production_dashboard.php');
} elseif ($user->hasRole('HR')) {
return redirect()->to('userarea/production_dashboard.php');
+ } elseif ($user->hasRole('employee-hr')) {
+ return redirect()->to('userarea/production_dashboard.php');
} elseif ($user->hasRole('SuperUser')) {
return redirect()->to('userarea/production_dashboard.php');
} elseif ($user->hasRole('Management')) {
return redirect()->to('userarea/production_dashboard.php');
+ } elseif ($user->hasRole('manager')) {
+ return redirect()->to('userarea/production_dashboard.php');
+ } elseif ($user->hasRole('employee')) {
+ return redirect()->to('userarea/production_dashboard.php');
} elseif ($user->hasRole('Quality')) {
return redirect()->to('userarea/production_dashboard.php');
}
diff --git a/public/userarea/cad-area.php b/public/userarea/cad-area.php
index 9a2b2d4..6a9a22d 100644
--- a/public/userarea/cad-area.php
+++ b/public/userarea/cad-area.php
@@ -468,6 +468,10 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
✏️ 3. Disegna profilo
+
+
@@ -600,6 +604,13 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
const POINT_HIT_RADIUS = 12;
const SEGMENT_HIT_RADIUS = 12;
+ let edgeSnapEnabled = false;
+ let edgeSnapPreviewPoint = null;
+
+ const EDGE_SNAP_RADIUS = 45;
+ const EDGE_DARK_THRESHOLD = 245;
+ const EDGE_GRADIENT_THRESHOLD = 8;
+
let lastManualResult = null;
$(document).ready(function() {
@@ -1057,6 +1068,15 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
document.getElementById('saveManualAreaBtn').disabled = true;
document.getElementById('manualCoordsPreview').innerText = 'Nessun dato calcolato.';
+ edgeSnapEnabled = false;
+
+ const edgeSnapBtn = document.getElementById('edgeSnapBtn');
+ if (edgeSnapBtn) {
+ edgeSnapBtn.classList.remove('btn-warning', 'active');
+ edgeSnapBtn.classList.add('btn-outline-warning');
+ edgeSnapBtn.innerText = '🧲 Magnete bordo OFF';
+ }
+
setTool('roi');
}
@@ -1100,7 +1120,8 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (selectedEditPoint) {
isDraggingEditPoint = true;
editDragMoved = false;
- updateSelectedPointPosition(pos);
+ const finalPos = getSnappedPoint(pos);
+ updateSelectedPointPosition(finalPos);
markManualResultDirty();
redrawManualOverlay();
updateManualPreview();
@@ -1141,13 +1162,24 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
if (currentTool === 'edit' && isDraggingEditPoint && selectedEditPoint) {
- updateSelectedPointPosition(pos);
+ const finalPos = getSnappedPoint(pos);
+ updateSelectedPointPosition(finalPos);
editDragMoved = true;
markManualResultDirty();
redrawManualOverlay();
updateManualPreview();
return;
}
+ if (edgeSnapEnabled && ['polygon', 'hole', 'edit'].includes(currentTool)) {
+ edgeSnapPreviewPoint = snapToNearestEdge(pos, EDGE_SNAP_RADIUS);
+ redrawManualOverlay();
+ return;
+ }
+
+ if (edgeSnapPreviewPoint) {
+ edgeSnapPreviewPoint = null;
+ redrawManualOverlay();
+ }
};
manualOverlayCanvas.onmouseup = function() {
@@ -1262,6 +1294,123 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
};
}
+ function getSnappedPoint(pos) {
+ if (!edgeSnapEnabled) {
+ return pos;
+ }
+
+ if (!manualPdfCanvas || !manualPdfCtx) {
+ return pos;
+ }
+
+ if (!['polygon', 'hole', 'edit'].includes(currentTool)) {
+ return pos;
+ }
+
+ const snapped = snapToNearestEdge(pos, EDGE_SNAP_RADIUS);
+
+ if (snapped) {
+ edgeSnapPreviewPoint = snapped;
+ return snapped;
+ }
+
+ edgeSnapPreviewPoint = null;
+ return pos;
+ }
+
+ function snapToNearestEdge(pos, radius) {
+ const canvasWidth = manualPdfCanvas.width;
+ const canvasHeight = manualPdfCanvas.height;
+
+ const cx = Math.round(pos.x);
+ const cy = Math.round(pos.y);
+
+ const x0 = Math.max(1, cx - radius);
+ const y0 = Math.max(1, cy - radius);
+ const x1 = Math.min(canvasWidth - 2, cx + radius);
+ const y1 = Math.min(canvasHeight - 2, cy + radius);
+
+ const w = x1 - x0 + 1;
+ const h = y1 - y0 + 1;
+
+ if (w <= 3 || h <= 3) {
+ return null;
+ }
+
+ let imageData;
+
+ try {
+ imageData = manualPdfCtx.getImageData(x0, y0, w, h);
+ } catch (e) {
+ console.warn('Edge snap getImageData failed:', e);
+ return null;
+ }
+
+ const data = imageData.data;
+
+ function grayAt(localX, localY) {
+ const index = (localY * w + localX) * 4;
+ const r = data[index];
+ const g = data[index + 1];
+ const b = data[index + 2];
+
+ return (r + g + b) / 3;
+ }
+
+ let best = null;
+ let bestScore = -Infinity;
+
+ for (let yy = 1; yy < h - 1; yy++) {
+ for (let xx = 1; xx < w - 1; xx++) {
+ const globalX = x0 + xx;
+ const globalY = y0 + yy;
+
+ const dx = globalX - pos.x;
+ const dy = globalY - pos.y;
+ const dist = Math.sqrt(dx * dx + dy * dy);
+
+ if (dist > radius) {
+ continue;
+ }
+
+ const center = grayAt(xx, yy);
+
+ const gx = Math.abs(grayAt(xx + 1, yy) - grayAt(xx - 1, yy));
+ const gy = Math.abs(grayAt(xx, yy + 1) - grayAt(xx, yy - 1));
+ const gradient = gx + gy;
+
+ const isDarkEnough = center < EDGE_DARK_THRESHOLD;
+ const isRealEdge = gradient > EDGE_GRADIENT_THRESHOLD;
+
+ if (!isDarkEnough && !isRealEdge) {
+ continue;
+ }
+
+ /*
+ * Prefer:
+ * - dark strokes
+ * - real contrast edges
+ * - points close to the cursor
+ */
+ const darknessScore = Math.max(0, 255 - center);
+ const gradientScore = gradient * 3;
+ const distancePenalty = dist * 4;
+
+ const score = darknessScore + gradientScore - distancePenalty;
+
+ if (score > bestScore) {
+ bestScore = score;
+ best = {
+ x: globalX,
+ y: globalY
+ };
+ }
+ }
+ }
+
+ return best;
+ }
+
function handleCalibrationClick(pos) {
if (calibrationPoints.length >= 2) {
calibrationPoints = [];
@@ -1334,7 +1483,8 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
return;
}
- polygonPoints.push(pos);
+ const finalPos = getSnappedPoint(pos);
+ polygonPoints.push(finalPos);
selectedEditPoint = {
type: 'outer',
pointIndex: polygonPoints.length - 1
@@ -1364,7 +1514,8 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
return;
}
- currentHolePoints.push(pos);
+ const finalPos = getSnappedPoint(pos);
+ currentHolePoints.push(finalPos);
selectedEditPoint = {
type: 'currentHole',
pointIndex: currentHolePoints.length - 1
@@ -1409,6 +1560,32 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
drawCalibration();
drawPolygon();
drawHoles();
+ drawEdgeSnapPreview();
+ }
+
+ function drawEdgeSnapPreview() {
+ if (!edgeSnapEnabled || !edgeSnapPreviewPoint) {
+ return;
+ }
+
+ manualOverlayCtx.save();
+
+ manualOverlayCtx.strokeStyle = 'rgba(245, 158, 11, 1)';
+ manualOverlayCtx.fillStyle = 'rgba(245, 158, 11, 0.95)';
+ manualOverlayCtx.lineWidth = 2;
+
+ manualOverlayCtx.beginPath();
+ manualOverlayCtx.arc(edgeSnapPreviewPoint.x, edgeSnapPreviewPoint.y, 8, 0, Math.PI * 2);
+ manualOverlayCtx.stroke();
+
+ manualOverlayCtx.beginPath();
+ manualOverlayCtx.moveTo(edgeSnapPreviewPoint.x - 13, edgeSnapPreviewPoint.y);
+ manualOverlayCtx.lineTo(edgeSnapPreviewPoint.x + 13, edgeSnapPreviewPoint.y);
+ manualOverlayCtx.moveTo(edgeSnapPreviewPoint.x, edgeSnapPreviewPoint.y - 13);
+ manualOverlayCtx.lineTo(edgeSnapPreviewPoint.x, edgeSnapPreviewPoint.y + 13);
+ manualOverlayCtx.stroke();
+
+ manualOverlayCtx.restore();
}
function drawRoi() {
@@ -1838,9 +2015,11 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
return false;
}
+ const finalPos = getSnappedPoint(pos);
+
arr.splice(segment.insertIndex, 0, {
- x: pos.x,
- y: pos.y
+ x: finalPos.x,
+ y: finalPos.y
});
selectedEditPoint = {
@@ -2411,9 +2590,12 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
function getCanvasMousePosition(event, canvas) {
const rect = canvas.getBoundingClientRect();
+ const scaleX = canvas.width / rect.width;
+ const scaleY = canvas.height / rect.height;
+
return {
- x: event.clientX - rect.left,
- y: event.clientY - rect.top
+ x: (event.clientX - rect.left) * scaleX,
+ y: (event.clientY - rect.top) * scaleY
};
}
@@ -2442,6 +2624,33 @@ $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
setTool('polygon');
});
+ document.getElementById('edgeSnapBtn').addEventListener('click', function() {
+ edgeSnapEnabled = !edgeSnapEnabled;
+
+ const btn = document.getElementById('edgeSnapBtn');
+
+ if (edgeSnapEnabled) {
+ btn.classList.remove('btn-outline-warning');
+ btn.classList.add('btn-warning', 'active');
+ btn.innerText = '🧲 Magnete bordo ON';
+
+ setManualStatus(
+ 'Magnete bordo attivo: quando disegni o sposti punti, il sistema prova ad agganciarli al bordo scuro più vicino.'
+ );
+ } else {
+ btn.classList.remove('btn-warning', 'active');
+ btn.classList.add('btn-outline-warning');
+ btn.innerText = '🧲 Magnete bordo OFF';
+
+ edgeSnapPreviewPoint = null;
+ redrawManualOverlay();
+
+ setManualStatus(
+ 'Magnete bordo disattivato: i punti verranno inseriti esattamente dove clicchi.'
+ );
+ }
+ });
+
document.getElementById('undoPointBtn').addEventListener('click', function() {
if (currentTool === 'hole' && currentHolePoints.length > 0) {
currentHolePoints.pop();