Files
zibo-dashboard/public/userarea/production_line_view2.php
T
2026-09-07 15:02:36 +02:00

2646 lines
93 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();
// --- DATI PER MODALE "NUOVO" (riga libera) ---
$allMatrici = $pdo->query("SELECT id, nome FROM matrice ORDER BY nome")->fetchAll();
$allMescole = $pdo->query("SELECT id, nome FROM mescole WHERE is_active = 1 ORDER BY nome")->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) {
// Chiudo l'ultima pausa aperta di questa produzione
$sqlClose = "UPDATE production_pauses
SET end_pause = UTC_TIMESTAMP()
WHERE id_production = :id AND end_pause IS NULL
ORDER BY start_pause DESC
LIMIT 1";
$stmtClose = $pdo->prepare($sqlClose);
$stmtClose->execute(['id' => $id]);
$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 {
$pdo->beginTransaction();
// Se sto stoppando mentre è in pausa, chiudo la pausa aperta
$sqlClose = "UPDATE production_pauses
SET end_pause = UTC_TIMESTAMP()
WHERE id_production = :id AND end_pause IS NULL";
$pdo->prepare($sqlClose)->execute(['id' => $id]);
// Leggo dati produzione
$sql = "SELECT start_time, id_status FROM productiondata WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute(['id' => $id]);
$row = $stmt->fetch();
// Tempo di occupazione linea (LORDO, pause incluse): start -> ora
$occupatoSec = 0;
if ($row && $row['start_time']) {
$start = new DateTime($row['start_time'], new DateTimeZone('UTC'));
$now = new DateTime('now', new DateTimeZone('UTC'));
$occupatoSec = max(0, $now->getTimestamp() - $start->getTimestamp());
}
// Somma totale delle pause di questa produzione (in secondi)
$sqlPause = "SELECT COALESCE(SUM(TIMESTAMPDIFF(SECOND, start_pause, end_pause)), 0)
FROM production_pauses
WHERE id_production = :id AND end_pause IS NOT NULL";
$stmtP = $pdo->prepare($sqlPause);
$stmtP->execute(['id' => $id]);
$pauseSec = (int)$stmtP->fetchColumn();
// 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,
tempo_totale_produzione = :occupato,
seconds_pause = :pause,
seconds_produzione = :netto
WHERE id = :id AND id_status IN ($statusProduzioneId, $statusPausaId)";
$stmt = $pdo->prepare($sql);
$stmt->execute([
'status' => $nextStatus,
'note' => $note ?: null,
'occupato' => $occupatoSec,
'pause' => $pauseSec,
'netto' => max(0, $occupatoSec - $pauseSec),
'id' => $id,
]);
$pdo->commit();
echo json_encode(['success' => true]);
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
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 {
$pdo->beginTransaction();
// Chiudo eventuali pause ancora aperte
$pdo->prepare("UPDATE production_pauses SET end_pause = UTC_TIMESTAMP()
WHERE id_production = :id AND end_pause IS NULL")
->execute(['id' => $id]);
// Leggo start_time per calcolare l'occupazione
$stmt = $pdo->prepare("SELECT start_time FROM productiondata WHERE id = :id");
$stmt->execute(['id' => $id]);
$startTime = $stmt->fetchColumn();
// Occupazione LORDA (start -> ora), pause incluse
$occupatoSec = 0;
if ($startTime) {
$start = new DateTime($startTime, new DateTimeZone('UTC'));
$now = new DateTime('now', new DateTimeZone('UTC'));
$occupatoSec = max(0, $now->getTimestamp() - $start->getTimestamp());
}
// Somma pause chiuse
$stmtP = $pdo->prepare("SELECT COALESCE(SUM(TIMESTAMPDIFF(SECOND, start_pause, end_pause)), 0)
FROM production_pauses
WHERE id_production = :id AND end_pause IS NOT NULL");
$stmtP->execute(['id' => $id]);
$pauseSec = (int)$stmtP->fetchColumn();
$seconds = $occupatoSec; // hourprod = tempo di occupazione (come da tua logica)
// 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),
tempo_totale_produzione = :occupato,
seconds_pause = :pause,
seconds_produzione = :netto,
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,
'occupato' => $occupatoSec,
'pause' => $pauseSec,
'netto' => max(0, $occupatoSec - $pauseSec),
'status' => $nextStatus,
'id' => $id
]);
$pdo->commit();
echo json_encode(['success' => true]);
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
echo json_encode(['success' => false, 'msg' => $e->getMessage()]);
}
exit;
}
// --- CREAZIONE RIGA LIBERA (Nuovo) + avvio immediato ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['create_quick'])) {
$idMatrice = (int)($_POST['id_matrice'] ?? 0);
$idLinea = (int)($_POST['id_linea'] ?? 0);
$mescRaw = $_POST['id_mescole'] ?? '';
$mescIds = $mescRaw !== '' ? array_values(array_unique(array_filter(array_map('intval', explode(',', $mescRaw))))) : [];
$descr = trim($_POST['descrizione'] ?? '');
try {
if ($idMatrice <= 0) {
echo json_encode(['success' => false, 'msg' => 'Seleziona una matrice.']);
exit;
}
if ($idLinea <= 0) {
echo json_encode(['success' => false, 'msg' => 'Seleziona una linea.']);
exit;
}
$avvia = !empty($_POST['avvia']);
$pdo->beginTransaction();
// Priorità: in fondo alla coda della linea (max+1)
$stmtPri = $pdo->prepare("SELECT COALESCE(MAX(priority),0)+1
FROM productiondata
WHERE id_linea = :linea AND id_status = :programmato");
$stmtPri->execute(['linea' => $idLinea, 'programmato' => $statusProgrammatoId]);
$nextPriority = (int)$stmtPri->fetchColumn();
// Se "avvia": nasce in PRODUZIONE (2) con start_time.
// Altrimenti: PROGRAMMATO (6), pronto per essere avviato dopo.
$statusIns = $avvia ? $statusProduzioneId : $statusProgrammatoId;
$startExpr = $avvia ? 'UTC_TIMESTAMP()' : 'NULL';
$sqlIns = "INSERT INTO productiondata
(Data, idmatrice, id_linea, note_extra, priority,
id_status, start_time, end_time,
fermomacchina, hour, hourprod, kgprod, mtprod, scarto)
VALUES
(CURDATE(), :idmatrice, :id_linea, :note_extra, :priority,
:status, $startExpr, NULL,
0, '00:00:00', '00:00:00', 0, 0, 0)";
$stmtIns = $pdo->prepare($sqlIns);
$stmtIns->execute([
'idmatrice' => $idMatrice,
'id_linea' => $idLinea,
'note_extra' => $descr ?: null,
'priority' => $nextPriority,
'status' => $statusIns,
]);
$newId = (int)$pdo->lastInsertId();
if (!empty($mescIds)) {
$stmtM = $pdo->prepare("INSERT INTO productiondata_mescole (id_productiondata, id_mescola) VALUES (:pid, :mid)");
foreach ($mescIds as $mid) {
if ($mid > 0) {
$stmtM->execute(['pid' => $newId, 'mid' => $mid]);
}
}
}
$pdo->commit();
echo json_encode(['success' => true, 'id' => $newId]);
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
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) : [];
// Linee già occupate da una produzione attiva (in produzione=2 o in pausa=7).
// Usato per bloccare l'avvio di una seconda produzione sulla stessa linea.
$lineeOccupate = [];
$stmtOcc = $pdo->query("SELECT DISTINCT id_linea FROM productiondata WHERE id_status IN (2, 7)");
foreach ($stmtOcc->fetchAll(PDO::FETCH_COLUMN) as $lid) {
$lineeOccupate[(int)$lid] = true;
}
// --- 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 inv_equipment 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 inv_equipment 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">
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet">
<style>
/* Select2 dentro il modale + z-index sopra l'overlay */
.select2-container {
width: 100% !important;
}
.select2-container--default .select2-selection--single {
height: auto;
padding: 0.6rem 0.6rem;
border: 1px solid #cbd5e1;
border-radius: 0.6rem;
font-size: 1.15rem;
}
.select2-container--default .select2-selection--single .select2-selection__arrow {
height: 100%;
}
.select2-dropdown {
z-index: 2000;
}
.select2-container--open {
z-index: 2000;
}
/* Select2 MULTIPLO: chip grandi e comodi per tablet */
.select2-container--default .select2-selection--multiple {
min-height: 3.2rem;
padding: 0.3rem 0.5rem;
border: 1px solid #cbd5e1;
border-radius: 0.6rem;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice {
font-size: 1.05rem;
padding: 0.35rem 0.7rem;
border-radius: 0.5rem;
margin-top: 0.35rem;
}
.select2-container--default .select2-results__option {
font-size: 1.1rem;
padding: 0.7rem 0.9rem;
}
</style>
<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" id="appWrapper">
<?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>
<div style="display:flex; gap:0.8rem; align-items:center;">
<button class="back-btn" id="btnNuovoQuick"
style="background:rgba(16,185,129,0.9); border-color:rgba(16,185,129,1);">
+ Nuovo
</button>
<button class="back-btn" onclick="location.href='production_dashboard.php'">Torna</button>
</div>
</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>
<!-- MODALE NUOVO -->
<div id="quickCreateModal" class="custom-modal">
<div class="modal-content" style="max-width:900px; width:92%; text-align:left;">
<h3 style="text-align:center;">Nuovo avvio rapido</h3>
<label style="font-weight:600; display:block; margin-top:1rem;">Descrizione</label>
<input type="text" id="qcDescrizione" class="input-big" placeholder="Descrizione...">
<div style="display:flex; gap:1.2rem; flex-wrap:wrap;">
<div style="flex:1; min-width:260px;">
<label style="font-weight:600; display:block;">Matrice *</label>
<select id="qcMatrice" class="input-big">
<option value="">Seleziona matrice</option>
<?php foreach ($allMatrici as $mx): ?>
<option value="<?= (int)$mx['id'] ?>"><?= htmlspecialchars($mx['nome']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div style="flex:1; min-width:260px;">
<label style="font-weight:600; display:block;">Linea *</label>
<select id="qcLinea" class="input-big">
<option value="">Seleziona linea</option>
<?php foreach ($linee as $l): ?>
<option value="<?= (int)$l['id'] ?>"><?= htmlspecialchars($l['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<label style="font-weight:600; display:block;">Mescole (una o più, opzionale)</label>
<select id="qcMescola" class="input-big" multiple>
<?php foreach ($allMescole as $me): ?>
<option value="<?= (int)$me['id'] ?>"><?= htmlspecialchars($me['nome']) ?></option>
<?php endforeach; ?>
</select>
<div class="modal-buttons" style="flex-wrap:wrap;">
<button class="modal-btn modal-cancel" id="qcCancel">Annulla</button>
<button class="modal-btn modal-confirm" id="qcCreateQueue"
style="background:#3b82f6;">Crea in coda</button>
<button class="modal-btn modal-confirm" id="qcCreateStart">Crea e avvia</button>
</div>
</div>
</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 src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.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 (sull’overlay 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 L’HTML 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':
// Sia "fine" che "problema" aprono il modale dati finali.
// Il reason viene passato e conservato per il salvataggio.
openFinalDataModal(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}`);
}
// --- NUOVO AVVIO RAPIDO ---
function initQuickSelects() {
$('#qcMatrice').select2({
dropdownParent: $('#quickCreateModal .modal-content'),
placeholder: 'Cerca matrice...',
allowClear: true,
width: '100%'
});
$('#qcMescola').select2({
dropdownParent: $('#quickCreateModal .modal-content'),
placeholder: 'Cerca e aggiungi mescole...',
allowClear: true,
width: '100%',
closeOnSelect: false
});
}
function destroyQuickSelects() {
if ($('#qcMatrice').data('select2')) $('#qcMatrice').select2('destroy');
if ($('#qcMescola').data('select2')) $('#qcMescola').select2('destroy');
}
function closeQuickModal() {
destroyQuickSelects();
$('#quickCreateModal').removeClass('active');
}
$('#btnNuovoQuick').on('click', function() {
$('#qcDescrizione').val('');
$('#qcMatrice').val('');
$('#qcMescola').val(null);
$('#qcLinea').val('');
$('#quickCreateModal').addClass('active');
initQuickSelects();
});
$('#qcCancel').on('click', closeQuickModal);
$(document).on('click', '#quickCreateModal', function(e) {
if ($(e.target).is('#quickCreateModal')) closeQuickModal();
});
function submitQuickCreate(avvia) {
const idMatrice = $('#qcMatrice').val();
const idLinea = $('#qcLinea').val();
const mescoleArr = $('#qcMescola').val() || []; // array di id
const descr = $('#qcDescrizione').val().trim();
if (!idMatrice) {
alert('Seleziona una matrice.');
return;
}
if (!idLinea) {
alert('Seleziona una linea.');
return;
}
$('#qcCreateQueue, #qcCreateStart').prop('disabled', true);
$.post('', {
create_quick: 1,
avvia: avvia ? 1 : 0,
id_matrice: idMatrice,
id_linea: idLinea,
id_mescole: mescoleArr.join(','), // CSV di id
descrizione: descr
}, function(r) {
$('#qcCreateQueue, #qcCreateStart').prop('disabled', false);
if (r.success) {
closeQuickModal();
playBeep();
loadRecords();
} else {
alert(r.msg || 'Errore durante la creazione.');
}
}, 'json').fail(function() {
$('#qcCreateQueue, #qcCreateStart').prop('disabled', false);
alert('Errore di comunicazione con il server.');
});
}
$('#qcCreateQueue').on('click', function() {
submitQuickCreate(false);
});
$('#qcCreateStart').on('click', function() {
submitQuickCreate(true);
});
loadRecords();
});
// --- MODALE MESCOLE ---
$(document).on("click", ".showMescole", function(e) {
e.stopPropagation();
// prendo la stringa grezza dall’attributo, 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>