1557 lines
51 KiB
PHP
1557 lines
51 KiB
PHP
<?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 L’HTML 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 */
|
||
">
|
||
×
|
||
</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>
|