zibo-dashboard/public/userarea/production_line_view2.php

2312 lines
79 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
ini_set('display_errors', 1);
error_reporting(E_ALL);
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 {
// Recupero tempo totale già calcolato
$sql = "SELECT tempo_totale_produzione FROM productiondata WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute(['id' => $id]);
$seconds = (int)$stmt->fetchColumn();
// Calcolo scarto %
$totaleKg = $kgprod + $scarto;
$scartoPerc = 0.00;
if ($totaleKg > 0) {
$scartoPerc = round(($scarto / $totaleKg) * 100, 2);
}
// Stato destinazione
$nextStatus = ($reason === 'problema') ? 8 : $statusQualita;
// Update finale
$sql = "UPDATE productiondata
SET kgprod = :kgprod,
mtprod = :mtprod,
scarto = :scarto,
scarto_perc = :scarto_perc,
note_operatore = :note,
hourprod = SEC_TO_TIME(:seconds),
id_status = :status,
end_time = COALESCE(end_time, UTC_TIMESTAMP())
WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute([
'kgprod' => $kgprod,
'mtprod' => $mtprod,
'scarto' => $scarto,
'scarto_perc' => $scartoPerc,
'note' => $note,
'seconds' => $seconds,
'status' => $nextStatus,
'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)
// --- RECORD IN PRODUZIONE (2,7,8)
$sql = "SELECT
p.*,
m.id AS matrice_id,
m.nome AS matrice,
m.photo AS matrice_photo,
(
SELECT GROUP_CONCAT(mes.nome ORDER BY mes.nome SEPARATOR ' | ')
FROM productiondata_mescole pm
JOIN mescole mes ON mes.id = pm.id_mescola
WHERE pm.id_productiondata = p.id
) AS mescole_list,
l.name AS linea,
c.nome AS cliente,
s.nome AS status_nome,
s.badge_color,
s.line_color,
p.tempo_totale_produzione,
-- 🔧 ADD: tools list & count
(
SELECT GROUP_CONCAT(t.name ORDER BY t.name SEPARATOR ' | ')
FROM productiondata_tools pt
JOIN production_tools t ON t.id = pt.tool_id
WHERE pt.productiondata_id = p.id
) AS tools_list,
(
SELECT COUNT(*)
FROM productiondata_tools pt
WHERE pt.productiondata_id = p.id
) AS tools_count
FROM productiondata p
LEFT JOIN matrice m ON p.idmatrice = m.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) {
// --- PARAMETRI LINEA (slot dinamici)
$sqlParams = "SELECT position, short_label, icon
FROM production_line_params
WHERE line_id = :line
ORDER BY position ASC";
$stmtP = $pdo->prepare($sqlParams);
$stmtP->execute(['line' => $r['id_linea']]);
$paramsLinea = $stmtP->fetchAll();
$r['param_slots'] = $paramsLinea;
include __DIR__ . "/render_production_card.php";
}
// --- RECORD IN STATO 6 ORDINATI PER PRIORITY
$sql2 = "SELECT
p.*,
m.id AS matrice_id,
m.nome AS matrice,
m.photo AS matrice_photo,
(
SELECT GROUP_CONCAT(mes.nome ORDER BY mes.nome SEPARATOR ' | ')
FROM productiondata_mescole pm
JOIN mescole mes ON mes.id = pm.id_mescola
WHERE pm.id_productiondata = p.id
) AS mescole_list,
l.name AS linea,
c.nome AS cliente,
s.nome AS status_nome,
s.badge_color,
s.line_color,
p.tempo_totale_produzione,
-- 🔧 ADD: tools list & count
(
SELECT GROUP_CONCAT(t.name ORDER BY t.name SEPARATOR ' | ')
FROM productiondata_tools pt
JOIN production_tools t ON t.id = pt.tool_id
WHERE pt.productiondata_id = p.id
) AS tools_list,
(
SELECT COUNT(*)
FROM productiondata_tools pt
WHERE pt.productiondata_id = p.id
) AS tools_count
FROM productiondata p
LEFT JOIN matrice m ON p.idmatrice = m.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;
/* top right bottom left */
padding: 1rem 1.5rem 5rem 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;
}
.custom-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 */
.custom-modal.active {
display: flex !important;
}
.custom-modal .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 */
.custom-modal .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;
}
/* Mantiene stile identico ai bottoni delle foto */
/* Stesso stile dei photo-btn */
.qc-btn {
width: 42px;
height: 42px;
border-radius: 10px;
border: 1px solid #000000;
/* bordo nero */
background: #c8f3df;
/* verde pastello interno */
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: 0.2s ease;
}
.qc-btn i {
font-size: 1.8rem;
color: #000000;
/* icona nera */
}
.qc-btn:hover {
background: #b3e9d3;
/* leggero scurimento */
transform: scale(1.07);
}
.param-grid {
display: grid;
gap: 10px;
margin-bottom: 15px;
}
@media (max-width: 900px) {
.param-grid {
grid-template-columns: repeat(3, 1fr);
}
}
.param-slot {
background: #ffffff;
border-radius: 12px;
padding: 10px;
text-align: center;
border: 2px solid #e2e8f0;
cursor: pointer;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.08);
transition: 0.2s ease;
}
.param-slot:hover {
border-color: #3b82f6;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
.thumb {
width: 100%;
height: 75px;
background: #f1f5f9;
border-radius: 8px;
overflow: hidden;
}
.thumb-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.param-label {
margin-top: 6px;
font-weight: 700;
font-size: 1rem;
}
.photo-tools-row .qc-right {
margin-left: auto;
/* la porta tutta a destra */
}
.show-matrice-files i {
font-size: 1.5rem;
}
.modal-backdrop.show {
z-index: 1050 !important;
}
.modal.show {
z-index: 1055 !important;
}
.modal-worksheet-list {
max-width: 1320px;
width: 97vw;
}
.modal-worksheet-view {
max-width: 1500px;
width: 97vw;
}
.worksheet-badge-fl {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 68px;
padding: 6px 12px;
border-radius: 999px;
background: #e8f7ee;
color: #198754;
font-weight: 800;
font-size: .85rem;
}
.worksheet-badge-rev {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 54px;
padding: 6px 10px;
border-radius: 999px;
background: #e7f1ff;
color: #0d6efd;
font-weight: 700;
font-size: .84rem;
}
.worksheet-badge-status-active {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 88px;
padding: 6px 10px;
border-radius: 999px;
background: #d1e7dd;
color: #0f5132;
font-weight: 700;
font-size: .84rem;
}
.worksheet-badge-status-inactive {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 88px;
padding: 6px 10px;
border-radius: 999px;
background: #f8d7da;
color: #842029;
font-weight: 700;
font-size: .84rem;
}
.worksheet-list-table th {
background: #e7f1ff !important;
color: #0b3d91;
}
.worksheet-chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: #e7f1ff;
color: #0d6efd;
font-weight: 700;
font-size: .82rem;
}
.readonly-card {
border: 1px solid #e9ecef;
border-radius: 16px;
background: #fff;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, .04);
height: 100%;
}
.readonly-card-header {
background: linear-gradient(135deg, #e7f1ff, #d6e9ff);
color: #0b3d91;
font-weight: 800;
padding: 12px 16px;
border-bottom: 1px solid #cfe2ff;
}
.readonly-card-body {
padding: 16px;
text-align: left;
}
.readonly-grid {
display: grid;
grid-template-columns: 220px 1fr;
gap: 8px 16px;
}
.readonly-label {
font-weight: 700;
color: #495057;
}
.readonly-value {
color: #1f2d3d;
word-break: break-word;
}
.worksheet-open-link {
background: #fff;
border: 1px solid #b6d4fe;
color: #0d6efd;
font-weight: 700;
border-radius: 10px;
padding: 9px 14px;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.worksheet-open-link:hover {
color: #0a58ca;
background: #f4f9ff;
}
.worksheet-title-box {
background: linear-gradient(135deg, #0d6efd, #3b82f6);
color: #fff;
border-radius: 18px;
padding: 18px 20px;
box-shadow: 0 8px 24px rgba(13, 110, 253, 0.2);
}
.worksheet-title-box small {
color: rgba(255, 255, 255, .9);
}
.mix-readonly-table thead th {
background: #dbeafe !important;
color: #0b3d91;
}
.btn-view-worksheet {
background: linear-gradient(135deg, #0d6efd, #3b82f6);
color: #fff;
border: 0;
border-radius: 8px;
padding: 8px 14px;
font-weight: 700;
}
.btn-view-worksheet:hover {
color: #fff;
opacity: .95;
}
</style>
</head>
<body>
<div class="wrapper toggled">
<?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="custom-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="custom-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>Scarto (%):</strong></label>
<input type="text" id="scartoPerc" class="input-big" readonly>
<label><strong>Note operatore:</strong></label>
<textarea id="finalNote" rows="3" class="input-big"></textarea>
</div>
</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>
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, slot = null) {
$("#photoGallery").html('<div style="color:#64748b;">Caricamento foto...</div>');
$.getJSON("get_photos.php", {
production_id: productionId,
photo_type: type,
param_position: slot
}, 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 per questa posizione.</div>'
);
return;
}
let html = "";
photos.forEach(p => {
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;">#${p.id}</div>
</div>
`;
});
$("#photoGallery").html(html);
});
}
// 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 - DELEGATO
$(document).on("click", "#previewCloseX", function(e) {
e.preventDefault();
$("#imagePreviewModal").removeClass("active");
});
// opzionale: chiudi cliccando fuori dal contenuto (sulloverlay scuro)
$(document).on("click", "#imagePreviewModal", function(e) {
if ($(e.target).is("#imagePreviewModal")) {
$("#imagePreviewModal").removeClass("active");
}
});
// chiusura modale principale (upload foto)
$("#photoModalCloseX").on("click", function() {
$("#photoModal").removeClass("active");
});
</script>
<?php include('include/worksheets-linked-modals.php'); ?>
<script src="assets/js/worksheets-linked-modals.js"></script>
<?php include('jsinclude.php'); ?>
<script>
$(function() {
$('#filterLine').on('change', loadRecords);
const beep = $('#beepSound')[0];
let timers = {};
// --- FUNZIONE PER RIDIMENSIONARE LE IMMAGINI PRIMA DELL'UPLOAD ---
async function resizeImage(file, maxSize, quality = 0.7) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = event => {
const img = new Image();
img.src = event.target.result;
img.onload = () => {
let width = img.width;
let height = img.height;
// scala mantenendo proporzioni
if (width > height && width > maxSize) {
height = height * (maxSize / width);
width = maxSize;
} else if (height > maxSize) {
width = width * (maxSize / height);
height = maxSize;
}
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob(
blob => resolve(blob),
"image/jpeg",
quality
);
};
};
reader.onerror = error => reject(error);
});
}
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();
});
function escapeHtml(str) {
return String(str || '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
function getFileExt(filename) {
const parts = String(filename || '').split('.');
return parts.length > 1 ? parts.pop().toLowerCase() : '';
}
function isImageExt(ext) {
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic', 'heif'].includes(ext);
}
function getFileIcon(ext) {
if (isImageExt(ext)) return '🖼️';
if (ext === 'pdf') return '📕';
if (['doc', 'docx'].includes(ext)) return '📘';
return '📎';
}
function getFileBadge(ext) {
if (isImageExt(ext)) return '<span style="background:#cff4fc;color:#055160;padding:3px 8px;border-radius:999px;font-size:0.78rem;font-weight:700;">Immagine</span>';
if (ext === 'pdf') return '<span style="background:#f8d7da;color:#842029;padding:3px 8px;border-radius:999px;font-size:0.78rem;font-weight:700;">PDF</span>';
if (['doc', 'docx'].includes(ext)) return '<span style="background:#cfe2ff;color:#084298;padding:3px 8px;border-radius:999px;font-size:0.78rem;font-weight:700;">DOC</span>';
return '<span style="background:#e2e3e5;color:#41464b;padding:3px 8px;border-radius:999px;font-size:0.78rem;font-weight:700;">File</span>';
}
function openFilePreview(filePath, fileName) {
const ext = getFileExt(fileName);
$("#filePreviewTitle").text(fileName || 'Anteprima file');
let html = '';
if (isImageExt(ext)) {
html = `
<div style="width:100%; height:100%; min-height:82vh; display:flex; justify-content:center; align-items:center;">
<img src="${escapeHtml(filePath)}"
alt="${escapeHtml(fileName)}"
style="max-width:100%; max-height:82vh; border-radius:10px;">
</div>
`;
} else if (ext === 'pdf') {
html = `
<iframe src="${escapeHtml(filePath)}#zoom=page-width"
style="display:block; width:100%; height:calc(92vh - 64px); border:none; background:#fff;">
</iframe>
`;
} else {
html = `
<div style="text-align:center; padding:40px 20px;">
<div style="font-size:3rem; margin-bottom:12px;">${getFileIcon(ext)}</div>
<div style="color:#475569; margin-bottom:16px;">Anteprima non disponibile per questo formato</div>
<a href="${escapeHtml(filePath)}" target="_blank"
style="display:inline-block; padding:10px 18px; border-radius:10px; text-decoration:none; background:#0d6efd; color:#fff; font-weight:600;">
Apri file
</a>
</div>
`;
}
$("#filePreviewContainer").html(html);
$("#filePreviewModal").addClass("active");
}
function loadMatriceAttachments(matriceId, matriceNome) {
$("#mf_matrice_name").text(matriceNome || '');
$("#matriceFilesList").html('<div style="color:#64748b;">Caricamento file...</div>');
$.getJSON("get_matrice_attachments.php", {
id: matriceId
}, function(r) {
if (!r.success) {
$("#matriceFilesList").html('<div style="color:#b91c1c;">Errore nel caricamento degli allegati.</div>');
return;
}
const files = r.attachments || [];
if (!files.length) {
$("#matriceFilesList").html('<div style="color:#64748b;">Nessun allegato disponibile per questa matrice.</div>');
return;
}
let html = '';
files.forEach(f => {
const fileName = f.file_name || '';
const filePath = f.file_url || f.file_path || '';
const desc = f.description || '';
const createdAt = f.created_at || '';
const ext = getFileExt(fileName);
const icon = getFileIcon(ext);
const badge = getFileBadge(ext);
let actionHtml = '';
if (isImageExt(ext) || ext === 'pdf') {
actionHtml = `
<button type="button"
class="open-matrice-file-preview"
data-file="${escapeHtml(filePath)}"
data-name="${escapeHtml(fileName)}"
style="padding:8px 14px; border-radius:10px; border:none; background:#0d6efd; color:#fff; font-weight:600; cursor:pointer;">
Apri
</button>
`;
} else {
actionHtml = `
<a href="${escapeHtml(filePath)}" target="_blank"
style="display:inline-block; padding:8px 14px; border-radius:10px; text-decoration:none; background:#0d6efd; color:#fff; font-weight:600;">
Apri
</a>
`;
}
let previewHtml = '';
if (isImageExt(ext)) {
previewHtml = `
<img src="${escapeHtml(filePath)}"
alt="${escapeHtml(fileName)}"
style="width:70px; height:70px; object-fit:cover; border-radius:10px; border:1px solid #dbe2ea;">
`;
} else {
previewHtml = `
<div style="width:70px; height:70px; border-radius:10px; background:#f1f5f9; display:flex; align-items:center; justify-content:center; font-size:1.8rem; border:1px solid #dbe2ea;">
${icon}
</div>
`;
}
html += `
<div style="border:1px solid #e2e8f0; border-radius:14px; padding:12px; background:#fff;">
<div style="display:flex; gap:14px; align-items:flex-start;">
<div>${previewHtml}</div>
<div style="flex:1;">
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:12px;">
<div>
<div style="font-weight:700; color:#1f2d3d;">${escapeHtml(fileName)}</div>
<div style="margin-top:4px;">${badge}</div>
${createdAt ? `<div style="margin-top:6px; color:#64748b; font-size:0.88rem;">${escapeHtml(createdAt)}</div>` : ''}
</div>
<div>${actionHtml}</div>
</div>
<div style="margin-top:10px; color:#475569; white-space:normal;">
${desc ? escapeHtml(desc) : '<em style="color:#94a3b8;">Nessuna descrizione</em>'}
</div>
</div>
</div>
</div>
`;
});
$("#matriceFilesList").html(html);
}).fail(function() {
$("#matriceFilesList").html('<div style="color:#b91c1c;">Errore di comunicazione col server.</div>');
});
}
$(document).on("click", ".show-matrice-files", function(e) {
e.stopPropagation();
const matriceId = $(this).data("matrice-id");
const matriceNome = $(this).data("matrice-nome") || "";
if (!matriceId) {
alert("Nessuna matrice associata a questo record.");
return;
}
loadMatriceAttachments(matriceId, matriceNome);
$("#matriceFilesModal").addClass("active");
});
$(document).on("click", "#closeMatriceFilesModal", function() {
$("#matriceFilesModal").removeClass("active");
});
$(document).on("click", "#matriceFilesModal", function(e) {
if ($(e.target).is("#matriceFilesModal")) {
$("#matriceFilesModal").removeClass("active");
}
});
$(document).on("click", ".open-matrice-file-preview", function(e) {
e.stopPropagation();
const file = $(this).data("file") || "";
const name = $(this).data("name") || "Anteprima file";
openFilePreview(file, name);
});
$(document).on("click", "#closeFilePreviewModal", function() {
$("#filePreviewModal").removeClass("active");
$("#filePreviewContainer").html("");
});
$(document).on("click", "#filePreviewModal", function(e) {
if ($(e.target).is("#filePreviewModal")) {
$("#filePreviewModal").removeClass("active");
$("#filePreviewContainer").html("");
}
});
$(document).on("click", "#filePreviewModal .modal-content", function(e) {
e.stopPropagation();
});
// --- 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();
// 🔧 Sposta l'icona qualità all'estrema destra della riga
$('.photo-tools-row').each(function() {
const $row = $(this);
const $qc = $row.find('.qc-btn.quality-btn').first();
if ($qc.length) {
$qc.detach();
$qc.appendTo($row);
$qc.addClass('qc-right');
}
});
$(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");
});
// FOTO PARAMETRI MACCHINA (7 posizioni)
$(document).on("click", ".param-slot", function(e) {
e.stopPropagation();
const slot = $(this).data("slot");
const productionId = $(this).data("production");
$("#photoType").val("parametri_macchina");
$("#photoProductionId").val(productionId);
$("#photoParamPosition").val(slot);
$("#photoModalTitle").text("Foto Parametro P" + slot);
$("#photoModalSubtitle").text("Produzione ID: " + productionId);
loadPhotoGallery(productionId, "parametri_macchina", slot);
$("#photoModal").addClass("active");
});
$("#photoCancel").off("click").on("click", () => {
$("#photoModal").removeClass("active");
// 🔥 ricarica le card (e quindi le thumbnail) via AJAX
loadRecords();
});
$("#photoModalCloseX").off("click").on("click", function() {
$("#photoModal").removeClass("active");
// 🔥 stessa logica: al close ricarico le card
loadRecords();
});
// --- SUBMIT FORM FOTO (con resize + upload) ---
$("#photoForm").off("submit").on("submit", async function(e) {
e.preventDefault();
const fileInput = document.getElementById("photoInput");
const file = fileInput.files[0];
if (!file) {
showPhotoError("Seleziona una foto prima di caricare.");
return;
}
// mostra loader, disabilita submit
$("#photoMessageSuccess, #photoMessageError").hide();
$("#photoLoading").show();
$("#photoForm .modal-confirm").prop("disabled", true);
try {
// ridimensiona lato lungo max 1600 px, qualità 0.7
const resizedBlob = await resizeImage(file, 1600, 0.7);
const formData = new FormData();
formData.append("production_id", $("#photoProductionId").val());
formData.append("photo_type", $("#photoType").val());
formData.append("param_position", $("#photoParamPosition").val());
formData.append("photo", resizedBlob, file.name);
$.ajax({
url: "upload_photo.php",
type: "POST",
data: formData,
processData: false,
contentType: false,
dataType: "json",
success: function(r) {
$("#photoLoading").hide();
$("#photoForm .modal-confirm").prop("disabled", false);
if (r.success) {
showPhotoSuccess();
loadPhotoGallery(
$("#photoProductionId").val(),
$("#photoType").val(),
$("#photoParamPosition").val() || null
);
} else {
showPhotoError(r.message || "Errore durante il caricamento della foto.");
}
},
error: function() {
$("#photoLoading").hide();
$("#photoForm .modal-confirm").prop("disabled", false);
showPhotoError("Errore di comunicazione con il server.");
}
});
} catch (err) {
console.error(err);
$("#photoLoading").hide();
$("#photoForm .modal-confirm").prop("disabled", false);
showPhotoError("Errore durante l'elaborazione dell'immagine.");
}
});
$('.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').removeClass('active');
});
}
// --- MODALE DATI FINE PRODUZIONE ---
function openFinalDataModal(id, reason, note) {
$('#finalDataModal').addClass('active');
// Pulisci i campi reali
$('#kgprod, #mtprod, #scarto').val('');
$('#finalNote').val(note || '');
$('#kgprod, #scarto').off('input').on('input', recalcScrapPerc);
$('#scartoPerc').val('');
// Recupera i dati teorici del record
const card = $(`.record-card[data-id="${id}"]`);
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');
});
}
function recalcScrapPerc() {
const kg = parseFloat($('#kgprod').val()) || 0;
const sc = parseFloat($('#scarto').val()) || 0;
const tot = kg + sc;
let perc = 0;
if (tot > 0) {
perc = (sc / tot) * 100;
}
$('#scartoPerc').val(perc.toFixed(2) + ' %');
}
// --- START PRODUZIONE ---
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();
});
// --- MODALE MESCOLE ---
$(document).on("click", ".showMescole", function(e) {
e.stopPropagation();
// prendo la stringa grezza dallattributo, NON .data()
const raw = $(this).attr("data-list") || "[]";
let list = [];
try {
list = JSON.parse(raw);
} catch (err) {
console.error("Errore parsing mescole:", err, raw);
list = [];
}
let html = "";
list.forEach(m => {
html += `<li style="margin:6px 0;font-size:1.1rem;">${m}</li>`;
});
$("#mescoleList").html(html || '<li style="margin:6px 0;">Nessuna mescola</li>');
$("#mescoleModal").addClass("active");
});
$(document).on("click", "#closeMescole", function() {
$("#mescoleModal").removeClass("active");
});
</script>
<!-- MODALE PER PREVIEW IMMAGINE -->
<div id="imagePreviewModal" class="custom-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>
<!-- MODALE MESCOLE -->
<div id="mescoleModal" class="custom-modal">
<div class="modal-content" style="max-width:400px;">
<h3>Mescole utilizzate</h3>
<ul id="mescoleList" style="text-align:left; padding-left:1rem;"></ul>
<div class="modal-buttons">
<button class="modal-btn modal-cancel" id="closeMescole">Chiudi</button>
</div>
</div>
</div>
<script>
// apre il file picker sul campo interno al form
$(document).on("click", "#choosePhotoBtn", function(e) {
e.stopPropagation();
$("#photoInput").click();
});
// mostra nome file scelto E fa partire subito l'upload
$(document).on("change", "#photoInput", function() {
const file = this.files[0];
if (!file) return;
$("#selectedPhotoName").text(file.name);
// 🔥 auto-submit → parte resize + upload
$("#photoForm").submit();
});
</script>
<!-- MODALE ALLEGATI MATRICE (SOLO LETTURA) -->
<div id="matriceFilesModal" class="custom-modal">
<div class="modal-content" style="width:95%; max-width:1100px; max-height:88vh; overflow:hidden; padding:0; border-radius:16px;">
<div style="background:#b9ebc7; padding:1rem 1.4rem; display:flex; justify-content:space-between; align-items:center;">
<div>
<h3 style="margin:0; color:#1f2d3d;">File correlati matrice</h3>
<div id="mf_matrice_name" style="font-size:0.95rem; color:#475569; margin-top:4px;"></div>
</div>
<button type="button" id="closeMatriceFilesModal"
style="font-size:1.9rem; line-height:1; background:none; border:none; cursor:pointer; color:#1f2d3d;">
&times;
</button>
</div>
<div style="padding:1.2rem 1.4rem; overflow-y:auto;">
<div id="matriceFilesList" style="display:flex; flex-direction:column; gap:12px;">
<div style="color:#64748b;">Caricamento file...</div>
</div>
</div>
</div>
</div>
<!-- MODALE PREVIEW FILE -->
<div id="filePreviewModal" class="custom-modal">
<div class="modal-content" style="background:#fff; max-width:1400px; width:98%; height:92vh; padding:0; overflow:hidden; text-align:left;">
<div style="background:#b9ebc7; padding:0.9rem 1.2rem; display:flex; justify-content:space-between; align-items:center;">
<div id="filePreviewTitle" style="font-weight:700; color:#1f2d3d;">Anteprima file</div>
<button type="button" id="closeFilePreviewModal"
style="font-size:1.9rem; line-height:1; background:none; border:none; cursor:pointer; color:#1f2d3d;">
&times;
</button>
</div>
<div id="filePreviewContainer"
style="padding:0; background:#f8fafc; height:calc(92vh - 64px); overflow:hidden;">
</div>
</div>
</div>
</body>
</html>