added mescole files drag drop
This commit is contained in:
+506
-104
@@ -131,16 +131,23 @@
|
||||
max-width: 260px;
|
||||
}
|
||||
|
||||
/* Stato */
|
||||
/* Certificati */
|
||||
#tabellaMescole th:nth-child(5),
|
||||
#tabellaMescole td:nth-child(5) {
|
||||
width: 140px;
|
||||
max-width: 140px;
|
||||
width: 130px;
|
||||
max-width: 130px;
|
||||
}
|
||||
|
||||
/* Stato */
|
||||
#tabellaMescole th:nth-child(6),
|
||||
#tabellaMescole td:nth-child(6) {
|
||||
width: 130px;
|
||||
max-width: 130px;
|
||||
}
|
||||
|
||||
/* Azioni */
|
||||
#tabellaMescole th:nth-child(6),
|
||||
#tabellaMescole td:nth-child(6) {
|
||||
#tabellaMescole th:nth-child(7),
|
||||
#tabellaMescole td:nth-child(7) {
|
||||
width: 190px;
|
||||
max-width: 190px;
|
||||
white-space: normal;
|
||||
@@ -163,6 +170,193 @@
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* ---------- DROP ZONE FILE ---------- */
|
||||
.dropzone {
|
||||
border: 2px dashed #bcd4f4;
|
||||
background: #f8fbff;
|
||||
border-radius: 14px;
|
||||
padding: 28px 20px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all .18s ease-in-out;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.dropzone:hover {
|
||||
background: #eef5ff;
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
|
||||
.dropzone.dragover {
|
||||
background: #dbeafe;
|
||||
border-color: #0d6efd;
|
||||
border-style: solid;
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
.dropzone-icon {
|
||||
font-size: 2.4rem;
|
||||
line-height: 1;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.dropzone-main {
|
||||
font-weight: 600;
|
||||
color: #1f2d3d;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.dropzone-sub {
|
||||
color: #64748b;
|
||||
font-size: .87rem;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.dz-queue {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.dz-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 8px;
|
||||
font-size: .92rem;
|
||||
}
|
||||
|
||||
.dz-item-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
text-align: left;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.dz-item-state {
|
||||
font-size: .85rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dz-ok {
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.dz-err {
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.dz-progress {
|
||||
width: 110px;
|
||||
height: 6px;
|
||||
background: #e2e8f0;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dz-progress-bar {
|
||||
height: 100%;
|
||||
width: 0;
|
||||
background: #0d6efd;
|
||||
transition: width .15s;
|
||||
}
|
||||
|
||||
/* ---------- TABELLA FILE NELLA MODALE ---------- */
|
||||
#fTable {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#fTable th,
|
||||
#fTable td {
|
||||
vertical-align: middle;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
/* Categoria */
|
||||
#fTable th:nth-child(1),
|
||||
#fTable td:nth-child(1) {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
/* Titolo */
|
||||
#fTable th:nth-child(2),
|
||||
#fTable td:nth-child(2) {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* Nome File */
|
||||
#fTable th:nth-child(3),
|
||||
#fTable td:nth-child(3) {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
/* Caricato il */
|
||||
#fTable th:nth-child(4),
|
||||
#fTable td:nth-child(4) {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
/* Azioni */
|
||||
#fTable th:nth-child(5),
|
||||
#fTable td:nth-child(5) {
|
||||
width: 110px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#fTable td a {
|
||||
color: #0d6efd;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#fTable td a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.file-title-cell {
|
||||
cursor: text;
|
||||
text-align: left;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.file-title-cell:hover {
|
||||
background: #f1f5f9;
|
||||
}
|
||||
|
||||
.file-title-input {
|
||||
width: 100%;
|
||||
border: 1px solid #0d6efd;
|
||||
border-radius: 6px;
|
||||
padding: 3px 6px;
|
||||
font-size: .95rem;
|
||||
}
|
||||
|
||||
.badge-files {
|
||||
display: inline-block;
|
||||
padding: 2px 9px;
|
||||
border-radius: 999px;
|
||||
font-size: .8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badge-files-ok {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.badge-files-none {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -205,6 +399,7 @@
|
||||
<th>Nome Uscita</th>
|
||||
<th>Q.tà Totale</th>
|
||||
<th>Linee Associate</th>
|
||||
<th>Certificati</th>
|
||||
<th>Stato</th>
|
||||
<th>Azioni</th>
|
||||
</tr>
|
||||
@@ -236,6 +431,8 @@
|
||||
m.nomeuscita,
|
||||
m.is_active,
|
||||
IFNULL(q.qty_totale, 0) AS qty_totale,
|
||||
(SELECT COUNT(*) FROM mescole_files mf
|
||||
WHERE mf.idmescola = m.id AND mf.categoria = 'certificati_analisi') AS n_certificati,
|
||||
GROUP_CONCAT(DISTINCT pl.name SEPARATOR ', ') AS linee
|
||||
FROM mescole m
|
||||
LEFT JOIN (
|
||||
@@ -262,6 +459,7 @@
|
||||
<td class='text-muted'>-</td>
|
||||
<td class='text-muted'>-</td>
|
||||
<td class='text-muted'>-</td>
|
||||
<td class='text-muted'>-</td>
|
||||
</tr>";
|
||||
} else {
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
@@ -278,6 +476,11 @@
|
||||
|
||||
$nomeUscitaSafe = htmlspecialchars($row['nomeuscita']);
|
||||
|
||||
$nCert = (int)($row['n_certificati'] ?? 0);
|
||||
$certBadge = $nCert > 0
|
||||
? "<span class='badge-files badge-files-ok' title='Certificati di analisi caricati'>📄 {$nCert}</span>"
|
||||
: "<span class='badge-files badge-files-none' title='Nessun certificato di analisi'>— nessuno</span>";
|
||||
|
||||
echo "<tr data-mescola-id='{$row['id']}'>
|
||||
<td>{$row['id']}</td>
|
||||
<td>
|
||||
@@ -287,6 +490,7 @@
|
||||
</td>
|
||||
<td><span class='fw-semibold'>{$qtyTot}</span></td>
|
||||
<td>{$linee}</td>
|
||||
<td>{$certBadge}</td>
|
||||
<td>{$badge}</td>
|
||||
<td class='azioni-cell'>
|
||||
<button type='button' class='btn btn-sm btn-outline-dark associa-fornitori'
|
||||
@@ -493,7 +697,7 @@
|
||||
|
||||
<!-- MODALE FILE / REPOSITORY -->
|
||||
<div class="modal fade" id="associaFileModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-dialog modal-xl modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header" style="background-color:#cfe3ff;">
|
||||
<h5 class="modal-title">📁 File - <span id="fNomeUscita"></span></h5>
|
||||
@@ -503,43 +707,45 @@
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="fIdMescola">
|
||||
|
||||
<div class="row g-2 align-items-end mb-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Categoria</label>
|
||||
<select class="form-select" id="fCategoria"></select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Titolo File</label>
|
||||
<input type="text" class="form-control" id="fTitolo">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">File</label>
|
||||
<input type="file" class="form-control" id="fFileInput">
|
||||
</div>
|
||||
<div class="col-md-12 text-end">
|
||||
<button class="btn btn-add" id="fSaveBtn">➕ Carica File</button>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Categoria del caricamento</label>
|
||||
<select class="form-select" id="fCategoria"></select>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped align-middle text-center" id="fTable">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Categoria</th>
|
||||
<th>Titolo</th>
|
||||
<th>Nome File</th>
|
||||
<th>Caricato il</th>
|
||||
<th>Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<div class="dropzone" id="fDropzone">
|
||||
<div class="dropzone-icon">📥</div>
|
||||
<div class="dropzone-main">Trascina qui i file dalla mail</div>
|
||||
<div class="dropzone-sub">
|
||||
oppure clicca per selezionarli · <kbd>Ctrl</kbd>+<kbd>V</kbd> per incollare<br>
|
||||
Il titolo viene creato dal nome del file — PDF, Word, Excel, immagini · max 15MB
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" id="fFileInput" multiple style="display:none;">
|
||||
|
||||
<div class="dz-queue" id="fQueue"></div>
|
||||
|
||||
<hr class="my-3">>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped align-middle text-center" id="fTable">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Categoria</th>
|
||||
<th>Titolo</th>
|
||||
<th>Nome File</th>
|
||||
<th>Caricato il</th>
|
||||
<th>Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include('jsinclude.php'); ?>
|
||||
|
||||
@@ -997,45 +1203,293 @@
|
||||
// FILE / REPOSITORY
|
||||
// ============================
|
||||
|
||||
function fEscape(s) {
|
||||
return String(s == null ? '' : s).replace(/[<>&"]/g, function(c) {
|
||||
return ({
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'&': '&',
|
||||
'"': '"'
|
||||
})[c];
|
||||
});
|
||||
}
|
||||
|
||||
function fFmtDate(s) {
|
||||
if (!s) return '—';
|
||||
var d = new Date(String(s).replace(' ', 'T'));
|
||||
if (isNaN(d.getTime())) return s;
|
||||
var p = function(n) {
|
||||
return String(n).padStart(2, '0');
|
||||
};
|
||||
return p(d.getDate()) + '/' + p(d.getMonth() + 1) + '/' + d.getFullYear() +
|
||||
' ' + p(d.getHours()) + ':' + p(d.getMinutes());
|
||||
}
|
||||
|
||||
function fRenderRow(f) {
|
||||
return `
|
||||
<tr data-file-id="${f.id}">
|
||||
<td>${fEscape(f.categoria_label)}</td>
|
||||
<td class="file-title-cell" title="Doppio click per rinominare">${fEscape(f.titolo)}</td>
|
||||
<td><a href="${fEscape(f.url)}" target="_blank" rel="noopener">${fEscape(f.original_filename)}</a></td>
|
||||
<td>${fFmtDate(f.uploaded_at)}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-outline-primary" href="${fEscape(f.url)}" target="_blank" rel="noopener">⬇️</a>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger f-del" data-id="${f.id}">🗑️</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
function fLoadRows(idMescola) {
|
||||
fetch("get_mescola_files.php?id=" + encodeURIComponent(idMescola))
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
// popola dropdown categorie (solo la prima volta che arrivano)
|
||||
// popola dropdown categorie (solo la prima volta)
|
||||
const sel = $("#fCategoria");
|
||||
if (sel.children().length === 0 && data.categorie) {
|
||||
Object.keys(data.categorie).forEach(slug => {
|
||||
sel.append(`<option value="${slug}">${data.categorie[slug]}</option>`);
|
||||
});
|
||||
// preseleziona i certificati di analisi: è il caso d'uso più frequente
|
||||
if (data.categorie['certificati_analisi']) {
|
||||
sel.val('certificati_analisi');
|
||||
}
|
||||
}
|
||||
|
||||
const tbody = $("#fTable tbody");
|
||||
tbody.empty();
|
||||
|
||||
if (!data.success || !Array.isArray(data.rows) || data.rows.length === 0) {
|
||||
tbody.append(`<tr>
|
||||
tbody.append(`<tr class="f-empty-row">
|
||||
<td class="text-muted" colspan="5">Nessun file caricato</td>
|
||||
</tr>`);
|
||||
return;
|
||||
}
|
||||
|
||||
data.rows.forEach(f => {
|
||||
tbody.append(`
|
||||
<tr>
|
||||
<td>${f.categoria_label}</td>
|
||||
<td>${f.titolo}</td>
|
||||
<td><a href="${f.url}" target="_blank" rel="noopener">${f.original_filename}</a></td>
|
||||
<td>${f.uploaded_at}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-outline-primary" href="${f.url}" target="_blank" rel="noopener">⬇️</a>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger f-del" data-id="${f.id}">🗑️</button>
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
data.rows.forEach(f => tbody.append(fRenderRow(f)));
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- UPLOAD CON CODA E PROGRESSO ----------
|
||||
function fUploadOne(file, idMescola, categoria) {
|
||||
return new Promise(function(resolve) {
|
||||
const $item = $(`
|
||||
<div class="dz-item">
|
||||
<span>📄</span>
|
||||
<span class="dz-item-name">${fEscape(file.name)}</span>
|
||||
<div class="dz-progress"><div class="dz-progress-bar"></div></div>
|
||||
<span class="dz-item-state">0%</span>
|
||||
</div>`);
|
||||
$("#fQueue").append($item);
|
||||
|
||||
const $bar = $item.find('.dz-progress-bar');
|
||||
const $state = $item.find('.dz-item-state');
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append("idmescola", idMescola);
|
||||
fd.append("categoria", categoria);
|
||||
fd.append("titolo", ""); // il server genera il titolo dal nome file
|
||||
fd.append("file", file);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "save_mescola_file.php", true);
|
||||
|
||||
xhr.upload.onprogress = function(e) {
|
||||
if (!e.lengthComputable) return;
|
||||
const pct = Math.round((e.loaded / e.total) * 100);
|
||||
$bar.css('width', pct + '%');
|
||||
$state.text(pct + '%');
|
||||
};
|
||||
|
||||
xhr.onload = function() {
|
||||
let data = null;
|
||||
try {
|
||||
data = JSON.parse(xhr.responseText);
|
||||
} catch (err) {
|
||||
data = null;
|
||||
}
|
||||
|
||||
if (data && data.success) {
|
||||
$bar.css('width', '100%');
|
||||
$state.removeClass().addClass('dz-item-state dz-ok').text('✓ Caricato');
|
||||
if (data.row) {
|
||||
$("#fTable tbody .f-empty-row").remove();
|
||||
$("#fTable tbody").prepend(fRenderRow(data.row));
|
||||
}
|
||||
setTimeout(function() {
|
||||
$item.fadeOut(300, function() {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 2500);
|
||||
} else {
|
||||
$item.find('.dz-progress').remove();
|
||||
$state.removeClass().addClass('dz-item-state dz-err')
|
||||
.text('✗ ' + ((data && data.message) ? data.message : 'Errore'));
|
||||
}
|
||||
resolve();
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
$item.find('.dz-progress').remove();
|
||||
$state.removeClass().addClass('dz-item-state dz-err').text('✗ Errore di rete');
|
||||
resolve();
|
||||
};
|
||||
|
||||
xhr.send(fd);
|
||||
});
|
||||
}
|
||||
|
||||
function fHandleFiles(fileList) {
|
||||
const idMescola = $("#fIdMescola").val();
|
||||
const categoria = $("#fCategoria").val();
|
||||
|
||||
if (!idMescola) return;
|
||||
if (!categoria) {
|
||||
Swal.fire({
|
||||
icon: "warning",
|
||||
title: "Attenzione",
|
||||
text: "Seleziona prima una categoria"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const files = Array.from(fileList || []);
|
||||
if (files.length === 0) return;
|
||||
|
||||
// Caricamento sequenziale: più gentile col server e progresso leggibile
|
||||
files.reduce(function(chain, file) {
|
||||
return chain.then(function() {
|
||||
return fUploadOne(file, idMescola, categoria);
|
||||
});
|
||||
}, Promise.resolve());
|
||||
}
|
||||
|
||||
// Click sulla dropzone → apre il selettore file
|
||||
$(document).on("click", "#fDropzone", function() {
|
||||
document.getElementById("fFileInput").click();
|
||||
});
|
||||
|
||||
$(document).on("change", "#fFileInput", function() {
|
||||
fHandleFiles(this.files);
|
||||
this.value = ""; // permette di ricaricare lo stesso file
|
||||
});
|
||||
|
||||
// Drag & drop
|
||||
['dragenter', 'dragover'].forEach(function(evt) {
|
||||
document.addEventListener(evt, function(e) {
|
||||
const dz = e.target.closest && e.target.closest('#fDropzone');
|
||||
if (!dz) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dz.classList.add('dragover');
|
||||
});
|
||||
});
|
||||
|
||||
['dragleave', 'drop'].forEach(function(evt) {
|
||||
document.addEventListener(evt, function(e) {
|
||||
const dz = e.target.closest && e.target.closest('#fDropzone');
|
||||
if (!dz) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dz.classList.remove('dragover');
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('drop', function(e) {
|
||||
const dz = e.target.closest && e.target.closest('#fDropzone');
|
||||
if (!dz) return;
|
||||
e.preventDefault();
|
||||
if (e.dataTransfer && e.dataTransfer.files) {
|
||||
fHandleFiles(e.dataTransfer.files);
|
||||
}
|
||||
});
|
||||
|
||||
// Impedisce al browser di aprire il file se cade fuori dalla dropzone
|
||||
['dragover', 'drop'].forEach(function(evt) {
|
||||
document.addEventListener(evt, function(e) {
|
||||
if (!$('#associaFileModal').hasClass('show')) return;
|
||||
if (e.target.closest && e.target.closest('#fDropzone')) return;
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
|
||||
// Ctrl+V dentro la modale
|
||||
document.addEventListener('paste', function(e) {
|
||||
if (!$('#associaFileModal').hasClass('show')) return;
|
||||
if (!e.clipboardData || !e.clipboardData.items) return;
|
||||
|
||||
const files = [];
|
||||
Array.from(e.clipboardData.items).forEach(function(item) {
|
||||
if (item.kind === 'file') {
|
||||
const f = item.getAsFile();
|
||||
if (f) files.push(f);
|
||||
}
|
||||
});
|
||||
|
||||
if (files.length > 0) {
|
||||
e.preventDefault();
|
||||
fHandleFiles(files);
|
||||
}
|
||||
});
|
||||
|
||||
// Rinomina il titolo con doppio click
|
||||
$(document).on("dblclick", ".file-title-cell", function() {
|
||||
const $td = $(this);
|
||||
if ($td.find('input').length) return;
|
||||
|
||||
const oldVal = $td.text().trim();
|
||||
const fileId = $td.closest('tr').data('file-id');
|
||||
|
||||
$td.html(`<input type="text" class="file-title-input" value="${fEscape(oldVal)}">`);
|
||||
const $input = $td.find('input');
|
||||
$input.trigger('focus').trigger('select');
|
||||
|
||||
function commit(save) {
|
||||
const newVal = $input.val().trim();
|
||||
if (!save || newVal === '' || newVal === oldVal) {
|
||||
$td.text(oldVal);
|
||||
return;
|
||||
}
|
||||
$td.text(newVal);
|
||||
const p = new URLSearchParams();
|
||||
p.append('id', fileId);
|
||||
p.append('titolo', newVal);
|
||||
fetch("rename_mescola_file.php", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
body: p.toString()
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
$td.text(oldVal);
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Errore",
|
||||
text: data.message || "Impossibile rinominare"
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
$td.text(oldVal);
|
||||
});
|
||||
}
|
||||
|
||||
$input.on('blur', function() {
|
||||
commit(true);
|
||||
});
|
||||
$input.on('keydown', function(ev) {
|
||||
if (ev.key === 'Enter') {
|
||||
ev.preventDefault();
|
||||
$input.trigger('blur');
|
||||
} else if (ev.key === 'Escape') {
|
||||
$input.off('blur');
|
||||
commit(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Open modal
|
||||
$(document).on("click", ".associa-file", function() {
|
||||
const idMescola = $(this).data("id");
|
||||
@@ -1043,65 +1497,13 @@
|
||||
|
||||
$("#fIdMescola").val(idMescola);
|
||||
$("#fNomeUscita").text(nomeUscita);
|
||||
$("#fTitolo").val("");
|
||||
$("#fQueue").empty();
|
||||
$("#fFileInput").val("");
|
||||
|
||||
$("#associaFileModal").modal("show");
|
||||
fLoadRows(idMescola);
|
||||
});
|
||||
|
||||
// Upload file
|
||||
$("#fSaveBtn").on("click", function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const idMescola = $("#fIdMescola").val();
|
||||
const categoria = $("#fCategoria").val();
|
||||
const titolo = $("#fTitolo").val().trim();
|
||||
const fileInput = document.getElementById("fFileInput");
|
||||
|
||||
if (!titolo) {
|
||||
Swal.fire({
|
||||
icon: "warning",
|
||||
title: "Attenzione",
|
||||
text: "Il titolo del file è obbligatorio"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!fileInput.files.length) {
|
||||
Swal.fire({
|
||||
icon: "warning",
|
||||
title: "Attenzione",
|
||||
text: "Seleziona un file da caricare"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("idmescola", idMescola);
|
||||
formData.append("categoria", categoria);
|
||||
formData.append("titolo", titolo);
|
||||
formData.append("file", fileInput.files[0]);
|
||||
|
||||
fetch("save_mescola_file.php", {
|
||||
method: "POST",
|
||||
body: formData
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
$("#fTitolo").val("");
|
||||
$("#fFileInput").val("");
|
||||
fLoadRows(idMescola);
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Errore",
|
||||
text: data.message || "Upload non riuscito"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Delete file
|
||||
$(document).on("click", ".f-del", function() {
|
||||
const id = $(this).data("id");
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
include('include/headscript.php');
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
|
||||
$titolo = trim($_POST['titolo'] ?? '');
|
||||
|
||||
if ($id <= 0) {
|
||||
echo json_encode(['success' => false, 'message' => 'ID non valido']);
|
||||
exit;
|
||||
}
|
||||
if ($titolo === '') {
|
||||
echo json_encode(['success' => false, 'message' => 'Il titolo non può essere vuoto']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$titolo = mb_substr($titolo, 0, 255);
|
||||
|
||||
try {
|
||||
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE mescole_files SET titolo = ? WHERE id = ?");
|
||||
$stmt->execute([$titolo, $id]);
|
||||
|
||||
echo json_encode(['success' => true, 'titolo' => $titolo]);
|
||||
} catch (Throwable $e) {
|
||||
echo json_encode(['success' => false, 'message' => 'Errore database: ' . $e->getMessage()]);
|
||||
}
|
||||
@@ -16,10 +16,6 @@ if (!array_key_exists($categoria, $categorie)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Categoria non valida']);
|
||||
exit;
|
||||
}
|
||||
if ($titolo === '') {
|
||||
echo json_encode(['success' => false, 'message' => 'Il titolo del file è obbligatorio']);
|
||||
exit;
|
||||
}
|
||||
if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
|
||||
echo json_encode(['success' => false, 'message' => 'File mancante o upload non riuscito']);
|
||||
exit;
|
||||
@@ -34,6 +30,16 @@ $tmpPath = $_FILES['file']['tmp_name'];
|
||||
$size = (int)$_FILES['file']['size'];
|
||||
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
|
||||
|
||||
// Titolo automatico dal nome file se non fornito
|
||||
if ($titolo === '') {
|
||||
$titolo = pathinfo($originalName, PATHINFO_FILENAME);
|
||||
$titolo = trim(preg_replace('/[_\-]+/', ' ', $titolo));
|
||||
if ($titolo === '') {
|
||||
$titolo = 'Documento del ' . date('d/m/Y');
|
||||
}
|
||||
}
|
||||
$titolo = mb_substr($titolo, 0, 255);
|
||||
|
||||
if (!in_array($ext, $allowedExt, true)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Estensione file non consentita (' . $ext . ')']);
|
||||
exit;
|
||||
@@ -72,7 +78,25 @@ try {
|
||||
");
|
||||
$stmt->execute([$idmescola, $categoria, $titolo, $storedName, $originalName, $mimeType, $size]);
|
||||
|
||||
echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]);
|
||||
$newId = (int)$pdo->lastInsertId();
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'id' => $newId,
|
||||
'row' => [
|
||||
'id' => $newId,
|
||||
'idmescola' => $idmescola,
|
||||
'categoria' => $categoria,
|
||||
'categoria_label' => $categorie[$categoria],
|
||||
'titolo' => $titolo,
|
||||
'filename' => $storedName,
|
||||
'original_filename' => $originalName,
|
||||
'mime_type' => $mimeType,
|
||||
'filesize' => $size,
|
||||
'uploaded_at' => date('Y-m-d H:i:s'),
|
||||
'url' => 'uploads/mescole/' . $idmescola . '/' . $storedName,
|
||||
],
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
// rollback file se il DB fallisce
|
||||
@unlink($destPath);
|
||||
|
||||
Reference in New Issue
Block a user