+
@@ -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 `
+
+ ${fEscape(f.categoria_label)}
+ ${fEscape(f.titolo)}
+ ${fEscape(f.original_filename)}
+ ${fFmtDate(f.uploaded_at)}
+
+ ⬇️
+ 🗑️
+
+ `;
+ }
+
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(`
${data.categorie[slug]} `);
});
+ // 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(`
+ tbody.append(`
Nessun file caricato
`);
return;
}
- data.rows.forEach(f => {
- tbody.append(`
-
- ${f.categoria_label}
- ${f.titolo}
- ${f.original_filename}
- ${f.uploaded_at}
-
- ⬇️
- 🗑️
-
-
- `);
- });
+ 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 = $(`
+
+
📄
+
${fEscape(file.name)}
+
+
0%
+
`);
+ $("#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(`
`);
+ 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");
diff --git a/public/userarea/rename_mescola_file.php b/public/userarea/rename_mescola_file.php
new file mode 100644
index 0000000..c2da0eb
--- /dev/null
+++ b/public/userarea/rename_mescola_file.php
@@ -0,0 +1,28 @@
+ 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()]);
+}
diff --git a/public/userarea/save_mescola_file.php b/public/userarea/save_mescola_file.php
index 687a882..edeb22b 100644
--- a/public/userarea/save_mescola_file.php
+++ b/public/userarea/save_mescola_file.php
@@ -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);