added files for mescole

This commit is contained in:
2026-07-03 09:28:21 +02:00
parent c675eb766e
commit 376252e263
10 changed files with 955 additions and 20 deletions
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class CreateMescoleFilesTable extends AbstractMigration
{
public function change(): void
{
$table = $this->table('mescole_files', [
'id' => 'id',
'signed' => true,
'engine' => 'InnoDB',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
]);
$table
->addColumn('idmescola', 'integer', [
'signed' => true,
'null' => false,
])
->addColumn('categoria', 'string', [
'limit' => 100,
'null' => false,
'comment' => 'slug categoria: schede_tecniche, certificati_analisi, ...',
])
->addColumn('titolo', 'string', [
'limit' => 255,
'null' => false,
])
->addColumn('filename', 'string', [
'limit' => 255,
'null' => false,
'comment' => 'nome fisico del file salvato su disco (random, non l\'originale)',
])
->addColumn('original_filename', 'string', [
'limit' => 255,
'null' => false,
])
->addColumn('mime_type', 'string', [
'limit' => 150,
'null' => true,
])
->addColumn('filesize', 'integer', [
'null' => true,
])
->addColumn('uploaded_at', 'datetime', [
'default' => 'CURRENT_TIMESTAMP',
])
->addIndex(['idmescola'])
->addIndex(['categoria'])
->addForeignKey('idmescola', 'mescole', 'id', [
'delete' => 'CASCADE',
'update' => 'CASCADE',
])
->create();
}
}
@@ -0,0 +1,14 @@
<?php
/**
* Categorie disponibili per i file associati alle mescole.
* Chiave = slug salvato nel DB (colonna mescole_files.categoria)
* Valore = etichetta mostrata in UI
*
* Per aggiungere una nuova categoria in futuro basta aggiungere
* una riga qui, senza toccare il database.
*/
return [
'schede_tecniche' => 'Schede Tecniche',
'certificati_analisi' => 'Certificati di Analisi',
];
+36
View File
@@ -0,0 +1,36 @@
<?php
include('include/headscript.php');
header('Content-Type: application/json; charset=utf-8');
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
if ($id <= 0) {
echo json_encode(['success' => false, 'message' => 'ID non valido']);
exit;
}
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT idmescola, filename FROM mescole_files WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
echo json_encode(['success' => false, 'message' => 'File non trovato']);
exit;
}
$del = $pdo->prepare("DELETE FROM mescole_files WHERE id = ?");
$del->execute([$id]);
$path = __DIR__ . '/uploads/mescole/' . $row['idmescola'] . '/' . $row['filename'];
if (is_file($path)) {
@unlink($path);
}
echo json_encode(['success' => true]);
} catch (Throwable $e) {
echo json_encode(['success' => false, 'message' => 'Errore database: ' . $e->getMessage()]);
}
+44
View File
@@ -0,0 +1,44 @@
<?php
include('include/headscript.php');
header('Content-Type: application/json; charset=utf-8');
$idmescola = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$categoria = $_GET['categoria'] ?? null;
if ($idmescola <= 0) {
echo json_encode(['success' => false, 'message' => 'ID mescola non valido']);
exit;
}
$categorie = require __DIR__ . '/config/mescole_file_categories.php';
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$sql = "SELECT id, idmescola, categoria, titolo, filename, original_filename, mime_type, filesize, uploaded_at
FROM mescole_files
WHERE idmescola = ?";
$params = [$idmescola];
if (!empty($categoria)) {
$sql .= " AND categoria = ?";
$params[] = $categoria;
}
$sql .= " ORDER BY uploaded_at DESC";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$rows = [];
while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
$r['categoria_label'] = $categorie[$r['categoria']] ?? $r['categoria'];
$r['url'] = 'uploads/mescole/' . $r['idmescola'] . '/' . $r['filename'];
$rows[] = $r;
}
echo json_encode(['success' => true, 'rows' => $rows, 'categorie' => $categorie]);
} catch (Throwable $e) {
echo json_encode(['success' => false, 'message' => 'Errore server: ' . $e->getMessage()]);
}
+48
View File
@@ -0,0 +1,48 @@
<?php
include('include/headscript.php');
header('Content-Type: application/json; charset=utf-8');
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($id <= 0) {
echo json_encode(['success' => false, 'message' => 'ID non valido']);
exit;
}
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$sql = "
SELECT
m.id,
m.nome,
m.nomeuscita,
m.is_active,
IFNULL(q.qty_totale, 0) AS qty_totale,
GROUP_CONCAT(DISTINCT pl.name SEPARATOR ', ') AS linee
FROM mescole m
LEFT JOIN (
SELECT idmescola, SUM(qty) AS qty_totale
FROM mescole_supplier_lots
GROUP BY idmescola
) q ON q.idmescola = m.id
LEFT JOIN mescole_lines ml ON m.id = ml.idmescola
LEFT JOIN production_lines pl ON ml.idlinea = pl.id
WHERE m.id = ?
GROUP BY m.id
";
$stmt = $pdo->prepare($sql);
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
echo json_encode(['success' => false, 'message' => 'Mescola non trovata']);
exit;
}
echo json_encode(['success' => true, 'data' => $row]);
} catch (Throwable $e) {
echo json_encode(['success' => false, 'message' => 'Errore database: ' . $e->getMessage()]);
}
+276 -20
View File
@@ -106,6 +106,17 @@
max-width: 360px;
}
#tabellaMescole td:nth-child(2) a {
color: #1f2d3d;
text-decoration: none;
font-weight: 600;
}
#tabellaMescole td:nth-child(2) a:hover {
text-decoration: underline;
color: #0d6efd;
}
/* Q.tà totale */
#tabellaMescole th:nth-child(3),
#tabellaMescole td:nth-child(3) {
@@ -130,8 +141,27 @@
/* Azioni */
#tabellaMescole th:nth-child(6),
#tabellaMescole td:nth-child(6) {
width: 330px;
max-width: 330px;
width: 190px;
max-width: 190px;
white-space: normal;
}
.azioni-cell {
display: flex;
justify-content: center;
gap: 6px;
flex-wrap: nowrap;
}
.azioni-cell .btn {
width: 34px;
height: 34px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 1rem;
line-height: 1;
}
</style>
</head>
@@ -246,33 +276,50 @@
$toggleText = $isActive ? "Disattiva" : "Attiva";
$toggleClass = $isActive ? "btn-outline-warning" : "btn-outline-success";
$nomeUscitaSafe = htmlspecialchars($row['nomeuscita']);
echo "<tr data-mescola-id='{$row['id']}'>
<td>{$row['id']}</td>
<td>" . htmlspecialchars($row['nomeuscita']) . "</td>
<td>
<a href='scheda_mescola.php?id={$row['id']}' title='Apri scheda mescola'>
{$nomeUscitaSafe}
</a>
</td>
<td><span class='fw-semibold'>{$qtyTot}</span></td>
<td>{$linee}</td>
<td>{$badge}</td>
<td>
<button class='btn btn-sm btn-outline-dark associa-fornitori'
<td class='azioni-cell'>
<button type='button' class='btn btn-sm btn-outline-dark associa-fornitori'
data-id='{$row['id']}'
data-nomeuscita='" . htmlspecialchars($row['nomeuscita'], ENT_QUOTES) . "'>
🧾 Fornitori
data-nomeuscita='" . htmlspecialchars($row['nomeuscita'], ENT_QUOTES) . "'
data-bs-toggle='tooltip' data-bs-placement='top' title='Fornitori / Lotti'>
🧾
</button>
<button class='btn btn-sm btn-outline-primary associa-linee'
data-id='{$row['id']}'>
⚙️ Linee
</button>
<button class='btn btn-sm {$toggleClass} toggle-active'
data-id='{$row['id']}'>
🔁 {$toggleText}
</button>
<button class='btn btn-sm btn-outline-secondary edit-mescola'
<button type='button' class='btn btn-sm btn-outline-info associa-file'
data-id='{$row['id']}'
data-nomeuscita='" . htmlspecialchars($row['nomeuscita'], ENT_QUOTES) . "'>
✏️ Modifica
data-nomeuscita='" . htmlspecialchars($row['nomeuscita'], ENT_QUOTES) . "'
data-bs-toggle='tooltip' data-bs-placement='top' title='File'>
📁
</button>
<button type='button' class='btn btn-sm btn-outline-primary associa-linee'
data-id='{$row['id']}'
data-bs-toggle='tooltip' data-bs-placement='top' title='Linee Associate'>
⚙️
</button>
<button type='button' class='btn btn-sm {$toggleClass} toggle-active'
data-id='{$row['id']}'
data-bs-toggle='tooltip' data-bs-placement='top' title='{$toggleText}'>
🔁
</button>
<button type='button' class='btn btn-sm btn-outline-secondary edit-mescola'
data-id='{$row['id']}'
data-nomeuscita='" . htmlspecialchars($row['nomeuscita'], ENT_QUOTES) . "'
data-bs-toggle='tooltip' data-bs-placement='top' title='Modifica'>
✏️
</button>
</td>
</tr>";
@@ -444,6 +491,56 @@
</div>
</div>
<!-- MODALE FILE / REPOSITORY -->
<div class="modal fade" id="associaFileModal" tabindex="-1">
<div class="modal-dialog modal-lg 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>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<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>
<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'); ?>
<script>
@@ -463,6 +560,8 @@
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.6/i18n/it-IT.json'
}
}).on('draw.dt', function() {
initTooltips();
});
// filter reload
@@ -472,8 +571,19 @@
url.searchParams.set('active', v);
window.location.href = url.toString();
});
initTooltips();
});
/* -------- TOOLTIP BOOTSTRAP SUI PULSANTI ICONA -------- */
function initTooltips() {
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => {
const existing = bootstrap.Tooltip.getInstance(el);
if (existing) existing.dispose();
new bootstrap.Tooltip(el);
});
}
/* -------- TOGGLE ACTIVE -------- */
$(document).on('click', '.toggle-active', function() {
const id = $(this).data('id');
@@ -882,6 +992,152 @@
});
});
});
// ============================
// FILE / REPOSITORY
// ============================
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)
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>`);
});
}
const tbody = $("#fTable tbody");
tbody.empty();
if (!data.success || !Array.isArray(data.rows) || data.rows.length === 0) {
tbody.append(`<tr>
<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>
`);
});
});
}
// Open modal
$(document).on("click", ".associa-file", function() {
const idMescola = $(this).data("id");
const nomeUscita = $(this).data("nomeuscita");
$("#fIdMescola").val(idMescola);
$("#fNomeUscita").text(nomeUscita);
$("#fTitolo").val("");
$("#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");
const idMescola = $("#fIdMescola").val();
Swal.fire({
title: "Eliminare il file?",
icon: "warning",
showCancelButton: true,
confirmButtonText: "Sì, elimina",
cancelButtonText: "Annulla",
confirmButtonColor: "#d33"
}).then((res) => {
if (!res.isConfirmed) return;
fetch("delete_mescola_file.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: `id=${encodeURIComponent(id)}`
})
.then(r => r.json())
.then(data => {
if (data.success) {
fLoadRows(idMescola);
} else {
Swal.fire({
icon: "error",
title: "Errore",
text: data.message || "Cancellazione non riuscita"
});
}
});
});
});
</script>
</body>
+80
View File
@@ -0,0 +1,80 @@
<?php
include('include/headscript.php');
header('Content-Type: application/json; charset=utf-8');
$categorie = require __DIR__ . '/config/mescole_file_categories.php';
$idmescola = isset($_POST['idmescola']) ? (int)$_POST['idmescola'] : 0;
$categoria = $_POST['categoria'] ?? '';
$titolo = trim($_POST['titolo'] ?? '');
if ($idmescola <= 0) {
echo json_encode(['success' => false, 'message' => 'ID mescola non valido']);
exit;
}
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;
}
// --- Validazioni di sicurezza sul file ---
$allowedExt = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'jpeg', 'png'];
$maxSize = 15 * 1024 * 1024; // 15 MB
$originalName = $_FILES['file']['name'];
$tmpPath = $_FILES['file']['tmp_name'];
$size = (int)$_FILES['file']['size'];
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if (!in_array($ext, $allowedExt, true)) {
echo json_encode(['success' => false, 'message' => 'Estensione file non consentita (' . $ext . ')']);
exit;
}
if ($size <= 0 || $size > $maxSize) {
echo json_encode(['success' => false, 'message' => 'Il file supera la dimensione massima di 15MB']);
exit;
}
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $tmpPath);
finfo_close($finfo);
// Cartella dedicata per mescola: uploads/mescole/{idmescola}/
$destDir = __DIR__ . '/uploads/mescole/' . $idmescola . '/';
if (!is_dir($destDir)) {
mkdir($destDir, 0775, true);
}
// Nome fisico casuale: evita collisioni e path traversal
$storedName = bin2hex(random_bytes(16)) . '.' . $ext;
$destPath = $destDir . $storedName;
if (!move_uploaded_file($tmpPath, $destPath)) {
echo json_encode(['success' => false, 'message' => 'Impossibile salvare il file sul server']);
exit;
}
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("
INSERT INTO mescole_files (idmescola, categoria, titolo, filename, original_filename, mime_type, filesize, uploaded_at)
VALUES (?, ?, ?, ?, ?, ?, ?, NOW())
");
$stmt->execute([$idmescola, $categoria, $titolo, $storedName, $originalName, $mimeType, $size]);
echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]);
} catch (Throwable $e) {
// rollback file se il DB fallisce
@unlink($destPath);
echo json_encode(['success' => false, 'message' => 'Errore database: ' . $e->getMessage()]);
}
+397
View File
@@ -0,0 +1,397 @@
<?php include('include/headscript.php'); ?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>Scheda Mescola - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<style>
body {
font-size: 1.05rem;
background: #f8fafc;
}
.card {
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.back-dashboard {
background-color: #cfe3ff !important;
color: #1f2d3d !important;
border: 1px solid #bcd4f4 !important;
border-radius: 10px;
font-weight: 600;
padding: 10px 18px;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease-in-out;
}
.back-dashboard:hover {
background-color: #b9d3ff !important;
transform: translateY(-2px);
}
.info-label {
font-size: .8rem;
text-transform: uppercase;
letter-spacing: .04em;
color: #6c757d;
margin-bottom: 2px;
}
.info-value {
font-size: 1.05rem;
font-weight: 600;
color: #1f2d3d;
}
.nav-tabs .nav-link.active {
background-color: #cfe3ff;
font-weight: 600;
}
.btn-add {
background-color: #0d6efd;
color: #fff;
border-radius: 8px;
font-weight: 500;
}
.btn-add:hover {
background-color: #0b5ed7;
}
</style>
</head>
<body>
<div class="wrapper" id="appWrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<!-- HEADER INFO GENERALI -->
<div class="card p-3 mb-3">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0" id="hNomeUscita">Scheda Mescola</h5>
<button type="button" class="btn back-dashboard" onclick="location.href='mescole.php'">
↩️ Torna a Gestione Mescole
</button>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-2">
<div class="info-label">ID</div>
<div class="info-value" id="hId">-</div>
</div>
<div class="col-md-3">
<div class="info-label">Nome Interno</div>
<div class="info-value" id="hNome">-</div>
</div>
<div class="col-md-3">
<div class="info-label">Q. Totale</div>
<div class="info-value" id="hQty">-</div>
</div>
<div class="col-md-2">
<div class="info-label">Stato</div>
<div class="info-value" id="hStato">-</div>
</div>
<div class="col-md-2">
<div class="info-label">Linee Associate</div>
<div class="info-value" id="hLinee">-</div>
</div>
</div>
</div>
</div>
<!-- TAB FILE PER CATEGORIA -->
<div class="card p-3">
<div class="card-body">
<ul class="nav nav-tabs" id="fileTabs" role="tablist"></ul>
<div class="tab-content pt-3" id="fileTabsContent"></div>
</div>
</div>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
<!-- MODALE AGGIUNGI FILE -->
<div class="modal fade" id="addFileModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header" style="background-color:#cfe3ff;">
<h5 class="modal-title">Aggiungi File</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="addFileForm">
<input type="hidden" id="fCategoria">
<div class="mb-3">
<label class="form-label fw-semibold">Titolo File</label>
<input type="text" class="form-control" id="fTitolo" required>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">File</label>
<input type="file" class="form-control" id="fFile" required>
</div>
<div class="text-center">
<button type="submit" class="btn btn-add">💾 Carica File</button>
</div>
</form>
</div>
</div>
</div>
</div>
<?php include('jsinclude.php'); ?>
<script>
const idMescola = new URLSearchParams(window.location.search).get('id');
let CATEGORIE = {};
if (!idMescola) {
Swal.fire({
icon: 'error',
title: 'Errore',
text: 'ID mescola mancante'
}).then(() => location.href = 'mescole.php');
}
function fmtQty(n) {
return Number(n || 0).toLocaleString('it-IT', {
minimumFractionDigits: 3,
maximumFractionDigits: 3
});
}
function loadGeneral() {
fetch('get_mescola_general.php?id=' + encodeURIComponent(idMescola))
.then(r => r.json())
.then(res => {
if (!res.success) {
Swal.fire({
icon: 'error',
title: 'Errore',
text: res.message
}).then(() => location.href = 'mescole.php');
return;
}
const d = res.data;
document.title = 'Scheda Mescola - ' + d.nomeuscita;
$('#hNomeUscita').text('🧪 ' + d.nomeuscita);
$('#hId').text(d.id);
$('#hNome').text(d.nome || '-');
$('#hQty').text(fmtQty(d.qty_totale));
$('#hStato').html(
Number(d.is_active) === 1 ?
"<span class='badge bg-success'>Attiva</span>" :
"<span class='badge bg-secondary'>Inattiva</span>"
);
$('#hLinee').text(d.linee || 'Nessuna');
});
}
function buildTabs() {
fetch('get_mescola_files.php?id=' + encodeURIComponent(idMescola))
.then(r => r.json())
.then(res => {
if (!res.success) return;
CATEGORIE = res.categorie;
const tabsUl = $('#fileTabs');
const tabsContent = $('#fileTabsContent');
tabsUl.empty();
tabsContent.empty();
let first = true;
Object.keys(CATEGORIE).forEach(slug => {
const label = CATEGORIE[slug];
const activeTab = first ? 'active' : '';
const activePane = first ? 'show active' : '';
tabsUl.append(`
<li class="nav-item" role="presentation">
<button class="nav-link ${activeTab}" id="tab-${slug}" data-bs-toggle="tab"
data-bs-target="#pane-${slug}" type="button" role="tab">
${label}
</button>
</li>
`);
tabsContent.append(`
<div class="tab-pane fade ${activePane}" id="pane-${slug}" role="tabpanel">
<div class="d-flex justify-content-end mb-2">
<button class="btn btn-add btn-sm add-file-btn" data-categoria="${slug}">
Aggiungi File
</button>
</div>
<div class="table-responsive">
<table class="table table-striped align-middle text-center" id="table-${slug}">
<thead class="table-light">
<tr>
<th>Titolo</th>
<th>Nome File</th>
<th>Caricato il</th>
<th>Azioni</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
`);
first = false;
});
// popola ogni tab con i file già caricati
Object.keys(CATEGORIE).forEach(slug => loadFilesForCategory(slug));
});
}
function loadFilesForCategory(slug) {
fetch('get_mescola_files.php?id=' + encodeURIComponent(idMescola) + '&categoria=' + encodeURIComponent(slug))
.then(r => r.json())
.then(res => {
const tbody = $('#table-' + slug + ' tbody');
tbody.empty();
if (!res.success || res.rows.length === 0) {
tbody.append(`<tr>
<td class="text-muted" colspan="4">Nessun file caricato in questa categoria</td>
</tr>`);
return;
}
res.rows.forEach(f => {
tbody.append(`
<tr>
<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">⬇️ Scarica</a>
<button class="btn btn-sm btn-outline-danger del-file-btn" data-id="${f.id}" data-categoria="${slug}">🗑️ Elimina</button>
</td>
</tr>
`);
});
});
}
$(document).on('click', '.add-file-btn', function() {
$('#fCategoria').val($(this).data('categoria'));
$('#fTitolo').val('');
$('#fFile').val('');
$('#addFileModal').modal('show');
});
$('#addFileForm').on('submit', function(e) {
e.preventDefault();
const categoria = $('#fCategoria').val();
const titolo = $('#fTitolo').val().trim();
const fileInput = document.getElementById('fFile');
if (!fileInput.files.length) {
Swal.fire({
icon: 'warning',
title: 'Attenzione',
text: 'Seleziona un file'
});
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(res => {
if (res.success) {
$('#addFileModal').modal('hide');
Swal.fire({
icon: 'success',
title: 'File caricato!'
});
loadFilesForCategory(categoria);
} else {
Swal.fire({
icon: 'error',
title: 'Errore',
text: res.message || 'Upload non riuscito'
});
}
});
});
$(document).on('click', '.del-file-btn', function() {
const id = $(this).data('id');
const categoria = $(this).data('categoria');
Swal.fire({
title: 'Eliminare il file?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Sì, elimina',
cancelButtonText: 'Annulla',
confirmButtonColor: '#d33'
}).then(res => {
if (!res.isConfirmed) return;
fetch('delete_mescola_file.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `id=${encodeURIComponent(id)}`
})
.then(r => r.json())
.then(data => {
if (data.success) {
loadFilesForCategory(categoria);
} else {
Swal.fire({
icon: 'error',
title: 'Errore',
text: data.message || 'Cancellazione non riuscita'
});
}
});
});
});
$(document).ready(function() {
loadGeneral();
buildTabs();
});
</script>
</body>
</html>