diff --git a/public/userarea/gridRenderer.js b/public/userarea/gridRenderer.js
index 502c21d..57ce0e0 100644
--- a/public/userarea/gridRenderer.js
+++ b/public/userarea/gridRenderer.js
@@ -6,7 +6,7 @@
* then re-renders visible rows. Save reads from gridData, not DOM.
*/
(function () {
- 'use strict';
+ "use strict";
const PAGE_SIZE = 20;
let revealedCount = PAGE_SIZE;
@@ -30,14 +30,14 @@
// ── Helpers ─────────────────────────────────────────────────────────────
function esc(str) {
- if (str === null || str === undefined) return '';
- const d = document.createElement('div');
+ if (str === null || str === undefined) return "";
+ const d = document.createElement("div");
d.textContent = String(str);
return d.innerHTML;
}
function getDetailValue(rowIndex, mappingId) {
- return data[rowIndex].details[String(mappingId)] ?? '';
+ return data[rowIndex].details[String(mappingId)] ?? "";
}
function setDetailValue(rowIndex, mappingId, value) {
@@ -46,7 +46,7 @@
}
function getFixedValue(rowIndex, key) {
- return data[rowIndex].fixedFields[key] ?? '';
+ return data[rowIndex].fixedFields[key] ?? "";
}
function setFixedValue(rowIndex, key, value) {
@@ -57,7 +57,7 @@
// ── Client data (AJAX Select2, no bulk loading) ──────────────────────
function formatClientLabel(client) {
- return (client.Nominativo || '').trim();
+ return (client.Nominativo || "").trim();
}
// Cache of resolved client names: id → name
@@ -76,47 +76,59 @@
// Pre-resolve all unique client IDs used in data rows, then re-render
async function resolveClientNames() {
const ids = new Set();
- data.forEach(row => {
+ data.forEach((row) => {
if (row.idclient) ids.add(String(row.idclient));
- if (row.cliente_fornitore_id) ids.add(String(row.cliente_fornitore_id));
+ if (row.cliente_fornitore_id)
+ ids.add(String(row.cliente_fornitore_id));
// Fixed fields that are client-sourced
if (row.fixedFields) {
for (const [key, val] of Object.entries(row.fixedFields)) {
const cfg = fixedFieldApiConfig[key];
- if (cfg && cfg.source === 'clients' && val) ids.add(String(val));
+ if (cfg && cfg.source === "clients" && val)
+ ids.add(String(val));
}
}
});
// Batch resolve via single request per ID
- const unresolvedIds = [...ids].filter(id => !clientNameCache[id]);
+ const unresolvedIds = [...ids].filter((id) => !clientNameCache[id]);
if (unresolvedIds.length === 0) return;
- await Promise.all(unresolvedIds.map(async (id) => {
- try {
- const resp = await fetch('search_clienti.php?id=' + encodeURIComponent(id));
- const json = await resp.json();
- const item = (json.results || [])[0];
- if (item) clientNameCache[id] = item.text;
- } catch (e) { /* ignore */ }
- }));
+ await Promise.all(
+ unresolvedIds.map(async (id) => {
+ try {
+ const resp = await fetch(
+ "search_clienti.php?id=" + encodeURIComponent(id),
+ );
+ const json = await resp.json();
+ const item = (json.results || [])[0];
+ if (item) clientNameCache[id] = item.text;
+ } catch (e) {
+ /* ignore */
+ }
+ }),
+ );
}
// Select2 AJAX config for client selects
const clientSelect2Config = {
- placeholder: 'Search client...',
+ placeholder: "Search client...",
allowClear: true,
- width: '100%',
+ width: "100%",
minimumInputLength: 0,
- dropdownCssClass: 'select2-dropdown-smaller',
+ dropdownCssClass: "select2-dropdown-smaller",
ajax: {
- url: 'search_clienti.php',
- dataType: 'json',
+ url: "search_clienti.php",
+ dataType: "json",
delay: 150,
- data: function(params) { return { q: params.term || '', limit: 20 }; },
- processResults: function(data) { return { results: data.results || [] }; },
- cache: true
- }
+ data: function (params) {
+ return { q: params.term || "", limit: 20 };
+ },
+ processResults: function (data) {
+ return { results: data.results || [] };
+ },
+ cache: true,
+ },
};
async function loadClientData() {
@@ -128,12 +140,30 @@
// ── Fixed field data loading ───────────────────────────────────────────
const fixedFieldApiConfig = {
- MoltiplicatorePrezzo: { endpoint: 'MoltiplicatorePrezzo', idKey: 'IdMoltiplicatorePrezzo', textKey: 'Descrizione' },
- AnagraficaCertestObject: { endpoint: 'AnagraficaCertestObject', idKey: 'IdAnagrafica', textKey: 'NomeAnagrafica' },
- AnagraficaCertestService: { endpoint: 'AnagraficaCertestService', idKey: 'IdAnagrafica', textKey: 'NomeAnagrafica' },
- ClienteResponsabile: { endpoint: 'ClienteResponsabile', idKey: 'IdClienteResponsabile', textKey: 'Nominativo', dependsOn: 'idclient', getParams: (cid) => ({ id_cliente: cid }) },
- ClienteFornitore: { source: 'clients' },
- ClienteAnalisi: { source: 'clients' },
+ MoltiplicatorePrezzo: {
+ endpoint: "MoltiplicatorePrezzo",
+ idKey: "IdMoltiplicatorePrezzo",
+ textKey: "Descrizione",
+ },
+ AnagraficaCertestObject: {
+ endpoint: "AnagraficaCertestObject",
+ idKey: "IdAnagrafica",
+ textKey: "NomeAnagrafica",
+ },
+ AnagraficaCertestService: {
+ endpoint: "AnagraficaCertestService",
+ idKey: "IdAnagrafica",
+ textKey: "NomeAnagrafica",
+ },
+ ClienteResponsabile: {
+ endpoint: "ClienteResponsabile",
+ idKey: "IdClienteResponsabile",
+ textKey: "Nominativo",
+ dependsOn: "idclient",
+ getParams: (cid) => ({ id_cliente: cid }),
+ },
+ ClienteFornitore: { source: "clients" },
+ ClienteAnalisi: { source: "clients" },
};
let _pendingFixed = {};
@@ -143,12 +173,12 @@
if (!config) return [];
// Client-sourced fields — handled by AJAX Select2, skip preloading
- if (config.source === 'clients') {
+ if (config.source === "clients") {
fixedFieldCache[fieldKey] = [];
return [];
}
- const cacheKey = fieldKey + (clientId ? '_' + clientId : '');
+ const cacheKey = fieldKey + (clientId ? "_" + clientId : "");
if (fixedFieldCache[cacheKey]) return fixedFieldCache[cacheKey];
if (_pendingFixed[cacheKey]) return _pendingFixed[cacheKey];
@@ -159,19 +189,32 @@
_pendingFixed[cacheKey] = (async () => {
try {
- const resp = await fetch('get_fixed_field_data.php?' + new URLSearchParams(params));
+ const resp = await fetch(
+ "get_fixed_field_data.php?" + new URLSearchParams(params),
+ );
const json = await resp.json();
- let items = (fieldKey === 'ClienteResponsabile') ? (json.Responsabili || []) : (json.value || json.d?.results || json || []);
- const results = items.map(item => ({
- id: item[config.idKey],
- text: (item.Codice ? item.Codice + ' - ' : '') + (item[config.textKey] || '')
- })).sort((a, b) => String(a.text).localeCompare(String(b.text), 'it', { sensitivity: 'base' }));
+ let items =
+ fieldKey === "ClienteResponsabile"
+ ? json.Responsabili || []
+ : json.value || json.d?.results || json || [];
+ const results = items
+ .map((item) => ({
+ id: item[config.idKey],
+ text:
+ (item.Codice ? item.Codice + " - " : "") +
+ (item[config.textKey] || ""),
+ }))
+ .sort((a, b) =>
+ String(a.text).localeCompare(String(b.text), "it", {
+ sensitivity: "base",
+ }),
+ );
fixedFieldCache[cacheKey] = results;
delete _pendingFixed[cacheKey];
return results;
} catch (e) {
delete _pendingFixed[cacheKey];
- console.error('Failed to load fixed field ' + fieldKey, e);
+ console.error("Failed to load fixed field " + fieldKey, e);
return [];
}
})();
@@ -181,22 +224,29 @@
// ── Custom field dropdown data loading ─────────────────────────────────
-
// Select2 AJAX config factory for SceltaMultipla
function sceltaSelect2Config(fieldId) {
return {
- placeholder: 'Search...',
+ placeholder: "Search...",
allowClear: true,
- width: '100%',
+ width: "100%",
minimumInputLength: 0,
ajax: {
- url: 'search_customfield_values.php',
- dataType: 'json',
+ url: "search_customfield_values.php",
+ dataType: "json",
delay: 150,
- data: function(params) { return { field_id: fieldId, q: params.term || '', limit: 10 }; },
- processResults: function(data) { return { results: data.results || [] }; },
- cache: true
- }
+ data: function (params) {
+ return {
+ field_id: fieldId,
+ q: params.term || "",
+ limit: 10,
+ };
+ },
+ processResults: function (data) {
+ return { results: data.results || [] };
+ },
+ cache: true,
+ },
};
}
@@ -207,19 +257,27 @@
await loadClientData();
// 2. Non-dependent fixed fields
- const nonDependent = Object.keys(fixedFieldApiConfig).filter(k => !fixedFieldApiConfig[k].dependsOn && !fixedFieldApiConfig[k].source);
- await Promise.all(nonDependent.map(k => loadFixedFieldOptions(k)));
+ const nonDependent = Object.keys(fixedFieldApiConfig).filter(
+ (k) =>
+ !fixedFieldApiConfig[k].dependsOn &&
+ !fixedFieldApiConfig[k].source,
+ );
+ await Promise.all(nonDependent.map((k) => loadFixedFieldOptions(k)));
// Client-sourced fixed fields
- const clientSourced = Object.keys(fixedFieldApiConfig).filter(k => fixedFieldApiConfig[k].source === 'clients');
- await Promise.all(clientSourced.map(k => loadFixedFieldOptions(k)));
+ const clientSourced = Object.keys(fixedFieldApiConfig).filter(
+ (k) => fixedFieldApiConfig[k].source === "clients",
+ );
+ await Promise.all(clientSourced.map((k) => loadFixedFieldOptions(k)));
// 3. Dependent fixed fields — collect unique clientIds
const clientIds = new Set();
- data.forEach(row => {
+ data.forEach((row) => {
if (row.idclient) clientIds.add(String(row.idclient));
});
- const dependent = Object.keys(fixedFieldApiConfig).filter(k => fixedFieldApiConfig[k].dependsOn);
+ const dependent = Object.keys(fixedFieldApiConfig).filter(
+ (k) => fixedFieldApiConfig[k].dependsOn,
+ );
for (const fieldKey of dependent) {
for (const cid of clientIds) {
await loadFixedFieldOptions(fieldKey, cid);
@@ -227,28 +285,41 @@
}
// 4. Warm server cache + resolve SceltaMultipla names in one request
- const allFieldIds = [...new Set(
- columns.filter(c => (c.type === 'detail' || c.type === 'main_field') && c.dataType === 'SceltaMultipla' && c.fieldId)
- .map(c => String(c.fieldId))
- )];
+ const allFieldIds = [
+ ...new Set(
+ columns
+ .filter(
+ (c) =>
+ (c.type === "detail" || c.type === "main_field") &&
+ c.dataType === "SceltaMultipla" &&
+ c.fieldId,
+ )
+ .map((c) => String(c.fieldId)),
+ ),
+ ];
if (allFieldIds.length > 0) {
try {
- const resp = await fetch('get_customfield_values.php?field_ids=' + allFieldIds.join(','));
+ const resp = await fetch(
+ "get_customfield_values.php?field_ids=" +
+ allFieldIds.join(","),
+ );
const json = await resp.json();
const entries = json.data ? json.data : json;
for (const [fid, values] of Object.entries(entries)) {
if (Array.isArray(values)) {
- values.forEach(v => {
- dropdownNameCache[fid + '_' + v.IdCustomFieldsValue] = v.Valore || '';
+ values.forEach((v) => {
+ dropdownNameCache[
+ fid + "_" + v.IdCustomFieldsValue
+ ] = v.Valore || "";
});
}
}
} catch (e) {
- console.error('Failed to preload dropdown data:', e);
+ console.error("Failed to preload dropdown data:", e);
}
}
- console.log('[gridRenderer] preload done:', {
+ console.log("[gridRenderer] preload done:", {
clients: clientData.length,
fixedFieldCache: Object.keys(fixedFieldCache),
dropdownOptionsCache: Object.keys(dropdownOptionsCache),
@@ -258,8 +329,8 @@
// ── Cell rendering ─────────────────────────────────────────────────────
function createCell(col, rowIndex, cellIndex) {
- const div = document.createElement('div');
- div.className = 'grid-cell editable-cell';
+ const div = document.createElement("div");
+ div.className = "grid-cell editable-cell";
div.dataset.col = col.key;
div.dataset.colType = col.type;
div.dataset.row = rowIndex;
@@ -269,73 +340,88 @@
const row = data[rowIndex];
switch (col.type) {
- case 'main_field':
- div.innerHTML = createInputHTML(col, row.mainFieldValue || '', rowIndex);
+ case "main_field":
+ div.innerHTML = createInputHTML(
+ col,
+ row.mainFieldValue || "",
+ rowIndex,
+ );
break;
- case 'status': {
- const st = row.status || 'i';
- const label = st === 'i' ? 'Imported' : (st === 'P' ? 'In Progress' : 'To LIMS');
+ case "status": {
+ const st = row.status || "i";
+ const label =
+ st === "i"
+ ? "Imported"
+ : st === "P"
+ ? "In Progress"
+ : "To LIMS";
let html = `${label}`;
if (row.commessaweb) {
html += `${esc(row.commessaweb)}`;
}
div.innerHTML = html;
- div.classList.remove('editable-cell');
+ div.classList.remove("editable-cell");
break;
}
- case 'idclient': {
- const sel = document.createElement('select');
- sel.className = 'cell-input dropdown-select client-select searchable-client';
- sel.dataset.currentValue = row.idclient || '';
+ case "idclient": {
+ const sel = document.createElement("select");
+ sel.className =
+ "cell-input dropdown-select client-select searchable-client";
+ sel.dataset.currentValue = row.idclient || "";
sel.innerHTML = buildClientOptionsHTML(row.idclient);
div.appendChild(sel);
break;
}
- case 'cliente_fornitore_id': {
- const sel = document.createElement('select');
- sel.className = 'cell-input dropdown-select client-select searchable-client fornitore-select';
- sel.dataset.currentValue = row.cliente_fornitore_id || '';
- sel.innerHTML = buildClientOptionsHTML(row.cliente_fornitore_id);
+ case "cliente_fornitore_id": {
+ const sel = document.createElement("select");
+ sel.className =
+ "cell-input dropdown-select client-select searchable-client fornitore-select";
+ sel.dataset.currentValue = row.cliente_fornitore_id || "";
+ sel.innerHTML = buildClientOptionsHTML(
+ row.cliente_fornitore_id,
+ );
div.appendChild(sel);
break;
}
- case 'detail': {
+ case "detail": {
const val = getDetailValue(rowIndex, col.key);
div.innerHTML = createInputHTML(col, val, rowIndex);
break;
}
- case 'tested_component':
- div.style.overflow = 'visible';
+ case "tested_component":
+ div.style.overflow = "visible";
div.innerHTML = `
`;
break;
- case 'awb':
- div.innerHTML = `` +
+ case "awb":
+ div.innerHTML =
+ `` +
`` +
``;
break;
- case 'tracking':
- div.className = 'grid-cell tracking-info';
+ case "tracking":
+ div.className = "grid-cell tracking-info";
div.dataset.row = rowIndex;
- div.innerHTML = 'Shipment Info';
+ div.innerHTML =
+ 'Shipment Info';
break;
- case 'fixed': {
+ case "fixed": {
const val = getFixedValue(rowIndex, col.key);
div.innerHTML = createFixedFieldHTML(col, val, rowIndex);
break;
}
- case 'static': {
- const val = row[col.key] || '';
- div.classList.remove('editable-cell');
- if (col.key === 'filename_import' && val) {
+ case "static": {
+ const val = row[col.key] || "";
+ div.classList.remove("editable-cell");
+ if (col.key === "filename_import" && val) {
div.innerHTML = `File`;
} else {
div.innerHTML = `${esc(val)}`;
@@ -348,39 +434,42 @@
}
function createInputHTML(col, value, rowIndex) {
- const cls = col.isManual ? 'manual-input' : 'auto-input';
- const reqCls = col.isRequired ? ' required-input' : '';
- const req = col.isRequired ? ' required' : '';
+ const cls = col.isManual ? "manual-input" : "auto-input";
+ const reqCls = col.isRequired ? " required-input" : "";
+ const req = col.isRequired ? " required" : "";
const v = esc(value);
- if (col.dataType === 'SceltaMultipla') {
+ if (col.dataType === "SceltaMultipla") {
const options = buildDropdownOptionsHTML(col.fieldId, value);
return ``;
}
- if (col.dataType === 'Data') {
+ if (col.dataType === "Data") {
return ``;
}
- if (col.dataType === 'INT') {
+ if (col.dataType === "INT") {
return ``;
}
- if (col.autoValue === 'import_time' || (meta.timeLabels || []).includes(col.label)) {
+ if (
+ col.autoValue === "import_time" ||
+ (meta.timeLabels || []).includes(col.label)
+ ) {
return ``;
}
return ``;
}
function createFixedFieldHTML(col, value, rowIndex) {
- if (col.dataType === 'DATE') {
- const reqCls = col.isRequired ? ' required-input' : '';
- const req = col.isRequired ? ' required' : '';
+ if (col.dataType === "DATE") {
+ const reqCls = col.isRequired ? " required-input" : "";
+ const req = col.isRequired ? " required" : "";
return ``;
}
// Client-sourced fields → AJAX Select2 (like idclient)
const config = fixedFieldApiConfig[col.key];
- if (config && config.source === 'clients') {
- const reqCls = col.isRequired ? ' required-input' : '';
- const req = col.isRequired ? ' required' : '';
+ if (config && config.source === "clients") {
+ const reqCls = col.isRequired ? " required-input" : "";
+ const req = col.isRequired ? " required" : "";
let opts = '';
if (value) {
const label = clientNameCache[value] || value;
@@ -391,26 +480,28 @@
// Select — build from cache
const isApiField = !!config;
- const selectClass = isApiField ? 'api-fixed-select' : '';
+ const selectClass = isApiField ? "api-fixed-select" : "";
let options = '';
const cacheKey = config?.dependsOn
- ? col.key + '_' + (data[rowIndex].idclient || '')
+ ? col.key + "_" + (data[rowIndex].idclient || "")
: col.key;
const items = fixedFieldCache[cacheKey] || [];
- items.forEach(item => {
- const sel = String(item.id) === String(value) ? ' selected' : '';
+ items.forEach((item) => {
+ const sel = String(item.id) === String(value) ? " selected" : "";
options += ``;
});
- const reqCls = col.isRequired ? ' required-input' : '';
- const req = col.isRequired ? ' required' : '';
+ const reqCls = col.isRequired ? " required-input" : "";
+ const req = col.isRequired ? " required" : "";
return ``;
}
function buildDropdownOptionsHTML(fieldId, selectedValue) {
let html = '';
if (selectedValue) {
- const label = dropdownNameCache[fieldId + '_' + selectedValue] || selectedValue;
+ const label =
+ dropdownNameCache[fieldId + "_" + selectedValue] ||
+ selectedValue;
html += ``;
}
return html;
@@ -420,15 +511,15 @@
function renderActionButtons(rowIndex) {
const row = data[rowIndex];
- const isExported = row.status === 'l';
- const div = document.createElement('div');
- div.className = 'grid-cell button-cell';
- div.style.flex = '0 0 auto';
- div.style.position = 'relative';
+ const isExported = row.status === "l";
+ const div = document.createElement("div");
+ div.className = "grid-cell button-cell";
+ div.style.flex = "0 0 auto";
+ div.style.position = "relative";
- let html = '';
+ let html = "";
if (meta.isAdmin) {
- html += ``;
+ html += ``;
}
html += ``;
html += ``;
@@ -441,57 +532,78 @@
function renderRow(rowIndex) {
const row = data[rowIndex];
- const rowDiv = document.createElement('div');
- rowDiv.className = 'grid-row';
+ const rowDiv = document.createElement("div");
+ rowDiv.className = "grid-row";
rowDiv.dataset.id = row.iddatadb;
- if (row._dirty) rowDiv.classList.add('row-dirty');
+ if (row._dirty) rowDiv.classList.add("row-dirty");
// Action buttons
rowDiv.appendChild(renderActionButtons(rowIndex));
// All columns
let cellIndex = 1;
- columns.forEach(col => {
+ columns.forEach((col) => {
rowDiv.appendChild(createCell(col, rowIndex, cellIndex));
cellIndex++;
});
// Restore validation errors if present
if (row._validationErrors && row._validationErrors.length > 0) {
- rowDiv.classList.add('validation-row-error');
+ rowDiv.classList.add("validation-row-error");
const messages = [];
- row._validationErrors.forEach(err => {
+ row._validationErrors.forEach((err) => {
messages.push(err.message);
if (!err.field) return;
let cell = null;
- if (err.field.startsWith('field_label:')) {
- const label = err.field.substring('field_label:'.length);
- const headers = document.querySelectorAll('.grid-header');
+ if (err.field.startsWith("field_label:")) {
+ const label = err.field.substring("field_label:".length);
+ const headers = document.querySelectorAll(".grid-header");
let targetIndex = null;
- headers.forEach(h => { if (h.textContent.trim() === label) targetIndex = h.getAttribute('data-index'); });
- if (targetIndex) cell = rowDiv.querySelector(`.grid-cell[data-index="${targetIndex}"]`);
+ headers.forEach((h) => {
+ if (h.textContent.trim() === label)
+ targetIndex = h.getAttribute("data-index");
+ });
+ if (targetIndex)
+ cell = rowDiv.querySelector(
+ `.grid-cell[data-index="${targetIndex}"]`,
+ );
} else {
- cell = rowDiv.querySelector(`.grid-cell[data-col="${err.field}"]`);
+ cell = rowDiv.querySelector(
+ `.grid-cell[data-col="${err.field}"]`,
+ );
}
if (cell) {
- cell.classList.add('validation-error');
- cell.querySelectorAll('input, select').forEach(el => el.classList.add('input-validation-error'));
- let tooltip = cell.querySelector('.validation-tooltip');
- if (!tooltip) { tooltip = document.createElement('div'); tooltip.className = 'validation-tooltip'; cell.appendChild(tooltip); }
+ cell.classList.add("validation-error");
+ cell.querySelectorAll("input, select").forEach((el) =>
+ el.classList.add("input-validation-error"),
+ );
+ let tooltip = cell.querySelector(".validation-tooltip");
+ if (!tooltip) {
+ tooltip = document.createElement("div");
+ tooltip.className = "validation-tooltip";
+ cell.appendChild(tooltip);
+ }
tooltip.textContent = err.message;
}
});
// Show error msg on button cell
- const btnCell2 = rowDiv.querySelector('.button-cell');
+ const btnCell2 = rowDiv.querySelector(".button-cell");
if (btnCell2) {
- const errorEl = document.createElement('div');
- errorEl.className = 'batch-error-msg';
- errorEl.textContent = 'Warning — click for details';
- errorEl.addEventListener('click', () => {
- document.getElementById('exportResponseMessage').innerHTML = messages.join('
');
- document.getElementById('exportResponseModalLabel').textContent = 'Validation Error (id: ' + row.iddatadb + ')';
- new bootstrap.Modal(document.getElementById('exportResponseModal'), { keyboard: false }).show();
+ const errorEl = document.createElement("div");
+ errorEl.className = "batch-error-msg";
+ errorEl.textContent = "Warning — click for details";
+ errorEl.addEventListener("click", () => {
+ document.getElementById("exportResponseMessage").innerHTML =
+ messages.join("
");
+ document.getElementById(
+ "exportResponseModalLabel",
+ ).textContent =
+ "Validation Error (id: " + row.iddatadb + ")";
+ new bootstrap.Modal(
+ document.getElementById("exportResponseModal"),
+ { keyboard: false },
+ ).show();
});
btnCell2.appendChild(errorEl);
}
@@ -499,16 +611,22 @@
// Restore export error indicator if present
if (row._exportError) {
- rowDiv.classList.add('batch-row-error');
- const btnCell = rowDiv.querySelector('.button-cell');
+ rowDiv.classList.add("batch-row-error");
+ const btnCell = rowDiv.querySelector(".button-cell");
if (btnCell) {
- const errorEl = document.createElement('div');
- errorEl.className = 'batch-error-msg';
- errorEl.textContent = 'Warning — click for details';
- errorEl.addEventListener('click', () => {
- document.getElementById('exportResponseMessage').innerHTML = row._exportError.replace(/\n/g, '
');
- document.getElementById('exportResponseModalLabel').textContent = 'Error (id: ' + row.iddatadb + ')';
- new bootstrap.Modal(document.getElementById('exportResponseModal'), { keyboard: false }).show();
+ const errorEl = document.createElement("div");
+ errorEl.className = "batch-error-msg";
+ errorEl.textContent = "Warning — click for details";
+ errorEl.addEventListener("click", () => {
+ document.getElementById("exportResponseMessage").innerHTML =
+ row._exportError.replace(/\n/g, "
");
+ document.getElementById(
+ "exportResponseModalLabel",
+ ).textContent = "Error (id: " + row.iddatadb + ")";
+ new bootstrap.Modal(
+ document.getElementById("exportResponseModal"),
+ { keyboard: false },
+ ).show();
});
btnCell.appendChild(errorEl);
}
@@ -527,9 +645,9 @@
if (revealedCount < totalRows) revealedCount = totalRows;
// Destroy Select2 on existing rows
- $(rowContainer).find('.select2-hidden-accessible').select2('destroy');
+ $(rowContainer).find(".select2-hidden-accessible").select2("destroy");
- rowContainer.innerHTML = '';
+ rowContainer.innerHTML = "";
const end = Math.min(revealedCount, totalRows);
for (let i = 0; i < end; i++) {
rowContainer.appendChild(renderRow(i));
@@ -543,19 +661,23 @@
}
function renderSingleRow(rowIndex) {
- const existing = rowContainer.querySelector(`.grid-row[data-id="${data[rowIndex].iddatadb}"]`);
+ const existing = rowContainer.querySelector(
+ `.grid-row[data-id="${data[rowIndex].iddatadb}"]`,
+ );
if (!existing) return;
// Destroy Select2 before removing
- $(existing).find('.select2-hidden-accessible').select2('destroy');
+ $(existing).find(".select2-hidden-accessible").select2("destroy");
const newRow = renderRow(rowIndex);
existing.replaceWith(newRow);
// Init flatpickr
- $(newRow).find('.date-picker').each(function () {
- flatpickr(this, { dateFormat: 'Y-m-d', allowInput: true });
- });
+ $(newRow)
+ .find(".date-picker")
+ .each(function () {
+ flatpickr(this, { dateFormat: "Y-m-d", allowInput: true });
+ });
}
function revealNextBatch() {
@@ -570,80 +692,96 @@
}
function initFlatpickr() {
- $(rowContainer).find('.date-picker:not(.flatpickr-input)').each(function () {
- flatpickr(this, { dateFormat: 'Y-m-d', allowInput: true });
- });
+ $(rowContainer)
+ .find(".date-picker:not(.flatpickr-input)")
+ .each(function () {
+ flatpickr(this, { dateFormat: "Y-m-d", allowInput: true });
+ });
}
// ── Headers & Propagate row ────────────────────────────────────────────
function renderHeaders() {
if (!headerContainer) return;
- headerContainer.innerHTML = '';
+ headerContainer.innerHTML = "";
// Actions header
- const actH = document.createElement('div');
- actH.className = 'grid-header button-header';
- actH.style.flex = '0 0 380px';
- actH.textContent = 'Actions';
+ const actH = document.createElement("div");
+ actH.className = "grid-header button-header";
+ actH.style.flex = "0 0 380px";
+ actH.textContent = "Actions";
headerContainer.appendChild(actH);
let idx = 1;
- columns.forEach(col => {
- const h = document.createElement('div');
- h.className = 'grid-header';
+ columns.forEach((col) => {
+ const h = document.createElement("div");
+ h.className = "grid-header";
h.dataset.index = idx;
h.style.flex = `0 0 ${col.width}px`;
- h.style.position = 'relative';
+ h.style.position = "relative";
h.innerHTML = esc(col.label) + '';
headerContainer.appendChild(h);
idx++;
});
+
+ initColumnResizers();
}
function renderTopRow() {
if (!topContainer) return;
- topContainer.innerHTML = '';
+ topContainer.innerHTML = "";
// Empty cell for actions column
- const empty = document.createElement('div');
- empty.className = 'grid-cell save-all-cell';
+ const empty = document.createElement("div");
+ empty.className = "grid-cell save-all-cell";
topContainer.appendChild(empty);
columns.forEach((col, colIdx) => {
- const cell = document.createElement('div');
- cell.className = 'grid-cell grid-top-cell';
+ const cell = document.createElement("div");
+ cell.className = "grid-cell grid-top-cell";
cell.style.flex = `0 0 ${col.width}px`;
- if (col.editable === false || col.type === 'static' || col.type === 'tracking') {
+ if (
+ col.editable === false ||
+ col.type === "static" ||
+ col.type === "tracking"
+ ) {
// Empty top cell
- } else if (col.type === 'idclient') {
- cell.innerHTML = `` +
+ } else if (col.type === "idclient") {
+ cell.innerHTML =
+ `` +
``;
- } else if (col.type === 'cliente_fornitore_id') {
- cell.innerHTML = `` +
+ } else if (col.type === "cliente_fornitore_id") {
+ cell.innerHTML =
+ `` +
``;
- } else if (col.type === 'fixed' && col.dataType === 'DATE') {
- cell.innerHTML = `` +
+ } else if (col.type === "fixed" && col.dataType === "DATE") {
+ cell.innerHTML =
+ `` +
``;
- } else if (col.type === 'fixed') {
+ } else if (col.type === "fixed") {
const isApiField = !!fixedFieldApiConfig[col.key];
if (isApiField) {
- cell.innerHTML = `` +
+ cell.innerHTML =
+ `` +
``;
} else {
- cell.innerHTML = `` +
+ cell.innerHTML =
+ `` +
``;
}
- } else if (col.type === 'detail' || col.type === 'main_field') {
- if (col.dataType === 'SceltaMultipla') {
- cell.innerHTML = `` +
+ } else if (col.type === "detail" || col.type === "main_field") {
+ if (col.dataType === "SceltaMultipla") {
+ cell.innerHTML =
+ `` +
``;
- } else if (col.dataType === 'Data') {
- cell.innerHTML = `` +
+ } else if (col.dataType === "Data") {
+ cell.innerHTML =
+ `` +
``;
} else {
- cell.innerHTML = `` +
+ cell.innerHTML =
+ `` +
``;
}
}
@@ -657,24 +795,24 @@
async function populateTopRowSelects() {
// Client selects in top row — AJAX mode
- const clientSel = document.getElementById('clientSelect');
+ const clientSel = document.getElementById("clientSelect");
if (clientSel) {
clientSel.innerHTML = buildClientOptionsHTML(meta.defaultIdclient);
$(clientSel).select2(clientSelect2Config);
}
- const fornitSel = document.getElementById('clienteFornitoreSelect');
+ const fornitSel = document.getElementById("clienteFornitoreSelect");
if (fornitSel) {
- fornitSel.innerHTML = buildClientOptionsHTML('');
+ fornitSel.innerHTML = buildClientOptionsHTML("");
$(fornitSel).select2(clientSelect2Config);
}
// Fixed field selects in top row
- topContainer.querySelectorAll('.api-fixed-select').forEach(sel => {
+ topContainer.querySelectorAll(".api-fixed-select").forEach((sel) => {
const fieldKey = sel.dataset.fixedKey;
const config = fixedFieldApiConfig[fieldKey];
// Client-sourced → init as AJAX Select2
- if (config && config.source === 'clients') {
+ if (config && config.source === "clients") {
$(sel).select2(clientSelect2Config);
return;
}
@@ -683,30 +821,40 @@
// For dependent fields: merge all cached values across all clientIds
const allItems = new Map();
for (const [key, items] of Object.entries(fixedFieldCache)) {
- if (key.startsWith(fieldKey + '_')) {
- items.forEach(item => allItems.set(String(item.id), item));
+ if (key.startsWith(fieldKey + "_")) {
+ items.forEach((item) =>
+ allItems.set(String(item.id), item),
+ );
}
}
sel.innerHTML = '';
[...allItems.values()]
- .sort((a, b) => String(a.text).localeCompare(String(b.text), 'it', { sensitivity: 'base' }))
- .forEach(item => sel.add(new Option(item.text, item.id)));
+ .sort((a, b) =>
+ String(a.text).localeCompare(String(b.text), "it", {
+ sensitivity: "base",
+ }),
+ )
+ .forEach((item) => sel.add(new Option(item.text, item.id)));
} else {
const items = fixedFieldCache[fieldKey] || [];
sel.innerHTML = '';
- items.forEach(item => sel.add(new Option(item.text, item.id)));
+ items.forEach((item) =>
+ sel.add(new Option(item.text, item.id)),
+ );
}
});
// Custom field dropdowns in top row — AJAX Select2
- topContainer.querySelectorAll('.dropdown-select[data-field-id]').forEach(sel => {
- const fieldId = sel.dataset.fieldId;
- if (fieldId) $(sel).select2(sceltaSelect2Config(fieldId));
- });
+ topContainer
+ .querySelectorAll(".dropdown-select[data-field-id]")
+ .forEach((sel) => {
+ const fieldId = sel.dataset.fieldId;
+ if (fieldId) $(sel).select2(sceltaSelect2Config(fieldId));
+ });
// Flatpickr in top row
- topContainer.querySelectorAll('.date-picker').forEach(el => {
- flatpickr(el, { dateFormat: 'Y-m-d', allowInput: true });
+ topContainer.querySelectorAll(".date-picker").forEach((el) => {
+ flatpickr(el, { dateFormat: "Y-m-d", allowInput: true });
});
}
@@ -716,38 +864,42 @@
if (!rowContainer) return;
// Cell value changes → write to gridData
- rowContainer.addEventListener('change', function (e) {
- const cell = e.target.closest('.grid-cell');
+ rowContainer.addEventListener("change", function (e) {
+ const cell = e.target.closest(".grid-cell");
if (!cell || !cell.dataset.row) return;
const rowIndex = parseInt(cell.dataset.row);
const colType = cell.dataset.colType;
const colKey = cell.dataset.col;
const value = e.target.value;
- if (colType === 'detail' || colType === 'main_field') {
- if (colType === 'main_field') {
+ if (colType === "detail" || colType === "main_field") {
+ if (colType === "main_field") {
data[rowIndex].mainFieldValue = value;
}
setDetailValue(rowIndex, colKey, value);
- } else if (colType === 'fixed') {
+ } else if (colType === "fixed") {
setFixedValue(rowIndex, colKey, value);
- } else if (colType === 'idclient') {
+ } else if (colType === "idclient") {
data[rowIndex].idclient = value;
data[rowIndex]._dirty = true;
- } else if (colType === 'cliente_fornitore_id') {
+ } else if (colType === "cliente_fornitore_id") {
data[rowIndex].cliente_fornitore_id = value;
data[rowIndex]._dirty = true;
- console.log('[gridRenderer] cliente_fornitore_id changed:', rowIndex, value);
+ console.log(
+ "[gridRenderer] cliente_fornitore_id changed:",
+ rowIndex,
+ value,
+ );
}
// Visual feedback
- cell.classList.add('cell-changed');
+ cell.classList.add("cell-changed");
updateDirtyIndicator();
});
// Propagate buttons
- document.addEventListener('click', function (e) {
- const btn = e.target.closest('.propagate-btn');
+ document.addEventListener("click", function (e) {
+ const btn = e.target.closest(".propagate-btn");
if (!btn) return;
const colIndex = parseInt(btn.dataset.colIndex);
@@ -755,37 +907,51 @@
if (isNaN(colIndex) && !column) return;
// Get value from the input/select in the same cell
- const cell = btn.closest('.grid-cell') || btn.closest('.grid-top-cell');
+ const cell =
+ btn.closest(".grid-cell") || btn.closest(".grid-top-cell");
if (!cell) return;
- const input = cell.querySelector('select, input');
+ const input = cell.querySelector("select, input");
if (!input) return;
- const value = $(input).hasClass('select2-hidden-accessible') ? $(input).val() : input.value;
+ const value = $(input).hasClass("select2-hidden-accessible")
+ ? $(input).val()
+ : input.value;
// Cache Select2 label so re-render shows name not ID
- if (value && $(input).hasClass('select2-hidden-accessible')) {
- const label = $(input).find('option:selected').text();
+ if (value && $(input).hasClass("select2-hidden-accessible")) {
+ const label = $(input).find("option:selected").text();
if (label && label !== value) {
clientNameCache[value] = label;
// Also cache for SceltaMultipla
const fieldId = input.dataset?.fieldId;
- if (fieldId) dropdownNameCache[fieldId + '_' + value] = label;
+ if (fieldId)
+ dropdownNameCache[fieldId + "_" + value] = label;
}
}
const col = columns[colIndex] || null;
- if (column === 'idclient') {
- data.forEach(row => { row.idclient = value; row._dirty = true; });
- } else if (column === 'cliente_fornitore_id') {
- data.forEach(row => { row.cliente_fornitore_id = value; row._dirty = true; });
- } else if (column && column.startsWith('fixed_')) {
- const fixedKey = column.replace('fixed_', '');
- data.forEach(row => { row.fixedFields[fixedKey] = value; row._dirty = true; });
+ if (column === "idclient") {
+ data.forEach((row) => {
+ row.idclient = value;
+ row._dirty = true;
+ });
+ } else if (column === "cliente_fornitore_id") {
+ data.forEach((row) => {
+ row.cliente_fornitore_id = value;
+ row._dirty = true;
+ });
+ } else if (column && column.startsWith("fixed_")) {
+ const fixedKey = column.replace("fixed_", "");
+ data.forEach((row) => {
+ row.fixedFields[fixedKey] = value;
+ row._dirty = true;
+ });
} else if (col) {
- if (col.type === 'detail' || col.type === 'main_field') {
- data.forEach(row => {
+ if (col.type === "detail" || col.type === "main_field") {
+ data.forEach((row) => {
row.details[col.key] = value;
- if (col.type === 'main_field') row.mainFieldValue = value;
+ if (col.type === "main_field")
+ row.mainFieldValue = value;
row._dirty = true;
});
}
@@ -795,52 +961,53 @@
});
// Select2 change events (don't bubble via native addEventListener)
- $(rowContainer).on('change', '.searchable-client', function () {
- const cell = this.closest('.grid-cell');
+ $(rowContainer).on("change", ".searchable-client", function () {
+ const cell = this.closest(".grid-cell");
if (!cell || !cell.dataset.row) return;
const rowIndex = parseInt(cell.dataset.row);
const colType = cell.dataset.colType;
- const value = $(this).val() || '';
+ const value = $(this).val() || "";
// Cache selected label
if (value) {
- const label = $(this).find('option:selected').text();
+ const label = $(this).find("option:selected").text();
if (label && label !== value) clientNameCache[value] = label;
}
- if (colType === 'idclient') {
+ if (colType === "idclient") {
data[rowIndex].idclient = value;
data[rowIndex]._dirty = true;
- } else if (colType === 'cliente_fornitore_id') {
+ } else if (colType === "cliente_fornitore_id") {
data[rowIndex].cliente_fornitore_id = value;
data[rowIndex]._dirty = true;
}
- cell.classList.add('cell-changed');
+ cell.classList.add("cell-changed");
updateDirtyIndicator();
});
// Cache labels on SceltaMultipla change
- $(rowContainer).on('change', '.searchable-dropdown', function () {
+ $(rowContainer).on("change", ".searchable-dropdown", function () {
const val = $(this).val();
const fieldId = this.dataset.fieldId;
if (val && fieldId) {
- const label = $(this).find('option:selected').text();
- if (label && label !== val) dropdownNameCache[fieldId + '_' + val] = label;
+ const label = $(this).find("option:selected").text();
+ if (label && label !== val)
+ dropdownNameCache[fieldId + "_" + val] = label;
}
});
// Select2 change on fixed field selects
- $(rowContainer).on('change', '.api-fixed-select', function () {
- const cell = this.closest('.grid-cell');
+ $(rowContainer).on("change", ".api-fixed-select", function () {
+ const cell = this.closest(".grid-cell");
if (!cell || !cell.dataset.row) return;
const rowIndex = parseInt(cell.dataset.row);
const key = this.dataset.fixedKey || cell.dataset.col;
- const value = $(this).val() || '';
+ const value = $(this).val() || "";
if (key) {
data[rowIndex].fixedFields[key] = value;
data[rowIndex]._dirty = true;
- cell.classList.add('cell-changed');
+ cell.classList.add("cell-changed");
updateDirtyIndicator();
}
});
@@ -849,13 +1016,18 @@
function onScroll() {
if (revealedCount >= totalRows) return;
const docEl = document.documentElement;
- const scrollBottom = Math.max(docEl.scrollTop + docEl.clientHeight, document.body.scrollTop + window.innerHeight);
+ const scrollBottom = Math.max(
+ docEl.scrollTop + docEl.clientHeight,
+ document.body.scrollTop + window.innerHeight,
+ );
if (scrollBottom >= docEl.scrollHeight - 300) {
revealNextBatch();
}
}
- window.addEventListener('scroll', onScroll, { passive: true });
- document.querySelector('.page-wrapper')?.addEventListener('scroll', onScroll, { passive: true });
+ window.addEventListener("scroll", onScroll, { passive: true });
+ document
+ .querySelector(".page-wrapper")
+ ?.addEventListener("scroll", onScroll, { passive: true });
}
// ── Save ───────────────────────────────────────────────────────────────
@@ -863,7 +1035,7 @@
window.buildSavePayload = function (rowIndex) {
const row = data[rowIndex];
const formData = new FormData();
- formData.append('iddatadb', row.iddatadb);
+ formData.append("iddatadb", row.iddatadb);
// Details
for (const [mappingId, value] of Object.entries(row.details)) {
@@ -871,8 +1043,8 @@
}
// Client
- if (row.idclient) formData.append('idclient', row.idclient);
- formData.append('cliente_fornitore_id', row.cliente_fornitore_id || '');
+ if (row.idclient) formData.append("idclient", row.idclient);
+ formData.append("cliente_fornitore_id", row.cliente_fornitore_id || "");
// Fixed fields → real column names
const aliasMap = meta.fixedAliasMap || {};
@@ -887,14 +1059,15 @@
// ── Dirty indicator ────────────────────────────────────────────────────
function updateDirtyIndicator() {
- const dirtyCount = data.filter(r => r._dirty).length;
- const indicator = document.getElementById('unsavedChanges');
- const changedEl = document.getElementById('changedRows');
+ const dirtyCount = data.filter((r) => r._dirty).length;
+ const indicator = document.getElementById("unsavedChanges");
+ const changedEl = document.getElementById("changedRows");
if (indicator) {
- indicator.style.display = dirtyCount > 0 ? '' : 'none';
+ indicator.style.display = dirtyCount > 0 ? "" : "none";
}
if (changedEl) {
- changedEl.textContent = dirtyCount > 0 ? `(${dirtyCount} rows)` : '';
+ changedEl.textContent =
+ dirtyCount > 0 ? `(${dirtyCount} rows)` : "";
}
}
@@ -903,7 +1076,7 @@
const shown = Math.min(revealedCount, totalRows);
if (shown >= totalRows) {
statusEl.textContent = `All ${totalRows} rows loaded`;
- setTimeout(() => statusEl.style.display = 'none', 2000);
+ setTimeout(() => (statusEl.style.display = "none"), 2000);
} else {
statusEl.textContent = `Showing ${shown} of ${totalRows} rows — scroll down for more`;
}
@@ -912,13 +1085,89 @@
// ── Lazy Select2 for row selects ───────────────────────────────────────
function initLazySelect2() {
- $(document).on('mouseenter', '.grid-row .grid-cell', function () {
- $(this).find('.searchable-client:not(.select2-hidden-accessible)').each(function () {
- $(this).select2(clientSelect2Config);
+ $(document).on("mouseenter", ".grid-row .grid-cell", function () {
+ $(this)
+ .find(".searchable-client:not(.select2-hidden-accessible)")
+ .each(function () {
+ $(this).select2(clientSelect2Config);
+ });
+ $(this)
+ .find(".searchable-dropdown:not(.select2-hidden-accessible)")
+ .each(function () {
+ const fieldId = this.dataset.fieldId;
+ if (fieldId) $(this).select2(sceltaSelect2Config(fieldId));
+ });
+ });
+ }
+
+ // ── Column resize ──────────────────────────────────────────────────────
+
+ function initColumnResizers() {
+ const resizers = document.querySelectorAll(".resizer");
+
+ let currentResizer = null;
+ let startX = 0;
+ let startWidth = 0;
+ let columnIndex = null;
+
+ function resize(e) {
+ if (!currentResizer || !columnIndex) return;
+
+ const deltaX = e.pageX - startX;
+ const newWidth = Math.max(80, startWidth + deltaX);
+
+ const header = document.querySelector(
+ `.grid-header[data-index="${columnIndex}"]`,
+ );
+ if (header) {
+ header.style.flex = `0 0 ${newWidth}px`;
+ }
+
+ const topCell = document.querySelector(
+ `.grid-top .grid-cell:nth-child(${parseInt(columnIndex, 10) + 1})`,
+ );
+ if (topCell) {
+ topCell.style.flex = `0 0 ${newWidth}px`;
+ }
+
+ const cells = document.querySelectorAll(
+ `.grid-row .grid-cell[data-index="${columnIndex}"]`,
+ );
+ cells.forEach((cell) => {
+ cell.style.flex = `0 0 ${newWidth}px`;
});
- $(this).find('.searchable-dropdown:not(.select2-hidden-accessible)').each(function () {
- const fieldId = this.dataset.fieldId;
- if (fieldId) $(this).select2(sceltaSelect2Config(fieldId));
+
+ // aggiorna anche la width nel meta così i rerender la mantengono
+ const colPos = parseInt(columnIndex, 10) - 1;
+ if (columns[colPos]) {
+ columns[colPos].width = newWidth;
+ }
+ }
+
+ function stopResize() {
+ if (currentResizer) {
+ document.removeEventListener("mousemove", resize);
+ document.removeEventListener("mouseup", stopResize);
+ currentResizer = null;
+ columnIndex = null;
+ }
+ }
+
+ resizers.forEach((resizer) => {
+ resizer.addEventListener("mousedown", function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ currentResizer = this;
+ const header = this.closest(".grid-header");
+ if (!header) return;
+
+ columnIndex = header.getAttribute("data-index");
+ startX = e.pageX;
+ startWidth = header.offsetWidth;
+
+ document.addEventListener("mousemove", resize);
+ document.addEventListener("mouseup", stopResize);
});
});
}
@@ -926,18 +1175,19 @@
// ── Init ───────────────────────────────────────────────────────────────
async function init() {
- rowContainer = document.getElementById('gridRowContainer');
- headerContainer = document.getElementById('gridHeaderContainer');
- topContainer = document.getElementById('gridTopContainer');
+ rowContainer = document.getElementById("gridRowContainer");
+ headerContainer = document.getElementById("gridHeaderContainer");
+ topContainer = document.getElementById("gridTopContainer");
if (!rowContainer) {
- console.error('gridRenderer: #gridRowContainer not found');
+ console.error("gridRenderer: #gridRowContainer not found");
return;
}
// Status bar
- statusEl = document.createElement('div');
- statusEl.style.cssText = 'text-align:center; padding:8px; color:#666; font-size:12px;';
+ statusEl = document.createElement("div");
+ statusEl.style.cssText =
+ "text-align:center; padding:8px; color:#666; font-size:12px;";
if (totalRows > PAGE_SIZE) {
statusEl.textContent = `Loading data...`;
rowContainer.parentElement.appendChild(statusEl);
@@ -950,6 +1200,7 @@
renderHeaders();
renderTopRow();
renderVisibleRows();
+ initColumnResizers();
// Events
attachEvents();
@@ -957,8 +1208,8 @@
}
// Start when DOM ready
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', init);
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
@@ -972,8 +1223,10 @@
getData: () => data,
getMeta: () => meta,
getClientData: () => clientData,
- getDirtyRows: () => data.filter(r => r._dirty).map((r, i) => i),
- clearDirty: (rowIndex) => { data[rowIndex]._dirty = false; updateDirtyIndicator(); },
+ getDirtyRows: () => data.filter((r) => r._dirty).map((r, i) => i),
+ clearDirty: (rowIndex) => {
+ data[rowIndex]._dirty = false;
+ updateDirtyIndicator();
+ },
};
-
})();
diff --git a/public/userarea/imported.php b/public/userarea/imported.php
index 77223e6..d7f50aa 100644
--- a/public/userarea/imported.php
+++ b/public/userarea/imported.php
@@ -342,8 +342,8 @@ $gridMeta = [
?>
@@ -797,7 +797,7 @@ window.gridMeta = = json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
background-color: rgba(0, 0, 0, 0.5);
}
- #photosModal > .modal-content {
+ #photosModal>.modal-content {
background-color: #fff;
margin: 5% auto;
padding: 20px;
@@ -892,9 +892,17 @@ window.gridMeta = = json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
}
@keyframes new-row-pulse {
- 0%, 100% { background-color: transparent; }
- 50% { background-color: #cce5ff; }
+
+ 0%,
+ 100% {
+ background-color: transparent;
+ }
+
+ 50% {
+ background-color: #cce5ff;
+ }
}
+
.row-just-created {
animation: new-row-pulse 1s ease-in-out 3;
}
@@ -1029,21 +1037,25 @@ window.gridMeta = = json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
font-size: 13px;
color: #495057;
}
+
.pager-rows-per-page {
display: flex;
align-items: center;
gap: 8px;
}
+
.pager-label {
font-weight: 500;
white-space: nowrap;
}
+
.pager-limit-group {
display: inline-flex;
border: 1px solid #ced4da;
border-radius: 4px;
overflow: hidden;
}
+
.pager-limit-btn {
padding: 3px 10px;
text-decoration: none;
@@ -1052,24 +1064,41 @@ window.gridMeta = = json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
transition: background .15s, color .15s;
font-weight: 500;
}
- .pager-limit-btn:last-child { border-right: none; }
- .pager-limit-btn:hover { background: #e9ecef; text-decoration: none; color: #212529; }
+
+ .pager-limit-btn:last-child {
+ border-right: none;
+ }
+
+ .pager-limit-btn:hover {
+ background: #e9ecef;
+ text-decoration: none;
+ color: #212529;
+ }
+
.pager-limit-btn.active {
background: #0d6efd;
color: #fff;
}
- .pager-limit-btn.active:hover { background: #0b5ed7; color: #fff; }
+
+ .pager-limit-btn.active:hover {
+ background: #0b5ed7;
+ color: #fff;
+ }
+
.pager-nav {
display: flex;
align-items: center;
gap: 4px;
}
+
.pager-info {
margin-right: 8px;
font-weight: 500;
white-space: nowrap;
}
- .pager-btn, .pager-num {
+
+ .pager-btn,
+ .pager-num {
display: inline-flex;
align-items: center;
justify-content: center;
@@ -1083,16 +1112,25 @@ window.gridMeta = = json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
font-weight: 500;
transition: background .15s, color .15s, border-color .15s;
}
- .pager-btn:hover, .pager-num:hover { background: #e9ecef; text-decoration: none; color: #212529; }
+
+ .pager-btn:hover,
+ .pager-num:hover {
+ background: #e9ecef;
+ text-decoration: none;
+ color: #212529;
+ }
+
.pager-num.active {
background: #0d6efd;
color: #fff;
border-color: #0d6efd;
}
+
.pager-btn.disabled {
pointer-events: none;
opacity: .4;
}
+
.pager-dots {
padding: 0 2px;
color: #adb5bd;
@@ -1112,20 +1150,20 @@ window.gridMeta = = json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
Imported (i)
To LIMS (l)
-
-
-
-
- (= $usePagination ? count($importedData) . " of {$totalRows}" : count($importedData) ?> records= !$show_all_users ? ' — my records only' : '' ?>)
-
+
+
+
+
+ (= $usePagination ? count($importedData) . " of {$totalRows}" : count($importedData) ?> records= !$show_all_users ? ' — my records only' : '' ?>)
+
-
-