zibo-dashboard/public/userarea/manager_produzione.php
2025-11-26 11:24:29 +01:00

576 lines
25 KiB
PHP

<?php
include('include/headscript.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// --- LISTE ---
$linee = $pdo->query("SELECT id, line_number, name FROM production_lines ORDER BY line_number")->fetchAll();
$status_list = $pdo->query("SELECT id, nome, badge_color, line_color FROM production_status ORDER BY ordinamento, nome")->fetchAll(PDO::FETCH_ASSOC);
// --- CARICO TUTTE LE PRODUZIONI UNA VOLTA SOLA ---
$sql = "SELECT
p.*,
m.nome AS matrice,
ms.nome AS mescola,
l.name AS linea,
c.nome AS cliente,
s.nome AS status_nome,
COALESCE(s.badge_color, '#6c757d') AS badge_color,
COALESCE(s.line_color, '#ffffff') AS line_color
FROM productiondata p
LEFT JOIN matrice m ON p.idmatrice = m.id
LEFT JOIN mescole ms ON p.idmescola = ms.id
LEFT JOIN production_lines l ON p.id_linea = l.id
LEFT JOIN clients c ON p.id_cliente = c.id
LEFT JOIN production_status s ON p.id_status = s.id
ORDER BY p.data_produzione DESC, p.Data DESC, p.id DESC";
$rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
// --- RECORD SPECIALI (IN PRODUZIONE / PAUSA / PROBLEMA) ---
$rows_special = array_filter($rows, function ($r) {
return in_array((int)$r['id_status'], [2, 7, 8]);
});
?>
<!doctype html>
<html lang="it">
<head>
<?php include('cssinclude.php'); ?>
<title>Produzione - Vista Manageriale | <?= $titlewebsite ?></title>
<!-- Bootstrap Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<!-- DataTables -->
<link href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css" rel="stylesheet">
<link href="https://cdn.datatables.net/responsive/2.5.0/css/responsive.bootstrap5.min.css" rel="stylesheet">
<style>
.table th {
background: #f8f9fa;
font-weight: 600;
vertical-align: middle;
}
.table td {
vertical-align: middle;
}
.badge {
padding: .35em .65em;
font-size: .75rem;
border-radius: .4rem;
}
.special-row,
.special-row td {
background-color: var(--rowcolor) !important;
}
.timer-cell {
font-family: "Courier New", monospace;
font-weight: 700;
}
.status-filter-select {
min-width: 220px;
}
.photo-btn {
font-size: 1.3rem;
cursor: pointer;
}
/* MODALE FOTO - FULLSCREEN BACKDROP */
#photoModal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.65);
z-index: 99999;
justify-content: center;
align-items: center;
}
#photoModal .modal-content {
background: white;
border-radius: 10px;
padding: 20px;
max-height: 90vh;
overflow-y: auto;
}
.photo-thumb {
width: 110px;
height: 110px;
object-fit: cover;
border-radius: 8px;
cursor: pointer;
margin: 5px;
}
/* MODALE PREVIEW FOTO */
#imagePreviewModal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.75);
z-index: 100000;
justify-content: center;
align-items: center;
}
#imagePreviewModal .modal-content {
position: relative;
max-width: 90%;
background: black;
border-radius: 10px;
padding: 10px;
}
#previewImage {
max-width: 100%;
max-height: 85vh;
display: block;
margin: auto;
}
#previewCloseX {
position: absolute;
top: 10px;
right: 15px;
font-size: 2rem;
color: white;
background: none;
border: none;
cursor: pointer;
}
</style>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<!-- ======================= -->
<!-- BLOCCO ALTO MANAGER -->
<!-- ======================= -->
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
<h5 class="mb-0">Produzione in corso / pausa / problema</h5>
<button class="btn btn-secondary btn-sm" onclick="location.href='production_dashboard.php'">
Vista Operatori
</button>
</div>
<div class="card-body">
<?php if (!empty($rows_special)): ?>
<div class="table-responsive">
<table id="tabSpecialManager" class="table table-hover align-middle mb-0">
<thead>
<tr>
<th>Matrice</th>
<th>Mescola</th>
<th>Linea</th>
<th>Cliente</th>
<th>Data Zibo</th>
<th>Data Cliente</th>
<!-- VALORI TEORICI (corretti) -->
<th>Metri</th>
<th>Kg SP</th>
<th>Ore previste</th>
<th>Timer</th>
<th>Status</th>
<th>Foto</th>
</tr>
</thead>
<tbody>
<?php foreach ($rows_special as $r): ?>
<?php
$sec = (int)($r['tempo_totale_produzione'] ?? 0);
$scartoKg = (float)($r['scarto'] ?? 0);
$scartoPct = isset($r['scarto_percent']) ? (float)$r['scarto_percent'] : 0;
?>
<tr class="special-row"
data-id="<?= (int)$r['id'] ?>"
data-seconds="<?= $sec ?>"
style="--rowcolor: <?= htmlspecialchars($r['line_color']) ?>;">
<td><?= htmlspecialchars($r['matrice']) ?></td>
<td><?= htmlspecialchars($r['mescola']) ?></td>
<td><?= htmlspecialchars($r['linea']) ?></td>
<td><?= htmlspecialchars($r['cliente']) ?></td>
<td><?= htmlspecialchars($r['data_zibo']) ?></td>
<td><?= htmlspecialchars($r['data_cliente']) ?></td>
<td><?= number_format((float)($r['metri'] ?? 0), 2, ',', '.') ?></td>
<td><?= number_format((float)($r['kg_sp'] ?? 0), 2, ',', '.') ?></td>
<td><?= number_format((float)($r['ore_previste'] ?? 0), 2, ',', '.') ?></td>
<td class="timer-cell" id="timer-<?= (int)$r['id'] ?>">
<?= gmdate("H:i:s", $sec) ?>
</td>
<td>
<span class="badge" style="background: <?= htmlspecialchars($r['badge_color']) ?>;">
<?= htmlspecialchars($r['status_nome']) ?>
</span>
</td>
<td class="text-center" style="white-space: nowrap;">
<i class="bi bi-camera-fill photo-btn me-2"
data-type="lotto_mescola"
data-production="<?= (int)$r['id'] ?>"></i>
<i class="bi bi-gear-fill photo-btn me-2"
data-type="parametri_macchina"
data-production="<?= (int)$r['id'] ?>"></i>
<i class="bi bi-exclamation-triangle-fill photo-btn"
data-type="problema"
data-production="<?= (int)$r['id'] ?>"
style="color:#b91c1c;"></i>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="text-muted fst-italic">
Nessuna produzione attualmente in stato <strong>In produzione / Pausa / Problema</strong>.
</div>
<?php endif; ?>
</div>
</div>
<!-- ======================= -->
<!-- TABELLA GLOBALE -->
<!-- ======================= -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-3">
<h5 class="mb-0">Tutte le produzioni (vista manageriale)</h5>
<div class="d-flex align-items-center gap-2 flex-wrap">
<!-- FILTRO PER STATO -->
<select id="statusFilter" class="form-select form-select-sm status-filter-select">
<option value="">Tutti gli stati</option>
<?php foreach ($status_list as $s): ?>
<option value="<?= (int)$s['id'] ?>">
<?= htmlspecialchars($s['nome']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="card-body pt-0">
<div class="table-responsive">
<table id="tabManager" class="table table-hover align-middle" style="width:100%">
<thead>
<tr>
<th>Matrice</th>
<th>Mescola</th>
<th>Linea</th>
<th>Cliente</th>
<th>Data Zibo</th>
<th>Data Cliente</th>
<th>Metri</th>
<th>Kg SP</th>
<th>Kg P</th>
<th>Kg prod.</th>
<th>Scarto (kg)</th>
<th>Scarto %</th>
<th>Ore previste</th>
<th>Tempo lavoro</th>
<th>Status</th>
<th>Foto</th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $r): ?>
<?php
$scartoKg = (float)($r['scarto'] ?? 0);
$scartoPct = isset($r['scarto_percent']) ? (float)$r['scarto_percent'] : 0;
$hourProd = $r['hourprod'] ?? null;
?>
<tr data-id="<?= (int)$r['id'] ?>"
data-status="<?= (int)$r['id_status'] ?>"
style="background-color: <?= htmlspecialchars($r['line_color']) ?>;">
<td><?= htmlspecialchars($r['matrice']) ?></td>
<td><?= htmlspecialchars($r['mescola']) ?></td>
<td><?= htmlspecialchars($r['linea']) ?></td>
<td><?= htmlspecialchars($r['cliente']) ?></td>
<td><?= htmlspecialchars($r['data_zibo']) ?></td>
<td><?= htmlspecialchars($r['data_cliente']) ?></td>
<td><?= number_format((float)($r['metri'] ?? 0), 2, ',', '.') ?></td>
<td><?= number_format((float)($r['kg_sp'] ?? 0), 2, ',', '.') ?></td>
<td><?= number_format((float)($r['kg_p'] ?? 0), 2, ',', '.') ?></td>
<td><?= number_format((float)($r['kgprod'] ?? 0), 2, ',', '.') ?></td>
<td><?= number_format($scartoKg, 2, ',', '.') ?></td>
<td><?= $scartoPct > 0 ? number_format($scartoPct, 1, ',', '.') . ' %' : '-' ?></td>
<td><?= number_format((float)($r['ore_previste'] ?? 0), 2, ',', '.') ?></td>
<td><?= $hourProd ? htmlspecialchars($hourProd) : '-' ?></td>
<td>
<span class="badge" style="background: <?= htmlspecialchars($r['badge_color']) ?>;">
<?= htmlspecialchars($r['status_nome']) ?>
</span>
</td>
<td class="text-center" style="white-space: nowrap;">
<i class="bi bi-camera-fill photo-btn me-2"
data-type="lotto_mescola"
data-production="<?= (int)$r['id'] ?>"></i>
<i class="bi bi-gear-fill photo-btn me-2"
data-type="parametri_macchina"
data-production="<?= (int)$r['id'] ?>"></i>
<i class="bi bi-exclamation-triangle-fill photo-btn"
data-type="problema"
data-production="<?= (int)$r['id'] ?>"
style="color:#b91c1c;"></i>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
<!-- MODALE FOTO (UGUALE ALL'ATTUALE) -->
<div id="photoModal">
<div class="modal-content">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5 id="photoModalTitle" class="mb-0">Carica Foto</h5>
<button type="button" id="photoModalCloseX" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-x-lg"></i>
</button>
</div>
<input type="hidden" id="photoType">
<input type="hidden" id="photoProductionId">
<div id="photoMessageSuccess" class="alert alert-success py-1 px-2 mb-2" style="display:none;">
✅ Foto caricata correttamente
</div>
<div id="photoMessageError" class="alert alert-danger py-1 px-2 mb-2" style="display:none;"></div>
<form id="photoForm" enctype="multipart/form-data">
<div class="mb-3">
<label class="form-label">Seleziona foto</label>
<input type="file" name="photo" class="form-control" accept="image/*" required>
</div>
<button type="submit" class="btn btn-primary btn-sm">Carica</button>
<button type="button" class="btn btn-secondary btn-sm" id="photoCancel">Chiudi</button>
</form>
<hr>
<h6>Foto esistenti</h6>
<div id="photoGallery" class="d-flex flex-wrap"></div>
</div>
</div>
<!-- MODALE PREVIEW FOTO -->
<div id="imagePreviewModal">
<div class="modal-content">
<button id="previewCloseX">&times;</button>
<img id="previewImage" src="">
</div>
</div>
<!-- JS -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js"></script>
<script src="https://cdn.datatables.net/responsive/2.5.0/js/dataTables.responsive.min.js"></script>
<?php include('jsinclude.php'); ?>
<script>
// ===== TIMER SOLO PER RIGHE SPECIALI =====
document.addEventListener("DOMContentLoaded", function() {
function updateTimers() {
document.querySelectorAll(".special-row").forEach(row => {
let id = row.dataset.id;
let sec = parseInt(row.dataset.seconds) + 1;
row.dataset.seconds = sec;
let h = String(Math.floor(sec / 3600)).padStart(2, '0');
let m = String(Math.floor((sec % 3600) / 60)).padStart(2, '0');
let s = String(sec % 60).padStart(2, '0');
let el = document.getElementById("timer-" + id);
if (el) el.innerText = `${h}:${m}:${s}`;
});
}
setInterval(updateTimers, 1000);
});
// ===== DATATABLE + FILTRO STATO =====
$(function() {
const table = $('#tabManager').DataTable({
responsive: true,
pageLength: 25,
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.7/i18n/it-IT.json'
},
order: [
[1, 'desc']
] // Data produzione, giusto per avere le ultime in alto
});
// Filtro per stato basato su data-status della riga
$.fn.dataTable.ext.search.push(function(settings, data, dataIndex) {
if (settings.nTable.id !== 'tabManager') return true;
const selected = $('#statusFilter').val();
if (!selected) return true;
const row = $(settings.aoData[dataIndex].nTr);
const rowStatus = row.data('status');
return String(rowStatus) === String(selected);
});
$('#statusFilter').on('change', function() {
table.draw();
});
});
// ===== GESTIONE FOTO (UGUALE ALL'ATTUALE) =====
function showPhotoSuccess() {
$("#photoMessageError").hide();
$("#photoMessageSuccess").fadeIn(150);
setTimeout(() => $("#photoMessageSuccess").fadeOut(200), 2000);
}
function showPhotoError(msg) {
$("#photoMessageSuccess").hide();
$("#photoMessageError").text("⚠️ " + msg).fadeIn(150);
}
function loadPhotoGallery(productionId, type) {
$("#photoGallery").html('<div style="color:#64748b;">Caricamento foto...</div>');
$.getJSON("get_photos.php", {
production_id: productionId,
photo_type: type
}, function(r) {
if (!r.success) {
$("#photoGallery").html('<div style="color:#b91c1c;">Errore nel caricamento delle foto.</div>');
return;
}
const photos = r.photos || [];
if (!photos.length) {
$("#photoGallery").html('<div style="color:#64748b;">Nessuna foto registrata.</div>');
return;
}
let html = "";
photos.forEach(p => {
html += `
<img src="photos/${p.filename}"
data-full="photos/${p.filename}"
class="photo-thumb">
`;
});
$("#photoGallery").html(html);
}).fail(function() {
$("#photoGallery").html('<div style="color:#b91c1c;">Errore di connessione.</div>');
});
}
// Apri modale foto
$(document).on("click", ".photo-btn", function() {
const type = $(this).data("type");
const id = $(this).data("production");
$("#photoType").val(type);
$("#photoProductionId").val(id);
let titles = {
lotto_mescola: "Foto Lotto Mescola",
parametri_macchina: "Foto Parametri Macchina",
problema: "Foto Problema di Produzione"
};
$("#photoModalTitle").text(titles[type] || "Carica Foto");
$("#photoMessageSuccess").hide();
$("#photoMessageError").hide();
loadPhotoGallery(id, type);
$("#photoModal").css("display", "flex");
});
$("#photoModalCloseX, #photoCancel").on("click", function() {
$("#photoModal").hide();
});
$("#photoModal").on("click", function(e) {
if (e.target.id === "photoModal") {
$("#photoModal").hide();
}
});
// submit upload
$("#photoForm").on("submit", function(e) {
e.preventDefault();
let formData = new FormData(this);
formData.append("photo_type", $("#photoType").val());
formData.append("production_id", $("#photoProductionId").val());
$.ajax({
url: "upload_photo.php",
type: "POST",
data: formData,
processData: false,
contentType: false,
dataType: "json",
success: function(r) {
if (r.success) {
showPhotoSuccess();
loadPhotoGallery($("#photoProductionId").val(), $("#photoType").val());
} else {
showPhotoError(r.message || "Errore durante il caricamento.");
}
},
error: function() {
showPhotoError("Errore di comunicazione con il server.");
}
});
});
// Preview grande
$(document).on("click", ".photo-thumb", function() {
const src = $(this).data("full");
$("#previewImage").attr("src", src);
$("#imagePreviewModal").css("display", "flex");
});
$("#previewCloseX").on("click", function() {
$("#imagePreviewModal").hide();
});
$("#imagePreviewModal").on("click", function(e) {
if (e.target.id === "imagePreviewModal") {
$("#imagePreviewModal").hide();
}
});
</script>
</body>
</html>