@@ -324,7 +437,31 @@
`;
- $("#worksheetDetailContainer").html(html);
+ const fullHtml = `
+ ${headerHtml}
+
+
+
+
+
+
+
+ ${html}
+
+
+
+
+
Caricamento disegni tecnici...
+
+
+ `;
+
+ $("#worksheetDetailContainer").html(fullHtml);
+ loadWorksheetDisegni(ws.idmatrice);
})
.catch((err) => {
console.error("Catch loadMatriceWorksheets:", err);
@@ -349,6 +486,29 @@
).show();
});
+ $(document).on("click", ".ws-tab", function () {
+ const tab = $(this).data("tab");
+
+ $(".ws-tab").removeClass("active");
+ $(this).addClass("active");
+
+ $(".ws-tab-pane").removeClass("active");
+ $(`.ws-tab-pane[data-pane="${tab}"]`).addClass("active");
+
+ // Carico il PDF solo la prima volta che si apre il tab
+ if (
+ tab === "disegni" &&
+ wsDisegniList.length &&
+ $("#wsDisegnoViewer").is(":empty")
+ ) {
+ showWorksheetDisegno(0);
+ }
+ });
+
+ $(document).on("click", ".ws-disegno-item", function () {
+ showWorksheetDisegno(parseInt($(this).data("index"), 10));
+ });
+
$(document).on("click", ".open-worksheet-detail", function () {
const worksheetId = $(this).data("id");
const endpoint =
diff --git a/public/userarea/get_matrice_attachments.php b/public/userarea/get_matrice_attachments.php
index 73339156..1f21ddb0 100644
--- a/public/userarea/get_matrice_attachments.php
+++ b/public/userarea/get_matrice_attachments.php
@@ -27,6 +27,7 @@ try {
file_type,
description,
sort_order,
+ is_disegno,
created_at,
updated_at
FROM matrice_attachments
@@ -50,6 +51,7 @@ try {
'file_type' => $row['file_type'],
'description' => $row['description'] ?? '',
'sort_order' => (int)($row['sort_order'] ?? 0),
+ 'is_disegno' => (int)($row['is_disegno'] ?? 0),
'created_at' => !empty($row['created_at']) ? date('d/m/Y H:i', strtotime($row['created_at'])) : '',
'updated_at' => !empty($row['updated_at']) ? date('d/m/Y H:i', strtotime($row['updated_at'])) : ''
];
diff --git a/public/userarea/import_worksheet_xlsx.php b/public/userarea/import_worksheet_xlsx.php
index 03b8e4ab..526b9b56 100644
--- a/public/userarea/import_worksheet_xlsx.php
+++ b/public/userarea/import_worksheet_xlsx.php
@@ -237,10 +237,12 @@ function extract_fields($grid)
$out['speed_expected_m_h'] = null;
$rv = row_by_label($grid, 'velocita di produzione');
if ($rv) {
- $out['speed_expected_kg_h'] = num_or_null(cell($grid, $rv, 'C'));
+ $v = num_or_null(cell($grid, $rv, 'C'));
+ $out['speed_expected_kg_h'] = $v === null ? null : round($v, 2);
for ($i = $rv; $i <= $rv + 2; $i++) {
if (norm(cell($grid, $i, 'B')) === 'm/h') {
- $out['speed_expected_m_h'] = num_or_null(cell($grid, $i, 'C'));
+ $v = num_or_null(cell($grid, $i, 'C'));
+ $out['speed_expected_m_h'] = $v === null ? null : round($v, 2);
break;
}
}
@@ -267,25 +269,37 @@ function extract_fields($grid)
* 3) MATCHER su DB
* ========================================================================= */
-function norm_mesc($s)
+function strip_code($s)
{
$s = strtolower((string)$s);
- $s = preg_replace('/\s+/', ' ', $s);
- return trim($s);
+ $s = str_replace(['à', 'è', 'é', 'ì', 'ò', 'ù'], ['a', 'e', 'e', 'i', 'o', 'u'], $s);
+ return preg_replace('/[^a-z0-9]+/', '', $s); // solo lettere + cifre (ignora spazi e punteggiatura)
+}
+
+/** porzione iniziale prima del primo separatore descrittivo (es. "ATES 71 - EPDM..." -> "ATES 71") */
+function raw_head($s)
+{
+ $parts = preg_split('/\s*[-–—|(:,]\s*/u', (string)$s, 2);
+ return trim($parts[0] ?? '');
}
function match_mescola($raw, array $mescole)
{
- $r = norm_mesc($raw);
- if ($r === '') return [null, 'none', null];
- foreach ($mescole as $m) if (norm_mesc($m['nome']) === $r) return [(int)$m['id'], 'exact', $m['nome']];
- foreach ($mescole as $m) {
- foreach (explode('/', $m['nome']) as $part) {
- if (norm_mesc($part) === $r) return [(int)$m['id'], 'exact-part', $m['nome']];
- }
+ if (trim((string)$raw) === '') return [null, 'none', null];
+ $rawStrip = strip_code($raw);
+ $headStrip = strip_code(raw_head($raw));
+
+ // 1) uguaglianza ignorando spazi/punteggiatura, sul nome intero (gestisce "ATES 71" = "ATES71")
+ foreach ($mescole as $m) if ($rawStrip !== '' && strip_code($m['nome']) === $rawStrip) return [(int)$m['id'], 'exact', $m['nome']];
+ // 2) parte di nome composito (a / b / c)
+ foreach ($mescole as $m) foreach (explode('/', $m['nome']) as $p) if ($rawStrip !== '' && strip_code($p) === $rawStrip) return [(int)$m['id'], 'exact-part', $m['nome']];
+ // 3) nome in uscita
+ foreach ($mescole as $m) if ($rawStrip !== '' && strip_code($m['nomeuscita'] ?? '') === $rawStrip) return [(int)$m['id'], 'exact-uscita', $m['nome']];
+ // 4) codice iniziale prima di " - descrizione" (gestisce "ATES 71 - EPDM...")
+ if ($headStrip !== '' && $headStrip !== $rawStrip) {
+ foreach ($mescole as $m) if (strip_code($m['nome']) === $headStrip) return [(int)$m['id'], 'code', $m['nome']];
+ foreach ($mescole as $m) foreach (explode('/', $m['nome']) as $p) if (strip_code($p) === $headStrip) return [(int)$m['id'], 'code-part', $m['nome']];
}
- foreach ($mescole as $m) if (norm_mesc($m['nomeuscita'] ?? '') === $r) return [(int)$m['id'], 'exact-uscita', $m['nome']];
- foreach ($mescole as $m) if ($r !== '' && strpos(norm_mesc($m['nome']), $r) !== false) return [(int)$m['id'], 'contains', $m['nome']];
return [null, 'none', null];
}
@@ -309,24 +323,51 @@ function dim_tokens($s)
function match_packaging($raw, array $packaging, $preferCat = null)
{
- $cands = dim_tokens($raw);
- if (!$cands) return [null, 'none', null, null];
- $best = null;
+ $rawStrip = strip_code($raw);
+ if ($rawStrip === '') return [null, 'none', null, null, true];
+
+ // 1) nome/codice ESATTO (categoria solo come spareggio) -> sicuro
+ $exact = [];
foreach ($packaging as $p) {
- $itemForms = array_merge(dim_tokens($p['item_name']), dim_tokens($p['item_code']));
- foreach ($cands as $c) {
- if (in_array($c, $itemForms, true)) {
- if ($preferCat && $p['category'] === $preferCat) {
- return [(int)$p['id'], 'dimension', $p['item_name'], $p['category']];
- }
- if ($best === null) $best = $p;
+ if (strip_code($p['item_code']) === $rawStrip || strip_code($p['item_name']) === $rawStrip) $exact[] = $p;
+ }
+ if ($exact) {
+ $pick = $exact[0];
+ foreach ($exact as $p) if ($preferCat && $p['category'] === $preferCat) {
+ $pick = $p;
+ break;
+ }
+ return [(int)$pick['id'], 'name-code', $pick['item_name'], $pick['category'], false];
+ }
+
+ // 2) DIMENSIONI -> sicuro se unico, proposta se ambiguo
+ $rd = dim_tokens($raw);
+ if ($rd) {
+ $dm = [];
+ foreach ($packaging as $p) {
+ $pf = array_merge(dim_tokens($p['item_name']), dim_tokens($p['item_code']));
+ if (array_intersect($rd, $pf)) $dm[] = $p;
+ }
+ if ($dm) {
+ $pick = $dm[0];
+ foreach ($dm as $p) if ($preferCat && $p['category'] === $preferCat) {
+ $pick = $p;
+ break;
}
+ $ambiguous = count($dm) > 1;
+ return [(int)$pick['id'], 'dimension', $pick['item_name'], $pick['category'], $ambiguous];
}
}
- if ($best) return [(int)$best['id'], 'dimension', $best['item_name'], $best['category']];
- return [null, 'none', null, null];
-}
+ // 3) ABBREVIAZIONE: testo corto, prefisso UNICO di un nome -> solo proposta
+ if (strlen($rawStrip) <= 4) {
+ $cand = [];
+ foreach ($packaging as $p) if (strpos(strip_code($p['item_name']), $rawStrip) === 0) $cand[] = $p;
+ if (count($cand) === 1) return [(int)$cand[0]['id'], 'abbrev', $cand[0]['item_name'], $cand[0]['category'], true];
+ }
+
+ return [null, 'none', null, null, true];
+}
/* ---- match generico su dropdown (ws_lookup_options) ---- */
function lk_norm($s)
@@ -461,7 +502,7 @@ try {
];
foreach ($pkgSpecs as $spec) {
if ($spec['raw'] === '') continue;
- [$pid, $pconf, $pname, $pcat] = match_packaging($spec['raw'], $packaging, $spec['cat']);
+ [$pid, $pconf, $pname, $pcat, $pneeds] = match_packaging($spec['raw'], $packaging, $spec['cat']);
$packagesOut[] = [
'source' => $spec['source'],
'raw' => $spec['raw'],
@@ -469,7 +510,7 @@ try {
'matched_name' => $pname,
'category' => $pcat,
'confidence' => $pconf,
- 'needs_review' => ($pid === null),
+ 'needs_review' => ($pid === null) ? true : $pneeds,
];
}
diff --git a/public/userarea/include/worksheets-linked-modals.php b/public/userarea/include/worksheets-linked-modals.php
index cad9c5d1..7718274b 100644
--- a/public/userarea/include/worksheets-linked-modals.php
+++ b/public/userarea/include/worksheets-linked-modals.php
@@ -1,3 +1,113 @@
+
+
diff --git a/public/userarea/manage-worksheet.php b/public/userarea/manage-worksheet.php
index a5528182..bf4fae8b 100644
--- a/public/userarea/manage-worksheet.php
+++ b/public/userarea/manage-worksheet.php
@@ -985,6 +985,22 @@ $isEdit = ($worksheet_id > 0);
margin-bottom: 16px;
font-size: 0.9rem;
}
+
+ tr.import-pending td {
+ background: #fff8db !important;
+ }
+
+ .pending-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ background: #fde68a;
+ color: #92400e;
+ border-radius: 999px;
+ padding: 3px 10px;
+ font-size: 0.75rem;
+ font-weight: 800;
+ }
@@ -1892,6 +1908,69 @@ $isEdit = ($worksheet_id > 0);
$(function() {
window.importStaged = null;
+ function renderPendingPackages(packages) {
+ $('#packageRowsBody tr.import-pending').remove();
+ $('#packageImportNote').remove();
+ const $tb = $('#packageRowsBody');
+ const nonAbbinati = [];
+
+ (packages || []).forEach(function(p) {
+ // righe solo per gli imballi ABBINATI (sicuri o proposti)
+ if (!p.package_code) {
+ nonAbbinati.push('“' + (p.raw || '') + '” (' + (p.source || '') + ')');
+ return;
+ }
+ const stato = p.needs_review ?
+ '
❓ proposto — verifica' :
+ '
⏳ verrà aggiunto al salvataggio';
+ const nome = p.matched_name || '—';
+ const $tr = $(
+ '
' +
+ '| — | ' +
+ '' + (p.category || '—') + ' | ' +
+ '' + nome + ' ' +
+ 'dal file: ' + (p.raw || '') + ' (' + (p.source || '') + ') | ' +
+ '' + stato + ' | ' +
+ '
'
+ );
+ $tb.prepend($tr);
+ });
+
+ // nota per gli imballi NON abbinati (niente righe vuote)
+ if (nonAbbinati.length) {
+ const $note = $(
+ '
' +
+ '⚠ Dal file non abbinati automaticamente: ' + nonAbbinati.join(', ') +
+ '. Aggiungili a mano qui sotto se servono.' +
+ '
'
+ );
+ $('#packageRowsBody').closest('.table-wrap').before($note);
+ }
+ }
+
+ function renderPendingMescole(mescole) {
+ // rimuove eventuali righe provvisorie precedenti
+ $('#tabellaMixRows tbody tr.import-pending').remove();
+ const $tb = $('#tabellaMixRows tbody');
+ (mescole || []).forEach(function(m) {
+ if (!m.idmescola) return; // le non riconosciute passano dal modal, non qui
+ const nome = m.matched_name || m.raw || '-';
+ const $tr = $(
+ '
' +
+ '| — | ' +
+ '' + nome + ' ' +
+ 'dal file: ' + (m.raw || '') + ' | ' +
+ '' + (m.mix_weight_g_m ?? '-') + ' | ' +
+ '' + (m.required_density ?? '-') + ' | ' +
+ '' + (m.required_hardness_shore_a ?? '-') + ' | ' +
+ '' + (m.lubrication_type ?? '-') + ' | ' +
+ '⏳ verrà aggiunta al salvataggio | ' +
+ '
'
+ );
+ $tb.prepend($tr);
+ });
+ }
+
function postForm(obj) {
return fetch('', {
method: 'POST',
@@ -1949,41 +2028,51 @@ $isEdit = ($worksheet_id > 0);
window.finalizeImportIfAny = async function(newId) {
const staged = window.importStaged;
if (!staged) return false;
- const unresolved = [];
+ const unresolvedPkgs = [];
+ const matchedMescole = [];
+ const unmatchedMescole = [];
+
+ // imballi: salva solo quelli SICURI; i proposti li segnaliamo da confermare
for (const p of (staged.packages || [])) {
- if (p.package_code) {
+ if (p.package_code && !p.needs_review) {
await postForm({
ajax: 1,
action: 'save_package_row',
worksheet_id: newId,
package_code: p.package_code
});
+ } else if (p.package_code && p.needs_review) {
+ unresolvedPkgs.push('Imballo “' + p.raw + '” (' + p.source + ') → proposto: ' + (p.matched_name || '') + ' — verifica e aggiungi a mano');
} else {
- unresolved.push('Imballo “' + p.raw + '” (' + p.source + ')');
+ unresolvedPkgs.push('Imballo “' + p.raw + '” (' + p.source + ') → nessun abbinamento, aggiungi a mano');
}
}
+
+ // mescole: separa risolte / non risolte
for (const m of (staged.mescole || [])) {
- if (m.idmescola) {
- await postForm({
- ajax: 1,
- action: 'save_mix_row',
- worksheet_id: newId,
- idmescola: m.idmescola,
- mix_weight_g_m: m.mix_weight_g_m ?? '',
- required_density: m.required_density ?? '',
- required_hardness_shore_a: m.required_hardness_shore_a ?? '',
- lubrication_type: m.lubrication_type ?? '',
- lubrication_notes: ''
- });
- } else {
- unresolved.push('Mescola “' + m.raw + '”');
- }
+ if (m.idmescola) matchedMescole.push(m);
+ else unmatchedMescole.push(m);
+ }
+
+ // salva automaticamente le mescole riconosciute
+ for (const m of matchedMescole) {
+ await postForm({
+ ajax: 1,
+ action: 'save_mix_row',
+ worksheet_id: newId,
+ idmescola: m.idmescola,
+ mix_weight_g_m: m.mix_weight_g_m ?? '',
+ required_density: m.required_density ?? '',
+ required_hardness_shore_a: m.required_hardness_shore_a ?? '',
+ lubrication_type: m.lubrication_type ?? '',
+ lubrication_notes: ''
+ });
}
window.importStaged = null;
- // Allega il file Excel di origine al foglio appena creato
+ // allega il file Excel di origine al foglio appena creato
if (window.importFile) {
try {
const fd = new FormData();
@@ -1999,10 +2088,44 @@ $isEdit = ($worksheet_id > 0);
window.importFile = null;
}
+ // mescola NON riconosciuta -> apri il modal già precompilato (il foglio ora esiste)
+ if (unmatchedMescole.length) {
+ const m = unmatchedMescole[0]; // il modulo ha 1 mescola per foglio
+ $('#wsId').val(newId);
+ $('#mixWorksheetId').val(newId);
+ if (!window.location.search.includes('id=')) {
+ window.history.replaceState({}, '', 'manage-worksheet.php?id=' + newId);
+ }
+
+ const extra = unresolvedPkgs.length ? ('\n\nDa aggiungere a mano: ' + unresolvedPkgs.join(', ')) : '';
+ await Swal.fire({
+ icon: 'info',
+ title: 'Mescola da abbinare',
+ text: 'La mescola “' + m.raw + '” non è stata riconosciuta. Selezionala dal menu: peso, densità, durezza e lubrificazione sono già compilati.' + extra
+ });
+
+ // precompila i campi dopo che il modal (e select2) è pronto
+ $('#mixModal').one('shown.bs.modal', function() {
+ $('#mixModalTitle').text('Mescola dal file: ' + m.raw + ' — seleziona la corrispondenza');
+ $('#mixRowId').val(0);
+ $('#mixWorksheetId').val(newId);
+ $('#mix_weight_g_m').val(m.mix_weight_g_m ?? '');
+ $('#required_density').val(m.required_density ?? '');
+ $('#required_hardness_shore_a').val(m.required_hardness_shore_a ?? '');
+ $('#lubrication_type').val(m.lubrication_type ?? '');
+ $('#idmescola').val('').trigger('change');
+ });
+
+ const modal = new bootstrap.Modal(document.getElementById('mixModal'));
+ modal.show();
+ return true; // niente reload: ci pensa il salvataggio del modal (save_mix_row + reload)
+ }
+
+ // tutto risolto: messaggio + reload
let msg = 'Testata e righe importate salvate correttamente.';
- if (unresolved.length) msg += '\n\nDa aggiungere a mano dopo il ricaricamento:\n• ' + unresolved.join('\n• ');
+ if (unresolvedPkgs.length) msg += '\n\nDa aggiungere a mano dopo il ricaricamento:\n• ' + unresolvedPkgs.join('\n• ');
await Swal.fire({
- icon: unresolved.length ? 'warning' : 'success',
+ icon: unresolvedPkgs.length ? 'warning' : 'success',
title: 'Import completato',
text: msg
});
@@ -2031,6 +2154,7 @@ $isEdit = ($worksheet_id > 0);
const fd = new FormData();
fd.append('xlsx_file', file);
+ $('#packageImportNote').remove(); // azzera nota import precedente
Swal.fire({
title: 'Lettura Excel…',
allowOutsideClick: false,
@@ -2083,6 +2207,8 @@ $isEdit = ($worksheet_id > 0);
warnings: data.warnings || []
};
renderBanner(data);
+ renderPendingMescole(data.mescole || []);
+ renderPendingPackages(data.packages || []);
Swal.fire({
icon: 'success',
diff --git a/public/userarea/matrici.php b/public/userarea/matrici.php
index a45e40a8..ec3776ef 100644
--- a/public/userarea/matrici.php
+++ b/public/userarea/matrici.php
@@ -547,6 +547,36 @@ function revisionLabel($rev)
color: #fff;
opacity: .95;
}
+
+ .attachments-existing-list .attachment-existing-row.is-disegno {
+ border: 2px solid #6f42c1;
+ background: #f7f3ff;
+ }
+
+ .badge-disegno {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ background: #6f42c1;
+ color: #fff;
+ font-size: .78rem;
+ font-weight: 700;
+ padding: 4px 9px;
+ border-radius: 999px;
+ margin-left: 6px;
+ vertical-align: middle;
+ }
+
+ .btn-toggle-disegno {
+ border-color: #6f42c1;
+ color: #6f42c1;
+ }
+
+ .btn-toggle-disegno:hover,
+ .btn-toggle-disegno.active {
+ background: #6f42c1;
+ color: #fff;
+ }
@@ -713,6 +743,7 @@ function revisionLabel($rev)
data-nome='" . htmlspecialchars($row['nome'], ENT_QUOTES) . "'
data-cliente='" . htmlspecialchars($row['cliente'], ENT_QUOTES) . "'
data-data='{$dataIT}'
+ data-photo='" . htmlspecialchars($hasPhoto ? $thumb : '', ENT_QUOTES) . "'
data-bs-toggle='tooltip' data-bs-title='Modifica'>
@@ -797,6 +828,19 @@ function revisionLabel($rev)
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
@@ -1131,7 +1175,9 @@ function revisionLabel($rev)
return;
}
- const list = data.attachments || [];
+ const list = (data.attachments || []).sort((a, b) =>
+ (parseInt(b.is_disegno, 10) || 0) - (parseInt(a.is_disegno, 10) || 0)
+ );
const container = $("#existingAttachmentsList");
container.empty();
@@ -1149,6 +1195,7 @@ function revisionLabel($rev)
const badge = getFileTypeBadge(ext);
const createdAt = item.created_at || '';
const id = item.id || 0;
+ const isDisegno = (parseInt(item.is_disegno, 10) || 0) === 1;
const previewHtml = isImageExt(ext) && filePath ?
`
})
` :
@@ -1174,19 +1221,29 @@ function revisionLabel($rev)
}
const html = `
-
+
${previewHtml}
-
${escapeHtml(fileName)}
+
+ ${escapeHtml(fileName)}
+ ${isDisegno ? ' Disegno tecnico' : ''}
+
${badge} ${createdAt ? ' • ' + escapeHtml(createdAt) : ''}
${openButtonHtml}
+
@@ -1272,12 +1329,49 @@ function revisionLabel($rev)
.attr('placeholder', 'Nome matrice, cliente…')
.addClass('form-control');
+ function resetFotoMatrice(photoUrl) {
+ $("#fotoMatrice").val('');
+ $("#removeFotoMatrice").val('0');
+ if (photoUrl) {
+ $("#fotoMatricePreview").attr("src", photoUrl);
+ $("#fotoMatricePreviewWrap").show();
+ } else {
+ $("#fotoMatricePreview").attr("src", "");
+ $("#fotoMatricePreviewWrap").hide();
+ }
+ }
+
$(document).on("click", ".edit", function() {
$("#idMatriceEdit").val($(this).data("id"));
$("#nomeMatrice").val($(this).data("nome"));
$("#clienteMatrice").val($(this).data("cliente"));
$("#dataMatrice").val($(this).data("data"));
- new bootstrap.Modal(document.getElementById('addMatriceModal')).show();
+ resetFotoMatrice($(this).data("photo") || "");
+ bootstrap.Modal.getOrCreateInstance(document.getElementById('addMatriceModal')).show();
+ });
+
+ // Anteprima quando si sceglie un file
+ $("#fotoMatrice").on("change", function() {
+ const file = this.files && this.files[0];
+ if (!file) return;
+ $("#removeFotoMatrice").val('0');
+ $("#fotoMatricePreview").attr("src", URL.createObjectURL(file));
+ $("#fotoMatricePreviewWrap").show();
+ });
+
+ // Rimuovi foto
+ $("#btnRimuoviFoto").on("click", function() {
+ $("#fotoMatrice").val('');
+ $("#removeFotoMatrice").val('1');
+ $("#fotoMatricePreview").attr("src", "");
+ $("#fotoMatricePreviewWrap").hide();
+ });
+
+ // Alla chiusura del modale svuota tutto (così "Aggiungi" parte pulito)
+ document.getElementById('addMatriceModal').addEventListener('hidden.bs.modal', function() {
+ $("#addMatriceForm")[0].reset();
+ $("#idMatriceEdit").val('');
+ resetFotoMatrice('');
});
$("#addMatriceForm").on("submit", function(e) {
@@ -1289,15 +1383,21 @@ function revisionLabel($rev)
let dataIT = $("#dataMatrice").val().trim();
let dataSQL = convertToMySQLDate(dataIT);
+ const formData = new FormData();
+ formData.append("id", id);
+ formData.append("nome", nome);
+ formData.append("cliente", cliente);
+ formData.append("data", dataSQL);
+ formData.append("remove_photo", $("#removeFotoMatrice").val());
+
+ const fotoFile = $("#fotoMatrice")[0].files[0];
+ if (fotoFile) {
+ formData.append("photo", fotoFile);
+ }
+
fetch("save_matrice.php", {
method: "POST",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded"
- },
- body: "id=" + encodeURIComponent(id) +
- "&nome=" + encodeURIComponent(nome) +
- "&cliente=" + encodeURIComponent(cliente) +
- "&data=" + encodeURIComponent(dataSQL)
+ body: formData
})
.then(r => r.json())
.then(data => {
@@ -1672,6 +1772,54 @@ function revisionLabel($rev)
});
});
+ $(document).on("click", ".toggle-disegno", function() {
+ const $btn = $(this);
+ const id = $btn.data("id");
+ const newValue = parseInt($btn.data("value"), 10) === 1 ? 0 : 1;
+ const idmatrice = $("#mf_idmatrice").val();
+
+ $btn.prop("disabled", true);
+
+ fetch("toggle_matrice_attachment_disegno.php", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded"
+ },
+ body: "id=" + encodeURIComponent(id) + "&is_disegno=" + newValue
+ })
+ .then(r => r.json())
+ .then(data => {
+ if (!data.success) {
+ Swal.fire({
+ icon: "error",
+ title: "Errore",
+ text: data.message || ""
+ });
+ $btn.prop("disabled", false);
+ return;
+ }
+
+ Swal.fire({
+ toast: true,
+ position: "top-end",
+ icon: "success",
+ title: data.message,
+ showConfirmButton: false,
+ timer: 1500
+ });
+
+ loadExistingAttachments(idmatrice);
+ })
+ .catch(() => {
+ Swal.fire({
+ icon: "error",
+ title: "Errore",
+ text: "Errore di comunicazione con il server"
+ });
+ $btn.prop("disabled", false);
+ });
+ });
+
$(document).on("click", ".preview-existing-file", function() {
const file = $(this).data("file") || "";
const name = $(this).data("name") || "Anteprima file";
diff --git a/public/userarea/photos/matrici/matrice_18_1790931953.png b/public/userarea/photos/matrici/matrice_18_1790931953.png
new file mode 100644
index 00000000..5f007e42
Binary files /dev/null and b/public/userarea/photos/matrici/matrice_18_1790931953.png differ
diff --git a/public/userarea/production_line_view2.php b/public/userarea/production_line_view2.php
index 2ff0cc7d..7e1b861e 100644
--- a/public/userarea/production_line_view2.php
+++ b/public/userarea/production_line_view2.php
@@ -1912,7 +1912,9 @@ if (!empty($_GET['ajax'])) {
return;
}
- const files = r.attachments || [];
+ const files = (r.attachments || []).sort((a, b) =>
+ (parseInt(b.is_disegno, 10) || 0) - (parseInt(a.is_disegno, 10) || 0)
+ );
if (!files.length) {
$("#matriceFilesList").html('
Nessun allegato disponibile per questa matrice.
');
@@ -1929,6 +1931,7 @@ if (!empty($_GET['ajax'])) {
const ext = getFileExt(fileName);
const icon = getFileIcon(ext);
const badge = getFileBadge(ext);
+ const isDisegno = (parseInt(f.is_disegno, 10) || 0) === 1;
let actionHtml = '';
if (isImageExt(ext) || ext === 'pdf') {
@@ -1966,7 +1969,9 @@ if (!empty($_GET['ajax'])) {
}
html += `
-
+
${previewHtml}
@@ -1974,7 +1979,10 @@ if (!empty($_GET['ajax'])) {
${escapeHtml(fileName)}
-
${badge}
+
+ ${badge}
+ ${isDisegno ? '📐 Disegno tecnico' : ''}
+
${createdAt ? `
${escapeHtml(createdAt)}
` : ''}
${actionHtml}
diff --git a/public/userarea/save_matrice.php b/public/userarea/save_matrice.php
index ac38e055..1894f739 100644
--- a/public/userarea/save_matrice.php
+++ b/public/userarea/save_matrice.php
@@ -1,28 +1,101 @@
getConnection();
-$id = $_POST['id'] ?? null;
-$nome = $_POST['nome'] ?? '';
-$cliente = $_POST['cliente'] ?? '';
-$data = $_POST['data'] ?? null; // formato YYYY-MM-DD
-
-if (!$nome) {
- echo json_encode(["success" => false, "message" => "Nome obbligatorio"]);
+function jsonOut($success, $message = '', $extra = [])
+{
+ if (ob_get_length()) ob_clean();
+ header('Content-Type: application/json; charset=utf-8');
+ echo json_encode(array_merge(['success' => $success, 'message' => $message], $extra));
exit;
}
-if ($id) {
- // UPDATE
- $sql = "UPDATE matrice SET nome = ?, cliente = ?, data_produzione = ? WHERE id = ?";
- $stmt = $pdo->prepare($sql);
- $ok = $stmt->execute([$nome, $cliente, $data, $id]);
-} else {
- // INSERT
- $sql = "INSERT INTO matrice (nome, cliente, data_produzione) VALUES (?,?,?)";
- $stmt = $pdo->prepare($sql);
- $ok = $stmt->execute([$nome, $cliente, $data]);
+$id = (int)($_POST['id'] ?? 0);
+$nome = trim($_POST['nome'] ?? '');
+$cliente = trim($_POST['cliente'] ?? '');
+$data = trim($_POST['data'] ?? '');
+$removePhoto = ($_POST['remove_photo'] ?? '0') === '1';
+
+if ($nome === '') {
+ jsonOut(false, 'Il nome matrice è obbligatorio');
}
-echo json_encode(["success" => $ok]);
+$data = ($data !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $data)) ? $data : null;
+$cliente = $cliente !== '' ? $cliente : null;
+
+$photoDir = __DIR__ . '/photos/matrici/';
+if (!is_dir($photoDir)) {
+ mkdir($photoDir, 0775, true);
+}
+
+// Validazione file (se presente)
+$hasUpload = isset($_FILES['photo']) && $_FILES['photo']['error'] !== UPLOAD_ERR_NO_FILE;
+$ext = '';
+if ($hasUpload) {
+ if ($_FILES['photo']['error'] !== UPLOAD_ERR_OK) {
+ jsonOut(false, 'Errore upload foto (codice ' . $_FILES['photo']['error'] . ')');
+ }
+ if ($_FILES['photo']['size'] > 10 * 1024 * 1024) {
+ jsonOut(false, 'La foto supera i 10 MB');
+ }
+ $ext = strtolower(pathinfo($_FILES['photo']['name'], PATHINFO_EXTENSION));
+ if (!in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'], true)) {
+ jsonOut(false, 'Formato foto non consentito (jpg, jpeg, png, gif, webp)');
+ }
+ if (@getimagesize($_FILES['photo']['tmp_name']) === false) {
+ jsonOut(false, 'Il file caricato non è un\'immagine valida');
+ }
+}
+
+try {
+ $pdo->beginTransaction();
+
+ $oldPhoto = null;
+
+ if ($id > 0) {
+ $st = $pdo->prepare("SELECT photo FROM matrice WHERE id = ?");
+ $st->execute([$id]);
+ $row = $st->fetch(PDO::FETCH_ASSOC);
+ if (!$row) {
+ $pdo->rollBack();
+ jsonOut(false, 'Matrice non trovata');
+ }
+ $oldPhoto = $row['photo'];
+
+ $st = $pdo->prepare("UPDATE matrice SET nome = ?, cliente = ?, data_produzione = ? WHERE id = ?");
+ $st->execute([$nome, $cliente, $data, $id]);
+ } else {
+ $st = $pdo->prepare("INSERT INTO matrice (nome, cliente, data_produzione) VALUES (?, ?, ?)");
+ $st->execute([$nome, $cliente, $data]);
+ $id = (int)$pdo->lastInsertId();
+ }
+
+ // Nuova foto
+ if ($hasUpload) {
+ $newName = 'matrice_' . $id . '_' . time() . '.' . $ext;
+ if (!move_uploaded_file($_FILES['photo']['tmp_name'], $photoDir . $newName)) {
+ $pdo->rollBack();
+ jsonOut(false, 'Impossibile salvare la foto sul server');
+ }
+ $pdo->prepare("UPDATE matrice SET photo = ? WHERE id = ?")->execute([$newName, $id]);
+
+ if ($oldPhoto && is_file($photoDir . basename($oldPhoto))) {
+ @unlink($photoDir . basename($oldPhoto));
+ }
+ } elseif ($removePhoto && $oldPhoto) {
+ // Rimozione foto
+ $pdo->prepare("UPDATE matrice SET photo = NULL WHERE id = ?")->execute([$id]);
+ if (is_file($photoDir . basename($oldPhoto))) {
+ @unlink($photoDir . basename($oldPhoto));
+ }
+ }
+
+ $pdo->commit();
+ jsonOut(true, 'Matrice salvata correttamente', ['id' => $id]);
+} catch (Throwable $e) {
+ if ($pdo->inTransaction()) $pdo->rollBack();
+ jsonOut(false, 'Errore: ' . $e->getMessage());
+}
diff --git a/public/userarea/toggle_matrice_attachment_disegno.php b/public/userarea/toggle_matrice_attachment_disegno.php
new file mode 100644
index 00000000..13c2b04e
--- /dev/null
+++ b/public/userarea/toggle_matrice_attachment_disegno.php
@@ -0,0 +1,29 @@
+getConnection();
+
+function jsonOut($success, $message = '')
+{
+ if (ob_get_length()) ob_clean();
+ header('Content-Type: application/json; charset=utf-8');
+ echo json_encode(['success' => $success, 'message' => $message]);
+ exit;
+}
+
+$id = (int)($_POST['id'] ?? 0);
+$value = ($_POST['is_disegno'] ?? '0') === '1' ? 1 : 0;
+
+if ($id <= 0) {
+ jsonOut(false, 'ID allegato non valido');
+}
+
+try {
+ $st = $pdo->prepare("UPDATE matrice_attachments SET is_disegno = ? WHERE id = ?");
+ $st->execute([$value, $id]);
+ jsonOut(true, $value ? 'Segnato come disegno tecnico' : 'Rimosso dai disegni tecnici');
+} catch (Throwable $e) {
+ jsonOut(false, 'Errore: ' . $e->getMessage());
+}