dashboard fix
This commit is contained in:
parent
4c63f16a54
commit
a1c9d9f789
576
public/userarea/manager_produzione.php
Normal file
576
public/userarea/manager_produzione.php
Normal file
@ -0,0 +1,576 @@
|
||||
<?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>
|
||||
281
public/userarea/manager_stats.php
Normal file
281
public/userarea/manager_stats.php
Normal file
@ -0,0 +1,281 @@
|
||||
<?php
|
||||
include('include/headscript.php');
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
// --- Filtro periodo ---
|
||||
$from = $_GET['from'] ?? date('Y-m-01');
|
||||
$to = $_GET['to'] ?? date('Y-m-t');
|
||||
|
||||
// --- Query base ---
|
||||
$baseWhere = "WHERE p.data_produzione BETWEEN :from AND :to";
|
||||
|
||||
// --- KPI ---
|
||||
$kpi = $pdo->prepare("
|
||||
SELECT
|
||||
COUNT(*) AS totale,
|
||||
SUM(p.ore_previste) AS orepreviste,
|
||||
SUM(p.metri) AS metri,
|
||||
SUM(p.kg_sp) AS kgsp
|
||||
FROM productiondata p
|
||||
$baseWhere
|
||||
");
|
||||
$kpi->execute(['from' => $from, 'to' => $to]);
|
||||
$kpi = $kpi->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// --- Grafico stati ---
|
||||
$stati = $pdo->prepare("
|
||||
SELECT s.nome, COUNT(*) AS totale
|
||||
FROM productiondata p
|
||||
LEFT JOIN production_status s ON p.id_status = s.id
|
||||
$baseWhere
|
||||
GROUP BY p.id_status
|
||||
");
|
||||
$stati->execute(['from' => $from, 'to' => $to]);
|
||||
$data_stati = $stati->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// --- Grafico per linea ---
|
||||
$linee = $pdo->prepare("
|
||||
SELECT l.name, COUNT(*) AS totale
|
||||
FROM productiondata p
|
||||
LEFT JOIN production_lines l ON p.id_linea = l.id
|
||||
$baseWhere
|
||||
GROUP BY p.id_linea
|
||||
");
|
||||
$linee->execute(['from' => $from, 'to' => $to]);
|
||||
$data_linee = $linee->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// --- Metri per mescola ---
|
||||
$mescole = $pdo->prepare("
|
||||
SELECT ms.nome, SUM(p.metri) AS metri
|
||||
FROM productiondata p
|
||||
LEFT JOIN mescole ms ON p.idmescola = ms.id
|
||||
$baseWhere
|
||||
GROUP BY p.idmescola
|
||||
");
|
||||
$mescole->execute(['from' => $from, 'to' => $to]);
|
||||
$data_mescole = $mescole->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// --- Ore previste per cliente ---
|
||||
$clienti = $pdo->prepare("
|
||||
SELECT c.nome, SUM(p.ore_previste) AS ore
|
||||
FROM productiondata p
|
||||
LEFT JOIN clients c ON p.id_cliente = c.id
|
||||
$baseWhere
|
||||
GROUP BY p.id_cliente
|
||||
");
|
||||
$clienti->execute(['from' => $from, 'to' => $to]);
|
||||
$data_clienti = $clienti->fetchAll(PDO::FETCH_ASSOC);
|
||||
?>
|
||||
|
||||
<!doctype html>
|
||||
<html lang="it">
|
||||
|
||||
<head>
|
||||
<?php include('cssinclude.php'); ?>
|
||||
<title>Statistiche Produzione | <?= $titlewebsite ?></title>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
|
||||
|
||||
<style>
|
||||
.kpi-box {
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 18px;
|
||||
text-align: center;
|
||||
border: 1px solid #dee2e6;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.kpi-box:hover {
|
||||
background: #eef2ff;
|
||||
}
|
||||
|
||||
.kpi-value {
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.kpi-label {
|
||||
font-size: .95rem;
|
||||
color: #555;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<?php include('include/navbar.php'); ?>
|
||||
<?php include('include/topbar.php'); ?>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<div class="page-content">
|
||||
|
||||
<!-- ====================== -->
|
||||
<!-- FILTRO PERIODO -->
|
||||
<!-- ====================== -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">Filtra periodo</div>
|
||||
<div class="card-body">
|
||||
<form method="GET" class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Dal</label>
|
||||
<input type="date" name="from" value="<?= $from ?>" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Al</label>
|
||||
<input type="date" name="to" value="<?= $to ?>" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-4 d-flex align-items-end">
|
||||
<button class="btn btn-primary w-100">Applica filtro</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====================== -->
|
||||
<!-- KPI BOX -->
|
||||
<!-- ====================== -->
|
||||
<div class="row g-3 mb-4">
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="kpi-box">
|
||||
<div class="kpi-value"><?= $kpi['totale'] ?></div>
|
||||
<div class="kpi-label">Produzioni</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="kpi-box">
|
||||
<div class="kpi-value"><?= number_format($kpi['orepreviste'], 2, ',', '.') ?></div>
|
||||
<div class="kpi-label">Ore previste</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="kpi-box">
|
||||
<div class="kpi-value"><?= number_format($kpi['metri'], 2, ',', '.') ?></div>
|
||||
<div class="kpi-label">Metri totali</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="kpi-box">
|
||||
<div class="kpi-value"><?= number_format($kpi['kgsp'], 2, ',', '.') ?></div>
|
||||
<div class="kpi-label">Kg SP totali</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ====================== -->
|
||||
<!-- GRAFICO STATI -->
|
||||
<!-- ====================== -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">Distribuzione per stato</div>
|
||||
<div class="card-body">
|
||||
<div id="chartStati"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====================== -->
|
||||
<!-- GRAFICO PER LINEA -->
|
||||
<!-- ====================== -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">Produzioni per linea</div>
|
||||
<div class="card-body">
|
||||
<div id="chartLinee"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====================== -->
|
||||
<!-- METRI PER MESCOLA -->
|
||||
<!-- ====================== -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">Metri programmati per mescola</div>
|
||||
<div class="card-body">
|
||||
<div id="chartMescole"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====================== -->
|
||||
<!-- ORE PER CLIENTE -->
|
||||
<!-- ====================== -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">Ore previste per cliente</div>
|
||||
<div class="card-body">
|
||||
<div id="chartClienti"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include('include/footer.php'); ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ======== GRAFICO STATI ========
|
||||
new ApexCharts(document.querySelector("#chartStati"), {
|
||||
chart: {
|
||||
type: 'donut',
|
||||
height: 350
|
||||
},
|
||||
labels: <?= json_encode(array_column($data_stati, 'nome')) ?>,
|
||||
series: <?= json_encode(array_map('intval', array_column($data_stati, 'totale'))) ?>,
|
||||
}).render();
|
||||
|
||||
// ======== GRAFICO PER LINEA ========
|
||||
new ApexCharts(document.querySelector("#chartLinee"), {
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 350
|
||||
},
|
||||
series: [{
|
||||
name: 'Produzioni',
|
||||
data: <?= json_encode(array_map('intval', array_column($data_linee, 'totale'))) ?>
|
||||
}],
|
||||
xaxis: {
|
||||
categories: <?= json_encode(array_column($data_linee, 'name')) ?>
|
||||
}
|
||||
}).render();
|
||||
|
||||
// ======== METRI PER MESCOLA ========
|
||||
new ApexCharts(document.querySelector("#chartMescole"), {
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 350
|
||||
},
|
||||
series: [{
|
||||
name: 'Metri',
|
||||
data: <?= json_encode(array_map('floatval', array_column($data_mescole, 'metri'))) ?>
|
||||
}],
|
||||
xaxis: {
|
||||
categories: <?= json_encode(array_column($data_mescole, 'nome')) ?>
|
||||
}
|
||||
}).render();
|
||||
|
||||
// ======== ORE PER CLIENTE ========
|
||||
new ApexCharts(document.querySelector("#chartClienti"), {
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 350
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: true
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: 'Ore previste',
|
||||
data: <?= json_encode(array_map('floatval', array_column($data_clienti, 'ore'))) ?>
|
||||
}],
|
||||
xaxis: {
|
||||
categories: <?= json_encode(array_column($data_clienti, 'nome')) ?>
|
||||
}
|
||||
}).render();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -40,8 +40,8 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 50px;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
}
|
||||
@ -93,7 +93,7 @@
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
justify-items: center;
|
||||
margin-bottom: 40px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.dashboard-grid-bottom {
|
||||
@ -203,6 +203,19 @@
|
||||
background-color: #dc2626 !important;
|
||||
/* rosso scuro hover */
|
||||
}
|
||||
|
||||
/* --- Pulsanti grandi (default) --- */
|
||||
.dash-btn-large {
|
||||
padding: 18px 10px;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
/* --- Pulsanti di servizio: più bassi --- */
|
||||
.dash-btn-small {
|
||||
padding: 9px 10px !important;
|
||||
font-size: 1.05rem !important;
|
||||
min-height: 80px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@ -253,36 +266,47 @@
|
||||
|
||||
<!-- ===== PRIMA RIGA ===== -->
|
||||
<div class="dashboard-grid">
|
||||
<button class="dash-btn btn-programmazione" onclick="location.href='produzione_programmazione.php'">
|
||||
<button class="dash-btn dash-btn-large btn-programmazione" onclick="location.href='produzione_programmazione_drag.php'">
|
||||
<div class="dash-icon">🗓️</div>
|
||||
<div>Programmazione</div>
|
||||
</button>
|
||||
|
||||
<button class="dash-btn btn-status" onclick="location.href='production_line_view.php'">
|
||||
<button class="dash-btn dash-btn-large btn-status" onclick="location.href='production_line_view.php'">
|
||||
<div class="dash-icon">⚙️</div>
|
||||
<div>Line View</div>
|
||||
</button>
|
||||
|
||||
|
||||
<button class="dash-btn btn-statistiche" onclick="location.href='production_stats.php'">
|
||||
<button class="dash-btn dash-btn-large btn-statistiche" onclick="location.href='production_stats.php'">
|
||||
<div class="dash-icon">📈</div>
|
||||
<div>Statistiche</div>
|
||||
</button>
|
||||
|
||||
<button class="dash-btn dash-btn-large btn-manager" onclick="location.href='manager_produzione.php'">
|
||||
<div class="dash-icon">👔</div>
|
||||
<div>Manager</div>
|
||||
</button>
|
||||
|
||||
<button class="dash-btn dash-btn-large btn-manager-stats" onclick="location.href='manager_stats.php'">
|
||||
<div class="dash-icon">📊</div>
|
||||
<div>Manager Stats</div>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ===== SECONDA RIGA ===== -->
|
||||
<div class="dashboard-grid">
|
||||
<button class="dash-btn btn-mescole" onclick="location.href='mescole.php'">
|
||||
<button class="dash-btn dash-btn-small btn-mescole" onclick="location.href='mescole.php'">
|
||||
<div class="dash-icon">⚗️</div>
|
||||
<div>Mescole</div>
|
||||
</button>
|
||||
|
||||
<button class="dash-btn btn-matrici" onclick="location.href='matrici.php'">
|
||||
<button class="dash-btn dash-btn-small btn-matrici" onclick="location.href='matrici.php'">
|
||||
<div class="dash-icon">🧩</div>
|
||||
<div>Matrici</div>
|
||||
</button>
|
||||
|
||||
<button class="dash-btn btn-linee" onclick="location.href='linee.php'">
|
||||
<button class="dash-btn dash-btn-small btn-linee" onclick="location.href='linee.php'">
|
||||
<div class="dash-icon">🏭</div>
|
||||
<div>Linee di Produzione</div>
|
||||
</button>
|
||||
@ -291,13 +315,13 @@
|
||||
<!-- ===== TERZA RIGA (solo 2 bottoni centrati) ===== -->
|
||||
<div class="dashboard-grid-bottom">
|
||||
|
||||
<button class="dash-btn btn-inserisci btn-inserisci-status" onclick="location.href='production_status.php'">
|
||||
<button class="dash-btn dash-btn-small btn-inserisci btn-inserisci-status" onclick="location.href='production_status.php'">
|
||||
<div class="dash-icon">📋</div>
|
||||
<div>Status</div>
|
||||
</button>
|
||||
|
||||
|
||||
<button class="dash-btn btn-problem" onclick="location.href='production_pause_reasons.php'">
|
||||
<button class="dash-btn dash-btn-small btn-problem" onclick="location.href='production_pause_reasons.php'">
|
||||
<div class="dash-icon">🛑</div>
|
||||
<div>Cause di Pausa</div>
|
||||
</button>
|
||||
|
||||
@ -196,6 +196,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['stop_production']))
|
||||
|
||||
// --- SALVATAGGIO DATI FINE PRODUZIONE ---
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['save_final_data'])) {
|
||||
|
||||
$id = (int)$_POST['id'];
|
||||
$kgprod = (float)$_POST['kgprod'];
|
||||
$mtprod = (float)$_POST['mtprod'];
|
||||
@ -204,30 +205,41 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['save_final_data']))
|
||||
$reason = $_POST['reason'] ?? 'fine';
|
||||
|
||||
try {
|
||||
// Calcolo automatico del tempo totale in ore
|
||||
// Recupero tempo totale già calcolato
|
||||
$sql = "SELECT tempo_totale_produzione FROM productiondata WHERE id = :id";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute(['id' => $id]);
|
||||
$seconds = (int)$stmt->fetchColumn();
|
||||
$hours = round($seconds / 3600, 2);
|
||||
|
||||
// Stato di destinazione
|
||||
// Calcolo scarto %
|
||||
$totaleKg = $kgprod + $scarto;
|
||||
$scartoPerc = 0.00;
|
||||
|
||||
if ($totaleKg > 0) {
|
||||
$scartoPerc = round(($scarto / $totaleKg) * 100, 2);
|
||||
}
|
||||
|
||||
// Stato destinazione
|
||||
$nextStatus = ($reason === 'problema') ? 8 : $statusQualita;
|
||||
|
||||
// Update finale
|
||||
$sql = "UPDATE productiondata
|
||||
SET kgprod = :kgprod,
|
||||
mtprod = :mtprod,
|
||||
scarto = :scarto,
|
||||
scarto_perc = :scarto_perc,
|
||||
note_operatore = :note,
|
||||
hourprod = SEC_TO_TIME(:seconds),
|
||||
id_status = :status,
|
||||
end_time = COALESCE(end_time, UTC_TIMESTAMP())
|
||||
WHERE id = :id";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
'kgprod' => $kgprod,
|
||||
'mtprod' => $mtprod,
|
||||
'scarto' => $scarto,
|
||||
'scarto_perc' => $scartoPerc,
|
||||
'note' => $note,
|
||||
'seconds' => $seconds,
|
||||
'status' => $nextStatus,
|
||||
@ -241,6 +253,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['save_final_data']))
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- AJAX PRINCIPALE: carica i record della data selezionata ---
|
||||
if (!empty($_GET['ajax'])) {
|
||||
|
||||
@ -913,6 +927,11 @@ if (!empty($_GET['ajax'])) {
|
||||
<input type="number" id="scarto" step="0.01" min="0" placeholder="0.00"
|
||||
class="input-big">
|
||||
|
||||
<label><strong>Scarto (%):</strong></label>
|
||||
<input type="text" id="scartoPerc" class="input-big" readonly>
|
||||
|
||||
|
||||
|
||||
<label><strong>Note operatore:</strong></label>
|
||||
<textarea id="finalNote" rows="3" class="input-big"></textarea>
|
||||
</div>
|
||||
@ -1387,6 +1406,9 @@ if (!empty($_GET['ajax'])) {
|
||||
// Pulisci i campi reali
|
||||
$('#kgprod, #mtprod, #scarto').val('');
|
||||
$('#finalNote').val(note || '');
|
||||
$('#kgprod, #scarto').off('input').on('input', recalcScrapPerc);
|
||||
$('#scartoPerc').val('');
|
||||
|
||||
|
||||
// Recupera i dati teorici del record
|
||||
const card = $(`.record-card[data-id="${id}"]`);
|
||||
@ -1430,6 +1452,20 @@ if (!empty($_GET['ajax'])) {
|
||||
});
|
||||
}
|
||||
|
||||
function recalcScrapPerc() {
|
||||
const kg = parseFloat($('#kgprod').val()) || 0;
|
||||
const sc = parseFloat($('#scarto').val()) || 0;
|
||||
const tot = kg + sc;
|
||||
let perc = 0;
|
||||
|
||||
if (tot > 0) {
|
||||
perc = (sc / tot) * 100;
|
||||
}
|
||||
|
||||
$('#scartoPerc').val(perc.toFixed(2) + ' %');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// --- START PRODUZIONE ---
|
||||
|
||||
@ -357,7 +357,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['action'])) {
|
||||
|
||||
<tbody>
|
||||
<?php foreach ($rows_special as $r): ?>
|
||||
<?php $sec = intval($r['tempo_totale_produzione'] ?? 0); ?>
|
||||
<?php if (!empty($r['start_time'])) {
|
||||
// Calcolo differenza tra adesso e lo start_time
|
||||
$start = strtotime($r['start_time']);
|
||||
$now = time();
|
||||
$sec = max(0, $now - $start);
|
||||
} else {
|
||||
$sec = 0;
|
||||
}
|
||||
?>
|
||||
|
||||
<tr class="special-row"
|
||||
data-id="<?= $r['id'] ?>"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user