2026-03-20 22:09:39 +01:00

2151 lines
92 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
ob_start();
ini_set('display_errors', 1);
error_reporting(E_ALL);
include('include/headscript.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
/**
* Helpers
*/
function h($v)
{
return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
}
function formatDateIT($d)
{
if (!$d || $d === '0000-00-00') return '';
return date("d/m/Y", strtotime($d));
}
function formatDateTimeIT($d)
{
if (!$d || $d === '0000-00-00 00:00:00') return '';
return date("d/m/Y H:i", strtotime($d));
}
function formatNullable($v, $fallback = '—')
{
$v = trim((string)$v);
return $v !== '' ? $v : $fallback;
}
/**
* AJAX HANDLERS
*/
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['ajax']) && $_POST['ajax'] == '1') {
while (ob_get_level()) {
ob_end_clean();
}
header('Content-Type: application/json; charset=utf-8');
$action = $_POST['action'] ?? '';
try {
if ($action === 'get_matrice_worksheets') {
$idmatrice = isset($_POST['idmatrice']) ? (int)$_POST['idmatrice'] : 0;
if ($idmatrice <= 0) {
echo json_encode([
'success' => false,
'message' => 'ID matrice non valido'
]);
exit;
}
$stmt = $pdo->prepare("
SELECT
ws.id,
ws.idmatrice,
ws.worksheet_date,
ws.customer_name,
ws.profile_type_code,
ws.marking,
ws.approved_by,
ws.created_at,
ws.updated_at,
(
SELECT COUNT(*)
FROM work_sheet_mescole wsm
WHERE wsm.worksheet_id = ws.id
) AS mix_count
FROM work_sheets ws
WHERE ws.idmatrice = ?
ORDER BY
CASE WHEN ws.worksheet_date IS NULL THEN 1 ELSE 0 END,
ws.worksheet_date DESC,
ws.id DESC
");
$stmt->execute([$idmatrice]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$data = [];
foreach ($rows as $r) {
$data[] = [
'id' => (int)$r['id'],
'idmatrice' => (int)$r['idmatrice'],
'worksheet_date' => $r['worksheet_date'],
'worksheet_date_it' => formatDateIT($r['worksheet_date']),
'customer_name' => $r['customer_name'] ?? '',
'profile_type_code' => $r['profile_type_code'] ?? '',
'marking' => $r['marking'] ?? '',
'approved_by' => $r['approved_by'] ?? '',
'created_at' => $r['created_at'] ?? '',
'created_at_it' => formatDateTimeIT($r['created_at'] ?? ''),
'updated_at' => $r['updated_at'] ?? '',
'updated_at_it' => formatDateTimeIT($r['updated_at'] ?? ''),
'mix_count' => (int)($r['mix_count'] ?? 0)
];
}
echo json_encode([
'success' => true,
'worksheets' => $data
]);
exit;
}
if ($action === 'get_worksheet_detail') {
$worksheetId = isset($_POST['worksheet_id']) ? (int)$_POST['worksheet_id'] : 0;
if ($worksheetId <= 0) {
echo json_encode([
'success' => false,
'message' => 'ID foglio non valido'
]);
exit;
}
$stmt = $pdo->prepare("
SELECT
ws.*,
m.nome AS matrice_nome,
m.cliente AS matrice_cliente
FROM work_sheets ws
LEFT JOIN matrice m ON m.id = ws.idmatrice
WHERE ws.id = ?
LIMIT 1
");
$stmt->execute([$worksheetId]);
$ws = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$ws) {
echo json_encode([
'success' => false,
'message' => 'Foglio di lavoro non trovato'
]);
exit;
}
$stmtMix = $pdo->prepare("
SELECT
wsm.*,
me.nome AS mescola_nome,
me.nomeuscita AS mescola_uscita
FROM work_sheet_mescole wsm
LEFT JOIN mescole me ON me.id = wsm.idmescola
WHERE wsm.worksheet_id = ?
ORDER BY wsm.mix_position ASC, wsm.id ASC
");
$stmtMix->execute([$worksheetId]);
$mixRows = $stmtMix->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'worksheet' => [
'id' => (int)$ws['id'],
'idmatrice' => (int)$ws['idmatrice'],
'matrice_nome' => $ws['matrice_nome'] ?? '',
'matrice_cliente' => $ws['matrice_cliente'] ?? '',
'worksheet_date' => $ws['worksheet_date'] ?? '',
'worksheet_date_it' => formatDateIT($ws['worksheet_date'] ?? ''),
'customer_name' => $ws['customer_name'] ?? '',
'profile_type_code' => $ws['profile_type_code'] ?? '',
'marking' => $ws['marking'] ?? '',
'prod_control_measure_settings' => $ws['prod_control_measure_settings'] ?? '',
'control_frequency_cut' => $ws['control_frequency_cut'] ?? '',
'control_frequency_drawing' => $ws['control_frequency_drawing'] ?? '',
'control_frequency_jig' => $ws['control_frequency_jig'] ?? '',
'control_mode_jig' => $ws['control_mode_jig'] ?? '',
'requested_package_code' => $ws['requested_package_code'] ?? '',
'meters_per_package' => $ws['meters_per_package'] ?? '',
'meters_per_package_tolerance' => $ws['meters_per_package_tolerance'] ?? '',
'meters_per_package_notes' => $ws['meters_per_package_notes'] ?? '',
'box_type' => $ws['box_type'] ?? '',
'packages_or_pieces_per_box' => $ws['packages_or_pieces_per_box'] ?? '',
'meters_per_box' => $ws['meters_per_box'] ?? '',
'pallet_type' => $ws['pallet_type'] ?? '',
'boxes_or_packages_per_pallet' => $ws['boxes_or_packages_per_pallet'] ?? '',
'speed_expected_kg_h' => $ws['speed_expected_kg_h'] ?? '',
'speed_actual_kg_h' => $ws['speed_actual_kg_h'] ?? '',
'speed_expected_m_h' => $ws['speed_expected_m_h'] ?? '',
'speed_actual_m_h' => $ws['speed_actual_m_h'] ?? '',
'approved_by' => $ws['approved_by'] ?? '',
'notes' => $ws['notes'] ?? '',
'created_at' => $ws['created_at'] ?? '',
'created_at_it' => formatDateTimeIT($ws['created_at'] ?? ''),
'updated_at' => $ws['updated_at'] ?? '',
'updated_at_it' => formatDateTimeIT($ws['updated_at'] ?? '')
],
'mix_rows' => array_map(function ($r) {
return [
'id' => (int)$r['id'],
'mix_position' => (int)$r['mix_position'],
'mescola_nome' => $r['mescola_nome'] ?? '',
'mescola_uscita' => $r['mescola_uscita'] ?? '',
'mix_weight_g_m' => $r['mix_weight_g_m'] ?? '',
'required_density' => $r['required_density'] ?? '',
'required_hardness_shore_a' => $r['required_hardness_shore_a'] ?? '',
'lubrication_type' => $r['lubrication_type'] ?? '',
'lubrication_notes' => $r['lubrication_notes'] ?? ''
];
}, $mixRows)
]);
exit;
}
echo json_encode([
'success' => false,
'message' => 'Azione AJAX sconosciuta'
]);
exit;
} catch (Exception $e) {
echo json_encode([
'success' => false,
'message' => $e->getMessage()
]);
exit;
}
}
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>Gestione Matrici - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
<!-- jQuery / SweetAlert -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<!-- DataTables -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<!-- Select2 -->
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.full.min.js"></script>
<style>
body {
font-size: 1.05rem;
background: #f8fafc;
}
.card {
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.back-dashboard {
background-color: #b9ebc7 !important;
color: #1f2d3d !important;
border-radius: 10px;
font-weight: 600;
font-size: 1rem;
padding: 10px 18px;
}
.btn-add {
background-color: #198754;
color: #fff;
border-radius: 8px;
padding: 10px 20px;
}
.btn-files {
background-color: #0dcaf0;
color: #0b2e38;
border-radius: 8px;
padding: 10px 18px;
font-weight: 600;
}
.btn-worksheets {
background: linear-gradient(135deg, #0d6efd, #3b82f6);
color: #fff;
border: none;
border-radius: 10px;
padding: 10px 18px;
font-weight: 700;
box-shadow: 0 6px 16px rgba(13, 110, 253, 0.18);
}
.btn-worksheets:hover {
color: #fff;
opacity: .95;
}
.table thead {
background-color: #b9ebc7;
text-align: center;
}
.action-btn {
border: none;
background: none;
cursor: pointer;
font-size: 1.2rem;
margin: 0 4px;
}
.edit {
color: #0d6efd;
}
.delete {
color: #dc3545;
}
.linee {
color: #198754;
}
.mescole {
color: #ff9800;
}
.files {
color: #0dcaf0;
}
.worksheets {
color: #0d6efd;
}
.thumb-img:hover {
transform: scale(1.05);
transition: 0.2s;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.dataTables_wrapper .dataTables_filter {
position: relative;
margin-bottom: 12px;
}
.dataTables_wrapper .dataTables_filter label {
font-weight: 700;
color: #1f2d3d;
display: flex;
align-items: center;
gap: 10px;
justify-content: flex-end;
}
.dataTables_wrapper .dataTables_filter input {
width: 340px !important;
max-width: 100%;
padding: 10px 14px !important;
padding-right: 42px !important;
border-radius: 12px !important;
border: 2px solid #198754 !important;
background: #fff !important;
box-shadow: 0 6px 18px rgba(25, 135, 84, 0.18);
outline: none !important;
transition: all .15s ease;
}
.dataTables_wrapper .dataTables_filter input:focus {
border-color: #146c43 !important;
box-shadow: 0 0 0 0.2rem rgba(25, 135, 84, 0.25), 0 10px 22px rgba(25, 135, 84, 0.22) !important;
}
.dataTables_wrapper .dataTables_filter::after {
content: "🔎";
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
opacity: 0.7;
font-size: 16px;
}
#tabellaMatrici {
table-layout: fixed;
width: 100% !important;
}
#tabellaMatrici th,
#tabellaMatrici td {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
#tabellaMatrici th:nth-child(1),
#tabellaMatrici td:nth-child(1) {
width: 90px;
max-width: 90px;
}
#tabellaMatrici th:nth-child(2),
#tabellaMatrici td:nth-child(2) {
width: 280px;
max-width: 280px;
}
#tabellaMatrici th:nth-child(3),
#tabellaMatrici td:nth-child(3) {
width: 260px;
max-width: 260px;
}
#tabellaMatrici th:nth-child(4),
#tabellaMatrici td:nth-child(4) {
width: 240px;
max-width: 240px;
}
#tabellaMatrici th:nth-child(5),
#tabellaMatrici td:nth-child(5) {
width: 130px;
max-width: 130px;
}
#tabellaMatrici th:nth-child(6),
#tabellaMatrici td:nth-child(6) {
width: 260px;
max-width: 260px;
}
#tabellaMatrici td:nth-child(1) img {
width: 70px;
height: 60px;
object-fit: cover;
}
.dropzone-attachments {
border: 2px dashed #86b7fe;
border-radius: 16px;
background: #f8fbff;
min-height: 150px;
padding: 24px;
text-align: center;
transition: all .2s ease;
cursor: pointer;
}
.dropzone-attachments.dragover {
background: #eef6ff;
border-color: #0d6efd;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.12);
}
.dropzone-title {
font-weight: 700;
color: #1f2d3d;
font-size: 1.05rem;
}
.dropzone-subtitle {
color: #6c757d;
margin-top: 6px;
font-size: 0.95rem;
}
.attachments-selected-list .attachment-upload-row,
.attachments-existing-list .attachment-existing-row {
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 12px;
background: #fff;
margin-bottom: 10px;
}
.attachment-file-icon {
font-size: 1.3rem;
min-width: 28px;
text-align: center;
}
.attachment-meta-title {
font-weight: 700;
color: #1f2d3d;
word-break: break-word;
}
.attachment-meta-sub {
font-size: 0.88rem;
color: #6c757d;
}
.attachment-preview-thumb {
width: 54px;
height: 54px;
border-radius: 10px;
object-fit: cover;
border: 1px solid #dee2e6;
}
.existing-attachments-wrap {
max-height: 500px;
overflow-y: auto;
padding-right: 4px;
}
.selected-attachments-wrap {
max-height: 420px;
overflow-y: auto;
padding-right: 4px;
}
.badge-type {
font-size: 0.78rem;
}
.file-desc-input {
font-size: 0.95rem;
}
.modal-xl-custom {
max-width: 1380px;
width: 95vw;
}
.modal-worksheet-list {
max-width: 980px;
width: 94vw;
}
.modal-worksheet-view {
max-width: 1500px;
width: 97vw;
}
.section-box {
background: #f8fafc;
border: 1px solid #e9ecef;
border-radius: 14px;
padding: 16px;
}
.empty-attachments {
color: #6c757d;
font-style: italic;
text-align: center;
padding: 18px 8px;
}
.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;
}
.worksheet-counter-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 34px;
height: 34px;
border-radius: 999px;
background: linear-gradient(135deg, #0d6efd, #3b82f6);
color: #fff;
font-weight: 800;
font-size: .95rem;
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.22);
}
.worksheet-counter-zero {
background: #e9ecef;
color: #6c757d;
box-shadow: none;
}
.worksheet-list-table th {
background: #e7f1ff !important;
color: #0b3d91;
}
.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;
}
.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="card p-3">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Gestione elenco Profili</h5>
<button type="button" class="btn back-dashboard" onclick="location.href='production_dashboard.php'">↩️ Torna alla Dashboard</button>
</div>
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="fw-semibold mb-0">Elenco Completo</h6>
<button class="btn btn-add" data-bs-toggle="modal" data-bs-target="#addMatriceModal"> Aggiungi Matrice</button>
</div>
<div class="table-responsive">
<table id="tabellaMatrici" class="table table-striped align-middle text-center">
<thead>
<tr>
<th>Foto</th>
<th>Nome</th>
<th>Cliente</th>
<th>Linee</th>
<th>Fogli</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
<?php
$stmt = $pdo->query("
SELECT
m.*,
COALESCE(lg.linee_associate, '—') AS linee_associate,
COALESCE(mg.mescole_associate, '') AS mescole_associate,
COALESCE(mg.mescole_count, 0) AS mescole_count,
COALESCE(wsg.worksheets_count, 0) AS worksheets_count
FROM matrice m
LEFT JOIN (
SELECT
ml.idmatrice,
GROUP_CONCAT(pl.name ORDER BY pl.name SEPARATOR ', ') AS linee_associate
FROM matrici_lines ml
JOIN production_lines pl ON pl.id = ml.idlinea
GROUP BY ml.idmatrice
) lg ON lg.idmatrice = m.id
LEFT JOIN (
SELECT
mm.idmatrice,
GROUP_CONCAT(ms.nome ORDER BY ms.nome SEPARATOR ', ') AS mescole_associate,
COUNT(*) AS mescole_count
FROM matrici_mescole mm
JOIN mescole ms ON ms.id = mm.idmescola
GROUP BY mm.idmatrice
) mg ON mg.idmatrice = m.id
LEFT JOIN (
SELECT
ws.idmatrice,
COUNT(*) AS worksheets_count
FROM work_sheets ws
GROUP BY ws.idmatrice
) wsg ON wsg.idmatrice = m.id
ORDER BY
CASE WHEN TRIM(COALESCE(m.nome, '')) = '' THEN 1 ELSE 0 END,
m.nome ASC
");
if ($stmt->rowCount() === 0) {
echo "<tr><td colspan='6' class='text-muted'>Nessuna matrice presente</td></tr>";
} else {
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$dataIT = formatDateIT($row['data_produzione']);
$foto = $row['photo'] ?? '';
$pathFoto = "photos/matrici/" . $foto;
$placeholder = "assets/images/no-photo.png";
if ($foto && file_exists($pathFoto)) {
$thumb = $pathFoto;
$hasPhoto = true;
} else {
$thumb = $placeholder;
$hasPhoto = false;
}
echo "<tr>";
$imgSrc = $hasPhoto ? $thumb : '';
echo "<td class='foto-cell'>
<img src='" . htmlspecialchars($imgSrc, ENT_QUOTES) . "'
class='thumb-img'
data-full='" . htmlspecialchars($thumb, ENT_QUOTES) . "'
style='height:60px; cursor:pointer; border-radius:6px; " . ($hasPhoto ? "" : "display:none;") . "'
title='" . ($hasPhoto ? "Clicca per ingrandire" : "") . "'
onerror=\"
this.style.display='none';
this.closest('td').querySelector('.no-photo').style.display='inline-block';
\">
<span class='no-photo text-muted fw-semibold' style='display:" . ($hasPhoto ? "none" : "inline-block") . ";'>NP</span>
</td>";
echo "<td title='" . htmlspecialchars($row['nome'], ENT_QUOTES) . "'>"
. htmlspecialchars($row['nome'])
. "</td>";
echo "<td title='" . htmlspecialchars($row['cliente'], ENT_QUOTES) . "'>"
. htmlspecialchars($row['cliente'])
. "</td>";
$lineeTxt = $row['linee_associate'] ?? '—';
$mescoleTxt = $row['mescole_associate'] ?? '';
$mescoleCount = (int)($row['mescole_count'] ?? 0);
$worksheetsCount = (int)($row['worksheets_count'] ?? 0);
$btnMescole = '';
if ($mescoleCount > 0) {
$btnMescole = " <button type='button'
class='btn btn-sm btn-outline-secondary ms-2 show-mescole'
data-nome='" . htmlspecialchars($row['nome'], ENT_QUOTES) . "'
data-mescole='" . htmlspecialchars($mescoleTxt, ENT_QUOTES) . "'
data-bs-toggle='tooltip' data-bs-title='Vedi mescole associate'>
<i class='fa-solid fa-flask'></i>
</button>";
}
echo "<td title='" . htmlspecialchars($lineeTxt, ENT_QUOTES) . "'>"
. htmlspecialchars($lineeTxt)
. $btnMescole
. "</td>";
echo "<td>
<div class='d-flex justify-content-center align-items-center gap-2'>
<span class='worksheet-counter-badge " . ($worksheetsCount <= 0 ? "worksheet-counter-zero" : "") . "'>{$worksheetsCount}</span>";
if ($worksheetsCount > 0) {
echo "<button type='button'
class='btn btn-sm btn-outline-primary show-worksheets'
data-id='" . (int)$row['id'] . "'
data-nome='" . htmlspecialchars($row['nome'], ENT_QUOTES) . "'
data-bs-toggle='tooltip'
data-bs-title='Apri fogli di lavoro collegati'>
<i class='fa-solid fa-clipboard-list'></i>
</button>";
} else {
echo "<span class='text-muted small'>—</span>";
}
echo " </div>
</td>";
echo "<td>
<button class='action-btn edit'
data-id='{$row['id']}'
data-nome='" . htmlspecialchars($row['nome'], ENT_QUOTES) . "'
data-cliente='" . htmlspecialchars($row['cliente'], ENT_QUOTES) . "'
data-data='{$dataIT}'
data-bs-toggle='tooltip' data-bs-title='Modifica'>
<i class='fa-solid fa-pen-to-square'></i>
</button>
<button class='action-btn delete'
data-id='{$row['id']}'
data-bs-toggle='tooltip' data-bs-title='Elimina'>
<i class='fa-solid fa-trash'></i>
</button>
<button class='action-btn linee'
data-id='{$row['id']}'
data-nome='" . htmlspecialchars($row['nome'], ENT_QUOTES) . "'
data-bs-toggle='tooltip' data-bs-title='Linee associate'>
<i class='fa-solid fa-sitemap'></i>
</button>
<button class='action-btn mescole'
data-id='{$row['id']}'
data-nome='" . htmlspecialchars($row['nome'], ENT_QUOTES) . "'
data-bs-toggle='tooltip' data-bs-title='Mescole associate'>
<i class='fa-solid fa-flask'></i>
</button>
<button class='action-btn worksheets'
data-id='{$row['id']}'
data-nome='" . htmlspecialchars($row['nome'], ENT_QUOTES) . "'
data-bs-toggle='tooltip' data-bs-title='Fogli di lavoro profilo'>
<i class='fa-solid fa-clipboard-list'></i>
</button>
<button class='action-btn files'
data-id='{$row['id']}'
data-nome='" . htmlspecialchars($row['nome'], ENT_QUOTES) . "'
data-bs-toggle='tooltip' data-bs-title='File correlati'>
<i class='fa-solid fa-paperclip'></i>
</button>
</td>";
echo "</tr>";
}
}
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
<!-- MODALE AGGIUNTA / MODIFICA MATRICE -->
<div class="modal fade" id="addMatriceModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header" style="background-color:#b9ebc7;">
<h5 class="modal-title">Aggiungi / Modifica Matrice</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="addMatriceForm">
<input type="hidden" id="idMatriceEdit">
<div class="mb-3">
<label class="fw-semibold">Nome Matrice</label>
<input type="text" class="form-control" id="nomeMatrice" required>
</div>
<div class="mb-3">
<label class="fw-semibold">Cliente</label>
<input type="text" class="form-control" id="clienteMatrice">
</div>
<div class="mb-3">
<label class="fw-semibold">Data</label>
<input type="text" class="form-control" id="dataMatrice" placeholder="dd/mm/yyyy">
</div>
<div class="text-center">
<button type="submit" class="btn btn-add">💾 Salva</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- MODALE LINEE ASSOCIATE -->
<div class="modal fade" id="matriceLineeModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header" style="background-color:#b9ebc7;">
<h5 class="modal-title">Linee associate</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="ml_idmatrice">
<div class="mb-2">
<div class="fw-semibold" id="ml_matrice_name" style="color:#1f2d3d;"></div>
<small class="text-muted">Seleziona una o più linee di produzione</small>
</div>
<div class="mb-3">
<label class="fw-semibold">Linee</label>
<select id="ml_linee" class="form-select" multiple="multiple" style="width:100%;"></select>
</div>
<div class="text-center">
<button type="button" class="btn btn-add" id="ml_save_btn">💾 Salva Linee</button>
</div>
</div>
</div>
</div>
</div>
<!-- MODALE MESCOLE ASSOCIATE -->
<div class="modal fade" id="matriceMescoleModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header" style="background-color:#b9ebc7;">
<h5 class="modal-title">Mescole associate</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="mm_idmatrice">
<div class="mb-2">
<div class="fw-semibold" id="mm_matrice_name" style="color:#1f2d3d;"></div>
<small class="text-muted">Seleziona una o più mescole</small>
</div>
<div class="mb-3">
<label class="fw-semibold">Mescole</label>
<select id="mm_mescole" class="form-select" multiple="multiple" style="width:100%;"></select>
</div>
<div class="text-center">
<button type="button" class="btn btn-add" id="mm_save_btn">💾 Salva Mescole</button>
</div>
</div>
</div>
</div>
</div>
<!-- MODALE FILE CORRELATI -->
<div class="modal fade" id="matriceFilesModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-xl-custom">
<div class="modal-content">
<div class="modal-header" style="background-color:#b9ebc7;">
<div>
<h5 class="modal-title mb-0">File correlati matrice</h5>
<small class="text-muted">Aggiunta, visualizzazione e gestione allegati</small>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="mf_idmatrice">
<div class="mb-3">
<div class="fw-semibold" id="mf_matrice_name" style="color:#1f2d3d; font-size:1.05rem;"></div>
</div>
<div class="row g-4">
<div class="col-lg-6">
<div class="section-box h-100">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<div class="fw-bold">Nuovi allegati</div>
<small class="text-muted">Trascina file oppure selezionali manualmente</small>
</div>
<button type="button" class="btn btn-files btn-sm" id="mf_pick_files_btn">📎 Seleziona file</button>
</div>
<div id="attachmentsDropzone" class="dropzone-attachments mb-3">
<div class="dropzone-title">Trascina qui PDF, immagini, HEIC, WEBP, DOC, DOCX</div>
<div class="dropzone-subtitle">Oppure clicca per aprire la selezione file</div>
</div>
<input type="file" id="mf_files_input" multiple hidden
accept=".pdf,.jpg,.jpeg,.png,.gif,.bmp,.webp,.heic,.heif,.doc,.docx,application/pdf,image/*,.doc,.docx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document">
<div class="selected-attachments-wrap attachments-selected-list" id="selectedAttachmentsList">
<div class="empty-attachments">Nessun nuovo file selezionato</div>
</div>
<div class="d-flex justify-content-end gap-2 mt-3">
<button type="button" class="btn btn-outline-secondary" id="mf_clear_selected_btn">Svuota selezione</button>
<button type="button" class="btn btn-add" id="mf_upload_btn">⬆️ Carica file</button>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="section-box h-100">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<div class="fw-bold">File già presenti</div>
<small class="text-muted">Apri, controlla o elimina allegati già caricati</small>
</div>
<button type="button" class="btn btn-outline-primary btn-sm" id="mf_refresh_existing_btn">⟳ Aggiorna</button>
</div>
<div class="existing-attachments-wrap attachments-existing-list" id="existingAttachmentsList">
<div class="empty-attachments">Nessun file presente</div>
</div>
</div>
</div>
</div>
<div class="mt-3">
<small class="text-muted">
Formati previsti: PDF, JPG, JPEG, PNG, GIF, BMP, WEBP, HEIC, HEIF, DOC, DOCX
</small>
</div>
</div>
</div>
</div>
</div>
<!-- MODALE FOTO GRANDE -->
<div class="modal fade" id="fotoModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content p-0" style="background:transparent; border:none;">
<img id="fotoGrande" src="" style="width:60vw; max-height:80vh; object-fit:contain; border-radius:10px;">
</div>
</div>
</div>
<div id="bigView"
style="display:none; position:fixed; top:0; left:0; width:100vw; height:100vh; background:rgba(0,0,0,0.9); z-index:9999; justify-content:center; align-items:center;">
<img id="bigImg" src="" style="max-width:70vw; max-height:90vh; border-radius:12px;">
</div>
<!-- MODALE LISTA MESCOLE ASSOCIATE -->
<div class="modal fade" id="mescoleListModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header" style="background-color:#b9ebc7;">
<h5 class="modal-title">Mescole associate</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="fw-semibold mb-2" id="mlm_matrice_name" style="color:#1f2d3d;"></div>
<div id="mlm_list" class="text-muted"></div>
</div>
</div>
</div>
</div>
<!-- MODALE LISTA FOGLI DI LAVORO -->
<div class="modal fade" id="worksheetsListModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-worksheet-list">
<div class="modal-content">
<div class="modal-header" style="background:linear-gradient(135deg,#e7f1ff,#d6e9ff);">
<div>
<h5 class="modal-title mb-0">
<i class="fa-solid fa-clipboard-list me-2"></i>Fogli di lavoro collegati
</h5>
<small class="text-muted">Elenco fogli associati al profilo selezionato</small>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="wl_idmatrice">
<div class="fw-semibold mb-3" id="wl_matrice_name" style="color:#0b3d91;"></div>
<div id="worksheetsListContainer">
<div class="text-muted">Caricamento fogli di lavoro...</div>
</div>
</div>
</div>
</div>
</div>
<!-- MODALE DETTAGLIO FOGLIO DI LAVORO -->
<div class="modal fade" id="worksheetDetailModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-worksheet-view">
<div class="modal-content" style="min-height:92vh;">
<div class="modal-header" style="background:linear-gradient(135deg,#ede9fe,#ddd6fe);">
<div>
<h5 class="modal-title mb-0">
<i class="fa-solid fa-file-lines me-2"></i>Dettaglio foglio di lavoro
</h5>
<small class="text-muted">Visualizzazione completa in sola lettura</small>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="worksheetDetailContainer">
<div class="text-muted">Caricamento dettaglio foglio...</div>
</div>
</div>
</div>
</div>
<!-- MODALE PREVIEW FILE -->
<div class="modal fade" id="filePreviewModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" style="max-width:1400px; width:98vw;">
<div class="modal-content" style="height:92vh;">
<div class="modal-header" style="background-color:#b9ebc7;">
<h5 class="modal-title" id="filePreviewTitle">Anteprima file</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div id="filePreviewContainer" style="height:calc(92vh - 64px); overflow:hidden; background:#f8fafc;"></div>
</div>
</div>
</div>
<?php include('jsinclude.php'); ?>
<script>
function convertToMySQLDate(dateStr) {
if (!dateStr) return "";
const parts = dateStr.split("/");
if (parts.length !== 3) return "";
return parts[2] + "-" + parts[1] + "-" + parts[0];
}
function escapeHtml(str) {
return String(str || '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
function formatBytes(bytes) {
if (!bytes || bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function getFileExt(filename) {
const parts = String(filename || '').split('.');
return parts.length > 1 ? parts.pop().toLowerCase() : '';
}
function getFileTypeBadge(ext) {
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic', 'heif'].includes(ext)) {
return '<span class="badge bg-info text-dark badge-type">Immagine</span>';
}
if (ext === 'pdf') {
return '<span class="badge bg-danger badge-type">PDF</span>';
}
if (['doc', 'docx'].includes(ext)) {
return '<span class="badge bg-primary badge-type">DOC</span>';
}
return '<span class="badge bg-secondary badge-type">File</span>';
}
function getFileIcon(ext) {
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic', 'heif'].includes(ext)) return '🖼️';
if (ext === 'pdf') return '📕';
if (['doc', 'docx'].includes(ext)) return '📘';
return '📎';
}
function isImageExt(ext) {
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic', 'heif'].includes(ext);
}
function valueOrDash(v) {
return (v === null || v === undefined || String(v).trim() === '') ? '—' : escapeHtml(v);
}
function renderReadonlyField(label, value) {
return `
<div class="readonly-label">${escapeHtml(label)}</div>
<div class="readonly-value">${valueOrDash(value)}</div>
`;
}
let selectedAttachmentFiles = [];
function renderSelectedAttachments() {
const container = $("#selectedAttachmentsList");
container.empty();
if (!selectedAttachmentFiles.length) {
container.html('<div class="empty-attachments">Nessun nuovo file selezionato</div>');
return;
}
selectedAttachmentFiles.forEach((item, index) => {
const file = item.file;
const ext = getFileExt(file.name);
const icon = getFileIcon(ext);
const badge = getFileTypeBadge(ext);
const objectUrl = isImageExt(ext) ? URL.createObjectURL(file) : '';
const previewHtml = isImageExt(ext) ?
`<img src="${objectUrl}" class="attachment-preview-thumb" alt="">` :
`<div class="attachment-file-icon">${icon}</div>`;
const html = `
<div class="attachment-upload-row" data-index="${index}">
<div class="d-flex gap-3 align-items-start">
<div>${previewHtml}</div>
<div class="flex-grow-1">
<div class="d-flex justify-content-between align-items-start gap-2">
<div>
<div class="attachment-meta-title">${escapeHtml(file.name)}</div>
<div class="attachment-meta-sub">${formatBytes(file.size)} • ${badge}</div>
</div>
<button type="button" class="btn btn-sm btn-outline-danger remove-selected-file" data-index="${index}">✖</button>
</div>
<div class="mt-2">
<label class="form-label mb-1 fw-semibold">Descrizione</label>
<input type="text" class="form-control file-desc-input selected-file-desc" data-index="${index}" value="${escapeHtml(item.description || '')}" placeholder="Inserisci una descrizione per questo allegato">
</div>
</div>
</div>
</div>
`;
container.append(html);
});
}
function addFilesToSelection(fileList) {
const allowedExtensions = ['pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic', 'heif', 'doc', 'docx'];
Array.from(fileList).forEach(file => {
const ext = getFileExt(file.name);
if (!allowedExtensions.includes(ext)) {
return;
}
selectedAttachmentFiles.push({
file: file,
description: ''
});
});
renderSelectedAttachments();
}
function loadExistingAttachments(idmatrice) {
$("#existingAttachmentsList").html('<div class="empty-attachments">Caricamento file...</div>');
fetch("get_matrice_attachments.php?id=" + encodeURIComponent(idmatrice))
.then(r => r.json())
.then(data => {
if (!data.success) {
$("#existingAttachmentsList").html('<div class="empty-attachments">Errore nel caricamento dei file</div>');
return;
}
const list = data.attachments || [];
const container = $("#existingAttachmentsList");
container.empty();
if (!list.length) {
container.html('<div class="empty-attachments">Nessun file presente</div>');
return;
}
list.forEach(item => {
const fileName = item.file_name || item.filename || '';
const filePath = item.file_url || item.file_path || '';
const description = item.description || '';
const ext = getFileExt(fileName);
const icon = getFileIcon(ext);
const badge = getFileTypeBadge(ext);
const createdAt = item.created_at || '';
const id = item.id || 0;
const previewHtml = isImageExt(ext) && filePath ?
`<img src="${escapeHtml(filePath)}" class="attachment-preview-thumb" alt="">` :
`<div class="attachment-file-icon">${icon}</div>`;
let openButtonHtml = '';
if (isImageExt(ext) || ext === 'pdf') {
openButtonHtml = `
<button type="button"
class="btn btn-sm btn-outline-primary preview-existing-file"
data-file="${escapeHtml(filePath)}"
data-name="${escapeHtml(fileName)}">
Apri
</button>
`;
} else {
openButtonHtml = `
<a href="${escapeHtml(filePath)}" target="_blank" class="btn btn-sm btn-outline-primary">
Apri
</a>
`;
}
const html = `
<div class="attachment-existing-row">
<div class="d-flex gap-3 align-items-start">
<div>${previewHtml}</div>
<div class="flex-grow-1">
<div class="d-flex justify-content-between align-items-start gap-2">
<div>
<div class="attachment-meta-title">${escapeHtml(fileName)}</div>
<div class="attachment-meta-sub">${badge} ${createdAt ? ' • ' + escapeHtml(createdAt) : ''}</div>
</div>
<div class="d-flex gap-1">
${openButtonHtml}
<button type="button" class="btn btn-sm btn-outline-danger delete-existing-file" data-id="${id}">Elimina</button>
</div>
</div>
<div class="mt-2 text-muted" style="white-space:normal;">
${description ? escapeHtml(description) : '<em>Nessuna descrizione</em>'}
</div>
</div>
</div>
</div>
`;
container.append(html);
});
})
.catch(() => {
$("#existingAttachmentsList").html('<div class="empty-attachments">Errore nel caricamento dei file</div>');
});
}
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%; display:flex; justify-content:center; align-items:center; background:#111;">
<img src="${escapeHtml(filePath)}"
alt="${escapeHtml(fileName)}"
style="max-width:100%; max-height:100%; object-fit:contain;">
</div>
`;
} else if (ext === 'pdf') {
html = `
<iframe src="${escapeHtml(filePath)}#zoom=page-width"
style="display:block; width:100%; height:100%; border:none; background:#fff;">
</iframe>
`;
} else {
html = `
<div style="height:100%; display:flex; justify-content:center; align-items:center; flex-direction:column; padding:30px;">
<div style="font-size:3rem; margin-bottom:12px;">${getFileIcon(ext)}</div>
<div class="text-muted mb-3">Anteprima non disponibile per questo formato</div>
<a href="${escapeHtml(filePath)}" target="_blank" class="btn btn-outline-primary">Apri file</a>
</div>
`;
}
$("#filePreviewContainer").html(html);
const modal = new bootstrap.Modal(document.getElementById('filePreviewModal'));
modal.show();
}
function loadMatriceWorksheets(idmatrice, matriceNome) {
$("#wl_idmatrice").val(idmatrice);
$("#wl_matrice_name").html(`<span class="worksheet-chip"><i class="fa-solid fa-layer-group"></i>${escapeHtml(matriceNome || '')}</span>`);
$("#worksheetsListContainer").html('<div class="text-muted">Caricamento fogli di lavoro...</div>');
fetch(window.location.href, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'ajax=1&action=get_matrice_worksheets&idmatrice=' + encodeURIComponent(idmatrice)
})
.then(r => r.json())
.then(data => {
if (!data.success) {
$("#worksheetsListContainer").html('<div class="text-danger">Errore nel caricamento dei fogli</div>');
return;
}
const rows = data.worksheets || [];
if (!rows.length) {
$("#worksheetsListContainer").html('<div class="text-muted">Nessun foglio di lavoro collegato a questo profilo</div>');
return;
}
let html = `
<div class="table-responsive">
<table class="table table-striped align-middle worksheet-list-table">
<thead>
<tr>
<th style="width:90px;">ID</th>
<th style="width:140px;">Data foglio</th>
<th>Cliente</th>
<th>Codice profilo</th>
<th style="width:110px;">Mescole</th>
<th style="width:230px;">Azioni</th>
</tr>
</thead>
<tbody>
`;
rows.forEach(r => {
html += `
<tr>
<td><strong>#${r.id}</strong></td>
<td>${escapeHtml(r.worksheet_date_it || '—')}</td>
<td title="${escapeHtml(r.customer_name || '')}">${escapeHtml(r.customer_name || '—')}</td>
<td>${escapeHtml(r.profile_type_code || '—')}</td>
<td>${escapeHtml(String(r.mix_count || 0))}</td>
<td class="text-nowrap">
<button type="button"
class="btn btn-view-worksheet open-worksheet-detail"
data-id="${r.id}">
<i class="fa-solid fa-eye me-1"></i>Apri dettaglio
</button>
<a class="worksheet-open-link ms-1"
href="manage-worksheet.php?id=${r.id}"
target="_blank">
<i class="fa-solid fa-arrow-up-right-from-square"></i>Apri pagina
</a>
</td>
</tr>
`;
});
html += `
</tbody>
</table>
</div>
`;
$("#worksheetsListContainer").html(html);
})
.catch(() => {
$("#worksheetsListContainer").html('<div class="text-danger">Errore nel caricamento dei fogli</div>');
});
}
function loadWorksheetDetail(worksheetId) {
$("#worksheetDetailContainer").html('<div class="text-muted">Caricamento dettaglio foglio...</div>');
fetch(window.location.href, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'ajax=1&action=get_worksheet_detail&worksheet_id=' + encodeURIComponent(worksheetId)
})
.then(r => r.json())
.then(data => {
if (!data.success) {
$("#worksheetDetailContainer").html('<div class="text-danger">Errore nel caricamento del dettaglio foglio</div>');
return;
}
const ws = data.worksheet || {};
const mixRows = data.mix_rows || [];
let mixHtml = `
<div class="text-muted">Nessuna mescola associata</div>
`;
if (mixRows.length) {
mixHtml = `
<div class="table-responsive">
<table class="table table-striped align-middle mix-readonly-table">
<thead>
<tr>
<th style="width:80px;">Pos</th>
<th>Mescola</th>
<th style="width:120px;">Peso g/m</th>
<th style="width:140px;">Densità</th>
<th style="width:150px;">Durezza</th>
<th style="width:130px;">Lubr.</th>
<th>Note lubr.</th>
</tr>
</thead>
<tbody>
`;
mixRows.forEach(r => {
const nomeMescola = r.mescola_uscita ?
`${escapeHtml(r.mescola_nome || '—')} <div class="small text-muted">${escapeHtml(r.mescola_uscita)}</div>` :
escapeHtml(r.mescola_nome || '—');
mixHtml += `
<tr>
<td>${escapeHtml(String(r.mix_position || ''))}</td>
<td>${nomeMescola}</td>
<td>${valueOrDash(r.mix_weight_g_m)}</td>
<td>${valueOrDash(r.required_density)}</td>
<td>${valueOrDash(r.required_hardness_shore_a)}</td>
<td>${valueOrDash(r.lubrication_type)}</td>
<td>${valueOrDash(r.lubrication_notes)}</td>
</tr>
`;
});
mixHtml += `
</tbody>
</table>
</div>
`;
}
const html = `
<div class="worksheet-title-box mb-4">
<div class="d-flex justify-content-between align-items-start gap-3 flex-wrap">
<div>
<h4 class="mb-1">Foglio di lavoro #${escapeHtml(String(ws.id || ''))}</h4>
<small>Profilo: ${escapeHtml(ws.matrice_nome || '—')} ${ws.matrice_cliente ? '• Cliente matrice: ' + escapeHtml(ws.matrice_cliente) : ''}</small>
</div>
<div>
<a href="manage-worksheet.php?id=${escapeHtml(String(ws.id || ''))}"
target="_blank"
class="worksheet-open-link">
<i class="fa-solid fa-arrow-up-right-from-square"></i>
Apri foglio completo
</a>
</div>
</div>
</div>
<div class="row g-3">
<div class="col-lg-6">
<div class="readonly-card">
<div class="readonly-card-header">Dati principali</div>
<div class="readonly-card-body">
<div class="readonly-grid">
${renderReadonlyField('ID foglio', ws.id)}
${renderReadonlyField('Data foglio', ws.worksheet_date_it)}
${renderReadonlyField('Cliente override', ws.customer_name)}
${renderReadonlyField('Codice profilo', ws.profile_type_code)}
${renderReadonlyField('Marchiatura', ws.marking)}
${renderReadonlyField('Approvato da', ws.approved_by)}
${renderReadonlyField('Creato il', ws.created_at_it)}
${renderReadonlyField('Ultimo aggiornamento', ws.updated_at_it)}
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="readonly-card">
<div class="readonly-card-header">Controlli produzione</div>
<div class="readonly-card-body">
<div class="readonly-grid">
${renderReadonlyField('Taglio', ws.control_frequency_cut)}
${renderReadonlyField('Disegno', ws.control_frequency_drawing)}
${renderReadonlyField('Dima', ws.control_frequency_jig)}
${renderReadonlyField('Modalità dima', ws.control_mode_jig)}
</div>
<hr>
<div class="readonly-label mb-2">Impostazione misure controllo produzione</div>
<div class="readonly-value" style="white-space:pre-wrap;">${valueOrDash(ws.prod_control_measure_settings)}</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="readonly-card">
<div class="readonly-card-header">Packaging / Confezionamento</div>
<div class="readonly-card-body">
<div class="readonly-grid">
${renderReadonlyField('Codice confezione', ws.requested_package_code)}
${renderReadonlyField('Metri per confezione', ws.meters_per_package)}
${renderReadonlyField('Tolleranza metri/conf.', ws.meters_per_package_tolerance)}
${renderReadonlyField('Scatola tipo', ws.box_type)}
${renderReadonlyField('Conf./pezzi per scatola', ws.packages_or_pieces_per_box)}
${renderReadonlyField('Metri per scatola', ws.meters_per_box)}
${renderReadonlyField('Bancale tipo', ws.pallet_type)}
${renderReadonlyField('Scatole/conf. per bancale', ws.boxes_or_packages_per_pallet)}
</div>
<hr>
<div class="readonly-label mb-2">Note metri / confezione</div>
<div class="readonly-value" style="white-space:pre-wrap;">${valueOrDash(ws.meters_per_package_notes)}</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="readonly-card">
<div class="readonly-card-header">Velocità e note</div>
<div class="readonly-card-body">
<div class="readonly-grid">
${renderReadonlyField('Vel. prevista kg/h', ws.speed_expected_kg_h)}
${renderReadonlyField('Vel. effettiva kg/h', ws.speed_actual_kg_h)}
${renderReadonlyField('Vel. prevista m/h', ws.speed_expected_m_h)}
${renderReadonlyField('Vel. effettiva m/h', ws.speed_actual_m_h)}
</div>
<hr>
<div class="readonly-label mb-2">Note</div>
<div class="readonly-value" style="white-space:pre-wrap;">${valueOrDash(ws.notes)}</div>
</div>
</div>
</div>
<div class="col-12">
<div class="readonly-card">
<div class="readonly-card-header">Mescole associate al foglio</div>
<div class="readonly-card-body">
${mixHtml}
</div>
</div>
</div>
</div>
`;
$("#worksheetDetailContainer").html(html);
})
.catch(() => {
$("#worksheetDetailContainer").html('<div class="text-danger">Errore nel caricamento del dettaglio foglio</div>');
});
}
$(document).ready(function() {
$('#tabellaMatrici').DataTable({
order: [],
columnDefs: [{
targets: 0,
orderable: false,
searchable: false,
render: function(data, type, row, meta) {
return data;
}
}, {
targets: 5,
orderable: false,
searchable: false
}],
pageLength: 50,
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.6/i18n/it-IT.json'
}
});
$('#tabellaMatrici_filter label').contents().filter(function() {
return this.nodeType === 3;
}).first().replaceWith('Cerca: ');
$('#tabellaMatrici_filter input')
.attr('placeholder', 'Nome matrice, cliente…')
.addClass('form-control');
$(document).on("click", ".edit", function() {
$("#idMatriceEdit").val($(this).data("id"));
$("#nomeMatrice").val($(this).data("nome"));
$("#clienteMatrice").val($(this).data("cliente"));
$("#dataMatrice").val($(this).data("data"));
new bootstrap.Modal(document.getElementById('addMatriceModal')).show();
});
$("#addMatriceForm").on("submit", function(e) {
e.preventDefault();
let id = $("#idMatriceEdit").val();
let nome = $("#nomeMatrice").val().trim();
let cliente = $("#clienteMatrice").val().trim();
let dataIT = $("#dataMatrice").val().trim();
let dataSQL = convertToMySQLDate(dataIT);
fetch("save_matrice.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: "id=" + encodeURIComponent(id) +
"&nome=" + encodeURIComponent(nome) +
"&cliente=" + encodeURIComponent(cliente) +
"&data=" + encodeURIComponent(dataSQL)
})
.then(r => r.json())
.then(data => {
Swal.fire({
icon: data.success ? "success" : "error",
title: data.success ? "Salvato!" : "Errore",
text: data.message || ""
}).then(() => location.reload());
});
});
$(document).on("click", ".delete", function() {
const id = $(this).data("id");
Swal.fire({
icon: "warning",
title: "Eliminare questa matrice?",
showCancelButton: true,
confirmButtonColor: "#d33",
confirmButtonText: "Sì, elimina",
cancelButtonText: "Annulla"
}).then(res => {
if (res.isConfirmed) {
fetch("delete_matrice.php?id=" + encodeURIComponent(id))
.then(r => r.json())
.then(data => {
Swal.fire({
icon: data.success ? "success" : "error",
title: data.success ? "Eliminata!" : "Errore",
text: data.message || ""
}).then(() => location.reload());
});
}
});
});
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => new bootstrap.Tooltip(el));
$('#ml_linee').select2({
theme: 'bootstrap-5',
dropdownParent: $('#matriceLineeModal'),
placeholder: 'Seleziona le linee...'
});
$(document).on("click", ".linee", function() {
const idmatrice = $(this).data("id");
const nome = $(this).data("nome") || "";
$("#ml_idmatrice").val(idmatrice);
$("#ml_matrice_name").text(nome);
const $sel = $("#ml_linee");
$sel.empty().trigger("change");
fetch("get_matrice_lines.php?id=" + encodeURIComponent(idmatrice))
.then(r => r.json())
.then(data => {
if (!data.success) {
Swal.fire({
icon: "error",
title: "Errore",
text: data.message || "Impossibile caricare le linee"
});
return;
}
data.lines.forEach(l => {
const label = `Linea ${l.line_number} - ${l.name}`;
const opt = new Option(label, l.id, false, false);
$sel.append(opt);
});
$sel.val((data.selected_ids || []).map(String)).trigger("change");
new bootstrap.Modal(document.getElementById('matriceLineeModal')).show();
});
});
$("#ml_save_btn").on("click", function() {
const idmatrice = $("#ml_idmatrice").val();
const selected = $("#ml_linee").val() || [];
fetch("save_matrice_lines.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: "idmatrice=" + encodeURIComponent(idmatrice) +
"&idlinee=" + encodeURIComponent(JSON.stringify(selected))
})
.then(r => r.json())
.then(data => {
Swal.fire({
icon: data.success ? "success" : "error",
title: data.success ? "Salvato!" : "Errore",
text: data.message || ""
}).then(() => {
if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('matriceLineeModal')).hide();
location.reload();
}
});
});
});
$('#mm_mescole').select2({
theme: 'bootstrap-5',
dropdownParent: $('#matriceMescoleModal'),
placeholder: 'Cerca e seleziona le mescole...',
minimumResultsForSearch: 0,
width: '100%'
});
$(document).on("click", ".mescole", function() {
const idmatrice = $(this).data("id");
const nome = $(this).data("nome") || "";
$("#mm_idmatrice").val(idmatrice);
$("#mm_matrice_name").text(nome);
const $sel = $("#mm_mescole");
$sel.empty();
const loadingOption = new Option("Caricamento mescole...", "", false, false);
$sel.append(loadingOption).trigger("change");
const modalEl = document.getElementById('matriceMescoleModal');
const modal = new bootstrap.Modal(modalEl);
modal.show();
fetch("get_matrice_mescole.php?id=" + encodeURIComponent(idmatrice))
.then(r => {
if (!r.ok) {
throw new Error("HTTP " + r.status);
}
return r.json();
})
.then(data => {
if (!data.success) {
throw new Error(data.message || "Impossibile caricare le mescole");
}
$sel.empty();
(data.mescole || []).forEach(m => {
const label = m.nomeuscita ? `${m.nome} (${m.nomeuscita})` : m.nome;
const opt = new Option(label, m.id, false, false);
$sel.append(opt);
});
$sel.val((data.selected_ids || []).map(String)).trigger("change");
})
.catch(err => {
console.error("Errore caricamento mescole:", err);
$sel.empty().trigger("change");
Swal.fire({
icon: "error",
title: "Errore",
text: err.message || "Errore durante il caricamento delle mescole"
});
});
});
$("#mm_save_btn").on("click", function() {
const idmatrice = $("#mm_idmatrice").val();
const selected = $("#mm_mescole").val() || [];
fetch("save_matrice_mescole.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: "idmatrice=" + encodeURIComponent(idmatrice) +
"&idmescole=" + encodeURIComponent(JSON.stringify(selected))
})
.then(r => r.json())
.then(data => {
Swal.fire({
icon: data.success ? "success" : "error",
title: data.success ? "Salvato!" : "Errore",
text: data.message || ""
}).then(() => {
if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('matriceMescoleModal')).hide();
location.reload();
}
});
});
});
$(document).on('click', '.show-mescole', function() {
const nome = $(this).data('nome') || '';
const mescole = $(this).data('mescole') || '';
$("#mlm_matrice_name").text(nome);
if (!mescole.trim()) {
$("#mlm_list").html("<span class='text-muted'>Nessuna mescola associata</span>");
} else {
const items = mescole.split(',').map(s => s.trim()).filter(Boolean);
$("#mlm_list").html(items.map(x => `<span class="badge bg-secondary me-1 mb-1">${escapeHtml(x)}</span>`).join(''));
}
new bootstrap.Modal(document.getElementById('mescoleListModal')).show();
});
$(document).on("click", ".files", function() {
const idmatrice = $(this).data("id");
const nome = $(this).data("nome") || "";
$("#mf_idmatrice").val(idmatrice);
$("#mf_matrice_name").text(nome);
selectedAttachmentFiles = [];
renderSelectedAttachments();
loadExistingAttachments(idmatrice);
new bootstrap.Modal(document.getElementById('matriceFilesModal')).show();
});
$("#mf_pick_files_btn, #attachmentsDropzone").on("click", function() {
$("#mf_files_input").trigger("click");
});
$("#mf_files_input").on("change", function() {
if (this.files && this.files.length) {
addFilesToSelection(this.files);
}
$(this).val('');
});
$("#attachmentsDropzone").on("dragenter dragover", function(e) {
e.preventDefault();
e.stopPropagation();
$(this).addClass("dragover");
});
$("#attachmentsDropzone").on("dragleave", function(e) {
e.preventDefault();
e.stopPropagation();
$(this).removeClass("dragover");
});
$("#attachmentsDropzone").on("drop", function(e) {
e.preventDefault();
e.stopPropagation();
$(this).removeClass("dragover");
const dt = e.originalEvent.dataTransfer;
if (dt && dt.files && dt.files.length) {
addFilesToSelection(dt.files);
}
});
$(document).on("input", ".selected-file-desc", function() {
const index = parseInt($(this).data("index"), 10);
if (!isNaN(index) && selectedAttachmentFiles[index]) {
selectedAttachmentFiles[index].description = $(this).val();
}
});
$(document).on("click", ".remove-selected-file", function() {
const index = parseInt($(this).data("index"), 10);
if (!isNaN(index)) {
selectedAttachmentFiles.splice(index, 1);
renderSelectedAttachments();
}
});
$("#mf_clear_selected_btn").on("click", function() {
selectedAttachmentFiles = [];
renderSelectedAttachments();
});
$("#mf_refresh_existing_btn").on("click", function() {
const idmatrice = $("#mf_idmatrice").val();
if (idmatrice) {
loadExistingAttachments(idmatrice);
}
});
$("#mf_upload_btn").on("click", function() {
const idmatrice = $("#mf_idmatrice").val();
if (!idmatrice) {
Swal.fire({
icon: "error",
title: "Errore",
text: "ID matrice non valido"
});
return;
}
if (!selectedAttachmentFiles.length) {
Swal.fire({
icon: "warning",
title: "Nessun file selezionato",
text: "Seleziona almeno un file da caricare"
});
return;
}
const formData = new FormData();
formData.append("idmatrice", idmatrice);
selectedAttachmentFiles.forEach((item, idx) => {
formData.append("files[]", item.file);
formData.append("descriptions[]", item.description || "");
});
fetch("upload_matrice_attachments.php", {
method: "POST",
body: formData
})
.then(r => r.json())
.then(data => {
Swal.fire({
icon: data.success ? "success" : "error",
title: data.success ? "Upload completato" : "Errore",
text: data.message || ""
}).then(() => {
if (data.success) {
selectedAttachmentFiles = [];
renderSelectedAttachments();
loadExistingAttachments(idmatrice);
}
});
})
.catch(() => {
Swal.fire({
icon: "error",
title: "Errore",
text: "Errore durante il caricamento dei file"
});
});
});
$(document).on("click", ".delete-existing-file", function() {
const id = $(this).data("id");
const idmatrice = $("#mf_idmatrice").val();
Swal.fire({
icon: "warning",
title: "Eliminare questo allegato?",
showCancelButton: true,
confirmButtonColor: "#d33",
confirmButtonText: "Sì, elimina",
cancelButtonText: "Annulla"
}).then(res => {
if (!res.isConfirmed) return;
fetch("delete_matrice_attachment.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: "id=" + encodeURIComponent(id)
})
.then(r => r.json())
.then(data => {
Swal.fire({
icon: data.success ? "success" : "error",
title: data.success ? "Eliminato" : "Errore",
text: data.message || ""
}).then(() => {
if (data.success) {
loadExistingAttachments(idmatrice);
}
});
});
});
});
$(document).on("click", ".preview-existing-file", function() {
const file = $(this).data("file") || "";
const name = $(this).data("name") || "Anteprima file";
openFilePreview(file, name);
});
document.getElementById('filePreviewModal').addEventListener('hidden.bs.modal', function() {
$("#filePreviewContainer").html("");
});
// OPEN WORKSHEETS LIST
$(document).on("click", ".worksheets, .show-worksheets", function() {
const idmatrice = $(this).data("id");
const nome = $(this).data("nome") || "";
loadMatriceWorksheets(idmatrice, nome);
new bootstrap.Modal(document.getElementById('worksheetsListModal')).show();
});
// OPEN WORKSHEET DETAIL
$(document).on("click", ".open-worksheet-detail", function() {
const worksheetId = $(this).data("id");
loadWorksheetDetail(worksheetId);
new bootstrap.Modal(document.getElementById('worksheetDetailModal')).show();
});
});
$(document).on("click", ".thumb-img", function() {
let full = $(this).data("full");
$("#bigImg").attr("src", full);
$("#bigView").css("display", "flex").hide().fadeIn(200);
});
$(document).on("click", "#bigView", function() {
$(this).fadeOut(200, function() {
$(this).css("display", "none");
});
});
</script>
</body>
</html>