zibo-dashboard/public/userarea/production_line_view.php
2025-11-22 20:34:51 +01:00

1452 lines
48 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 ---
$linee = $pdo->query("SELECT id, name, color FROM production_lines ORDER BY line_number")->fetchAll();
// --- STATUS DINAMICI ---
$statusProgrammato = $pdo->query("SELECT id, nome, badge_color, line_color FROM production_status WHERE id = 6 LIMIT 1")->fetch();
$statusProduzione = $pdo->query("SELECT id, nome, badge_color, line_color FROM production_status WHERE id = 2 LIMIT 1")->fetch();
$statusPausa = $pdo->query("SELECT id, nome, badge_color, line_color FROM production_status WHERE id = 7 LIMIT 1")->fetch();
$statusQualita = $pdo->query("SELECT id FROM production_status WHERE id = 3 LIMIT 1")->fetchColumn();
if (!$statusProgrammato || !$statusProduzione || !$statusPausa || !$statusQualita) {
die("Errore: uno o più status non trovati in production_status");
}
$statusProgrammatoId = $statusProgrammato['id'];
$statusProduzioneId = $statusProduzione['id'];
$statusPausaId = $statusPausa['id'];
// --- AVVIO / RIPRESA PRODUZIONE ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['start_production'])) {
$id = (int)$_POST['id'];
try {
$sql = "UPDATE productiondata
SET id_status = :status, data_avvio = UTC_TIMESTAMP()
WHERE id = :id AND id_status IN (:programmato, :pausa)";
$stmt = $pdo->prepare($sql);
$stmt->execute([
'status' => $statusProduzioneId,
'id' => $id,
'programmato' => $statusProgrammatoId,
'pausa' => $statusPausaId
]);
echo json_encode(['success' => true]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'msg' => $e->getMessage()]);
}
exit;
}
// --- PAUSA PRODUZIONE (aggiorna tempo e stato in base al motivo) ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['pause_production'])) {
$id = (int)$_POST['id'];
$reason = $_POST['reason'] ?? 'tecnica';
$note = trim($_POST['note'] ?? '');
try {
$sql = "SELECT data_avvio FROM productiondata WHERE id = :id AND id_status = :produzione";
$stmt = $pdo->prepare($sql);
$stmt->execute(['id' => $id, 'produzione' => $statusProduzioneId]);
$row = $stmt->fetch();
$seconds = 0;
if ($row && $row['data_avvio']) {
$start = new DateTime($row['data_avvio'], new DateTimeZone('UTC'));
$now = new DateTime('now', new DateTimeZone('UTC'));
$seconds = $now->getTimestamp() - $start->getTimestamp();
}
// Stato di destinazione
$nextStatus = ($reason === 'problema') ? 8 : $statusPausaId; // 8 = Problema in produzione
$sql = "UPDATE productiondata
SET id_status = :status,
tempo_totale_produzione = COALESCE(tempo_totale_produzione, 0) + :seconds,
note_operatore = :note
WHERE id = :id AND id_status = :produzione";
$stmt = $pdo->prepare($sql);
$stmt->execute([
'status' => $nextStatus,
'seconds' => $seconds,
'note' => $note ?: null,
'id' => $id,
'produzione' => $statusProduzioneId
]);
echo json_encode(['success' => true]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'msg' => $e->getMessage()]);
}
exit;
}
// --- STOP PRODUZIONE (vai a Qualità + salva tempo finale) ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['stop_production'])) {
$id = (int)$_POST['id'];
$reason = $_POST['reason'] ?? 'fine';
$note = trim($_POST['note'] ?? '');
try {
$sql = "SELECT data_avvio, id_status FROM productiondata WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute(['id' => $id]);
$row = $stmt->fetch();
$seconds = 0;
if ($row && $row['data_avvio'] && $row['id_status'] == $statusProduzioneId) {
$start = new DateTime($row['data_avvio'], new DateTimeZone('UTC'));
$now = new DateTime('now', new DateTimeZone('UTC'));
$seconds = $now->getTimestamp() - $start->getTimestamp();
}
// Stato di destinazione
$nextStatus = ($reason === 'problema') ? 8 : $statusQualita; // 8 = Problema in produzione
$sql = "UPDATE productiondata
SET id_status = :status,
tempo_totale_produzione = COALESCE(tempo_totale_produzione, 0) + :seconds,
data_avvio = NULL,
note_operatore = :note
WHERE id = :id AND id_status IN (:produzione, :pausa)";
$stmt = $pdo->prepare($sql);
$stmt->execute([
'status' => $nextStatus,
'seconds' => $seconds,
'note' => $note ?: null,
'id' => $id,
'produzione' => $statusProduzioneId,
'pausa' => $statusPausaId
]);
echo json_encode(['success' => true]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'msg' => $e->getMessage()]);
}
exit;
}
// --- 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'];
$scarto = (float)$_POST['scarto'];
$note = trim($_POST['note'] ?? '');
$reason = $_POST['reason'] ?? 'fine';
try {
// Calcolo automatico del tempo totale in ore
$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
$nextStatus = ($reason === 'problema') ? 8 : $statusQualita;
$sql = "UPDATE productiondata
SET kgprod = :kgprod,
mtprod = :mtprod,
scarto = :scarto,
note_operatore = :note,
hourprod = SEC_TO_TIME(:seconds),
id_status = :status
WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute([
'kgprod' => $kgprod,
'mtprod' => $mtprod,
'scarto' => $scarto,
'note' => $note,
'seconds' => $seconds,
'status' => $nextStatus,
'id' => $id
]);
echo json_encode(['success' => true]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'msg' => $e->getMessage()]);
}
exit;
}
// --- AJAX PRINCIPALE: carica i record della data selezionata ---
if (!empty($_GET['ajax'])) {
$lineRaw = $_GET['line'] ?? '';
$lineArray = $lineRaw !== '' ? explode(',', $lineRaw) : [];
// --- RECORD IN PRODUZIONE (2,7,8)
$sql = "SELECT
p.*,
m.nome AS matrice,
ms.nome AS mescola,
l.name AS linea,
c.nome AS cliente,
s.nome AS status_nome,
s.badge_color,
s.line_color,
p.tempo_totale_produzione
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
WHERE p.id_status IN (2, 7, 8)
" . (!empty($lineArray) ? " AND p.id_linea IN (" . implode(',', array_map('intval', $lineArray)) . ")" : "") . "
ORDER BY l.line_number, p.Data";
$stmt = $pdo->prepare($sql);
$stmt->execute();
$records = $stmt->fetchAll();
foreach ($records as $r) {
include __DIR__ . "/render_production_card.php";
}
// --- RECORD IN STATO 6 ORDINATI PER PRIORITY
$sql2 = "SELECT
p.*,
m.nome AS matrice,
ms.nome AS mescola,
l.name AS linea,
c.nome AS cliente,
s.nome AS status_nome,
s.badge_color,
s.line_color,
p.tempo_totale_produzione
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
WHERE p.id_status = 6
" . (!empty($lineArray) ? " AND p.id_linea IN (" . implode(',', array_map('intval', $lineArray)) . ")" : "") . "
ORDER BY p.priority ASC";
$stmt2 = $pdo->prepare($sql2);
$stmt2->execute();
$recordsPriority = $stmt2->fetchAll();
if (!empty($recordsPriority)) {
echo '<div style="margin:20px 0; font-size:1.3rem; font-weight:700; color:#1e40af;">Programmato (in ordine di priorità)</div>';
foreach ($recordsPriority as $r) {
include __DIR__ . "/render_production_card.php";
}
}
exit;
}
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>Linea Produzione - Tablet</title>
<?php include('cssinclude.php'); ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<style>
* {
box-sizing: border-box;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
font-family: 'Segoe UI', sans-serif;
}
body {
background: #f1f5f9;
-webkit-tap-highlight-color: transparent;
}
.wrapper,
.page-wrapper,
.page-content {
height: 100%;
display: flex;
flex-direction: column;
}
.page-content {
padding: 0;
}
.main-card {
flex: 1;
display: flex;
flex-direction: column;
margin: 0.8rem;
border-radius: 1.2rem;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
background: #fff;
}
.card-header {
background: linear-gradient(135deg, #1d4ed8, #3b82f6);
color: white;
padding: 1.2rem 1.5rem;
flex-shrink: 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-header h5 {
margin: 0;
font-weight: 600;
font-size: 1.35rem;
}
.back-btn {
background: rgba(255, 255, 255, 0.25);
border: 1px solid rgba(255, 255, 255, 0.4);
color: white;
padding: 0.5rem 1rem;
border-radius: 0.8rem;
font-weight: 500;
}
.back-btn:hover {
background: rgba(255, 255, 255, 0.35);
}
.filters {
display: flex;
gap: 1rem;
padding: 1rem 1.5rem;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
flex-shrink: 0;
flex-wrap: wrap;
}
.filter-group {
flex: 1;
min-width: 160px;
}
.filter-group label {
display: block;
font-weight: 600;
margin-bottom: 0.4rem;
color: #1e40af;
font-size: 0.9rem;
}
.filter-group input,
.filter-group select {
width: 100%;
padding: 0.7rem 1rem;
border: 1px solid #cbd5e1;
border-radius: 0.8rem;
font-size: 1rem;
background: white;
}
.filter-group input:focus,
.filter-group select:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
#recordsContainer {
flex: 1;
overflow-y: auto;
padding: 1rem 1.5rem;
background: #f8fafc;
}
.record-card {
margin-bottom: 1rem;
border-radius: 1rem;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.06);
background: white;
transition: all 0.3s ease;
cursor: pointer;
}
.record-card:hover {
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.record-card.in-production {
background: var(--line-bg);
border-left: 6px solid var(--badge-bg);
}
.record-summary {
padding: 1rem 1.2rem;
position: relative;
background: transparent;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 0.8rem;
font-size: 0.9rem;
}
.summary-grid strong {
display: block;
color: #1e293b;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.6px;
margin-bottom: 0.2rem;
font-weight: 700;
}
.summary-grid span {
color: #475569;
font-weight: 500;
}
.status-badge {
position: absolute;
top: 1rem;
right: 1.2rem;
padding: 0.35rem 0.75rem;
border-radius: 2rem;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
color: white;
}
.expand-arrow {
display: inline-block;
margin-left: 0.5rem;
font-size: 1rem;
transition: transform 0.3s ease;
font-weight: bold;
}
.record-card.expanded .expand-arrow {
transform: rotate(180deg);
}
.record-expanded {
max-height: 0;
overflow: hidden;
transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1), padding 0.4s ease;
padding: 0 1.2rem;
background: #f8fafc;
}
.record-card.expanded .record-expanded {
max-height: 400px;
padding: 1.2rem;
}
.timer-display {
text-align: center;
font-size: 2.4rem;
font-weight: 700;
color: #059669;
margin: 1rem 0;
font-family: 'Courier New', monospace;
letter-spacing: 2px;
}
.action-buttons {
display: flex;
gap: 0.8rem;
}
.action-btn {
flex: 1;
padding: 1rem;
font-size: 1.2rem;
font-weight: 700;
border: none;
color: white;
text-transform: uppercase;
letter-spacing: 1.2px;
cursor: pointer;
border-radius: 0.8rem;
transition: all 0.2s;
}
.start-btn {
background: linear-gradient(135deg, #10b981, #059669);
}
.start-btn:hover {
background: linear-gradient(135deg, #059669, #047857);
}
.pause-btn {
background: linear-gradient(135deg, #f59e0b, #d97706);
}
.pause-btn:hover {
background: linear-gradient(135deg, #d97706, #c05621);
}
.resume-btn {
background: linear-gradient(135deg, #10b981, #059669);
}
.resume-btn:hover {
background: linear-gradient(135deg, #059669, #047857);
}
.stop-btn {
background: linear-gradient(135deg, #ef4444, #dc2626);
}
.stop-btn:hover {
background: linear-gradient(135deg, #dc2626, #b91c1c);
}
.no-records {
text-align: center;
padding: 4rem 1.5rem;
color: #64748b;
font-size: 1.15rem;
}
.no-records i {
font-size: 3.5rem;
margin-bottom: 1rem;
color: #cbd5e1;
}
.modal {
display: none;
/* di default invisibile */
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
justify-content: center;
align-items: center;
}
/* quando la modale è attiva, la centriamo */
.modal.active {
display: flex !important;
}
.modal-content {
background: white;
padding: 2rem;
border-radius: 1.2rem;
width: 90%;
max-width: 400px;
text-align: center;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.2);
animation: slideUp 0.25s ease-out;
}
/* MODALE GRANDE PER PREVIEW FOTO */
.preview-large {
width: 95% !important;
max-width: 1200px !important;
max-height: 90vh !important;
padding: 0 !important;
background: black !important;
position: relative;
border-radius: 12px !important;
display: flex;
justify-content: center;
align-items: center;
}
/* animazione dolce */
@keyframes slideUp {
from {
transform: translateY(30px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.modal-buttons {
display: flex;
gap: 1rem;
margin-top: 1.5rem;
}
.modal-btn {
flex: 1;
padding: 0.9rem;
font-weight: 600;
border: none;
border-radius: 0.8rem;
cursor: pointer;
}
.modal-confirm {
background: #10b981;
color: white;
}
.modal-cancel {
background: #e2e8f0;
color: #475569;
}
.line-btn {
flex: 0 0 150px;
padding: 0.8rem 0;
font-size: 1.4rem;
font-weight: 700;
border-radius: 0.8rem;
border: none;
cursor: pointer;
transition: 0.2s;
color: white;
}
.line-btn {
flex: 0 0 150px;
padding: 0.8rem 0;
font-size: 1.4rem;
font-weight: 700;
border-radius: 0.8rem;
border: none;
cursor: pointer;
transition: 0.2s;
color: white;
}
.line-btn.active {
background: var(--line-color);
}
.line-btn.inactive {
background: #cbd5e1 !important;
color: #475569 !important;
}
/* Modale più grande e ottimizzata */
.modal-content.final-wide {
width: 95%;
max-width: 780px;
}
.final-flex {
display: flex;
gap: 2rem;
margin-top: 1.2rem;
}
.final-col {
flex: 1;
}
.teo-box {
background: #eef6ff;
padding: 1rem;
border-radius: 0.8rem;
}
.teo-row {
margin-bottom: 1rem;
font-size: 1.1rem;
}
.teo-row label {
font-weight: 600;
color: #1e3a8a;
}
.teo-row span {
font-weight: 700;
color: #0f172a;
font-size: 1.25rem;
}
.real-box label {
margin-top: 0.6rem;
font-weight: 600;
}
.input-big {
width: 100%;
padding: 0.9rem;
font-size: 1.25rem;
border-radius: 0.6rem;
border: 1px solid #cbd5e1;
margin-bottom: 1rem;
}
#imagePreviewModal .modal-content {
max-width: 1200px !important;
width: 95% !important;
}
</style>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="main-card">
<div class="card-header">
<h5 style="color:#ffffff;">Linea di Produzione</h5>
<button class="back-btn" onclick="location.href='production_dashboard.php'">Torna</button>
</div>
<div class="filters">
<div class="filter-group">
<label>Linee</label>
<div id="lineButtons" style="display:flex; gap:0.8rem;">
<?php foreach ($linee as $l): ?>
<button
class="line-btn active"
data-line="<?= $l['id'] ?>"
style="--line-color: <?= htmlspecialchars($l['color']) ?>;">
<?= htmlspecialchars($l['name']) ?>
</button>
<?php endforeach; ?>
</div>
<!-- Campo nascosto che userà JS per passare le linee selezionate -->
<input type="hidden" id="filterLine" value="">
</div>
</div>
<div id="recordsContainer">
<!-- Caricato via JS -->
</div>
</div>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
<!-- Notifica sonora -->
<audio id="beepSound" src="beep.wav" preload="auto"></audio>
<!-- Modal di conferma azioni produzione -->
<div id="reasonModal" class="modal">
<div class="modal-content">
<h3 id="modalTitle">Conferma azione</h3>
<p id="modalQuestion"></p>
<!-- Opzioni visibili solo per STOP o PAUSA -->
<div class="modal-options" id="modalOptions" style="display:none;margin-top:1rem;">
<label><input type="radio" name="reason" value="fine"> Fine produzione</label><br>
<label><input type="radio" name="reason" value="tecnica"> Pausa tecnica</label><br>
<label><input type="radio" name="reason" value="problema"> Problema tecnico</label>
</div>
<!-- Campo nota -->
<div id="noteContainer" style="margin-top:1rem;display:none;">
<label for="modalNote"><strong>Nota (obbligatoria se problema):</strong></label>
<textarea id="modalNote" rows="3" style="width:100%;padding:0.5rem;border-radius:0.5rem;border:1px solid #ccc;"></textarea>
</div>
<div class="modal-buttons">
<button id="modalCancel" class="modal-btn modal-cancel">Annulla</button>
<button id="modalConfirm" class="modal-btn modal-confirm">Conferma</button>
</div>
</div>
</div>
<!-- Modal foto -->
<?php include __DIR__ . "/components/photo_modal.php"; ?>
<!-- Modal dati fine produzione -->
<div id="finalDataModal" class="modal">
<div class="modal-content final-wide">
<h3>Dati Fine Produzione</h3>
<p>Confronta i valori teorici e inserisci quelli reali:</p>
<div class="final-flex">
<!-- COLONNA SINISTRA (TEORICI) -->
<div class="final-col teo-box">
<h4>Teorici</h4>
<div class="teo-row">
<label><strong>Kg teorici:</strong></label>
<span id="teoKg">0.00</span>
</div>
<div class="teo-row">
<label><strong>Metri teorici:</strong></label>
<span id="teoMt">0.00</span>
</div>
</div>
<!-- COLONNA DESTRA (REALi DA INSERIRE) -->
<div class="final-col real-box">
<h4>Reali</h4>
<label><strong>Kg prodotti:</strong></label>
<input type="number" id="kgprod" step="0.01" min="0" placeholder="0.00"
class="input-big">
<label><strong>Metri prodotti:</strong></label>
<input type="number" id="mtprod" step="0.01" min="0" placeholder="0.00"
class="input-big">
<label><strong>Scarto (kg):</strong></label>
<input type="number" id="scarto" step="0.01" min="0" placeholder="0.00"
class="input-big">
<label><strong>Note operatore:</strong></label>
<textarea id="finalNote" rows="3" class="input-big"></textarea>
</div>
</div>
<div class="modal-buttons" style="margin-top:1.5rem;">
<button id="finalCancel" class="modal-btn modal-cancel">Annulla</button>
<button id="finalConfirm" class="modal-btn modal-confirm">Conferma</button>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(document).on("mousedown", function(e) {
// 🔥 Se clicco sulla X, non chiudere altri modali
if ($(e.target).attr("id") === "previewCloseX") {
return;
}
// --- Se il PREVIEW è aperto ---
if ($("#imagePreviewModal").hasClass("active")) {
// click fuori → chiudi SOLO il preview
if ($(e.target).closest("#imagePreviewModal .modal-content").length === 0) {
$("#imagePreviewModal").removeClass("active");
}
return; // 🔥 BLOCCA la chiusura del photoModal
}
// --- Se è aperto solo il PHOTO MODAL ---
if ($("#photoModal").hasClass("active")) {
if ($(e.target).closest("#photoModal .modal-content").length === 0) {
$("#photoModal").removeClass("active");
}
}
});
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 === 0) {
$("#photoGallery").html(
'<div style="color:#64748b;">Nessuna foto registrata per questa tipologia.</div>'
);
return;
}
let html = "";
photos.forEach(p => {
const aiLabel = p.ai_processed == 1 ? ' (AI✔)' : '';
html += `
<div style="width:90px; text-align:center; font-size:0.7rem;">
<div class="photo-thumb"
data-full="photos/${p.filename}"
style="width:90px; height:70px; border-radius:8px; overflow:hidden; border:1px solid #e2e8f0; margin-bottom:4px; cursor:pointer;">
<img src="photos/${p.filename}"
alt="photo"
style="width:100%; height:100%; object-fit:cover;">
</div>
<div style="color:#475569; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
#${p.id}${aiLabel}
</div>
</div>
`;
});
$("#photoGallery").html(html);
}).fail(function() {
$("#photoGallery").html(
'<div style="color:#b91c1c;">Errore di connessione nel caricamento delle foto.</div>'
);
});
}
// APRI PREVIEW — evento CLICK (non mousedown)
$(document).on("click", ".photo-thumb", function(e) {
e.stopPropagation(); // evita che il click chiuda subito il modale
const fullImage = $(this).data("full");
$("#previewImage").attr("src", fullImage);
$("#imagePreviewModal").addClass("active");
});
// chiusura preview con X
$("#previewCloseX").on("click", function(e) {
e.preventDefault();
e.stopImmediatePropagation(); // 🔥 blocca davvero tutto
$("#imagePreviewModal").removeClass("active");
});
// chiusura modale principale (upload foto)
$("#photoModalCloseX").on("click", function() {
$("#photoModal").removeClass("active");
});
</script>
<?php include('jsinclude.php'); ?>
<script>
$(function() {
$('#filterLine').on('change', loadRecords);
const beep = $('#beepSound')[0];
let timers = {};
function playBeep() {
beep.currentTime = 0;
beep.play().catch(() => {});
}
// --- BOTTONI LINEE ---
let selectedLines = [];
// inizialmente selezioniamo tutte
$('#lineButtons .line-btn').each(function() {
selectedLines.push($(this).data('line'));
});
$('#filterLine').val(selectedLines.join(','));
// logica pulsanti
$(document).on('click', '.line-btn', function() {
const id = $(this).data('line');
if ($(this).hasClass('active')) {
// se la deseleziono
$(this).removeClass('active').addClass('inactive');
selectedLines = selectedLines.filter(l => l !== id);
} else {
// se la seleziono
$(this).removeClass('inactive').addClass('active');
selectedLines.push(id);
}
// assicurati che non sia vuoto
if (selectedLines.length === 0) {
// se deselezionano tutto → riseleziona tutto
$('#lineButtons .line-btn').removeClass('inactive').addClass('active');
selectedLines = $('#lineButtons .line-btn').map(function() {
return $(this).data('line');
}).get();
}
$('#filterLine').val(selectedLines.join(','));
loadRecords();
});
// --- CARICA RECORD ---
function loadRecords() {
$('#recordsContainer').html('<div class="text-center p-5"><i class="bi bi-hourglass-split" style="font-size:2.5rem;color:#94a3b8;"></i></div>');
const line = $('#filterLine').val();
$.get('', {
ajax: 1,
line: line
}, function(html) {
// 🔥 INSERIRE LHTML NEL CONTAINER
$('#recordsContainer').html(html);
startAllTimers();
setupEventHandlers();
$(document).on("click", ".photo-btn", function(e) {
e.stopPropagation();
const type = $(this).data("type");
const production = $(this).data("production");
$("#photoType").val(type);
$("#photoProductionId").val(production);
let titles = {
lotto_mescola: "Foto Lotto Mescola",
parametri_macchina: "Foto Parametri Macchina",
problema: "Foto Problema di Produzione"
};
$("#photoModalTitle").text(titles[type] || "Carica Foto");
$("#photoModalSubtitle").text("Produzione ID: " + production);
// pulisci messaggi
$("#photoMessageSuccess").hide();
$("#photoMessageError").hide();
// carica galleria foto esistenti per questo record + tipologia
loadPhotoGallery(production, type);
$("#photoModal").addClass("active");
});
$("#photoCancel").on("click", () => {
$("#photoModal").removeClass("active");
});
// --- SUBMIT FORM FOTO ---
$("#photoForm").off("submit").on("submit", function(e) {
e.preventDefault();
let formData = new FormData(this);
$.ajax({
url: "upload_photo.php",
type: "POST",
data: formData,
processData: false,
contentType: false,
dataType: "json",
success: function(r) {
if (r.success) {
// conferma + refresh galleria
showPhotoSuccess();
loadPhotoGallery(
$("#photoProductionId").val(),
$("#photoType").val()
);
} else {
showPhotoError(r.message || "Errore durante il caricamento della foto.");
}
},
error: function() {
showPhotoError("Errore di comunicazione con il server.");
}
});
});
$('.record-card.in-production').each(function() {
const $card = $(this);
const $expanded = $card.find('.record-expanded');
$card.addClass('expanded');
$expanded.css('max-height', $expanded[0].scrollHeight + 50 + 'px');
});
}).fail(function() {
$('#recordsContainer').html('<div class="no-records">Errore di connessione</div>');
});
}
// --- EVENTI DEI PULSANTI ---
function setupEventHandlers() {
$(document).off('click', '.record-card');
$(document).on('click', '.record-card', function(e) {
if ($(e.target).closest('button').length) return;
const $card = $(this);
const $arrow = $card.find('.expand-arrow');
const $expanded = $card.find('.record-expanded');
if ($card.hasClass('expanded')) {
$card.removeClass('expanded');
$arrow.css('transform', 'rotate(0deg)');
$expanded.css('max-height', $expanded[0].scrollHeight + 'px');
setTimeout(() => $expanded.css('max-height', '0'), 10);
} else {
$card.addClass('expanded');
$arrow.css('transform', 'rotate(180deg)');
$expanded.css('max-height', '0');
setTimeout(() => $expanded.css('max-height', $expanded[0].scrollHeight + 50 + 'px'), 10);
}
});
// --- AVVIA PRODUZIONE ---
$(document).off('click', '.start-btn').on('click', '.start-btn', function(e) {
e.stopPropagation();
playBeep();
const id = $(this).data('id');
showReasonModal('start', id);
});
// --- RIPRENDI ---
$(document).off('click', '.resume-btn').on('click', '.resume-btn', function(e) {
e.stopPropagation();
playBeep();
const id = $(this).data('id');
showReasonModal('resume', id);
});
// --- PAUSA ---
$(document).off('click', '.pause-btn').on('click', '.pause-btn', function(e) {
e.stopPropagation();
playBeep();
const id = $(this).data('id');
showReasonModal('pause', id);
});
// --- STOP ---
$(document).off('click', '.stop-btn').on('click', '.stop-btn', function(e) {
e.stopPropagation();
playBeep();
const id = $(this).data('id');
showReasonModal('stop', id);
});
}
// --- CONVERT DATE ---
function formatItalianDate(isoDate) {
const months = [
"gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno",
"luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre"
];
const d = new Date(isoDate);
const day = d.getDate();
const month = months[d.getMonth()];
const year = d.getFullYear();
return `${day} ${month} ${year}`;
}
// --- MODALE UNIFICATA PER TUTTE LE AZIONI ---
function showReasonModal(action, id) {
$('#reasonModal').addClass('active');
$('#modalNote').val('');
$('input[name="reason"]').prop('checked', false);
$('#modalOptions').hide();
$('#noteContainer').hide();
// Titolo e testo dinamici
switch (action) {
case 'start':
$('#modalTitle').text('Avvio produzione');
$('#modalQuestion').text('Vuoi avviare la produzione?');
break;
case 'resume':
$('#modalTitle').text('Ripresa produzione');
$('#modalQuestion').text('Vuoi riprendere la produzione?');
break;
case 'pause':
$('#modalTitle').text('Pausa produzione');
$('#modalQuestion').text('Seleziona il motivo della pausa:');
$('#modalOptions').show();
$('input[value="fine"]').closest('label').hide();
$('input[value="tecnica"]').closest('label').show();
$('input[value="problema"]').closest('label').show();
$('#noteContainer').show();
break;
case 'stop':
$('#modalTitle').text('Stop produzione');
$('#modalQuestion').text('Seleziona il motivo dello stop:');
$('#modalOptions').show();
$('input[value="fine"]').closest('label').show();
$('input[value="tecnica"]').closest('label').hide();
$('input[value="problema"]').closest('label').show();
$('#noteContainer').show();
break;
}
// --- Pulsante conferma ---
$('#modalConfirm').off('click').on('click', function() {
const reason = $('input[name="reason"]:checked').val();
const note = $('#modalNote').val().trim();
if ((action === 'pause' || action === 'stop') && !reason) {
alert('Seleziona un motivo.');
return;
}
if (reason === 'problema' && note === '') {
alert('Inserisci una nota per segnalare il problema.');
return;
}
$('#reasonModal').removeClass('active');
// Azione specifica
switch (action) {
case 'start':
case 'resume':
startProduction(id);
break;
case 'pause':
pauseProduction(id, reason, note);
break;
case 'stop':
if (reason === 'fine') {
// Apri modale per inserimento dati finali
$('#reasonModal').removeClass('active');
openFinalDataModal(id, reason, note);
} else {
// Problema: stop diretto
stopProduction(id, reason, note);
}
break;
}
});
// --- Pulsante annulla ---
$('#modalCancel').off('click').on('click', function() {
$('#reasonModal').fadeOut(200);
});
}
// --- MODALE DATI FINE PRODUZIONE ---
function openFinalDataModal(id, reason, note) {
$('#finalDataModal').addClass('active');
// Pulisci i campi reali
$('#kgprod, #mtprod, #scarto').val('');
$('#finalNote').val(note || '');
// Recupera i dati teorici del record
const card = $(`.record-card[data-id="${id}"]`);
const teoKg = card.find('[data-field="kg_sp"]').text() || "0.00";
const teoMt = card.find('[data-field="metri"]').text() || "0.00";
// Mostra teorici
$('#teoKg').text(teoKg);
$('#teoMt').text(teoMt);
$('#finalConfirm').off('click').on('click', function() {
const kg = parseFloat($('#kgprod').val()) || 0;
const mt = parseFloat($('#mtprod').val()) || 0;
const scarto = parseFloat($('#scarto').val()) || 0;
const noteFinal = $('#finalNote').val().trim();
if (kg <= 0 && mt <= 0) {
alert('Inserisci almeno uno tra Kg o Metri prodotti.');
return;
}
$('#finalDataModal').removeClass('active');
$.post('', {
save_final_data: 1,
id: id,
reason: reason,
kgprod: kg,
mtprod: mt,
scarto: scarto,
note: noteFinal
}, function(r) {
if (r.success) loadRecords();
else alert(r.msg || 'Errore nel salvataggio dei dati finali.');
}, 'json');
});
$('#finalCancel').off('click').on('click', function() {
$('#finalDataModal').removeClass('active');
});
}
// --- START PRODUZIONE ---
function startProduction(id) {
$.post('', {
start_production: 1,
id: id
}, function(r) {
if (r.success) loadRecords();
else alert(r.msg || 'Errore durante l\'avvio.');
}, 'json');
}
// --- PAUSA PRODUZIONE ---
function pauseProduction(id, reason, note) {
$.post('', {
pause_production: 1,
id: id,
reason: reason,
note: note
}, function(r) {
if (r.success) loadRecords();
else alert(r.msg || 'Errore durante la pausa.');
}, 'json');
}
// --- STOP PRODUZIONE ---
function stopProduction(id, reason, note) {
$.post('', {
stop_production: 1,
id: id,
reason: reason,
note: note
}, function(r) {
if (r.success) loadRecords();
else alert(r.msg || 'Errore durante lo stop.');
}, 'json');
}
// --- TIMER PRODUZIONE ---
function startAllTimers() {
clearInterval(window.timerInterval);
timers = {};
$('.timer-display').each(function() {
const $el = $(this);
const start = $el.data('start') * 1000;
const id = $el.closest('.record-card').data('id');
const totalSeconds = parseInt($el.closest('.record-card').data('total-seconds') || 0, 10);
const isPaused = $el.closest('.record-card').find('.resume-btn').length > 0;
let effectiveStart = isPaused ?
Date.now() - (totalSeconds * 1000) :
start - (totalSeconds * 1000);
timers[id] = {
el: $el,
start: new Date(effectiveStart)
};
updateTimer(id);
});
window.timerInterval = setInterval(() => {
Object.keys(timers).forEach(id => {
const t = timers[id];
const $card = t.el.closest('.record-card');
const isPaused = $card.find('.resume-btn').length > 0;
if (!isPaused) {
updateTimer(id);
}
});
}, 1000);
}
function updateTimer(id) {
const t = timers[id];
if (!t) return;
const diff = Date.now() - t.start.getTime();
const h = String(Math.floor(diff / 3600000)).padStart(2, '0');
const m = String(Math.floor((diff % 3600000) / 60000)).padStart(2, '0');
const s = String(Math.floor((diff % 60000) / 1000)).padStart(2, '0');
t.el.find('.timer').text(`${h}:${m}:${s}`);
}
loadRecords();
});
</script>
<!-- MODALE PER PREVIEW IMMAGINE -->
<div id="imagePreviewModal" class="modal">
<div class="modal-content preview-large">
<!-- X per chiudere -->
<button id="previewCloseX"
style="
position:absolute;
top:10px;
right:15px;
font-size:2rem;
color:white;
background:none;
border:none;
cursor:pointer;
z-index:9999; /* 🔥 aggiungi questo */
">
&times;
</button>
<img id="previewImage"
src=""
style="
max-width: 95%;
max-height: 85vh;
width: auto;
height: auto;
display: block;
margin: auto;
border-radius: 6px;
">
</div>
</div>
</body>
</html>