fixed dettaglio worksheet

This commit is contained in:
2026-10-20 08:59:38 +02:00
parent 3209ac969f
commit 3f5c14836f
3 changed files with 584 additions and 220 deletions
@@ -1,6 +1,6 @@
(function () {
function escapeHtml(str) {
return String(str || "")
return String(str === null || str === undefined ? "" : str)
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
@@ -8,16 +8,57 @@
.replace(/'/g, "&#039;");
}
function valueOrDash(v) {
return v === null || v === undefined || String(v).trim() === ""
? "—"
: escapeHtml(v);
function isEmpty(v) {
return v === null || v === undefined || String(v).trim() === "";
}
function renderReadonlyField(label, value) {
function valueOrDash(v) {
return isEmpty(v) ? "—" : escapeHtml(v);
}
// Traduce il valore salvato (es. "every_2h") nell'etichetta del menu a tendina,
// usando le etichette di ws_lookup_options restituite dall'endpoint.
// Se l'etichetta non c'è, mostra il valore così com'è.
function lookupLabel(lookups, category, value) {
if (isEmpty(value)) return "";
const map = lookups && lookups[category];
if (map && map[String(value)] !== undefined) return map[String(value)];
return value;
}
// Campo in sola lettura: etichetta piccola sopra, valore in evidenza sotto
function wsdField(label, value, extraClass) {
const empty = isEmpty(value);
return `
<div class="readonly-label">${escapeHtml(label)}</div>
<div class="readonly-value">${valueOrDash(value)}</div>
<div class="wsd-field ${extraClass || ""}">
<div class="wsd-label">${escapeHtml(label)}</div>
<div class="wsd-value ${empty ? "wsd-empty" : ""}">${valueOrDash(value)}</div>
</div>
`;
}
// Blocco di testo lungo (note, misure di controllo)
function wsdText(label, value, emptyText) {
const empty = isEmpty(value);
return `
<div class="wsd-text-block">
${label ? `<div class="wsd-label mb-1">${escapeHtml(label)}</div>` : ""}
<div class="wsd-text ${empty ? "wsd-empty" : ""}">${empty ? escapeHtml(emptyText || "—") : escapeHtml(value)}</div>
</div>
`;
}
function wsdSection(theme, title, badge, body, colClass) {
return `
<div class="${colClass || "col-12"}">
<div class="wsd-section wsd-${theme}">
<div class="wsd-head">
<span>${title}</span>
${badge ? `<span class="wsd-head-badge">${badge}</span>` : ""}
</div>
<div class="wsd-body">${body}</div>
</div>
</div>
`;
}
@@ -292,10 +333,316 @@
});
}
// ================================================================
// Dettaglio foglio di lavoro (sola lettura)
// Rispecchia le sezioni di manage-worksheet.php:
// 1 Dati principali · 2 Controlli · 3 Imballo · 4 Velocità · 5 Note · 6 Mescole
// ================================================================
function renderWorksheetDetail(data) {
const ws = data.worksheet || {};
const mixRows = data.mix_rows || [];
const packageRows = data.package_rows || [];
const lookups = data.lookups || {};
const isInactive = ws.worksheet_status === "inactive";
const statusLabel =
ws.worksheet_status_label || (isInactive ? "Inattivo" : "Attivo");
const statusBadge = isInactive
? `<span class="worksheet-badge-status-inactive">${escapeHtml(statusLabel)}</span>`
: `<span class="worksheet-badge-status-active">${escapeHtml(statusLabel)}</span>`;
const cliente = ws.customer_name || ws.matrice_cliente || "";
// ---------- Intestazione ----------
const excelLink = ws.source_xlsx_path
? `<a href="download_worksheet_file.php?id=${encodeURIComponent(ws.id || "")}"
target="_blank" class="worksheet-open-link">
<i class="fa-solid fa-file-excel"></i> Excel di origine
</a>`
: "";
const headerHtml = `
<div class="worksheet-title-box mb-3">
<div class="d-flex justify-content-between align-items-center gap-3 flex-wrap">
<div>
<h4 class="mb-1">${escapeHtml(ws.worksheet_number_label || "—")} / Rev. ${escapeHtml(ws.revision_label || "0")}</h4>
<small>${ws.matrice_nome ? "Profilo: " + escapeHtml(ws.matrice_nome) : "Nessun profilo collegato"}</small>
</div>
<div class="d-flex gap-2 flex-wrap">
${excelLink}
<a href="manage-worksheet.php?id=${encodeURIComponent(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>
`;
// ---------- Striscia riepilogo ----------
const summaryHtml = `
<div class="wsd-summary mb-3">
<div class="wsd-sum-item">
<div class="wsd-label">Stato</div>
<div>${statusBadge}</div>
</div>
<div class="wsd-sum-item">
<div class="wsd-label">Cliente</div>
<div class="wsd-sum-value">${valueOrDash(cliente)}</div>
</div>
<div class="wsd-sum-item">
<div class="wsd-label">Codice profilo</div>
<div class="wsd-sum-value">${valueOrDash(ws.profile_type_code)}</div>
</div>
<div class="wsd-sum-item">
<div class="wsd-label">Data foglio</div>
<div class="wsd-sum-value">${valueOrDash(ws.worksheet_date_it)}</div>
</div>
<div class="wsd-sum-item">
<div class="wsd-label">Mescole / Imballi</div>
<div class="wsd-sum-value">${mixRows.length} / ${packageRows.length}</div>
</div>
</div>
`;
// ---------- 1. Dati principali ----------
const sec1 = wsdSection(
"blue",
"1. Dati principali",
"",
`
<div class="wsd-fields">
${wsdField("Matrice / Profilo", ws.matrice_nome)}
${wsdField("Data foglio", ws.worksheet_date_it)}
${wsdField("Codice profilo", ws.profile_type_code)}
${wsdField("Cliente (override)", ws.customer_name)}
${wsdField("Marchiatura", lookupLabel(lookups, "marking", ws.marking))}
${wsdField("Visto / Approvazione", ws.approved_by)}
</div>
<div class="wsd-meta">
Creato il ${valueOrDash(ws.created_at_it)} · Ultimo aggiornamento ${valueOrDash(ws.updated_at_it)}
</div>
`,
"col-lg-6",
);
// ---------- 2. Controlli di produzione ----------
const sec2 = wsdSection(
"green",
"2. Controlli di produzione",
"",
`
<div class="wsd-fields">
${wsdField("Taglio", lookupLabel(lookups, "control_frequency_cut", ws.control_frequency_cut))}
${wsdField("Disegno", lookupLabel(lookups, "control_frequency_drawing", ws.control_frequency_drawing))}
${wsdField("Dima", lookupLabel(lookups, "control_frequency_jig", ws.control_frequency_jig))}
${wsdField("Modalità dima", lookupLabel(lookups, "control_mode_jig", ws.control_mode_jig))}
</div>
${wsdText("Impostazione misure controllo produzione", ws.prod_control_measure_settings, "Nessuna indicazione")}
`,
"col-lg-6",
);
// ---------- 3. Imballo e confezionamento ----------
let packagesHtml = `<div class="wsd-empty mb-2">Nessun imballo associato</div>`;
if (packageRows.length) {
packagesHtml = `
<div class="table-responsive mb-3">
<table class="table table-sm table-striped align-middle mb-0 wsd-table">
<thead>
<tr>
<th style="width:60px;">Pos</th>
<th style="width:200px;">Categoria</th>
<th>Imballo</th>
</tr>
</thead>
<tbody>
${packageRows
.map(
(p) => `
<tr>
<td>${valueOrDash(p.package_position)}</td>
<td>${valueOrDash(p.package_category)}</td>
<td>
<div class="fw-bold">${valueOrDash(p.package_name)}</div>
${p.package_item_code ? `<div class="small text-muted">${escapeHtml(p.package_item_code)}</div>` : ""}
</td>
</tr>`,
)
.join("")}
</tbody>
</table>
</div>
`;
}
const sec3 = wsdSection(
"orange",
"3. Imballo e confezionamento",
`${packageRows.length} imball${packageRows.length === 1 ? "o" : "i"}`,
`
<div class="row g-3">
<div class="col-lg-6">
${packagesHtml}
</div>
<div class="col-lg-6">
<div class="wsd-fields">
${wsdField("Metri per confezione", ws.meters_per_package)}
${wsdField("Tolleranza metri/conf.", ws.meters_per_package_tolerance)}
${wsdField("Conf./pezzi per scatola", ws.packages_or_pieces_per_box)}
${wsdField("Metri per scatola", ws.meters_per_box)}
${wsdField("Scatole/conf. per bancale", ws.boxes_or_packages_per_pallet)}
</div>
${wsdText("Note metri/confezione", ws.meters_per_package_notes, "Nessuna nota")}
</div>
</div>
`,
"col-12",
);
// ---------- 4. Velocità di produzione ----------
const sec4 = wsdSection(
"purple",
"4. Velocità di produzione",
"",
`
<table class="wsd-speed">
<thead>
<tr><th></th><th>Prevista</th><th>Effettiva</th></tr>
</thead>
<tbody>
<tr>
<th>kg/h</th>
<td class="${isEmpty(ws.speed_expected_kg_h) ? "wsd-empty" : ""}">${valueOrDash(ws.speed_expected_kg_h)}</td>
<td class="${isEmpty(ws.speed_actual_kg_h) ? "wsd-empty" : ""}">${valueOrDash(ws.speed_actual_kg_h)}</td>
</tr>
<tr>
<th>m/h</th>
<td class="${isEmpty(ws.speed_expected_m_h) ? "wsd-empty" : ""}">${valueOrDash(ws.speed_expected_m_h)}</td>
<td class="${isEmpty(ws.speed_actual_m_h) ? "wsd-empty" : ""}">${valueOrDash(ws.speed_actual_m_h)}</td>
</tr>
</tbody>
</table>
`,
"col-lg-6",
);
// ---------- 5. Note finali ----------
const sec5 = wsdSection(
"gray",
"5. Note finali",
"",
wsdText("", ws.notes, "Nessuna nota"),
"col-lg-6",
);
// ---------- 6. Mescole ----------
let mixHtml = `<div class="wsd-empty">Nessuna mescola associata</div>`;
if (mixRows.length) {
mixHtml = `
<div class="table-responsive">
<table class="table table-striped align-middle mb-0 wsd-table">
<thead>
<tr>
<th style="width:60px;">Pos</th>
<th>Mescola</th>
<th style="width:120px;">Peso g/m</th>
<th style="width:150px;">Densità</th>
<th style="width:150px;">Durezza (Sh A)</th>
<th style="width:150px;">Lubrificazione</th>
<th>Note lubr.</th>
</tr>
</thead>
<tbody>
${mixRows
.map(
(r) => `
<tr>
<td>${valueOrDash(r.mix_position)}</td>
<td>
<div class="fw-bold">${valueOrDash(r.mescola_nome)}</div>
${r.mescola_uscita ? `<div class="small text-muted">${escapeHtml(r.mescola_uscita)}</div>` : ""}
</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(lookupLabel(lookups, "lubrication_type", r.lubrication_type))}</td>
<td>${valueOrDash(r.lubrication_notes)}</td>
</tr>`,
)
.join("")}
</tbody>
</table>
</div>
`;
}
const sec6 = wsdSection(
"cyan",
"6. Mescole associate al foglio",
`${mixRows.length} rig${mixRows.length === 1 ? "a" : "he"}`,
mixHtml,
"col-12",
);
const datiHtml = `
${summaryHtml}
<div class="row g-3">
${sec1}
${sec2}
${sec3}
${sec4}
${sec5}
${sec6}
</div>
`;
// ---------- Tab allegati ----------
const attTabsHtml = Object.keys(WS_ATT_KINDS)
.map((k) => {
const c = WS_ATT_KINDS[k];
return `
<button type="button" class="ws-tab" data-tab="${k}">
${c.icon} ${escapeHtml(c.label)} <span class="ws-tab-count" id="wsCount_${k}">…</span>
</button>`;
})
.join("");
const attPanesHtml = Object.keys(WS_ATT_KINDS)
.map((k) => {
const c = WS_ATT_KINDS[k];
return `
<div class="ws-tab-pane" data-pane="${k}">
<div id="wsContainer_${k}">
<div class="text-muted">Caricamento ${escapeHtml(c.label.toLowerCase())}...</div>
</div>
</div>`;
})
.join("");
return `
${headerHtml}
<div class="ws-tabs">
<button type="button" class="ws-tab active" data-tab="dati">
<i class="fa-solid fa-file-lines me-1"></i> Dati foglio
</button>
${attTabsHtml}
</div>
<div class="ws-tab-pane active" data-pane="dati">
${datiHtml}
</div>
${attPanesHtml}
`;
}
window.loadWorksheetDetail = function (worksheetId, endpoint) {
endpoint = endpoint || "ajax/worksheet-linked-data.php";
$("#worksheetDetailContainer").html(
const $box = $("#worksheetDetailContainer");
$box.html(
'<div class="text-muted">Caricamento dettaglio foglio...</div>',
);
@@ -317,10 +664,9 @@
})
.then((data) => {
if (!data.success) {
console.error("Errore worksheets:", data);
$("#worksheetsListContainer").html(
'<div class="text-danger">Errore nel caricamento dei fogli: ' +
console.error("Errore dettaglio foglio:", data);
$box.html(
'<div class="text-danger">Errore nel caricamento del foglio: ' +
escapeHtml(
data.message || "nessun dettaglio restituito",
) +
@@ -329,213 +675,13 @@
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 headerHtml = `
<div class="worksheet-title-box mb-3">
<div class="d-flex justify-content-between align-items-start gap-3 flex-wrap">
<div>
<h4 class="mb-1">${escapeHtml(ws.worksheet_number_label || "—")} / Rev. ${escapeHtml(ws.revision_label || "0")}</h4>
<small>
Stato: ${escapeHtml(ws.worksheet_status_label || "Attivo")}
${ws.matrice_nome ? " • 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>
`;
const html = `
<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("Foglio", ws.worksheet_number_label)}
${renderReadonlyField("Revisione", ws.revision_label)}
${renderReadonlyField("Stato", ws.worksheet_status_label)}
${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>
`;
const attTabsHtml = Object.keys(WS_ATT_KINDS)
.map((k) => {
const c = WS_ATT_KINDS[k];
return `
<button type="button" class="ws-tab" data-tab="${k}">
${c.icon} ${escapeHtml(c.label)} <span class="ws-tab-count" id="wsCount_${k}">…</span>
</button>`;
})
.join("");
const attPanesHtml = Object.keys(WS_ATT_KINDS)
.map((k) => {
const c = WS_ATT_KINDS[k];
return `
<div class="ws-tab-pane" data-pane="${k}">
<div id="wsContainer_${k}">
<div class="text-muted">Caricamento ${escapeHtml(c.label.toLowerCase())}...</div>
</div>
</div>`;
})
.join("");
const fullHtml = `
${headerHtml}
<div class="ws-tabs">
<button type="button" class="ws-tab active" data-tab="dati">
<i class="fa-solid fa-file-lines me-1"></i> Dati foglio
</button>
${attTabsHtml}
</div>
<div class="ws-tab-pane active" data-pane="dati">
${html}
</div>
${attPanesHtml}
`;
$("#worksheetDetailContainer").html(fullHtml);
loadWorksheetAttachments(ws.idmatrice);
$box.html(renderWorksheetDetail(data));
loadWorksheetAttachments((data.worksheet || {}).idmatrice);
})
.catch((err) => {
console.error("Catch loadMatriceWorksheets:", err);
$("#worksheetsListContainer").html(
'<div class="text-danger">Errore nel caricamento dei fogli: ' +
console.error("Errore dettaglio foglio:", err);
$box.html(
'<div class="text-danger">Errore nel caricamento del foglio: ' +
escapeHtml(err.message || "errore JavaScript/fetch") +
"</div>",
);