576 lines
25 KiB
PHP
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">×</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>
|