dashboard fix

This commit is contained in:
Claudio 2025-11-26 11:24:29 +01:00
parent 4c63f16a54
commit a1c9d9f789
5 changed files with 957 additions and 32 deletions

View 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">&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>

View 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>

View File

@ -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>

View File

@ -196,42 +196,54 @@ 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'];
$id = (int)$_POST['id'];
$kgprod = (float)$_POST['kgprod'];
$mtprod = (float)$_POST['mtprod'];
$scarto = (float)$_POST['scarto'];
$note = trim($_POST['note'] ?? '');
$note = trim($_POST['note'] ?? '');
$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,
note_operatore = :note,
hourprod = SEC_TO_TIME(:seconds),
id_status = :status,
end_time = COALESCE(end_time, UTC_TIMESTAMP())
WHERE id = :id";
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,
'note' => $note,
'seconds' => $seconds,
'status' => $nextStatus,
'id' => $id
'kgprod' => $kgprod,
'mtprod' => $mtprod,
'scarto' => $scarto,
'scarto_perc' => $scartoPerc,
'note' => $note,
'seconds' => $seconds,
'status' => $nextStatus,
'id' => $id
]);
echo json_encode(['success' => true]);
@ -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 ---

View File

@ -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'] ?>"