zibo-dashboard/public/userarea/manager_produzione.php

715 lines
32 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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,
m.photo AS matrice_photo,
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,
/* 🔥 NUOVA LISTA MESCOLE MULTI */
GROUP_CONCAT(ms.nome ORDER BY ms.nome SEPARATOR ' | ') AS mescole_list
FROM productiondata p
LEFT JOIN matrice m ON p.idmatrice = m.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
/* NUOVA JOIN A TABELLA MESCOLE */
LEFT JOIN productiondata_mescole pm ON p.id = pm.id_productiondata
LEFT JOIN mescole ms ON pm.id_mescola = ms.id
GROUP BY p.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 toggled">
<?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>Conf. Ord.</th>
<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['conferma_ordine'] ?? '') ?></td>
<td>
<?= htmlspecialchars($r['matrice']) ?>
<?php if (!empty($r['matrice_photo'])): ?>
<br>
<img src="photos/matrici/<?= htmlspecialchars($r['matrice_photo']) ?>"
data-full="photos/matrici/<?= htmlspecialchars($r['matrice_photo']) ?>"
class="photo-thumb"
style="width:42px;height:42px;object-fit:cover;
border-radius:6px;border:1px solid #ced4da;
cursor:pointer;margin-top:3px;">
<?php endif; ?>
</td>
<?php
$mescRaw = $r['mescole_list'] ?? '';
$mescArray = $mescRaw !== '' ? explode(' | ', $mescRaw) : [];
$mescCount = count($mescArray);
?>
<td>
<?php if ($mescCount === 0): ?>
<span style="color:#999;">N/A</span>
<?php elseif ($mescCount === 1): ?>
<?= htmlspecialchars($mescArray[0]) ?>
<?php else: ?>
<button class="btn btn-warning btn-sm showMescole"
data-list="<?= htmlspecialchars(json_encode($mescArray), ENT_QUOTES) ?>">
Multi (<?= $mescCount ?>)
</button>
<?php endif; ?>
</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>Conf. Ord.</th>
<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['conferma_ordine'] ?? '') ?></td>
<td>
<?= htmlspecialchars($r['matrice']) ?>
<?php if (!empty($r['matrice_photo'])): ?>
<br>
<img src="photos/matrici/<?= htmlspecialchars($r['matrice_photo']) ?>"
data-full="photos/matrici/<?= htmlspecialchars($r['matrice_photo']) ?>"
class="photo-thumb"
style="width:42px;height:42px;object-fit:cover;
border-radius:6px;border:1px solid #ced4da;
cursor:pointer;margin-top:3px;">
<?php endif; ?>
</td>
<?php
$mescRaw = $r['mescole_list'] ?? '';
$mescArray = $mescRaw !== '' ? explode(' | ', $mescRaw) : [];
$mescCount = count($mescArray);
?>
<td>
<?php if ($mescCount === 0): ?>
<span style="color:#999;">N/A</span>
<?php elseif ($mescCount === 1): ?>
<?= htmlspecialchars($mescArray[0]) ?>
<?php else: ?>
<button class="btn btn-warning btn-sm showMescole"
data-list="<?= htmlspecialchars(json_encode($mescArray), ENT_QUOTES) ?>">
Multi (<?= $mescCount ?>)
</button>
<?php endif; ?>
</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");
});
// ❌ Chiudi modale foto (delegato)
$(document).on("click", "#photoModalCloseX, #photoCancel", function() {
$("#photoModal").hide();
});
// ❌ Chiudi cliccando sul backdrop (delegato)
$(document).on("click", "#photoModal", function(e) {
if (e.target.id === "photoModal") {
$("#photoModal").hide();
}
});
$("#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");
});
// ❌ Chiudi preview con la X (delegato)
$(document).on("click", "#previewCloseX", function() {
$("#imagePreviewModal").hide();
});
// ❌ Chiudi cliccando fuori (delegato)
$(document).on("click", "#imagePreviewModal", function(e) {
if (e.target.id === "imagePreviewModal") {
$("#imagePreviewModal").hide();
}
});
// ===== MODALE MESCOLE (Multi) =====
$(document).on("click", ".showMescole", function(e) {
e.preventDefault();
e.stopPropagation();
// prendo la stringa grezza dallattributo, NON .data()
const raw = $(this).attr("data-list") || "[]";
let list = [];
try {
list = JSON.parse(raw);
} catch (err) {
console.error("Errore parsing mescole:", err, raw);
list = [];
}
let html = "";
list.forEach(m => {
html += `<li style="margin:4px 0;">${m}</li>`;
});
$("#mescoleList").html(html || '<li style="margin:4px 0;">Nessuna mescola</li>');
const modalEl = document.getElementById('mescoleModal');
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
modal.show();
});
</script>
<!-- MODALE MESCOLE -->
<div class="modal fade" id="mescoleModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Mescole utilizzate</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
</div>
<div class="modal-body">
<ul id="mescoleList" style="padding-left:1rem;"></ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Chiudi</button>
</div>
</div>
</div>
</div>
</body>
</html>