zibo-dashboard/public/userarea/production_line_view2.php
2025-11-24 20:02:40 +01:00

1557 lines
51 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'];
// --- PAUSE REASONS ---
$pauseReasons = $pdo->query("SELECT id, name, is_problem FROM pause_reasons ORDER BY name")->fetchAll();
// --- AVVIO / RIPRESA PRODUZIONE ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['start_production'])) {
$id = (int)$_POST['id'];
try {
// 1) Leggo stato attuale
$sql = "SELECT id_status FROM productiondata WHERE id = :id LIMIT 1";
$stmt = $pdo->prepare($sql);
$stmt->execute(['id' => $id]);
$currentStatus = $stmt->fetchColumn();
if ($currentStatus === false) {
echo json_encode(['success' => false, 'msg' => 'Record produzione non trovato.']);
exit;
}
// 2) Primo avvio: da PROGRAMMATO (6) → PRODUZIONE (2) + set start_time
if ((int)$currentStatus === (int)$statusProgrammatoId) {
$sqlUpdate = "UPDATE productiondata
SET id_status = :status,
start_time = UTC_TIMESTAMP(),
end_time = NULL
WHERE id = :id AND id_status = :programmato";
$stmtUpdate = $pdo->prepare($sqlUpdate);
$stmtUpdate->execute([
'status' => $statusProduzioneId,
'id' => $id,
'programmato' => $statusProgrammatoId
]);
// 3) Ripresa: da PAUSA (7) → PRODUZIONE (2) **senza toccare start_time**
} elseif ((int)$currentStatus === (int)$statusPausaId) {
$sqlUpdate = "UPDATE productiondata
SET id_status = :status,
end_time = NULL
WHERE id = :id AND id_status = :pausa";
$stmtUpdate = $pdo->prepare($sqlUpdate);
$stmtUpdate->execute([
'status' => $statusProduzioneId,
'id' => $id,
'pausa' => $statusPausaId
]);
} else {
echo json_encode([
'success' => false,
'msg' => 'Lo stato corrente non consente avvio/ripresa (stato id: ' . $currentStatus . ')'
]);
exit;
}
echo json_encode(['success' => true]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'msg' => $e->getMessage()]);
}
exit;
}
// --- PAUSA PRODUZIONE (crea record in production_pauses + cambia stato) ---
// --- PAUSA PRODUZIONE (crea record in production_pauses + cambia stato) ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['pause_production'])) {
$id = (int)$_POST['id'];
$reasonId = isset($_POST['reason_id']) ? (int)$_POST['reason_id'] : 0;
$note = trim($_POST['note'] ?? '');
try {
if ($reasonId <= 0) {
echo json_encode(['success' => false, 'msg' => 'Motivo pausa non valido.']);
exit;
}
// Leggo se il motivo è "problema" oppure pausa normale
$sql = "SELECT is_problem FROM pause_reasons WHERE id = :id LIMIT 1";
$stmt = $pdo->prepare($sql);
$stmt->execute(['id' => $reasonId]);
$isProblem = $stmt->fetchColumn();
if ($isProblem === false) {
echo json_encode(['success' => false, 'msg' => 'Motivo pausa non trovato.']);
exit;
}
$isProblem = (int)$isProblem;
// ❌ PRIMA sbagliato per la tua logica:
// $nextStatus = $isProblem ? 8 : $statusPausaId; // 8 = Problema in produzione
// ✅ ORA in PAUSA lo stato è SEMPRE pausa (7)
$nextStatus = $statusPausaId;
// Aggiorno lo stato sulla produzione SOLO se è in produzione (id_status = produzione)
$sql = "UPDATE productiondata
SET id_status = :status,
note_operatore = :note
WHERE id = :id AND id_status = :produzione";
$stmtUpdate = $pdo->prepare($sql);
$stmtUpdate->execute([
'status' => $nextStatus,
'note' => $note ?: null,
'id' => $id,
'produzione' => $statusProduzioneId
]);
// Se effettivamente ho cambiato lo stato, registro la pausa
if ($stmtUpdate->rowCount() > 0) {
$sqlPause = "INSERT INTO production_pauses
(id_production, reason_id, start_pause, note)
VALUES (:id_production, :reason_id, UTC_TIMESTAMP(), :note)";
$stmtPause = $pdo->prepare($sqlPause);
$stmtPause->execute([
'id_production' => $id,
'reason_id' => $reasonId,
'note' => $note ?: null
]);
}
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 start_time, id_status FROM productiondata WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute(['id' => $id]);
$row = $stmt->fetch();
$seconds = 0;
if ($row && $row['start_time'] && $row['id_status'] == $statusProduzioneId) {
$start = new DateTime($row['start_time'], 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,
end_time = UTC_TIMESTAMP(),
note_operatore = :note
WHERE id = :id AND id_status IN ($statusProduzioneId, $statusPausaId)";
$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,
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
]);
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>
<!-- Motivo pausa (dropdown, solo per PAUSA) -->
<div id="pauseReasonContainer" style="margin-top:1rem; display:none;">
<label for="pauseReason"><strong>Motivo pausa:</strong></label>
<select id="pauseReason"
style="width:100%;padding:0.5rem;border-radius:0.5rem;border:1px solid #ccc;">
<option value="">Seleziona un motivo</option>
<?php foreach ($pauseReasons as $pr): ?>
<option value="<?= $pr['id'] ?>"
data-problem="<?= (int)$pr['is_problem'] ?>">
<?= htmlspecialchars($pr['name']) ?>
</option>
<?php endforeach; ?>
</select>
</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();
$('#pauseReasonContainer').hide();
$('#pauseReason').val('');
// 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').hide(); // niente radio
$('#pauseReasonContainer').show(); // mostra tendina
$('#noteContainer').show(); // nota opzionale / obbligatoria se problema
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 note = $('#modalNote').val().trim();
// --- LOGICA PAUSA ---
if (action === 'pause') {
const pauseReasonId = $('#pauseReason').val();
if (!pauseReasonId) {
alert('Seleziona un motivo di pausa.');
return;
}
const isProblem = $('#pauseReason option:selected').data('problem') == 1;
if (isProblem && note === '') {
alert('Inserisci una nota per una pausa con problema tecnico.');
return;
}
$('#reasonModal').removeClass('active');
pauseProduction(id, pauseReasonId, note);
return;
}
// --- LOGICA STOP ---
let reason = null;
if (action === 'stop') {
reason = $('input[name="reason"]:checked').val();
if (!reason) {
alert('Seleziona un motivo.');
return;
}
if (reason === 'problema' && note === '') {
alert('Inserisci una nota per segnalare il problema.');
return;
}
}
$('#reasonModal').removeClass('active');
// Azioni specifiche
switch (action) {
case 'start':
case 'resume':
startProduction(id);
break;
case 'stop':
if (reason === 'fine') {
// Apertura modale dati finali
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, reasonId, note) {
$.post('', {
pause_production: 1,
id: id,
reason_id: reasonId,
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');
const end = $el.data('end');
if (!start) return;
const id = $el.closest('.record-card').data('id');
timers[id] = {
el: $el,
start: start * 1000,
end: end ? end * 1000 : null
};
});
window.timerInterval = setInterval(() => {
Object.keys(timers).forEach(id => updateTimer(id));
}, 1000);
}
function updateTimer(id) {
const t = timers[id];
if (!t) return;
let diff = t.end ? (t.end - t.start) : (Date.now() - t.start);
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>