cache and view improvements
This commit is contained in:
parent
7463fc6726
commit
4f0dbc7e91
@ -33,15 +33,26 @@ try {
|
||||
$stmt->execute([$templateId, $userId, $idclient, $importReferenceCode]);
|
||||
$iddatadb = (int)$pdo->lastInsertId();
|
||||
|
||||
// Create empty import_data_details for all mappings
|
||||
$mappingStmt = $pdo->prepare("SELECT id FROM template_mapping WHERE template_id = ?");
|
||||
// Create import_data_details for all mappings (with auto_value support)
|
||||
$mappingStmt = $pdo->prepare("SELECT id, auto_value, manual_default, data_type FROM template_mapping WHERE template_id = ?");
|
||||
$mappingStmt->execute([$templateId]);
|
||||
$mappings = $mappingStmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
$mappings = $mappingStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!empty($mappings)) {
|
||||
$insertStmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, '')");
|
||||
foreach ($mappings as $mappingId) {
|
||||
$insertStmt->execute([$iddatadb, $mappingId]);
|
||||
$insertStmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
|
||||
foreach ($mappings as $m) {
|
||||
$val = '';
|
||||
$auto = $m['auto_value'] ?? 'none';
|
||||
if ($auto === 'import_date') {
|
||||
$val = date('Y-m-d');
|
||||
} elseif ($auto === 'import_time') {
|
||||
$val = date('H:i');
|
||||
} elseif ($m['data_type'] === 'DATE' && ($m['manual_default'] ?? '') === 'today') {
|
||||
$val = date('Y-m-d');
|
||||
} elseif (!empty($m['manual_default'])) {
|
||||
$val = $m['manual_default'];
|
||||
}
|
||||
$insertStmt->execute([$iddatadb, $m['id'], $val]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -74,10 +74,21 @@ try {
|
||||
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
|
||||
}
|
||||
|
||||
// Cache file (1 hour TTL)
|
||||
$cacheFile = __DIR__ . '/cache/clienti.json';
|
||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||
readfile($cacheFile);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Esegui la chiamata con retry
|
||||
$data = makeApiRequest($api, $endpoint);
|
||||
|
||||
echo json_encode($data);
|
||||
$json = json_encode($data);
|
||||
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
|
||||
file_put_contents($cacheFile, $json);
|
||||
|
||||
echo $json;
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
$errorResponse = [
|
||||
|
||||
@ -21,16 +21,25 @@ try {
|
||||
}
|
||||
|
||||
$results = [];
|
||||
$cacheDir = __DIR__ . '/cache';
|
||||
if (!is_dir($cacheDir)) mkdir($cacheDir, 0777, true);
|
||||
|
||||
foreach ($fieldIds as $customFieldId) {
|
||||
$endpoint = "CustomField($customFieldId)?\$expand=CustomFieldsValues";
|
||||
$data = $api->get($endpoint);
|
||||
$cacheFile = $cacheDir . '/customfield_' . $customFieldId . '.json';
|
||||
|
||||
$results[$customFieldId] = $data['CustomFieldsValues'] ?? [];
|
||||
// Use cache if fresh (1 hour)
|
||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||
$results[$customFieldId] = json_decode(file_get_contents($cacheFile), true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Debug ფაილი
|
||||
file_put_contents(__DIR__ . '/customfield_values_response.json', json_encode($results));
|
||||
$endpoint = "CustomField($customFieldId)?\$expand=CustomFieldsValues";
|
||||
$data = $api->get($endpoint);
|
||||
$values = $data['CustomFieldsValues'] ?? [];
|
||||
$results[$customFieldId] = $values;
|
||||
|
||||
file_put_contents($cacheFile, json_encode($values));
|
||||
}
|
||||
|
||||
echo json_encode($results);
|
||||
} catch (Exception $e) {
|
||||
|
||||
@ -10,7 +10,8 @@
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
let revealedCount = PAGE_SIZE;
|
||||
let dropdownOptionsCache = {}; // fieldId -> [{id, text}]
|
||||
let dropdownOptionsCache = {}; // fieldId -> [{id, text}] — used only for small lists
|
||||
const dropdownNameCache = {}; // "fieldId_valueId" -> label
|
||||
let clientData = []; // loaded from get_clienti.php
|
||||
let fixedFieldCache = window.fixedFieldDataCache || {};
|
||||
window.fixedFieldDataCache = fixedFieldCache;
|
||||
@ -53,37 +54,76 @@
|
||||
data[rowIndex]._dirty = true;
|
||||
}
|
||||
|
||||
// ── Client data loading ────────────────────────────────────────────────
|
||||
// ── Client data (AJAX Select2, no bulk loading) ──────────────────────
|
||||
|
||||
function formatClientLabel(client) {
|
||||
const nome = client.Nominativo || '';
|
||||
const id = client.IdCliente || '';
|
||||
const code = (client.CodiceCliente || '').toString().trim();
|
||||
const suffix = (code.split('_')[1] || '').trim();
|
||||
const short = suffix || (code ? code.charAt(0) : '--');
|
||||
return `${nome.trim()} - ${short} (ID: ${id})`;
|
||||
return (client.Nominativo || '').trim();
|
||||
}
|
||||
|
||||
// Cache of resolved client names: id → name
|
||||
const clientNameCache = {};
|
||||
|
||||
// Build select with only the selected option
|
||||
function buildClientOptionsHTML(selectedId) {
|
||||
let html = '<option value="">Select a client...</option>';
|
||||
clientData.forEach(c => {
|
||||
const id = c.IdCliente || '';
|
||||
const sel = String(id) === String(selectedId) ? ' selected' : '';
|
||||
html += `<option value="${id}"${sel}>${esc(formatClientLabel(c))}</option>`;
|
||||
});
|
||||
if (selectedId) {
|
||||
const label = clientNameCache[selectedId] || selectedId;
|
||||
html += `<option value="${esc(String(selectedId))}" selected>${esc(String(label))}</option>`;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
async function loadClientData() {
|
||||
if (clientData.length > 0) return;
|
||||
try {
|
||||
const resp = await fetch('get_clienti.php');
|
||||
const json = await resp.json();
|
||||
clientData = json.value || [];
|
||||
} catch (e) {
|
||||
console.error('Failed to load clients:', e);
|
||||
// Pre-resolve all unique client IDs used in data rows, then re-render
|
||||
async function resolveClientNames() {
|
||||
const ids = new Set();
|
||||
data.forEach(row => {
|
||||
if (row.idclient) ids.add(String(row.idclient));
|
||||
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));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Batch resolve via single request per 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 */ }
|
||||
}));
|
||||
}
|
||||
|
||||
// Select2 AJAX config for client selects
|
||||
const clientSelect2Config = {
|
||||
placeholder: 'Search client...',
|
||||
allowClear: true,
|
||||
width: '100%',
|
||||
minimumInputLength: 0,
|
||||
dropdownCssClass: 'select2-dropdown-smaller',
|
||||
ajax: {
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
async function loadClientData() {
|
||||
// No longer loads all clients — AJAX Select2 handles search
|
||||
// Just resolve names for pre-selected values
|
||||
await resolveClientNames();
|
||||
}
|
||||
|
||||
// ── Fixed field data loading ───────────────────────────────────────────
|
||||
|
||||
@ -102,13 +142,10 @@
|
||||
const config = fixedFieldApiConfig[fieldKey];
|
||||
if (!config) return [];
|
||||
|
||||
// Client-sourced fields
|
||||
// Client-sourced fields — handled by AJAX Select2, skip preloading
|
||||
if (config.source === 'clients') {
|
||||
await loadClientData();
|
||||
const results = clientData.map(c => ({ id: c.IdCliente, text: formatClientLabel(c) }));
|
||||
results.sort((a, b) => String(a.text).localeCompare(String(b.text), 'it', { sensitivity: 'base' }));
|
||||
fixedFieldCache[fieldKey] = results;
|
||||
return results;
|
||||
fixedFieldCache[fieldKey] = [];
|
||||
return [];
|
||||
}
|
||||
|
||||
const cacheKey = fieldKey + (clientId ? '_' + clientId : '');
|
||||
@ -144,26 +181,23 @@
|
||||
|
||||
// ── Custom field dropdown data loading ─────────────────────────────────
|
||||
|
||||
async function loadDropdownOptions(fieldIds) {
|
||||
const missing = fieldIds.filter(id => !dropdownOptionsCache[id]);
|
||||
if (missing.length > 0) {
|
||||
try {
|
||||
const resp = await fetch('get_customfield_values.php?field_ids=' + missing.join(','));
|
||||
const json = await resp.json();
|
||||
// API returns { fieldId: [values] } directly (no success/data wrapper)
|
||||
const entries = json.data ? json.data : json;
|
||||
for (const [fid, values] of Object.entries(entries)) {
|
||||
if (Array.isArray(values)) {
|
||||
const sorted = values.sort((a, b) =>
|
||||
String(a.Valore || '').localeCompare(String(b.Valore || ''), 'it', { sensitivity: 'base' })
|
||||
);
|
||||
dropdownOptionsCache[fid] = sorted;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load dropdown options:', e);
|
||||
}
|
||||
|
||||
// Select2 AJAX config factory for SceltaMultipla
|
||||
function sceltaSelect2Config(fieldId) {
|
||||
return {
|
||||
placeholder: 'Search...',
|
||||
allowClear: true,
|
||||
width: '100%',
|
||||
minimumInputLength: 0,
|
||||
ajax: {
|
||||
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
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ── Preload all data ───────────────────────────────────────────────────
|
||||
@ -192,13 +226,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Custom field dropdowns
|
||||
const fieldIds = columns
|
||||
.filter(c => c.type === 'detail' && c.dataType === 'SceltaMultipla' && c.fieldId)
|
||||
.map(c => String(c.fieldId));
|
||||
const uniqueIds = [...new Set(fieldIds)];
|
||||
if (uniqueIds.length > 0) {
|
||||
await loadDropdownOptions(uniqueIds);
|
||||
// 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))
|
||||
)];
|
||||
if (allFieldIds.length > 0) {
|
||||
try {
|
||||
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 || '';
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to preload dropdown data:', e);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[gridRenderer] preload done:', {
|
||||
@ -308,7 +355,7 @@
|
||||
|
||||
if (col.dataType === 'SceltaMultipla') {
|
||||
const options = buildDropdownOptionsHTML(col.fieldId, value);
|
||||
return `<select class="cell-input dropdown-select ${cls}${reqCls}" data-field-id="${col.fieldId}"${req}>${options}</select>`;
|
||||
return `<select class="cell-input dropdown-select searchable-dropdown ${cls}${reqCls}" data-field-id="${col.fieldId}"${req}>${options}</select>`;
|
||||
}
|
||||
if (col.dataType === 'Data') {
|
||||
return `<input type="text" class="cell-input date-picker ${cls}${reqCls}" value="${v}"${req}>`;
|
||||
@ -329,11 +376,24 @@
|
||||
return `<input type="text" class="cell-input date-picker manual-input${reqCls} fixed-input" data-fixed-key="${col.key}" value="${esc(value)}"${req}>`;
|
||||
}
|
||||
|
||||
// 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' : '';
|
||||
let opts = '<option value="">Select...</option>';
|
||||
if (value) {
|
||||
const label = clientNameCache[value] || value;
|
||||
opts += `<option value="${esc(String(value))}" selected>${esc(String(label))}</option>`;
|
||||
}
|
||||
return `<select class="cell-input manual-input fixed-input searchable-client api-fixed-select${reqCls}" data-fixed-key="${col.key}" data-current-value="${esc(value)}"${req}>${opts}</select>`;
|
||||
}
|
||||
|
||||
// Select — build from cache
|
||||
const isApiField = !!fixedFieldApiConfig[col.key];
|
||||
const isApiField = !!config;
|
||||
const selectClass = isApiField ? 'api-fixed-select' : '';
|
||||
let options = '<option value="">Seleziona...</option>';
|
||||
const cacheKey = fixedFieldApiConfig[col.key]?.dependsOn
|
||||
const cacheKey = config?.dependsOn
|
||||
? col.key + '_' + (data[rowIndex].idclient || '')
|
||||
: col.key;
|
||||
const items = fixedFieldCache[cacheKey] || [];
|
||||
@ -349,11 +409,10 @@
|
||||
|
||||
function buildDropdownOptionsHTML(fieldId, selectedValue) {
|
||||
let html = '<option value="">Seleziona...</option>';
|
||||
const items = dropdownOptionsCache[fieldId] || [];
|
||||
items.forEach(item => {
|
||||
const sel = String(item.IdCustomFieldsValue) === String(selectedValue) ? ' selected' : '';
|
||||
html += `<option value="${item.IdCustomFieldsValue}"${sel}>${esc(item.Valore)}</option>`;
|
||||
});
|
||||
if (selectedValue) {
|
||||
const label = dropdownNameCache[fieldId + '_' + selectedValue] || selectedValue;
|
||||
html += `<option value="${esc(String(selectedValue))}" selected>${esc(String(label))}</option>`;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
@ -597,16 +656,16 @@
|
||||
}
|
||||
|
||||
async function populateTopRowSelects() {
|
||||
// Client selects in top row
|
||||
// Client selects in top row — AJAX mode
|
||||
const clientSel = document.getElementById('clientSelect');
|
||||
if (clientSel) {
|
||||
clientSel.innerHTML = buildClientOptionsHTML(meta.defaultIdclient);
|
||||
$(clientSel).select2({ placeholder: 'Select a client...', allowClear: true, width: '100%', minimumInputLength: 1 });
|
||||
$(clientSel).select2(clientSelect2Config);
|
||||
}
|
||||
const fornitSel = document.getElementById('clienteFornitoreSelect');
|
||||
if (fornitSel) {
|
||||
fornitSel.innerHTML = buildClientOptionsHTML('');
|
||||
$(fornitSel).select2({ placeholder: 'Select a supplier...', allowClear: true, width: '100%', minimumInputLength: 1 });
|
||||
$(fornitSel).select2(clientSelect2Config);
|
||||
}
|
||||
|
||||
// Fixed field selects in top row
|
||||
@ -614,6 +673,12 @@
|
||||
const fieldKey = sel.dataset.fixedKey;
|
||||
const config = fixedFieldApiConfig[fieldKey];
|
||||
|
||||
// Client-sourced → init as AJAX Select2
|
||||
if (config && config.source === 'clients') {
|
||||
$(sel).select2(clientSelect2Config);
|
||||
return;
|
||||
}
|
||||
|
||||
if (config && config.dependsOn) {
|
||||
// For dependent fields: merge all cached values across all clientIds
|
||||
const allItems = new Map();
|
||||
@ -633,14 +698,10 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Custom field dropdowns in top row
|
||||
// Custom field dropdowns in top row — AJAX Select2
|
||||
topContainer.querySelectorAll('.dropdown-select[data-field-id]').forEach(sel => {
|
||||
const fieldId = sel.dataset.fieldId;
|
||||
const items = dropdownOptionsCache[fieldId] || [];
|
||||
sel.innerHTML = '<option value="">Seleziona...</option>';
|
||||
items.forEach(item => {
|
||||
sel.add(new Option(item.Valore, item.IdCustomFieldsValue));
|
||||
});
|
||||
if (fieldId) $(sel).select2(sceltaSelect2Config(fieldId));
|
||||
});
|
||||
|
||||
// Flatpickr in top row
|
||||
@ -700,6 +761,17 @@
|
||||
if (!input) return;
|
||||
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 (label && label !== value) {
|
||||
clientNameCache[value] = label;
|
||||
// Also cache for SceltaMultipla
|
||||
const fieldId = input.dataset?.fieldId;
|
||||
if (fieldId) dropdownNameCache[fieldId + '_' + value] = label;
|
||||
}
|
||||
}
|
||||
|
||||
const col = columns[colIndex] || null;
|
||||
|
||||
if (column === 'idclient') {
|
||||
@ -730,6 +802,12 @@
|
||||
const colType = cell.dataset.colType;
|
||||
const value = $(this).val() || '';
|
||||
|
||||
// Cache selected label
|
||||
if (value) {
|
||||
const label = $(this).find('option:selected').text();
|
||||
if (label && label !== value) clientNameCache[value] = label;
|
||||
}
|
||||
|
||||
if (colType === 'idclient') {
|
||||
data[rowIndex].idclient = value;
|
||||
data[rowIndex]._dirty = true;
|
||||
@ -741,6 +819,16 @@
|
||||
updateDirtyIndicator();
|
||||
});
|
||||
|
||||
// Cache labels on SceltaMultipla change
|
||||
$(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;
|
||||
}
|
||||
});
|
||||
|
||||
// Select2 change on fixed field selects
|
||||
$(rowContainer).on('change', '.api-fixed-select', function () {
|
||||
const cell = this.closest('.grid-cell');
|
||||
@ -826,18 +914,11 @@
|
||||
function initLazySelect2() {
|
||||
$(document).on('mouseenter', '.grid-row .grid-cell', function () {
|
||||
$(this).find('.searchable-client:not(.select2-hidden-accessible)').each(function () {
|
||||
$(this).select2({
|
||||
placeholder: 'Select a client...',
|
||||
allowClear: true,
|
||||
width: '100%',
|
||||
dropdownCssClass: 'select2-dropdown-smaller',
|
||||
minimumInputLength: 1
|
||||
$(this).select2(clientSelect2Config);
|
||||
});
|
||||
});
|
||||
$(this).find('select[data-field-id]:not(.select2-hidden-accessible)').each(function () {
|
||||
if ((this.options || []).length > 12) {
|
||||
$(this).select2({ placeholder: 'Seleziona...', allowClear: true, width: '100%' });
|
||||
}
|
||||
$(this).find('.searchable-dropdown:not(.select2-hidden-accessible)').each(function () {
|
||||
const fieldId = this.dataset.fieldId;
|
||||
if (fieldId) $(this).select2(sceltaSelect2Config(fieldId));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ $pdo = $db->getConnection();
|
||||
$importReferenceCode = date('YmdHis') . '-' . uniqid();
|
||||
|
||||
// Recupera tutti i mapping dal template
|
||||
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field FROM template_mapping WHERE template_id = ?");
|
||||
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field, auto_value FROM template_mapping WHERE template_id = ?");
|
||||
$stmt->execute([$template_id]);
|
||||
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
@ -135,6 +135,15 @@ foreach ($selected_rows as $rowIndex) {
|
||||
$fieldValue = date('Y-m-d');
|
||||
}
|
||||
}
|
||||
// Apply auto_value if field is still empty
|
||||
if (($fieldValue === null || $fieldValue === '') && !empty($mapping['auto_value']) && $mapping['auto_value'] !== 'none') {
|
||||
if ($mapping['auto_value'] === 'import_date') {
|
||||
$fieldValue = date('Y-m-d');
|
||||
} elseif ($mapping['auto_value'] === 'import_time') {
|
||||
$fieldValue = date('H:i');
|
||||
}
|
||||
}
|
||||
|
||||
if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) {
|
||||
error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
|
||||
}
|
||||
|
||||
58
public/userarea/search_clienti.php
Normal file
58
public/userarea/search_clienti.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
$q = mb_strtolower(trim($_GET['q'] ?? ''));
|
||||
$limit = max(1, min(50, intval($_GET['limit'] ?? 20)));
|
||||
$id = isset($_GET['id']) ? intval($_GET['id']) : null;
|
||||
|
||||
try {
|
||||
// Load from cache or API
|
||||
$cacheFile = __DIR__ . '/cache/clienti.json';
|
||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||
$data = json_decode(file_get_contents($cacheFile), true);
|
||||
} else {
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
$params = [
|
||||
'$select' => 'IdCliente,Nominativo,CodiceCliente',
|
||||
'$orderby' => 'Nominativo asc'
|
||||
];
|
||||
$data = $api->get("Cliente?" . http_build_query($params));
|
||||
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
|
||||
file_put_contents($cacheFile, json_encode($data));
|
||||
}
|
||||
|
||||
$clients = $data['value'] ?? [];
|
||||
|
||||
// If requesting by specific ID (for loading selected value)
|
||||
if ($id !== null) {
|
||||
foreach ($clients as $c) {
|
||||
if ((int)$c['IdCliente'] === $id) {
|
||||
echo json_encode(['results' => [['id' => $c['IdCliente'], 'text' => trim($c['Nominativo'] ?? '')]]]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
echo json_encode(['results' => []]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Search by query
|
||||
$results = [];
|
||||
foreach ($clients as $c) {
|
||||
$name = trim($c['Nominativo'] ?? '');
|
||||
$code = trim($c['CodiceCliente'] ?? '');
|
||||
if ($q === '' || mb_strpos(mb_strtolower($name), $q) !== false || mb_strpos(mb_strtolower($code), $q) !== false) {
|
||||
$results[] = ['id' => $c['IdCliente'], 'text' => $name];
|
||||
if (count($results) >= $limit) break;
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(['results' => $results]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
62
public/userarea/search_customfield_values.php
Normal file
62
public/userarea/search_customfield_values.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
$fieldId = intval($_GET['field_id'] ?? 0);
|
||||
$q = mb_strtolower(trim($_GET['q'] ?? ''));
|
||||
$id = isset($_GET['id']) ? intval($_GET['id']) : null;
|
||||
$limit = max(1, min(50, intval($_GET['limit'] ?? 20)));
|
||||
|
||||
if (!$fieldId) {
|
||||
echo json_encode(['results' => []]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$cacheDir = __DIR__ . '/cache';
|
||||
$cacheFile = $cacheDir . '/customfield_' . $fieldId . '.json';
|
||||
|
||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||
$values = json_decode(file_get_contents($cacheFile), true);
|
||||
} else {
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
$data = $api->get("CustomField($fieldId)?\$expand=CustomFieldsValues");
|
||||
$values = $data['CustomFieldsValues'] ?? [];
|
||||
if (!is_dir($cacheDir)) mkdir($cacheDir, 0777, true);
|
||||
file_put_contents($cacheFile, json_encode($values));
|
||||
}
|
||||
|
||||
// Lookup by ID
|
||||
if ($id !== null) {
|
||||
foreach ($values as $v) {
|
||||
if ((int)($v['IdCustomFieldsValue'] ?? 0) === $id) {
|
||||
echo json_encode(['results' => [['id' => $v['IdCustomFieldsValue'], 'text' => $v['Valore'] ?? '']]]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
echo json_encode(['results' => []]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Search by query
|
||||
$results = [];
|
||||
foreach ($values as $v) {
|
||||
$text = $v['Valore'] ?? '';
|
||||
if ($q === '' || mb_strpos(mb_strtolower($text), $q) !== false) {
|
||||
$results[] = ['id' => $v['IdCustomFieldsValue'], 'text' => $text];
|
||||
if (count($results) >= $limit) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort alphabetically
|
||||
usort($results, fn($a, $b) => strcasecmp($a['text'], $b['text']));
|
||||
|
||||
echo json_encode(['results' => $results]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
@ -1007,6 +1007,48 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
||||
padding-right: 18px !important;
|
||||
appearance: auto;
|
||||
}
|
||||
/* Select2 inside filter row */
|
||||
.grid-filter-row .select2-container {
|
||||
width: 100% !important;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.grid-filter-row .select2-container .select2-selection--single {
|
||||
height: 26px !important;
|
||||
min-height: 26px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
padding: 0 24px 0 6px;
|
||||
font-size: 11px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
}
|
||||
.grid-filter-row .select2-container .select2-selection__rendered {
|
||||
line-height: 1 !important;
|
||||
font-size: 11px;
|
||||
padding: 0 !important;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.grid-filter-row .select2-container .select2-selection__arrow {
|
||||
height: 26px !important;
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
top: 0;
|
||||
}
|
||||
.grid-filter-row .filter-wrap {
|
||||
overflow: visible;
|
||||
}
|
||||
/* Hide our clear button when Select2 is active — Select2 has its own allowClear */
|
||||
.filter-wrap:has(.select2-container) .filter-clear-btn {
|
||||
display: none;
|
||||
}
|
||||
.grid-filter-row .grid-cell {
|
||||
overflow: visible !important;
|
||||
}
|
||||
.filter-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -1747,15 +1789,14 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
||||
</div>
|
||||
<?php include('jsinclude.php'); ?>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Resolve SceltaMultipla IDs → Valore (cells + filter selects)
|
||||
// Resolve SceltaMultipla IDs → Valore in cells
|
||||
(function() {
|
||||
const sceltaEls = document.querySelectorAll('.scelta-value');
|
||||
const sceltaFilters = document.querySelectorAll('select.scelta-filter');
|
||||
const fieldIds = new Set();
|
||||
sceltaEls.forEach(el => { if (el.dataset.fieldId) fieldIds.add(el.dataset.fieldId); });
|
||||
sceltaFilters.forEach(el => { if (el.dataset.fieldId) fieldIds.add(el.dataset.fieldId); });
|
||||
if (!fieldIds.size) return;
|
||||
|
||||
$.getJSON('get_customfield_values.php', { field_ids: [...fieldIds].join(',') }, function(data) {
|
||||
@ -1766,7 +1807,6 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
||||
lookup[fid][v.IdCustomFieldsValue] = v.Valore;
|
||||
});
|
||||
}
|
||||
// Resolve cell values
|
||||
sceltaEls.forEach(el => {
|
||||
const fid = el.dataset.fieldId;
|
||||
const raw = (el.textContent || '').trim();
|
||||
@ -1774,55 +1814,116 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
||||
el.textContent = lookup[fid][raw];
|
||||
}
|
||||
});
|
||||
// Populate filter selects
|
||||
sceltaFilters.forEach(sel => {
|
||||
const fid = sel.dataset.fieldId;
|
||||
if (!fid || !lookup[fid]) return;
|
||||
const currentVal = sel.value;
|
||||
// Sort by Valore
|
||||
const sorted = Object.entries(lookup[fid]).sort((a, b) => a[1].localeCompare(b[1]));
|
||||
sel.innerHTML = '<option value="">All</option>';
|
||||
sorted.forEach(([id, label]) => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = id;
|
||||
opt.textContent = label;
|
||||
if (id === currentVal) opt.selected = true;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
// Load client names → resolve cells + populate filter selects
|
||||
$.getJSON('get_clienti.php', function(data) {
|
||||
const clients = data.value || [];
|
||||
const map = {};
|
||||
const sorted = [];
|
||||
clients.forEach(c => {
|
||||
const name = (c.Nominativo || '').trim();
|
||||
map[c.IdCliente] = name;
|
||||
sorted.push({ id: c.IdCliente, name: name });
|
||||
// SceltaMultipla filter selects → AJAX Select2
|
||||
document.querySelectorAll('select.scelta-filter').forEach(sel => {
|
||||
const fieldId = sel.dataset.fieldId;
|
||||
if (!fieldId) return;
|
||||
const currentVal = sel.value;
|
||||
if (currentVal) {
|
||||
fetch('search_customfield_values.php?field_id=' + fieldId + '&id=' + encodeURIComponent(currentVal))
|
||||
.then(r => r.json())
|
||||
.then(json => {
|
||||
const item = (json.results || [])[0];
|
||||
if (item) sel.innerHTML = '<option value="">All</option><option value="' + item.id + '" selected>' + item.text + '</option>';
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
$(sel).select2({
|
||||
placeholder: 'All',
|
||||
allowClear: true,
|
||||
width: '100%',
|
||||
minimumInputLength: 0,
|
||||
ajax: {
|
||||
url: 'search_customfield_values.php',
|
||||
dataType: 'json',
|
||||
delay: 150,
|
||||
data: function(params) { return { field_id: fieldId, q: params.term || '', limit: 20 }; },
|
||||
processResults: function(data) { return { results: data.results || [] }; },
|
||||
cache: true
|
||||
}
|
||||
});
|
||||
});
|
||||
sorted.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
// Resolve cell values
|
||||
// SceltaMultipla filter → submit on change
|
||||
$(document).on('change', 'select.scelta-filter', function() {
|
||||
submitFilters();
|
||||
});
|
||||
|
||||
// Resolve client names by unique IDs (lightweight)
|
||||
(function() {
|
||||
const ids = new Set();
|
||||
document.querySelectorAll('.client-text').forEach(el => {
|
||||
const id = el.getAttribute('data-client-id');
|
||||
if (id && map[id]) el.textContent = map[id];
|
||||
if (id) ids.add(id);
|
||||
});
|
||||
if (!ids.size) return;
|
||||
|
||||
// Populate client filter selects
|
||||
const promises = [...ids].map(id =>
|
||||
fetch('search_clienti.php?id=' + encodeURIComponent(id))
|
||||
.then(r => r.json())
|
||||
.then(json => {
|
||||
const item = (json.results || [])[0];
|
||||
if (item) {
|
||||
document.querySelectorAll(`.client-text[data-client-id="${id}"]`).forEach(el => {
|
||||
el.textContent = item.text;
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
);
|
||||
Promise.all(promises);
|
||||
})();
|
||||
|
||||
// Client filter selects → AJAX Select2
|
||||
document.querySelectorAll('select.client-filter').forEach(sel => {
|
||||
const currentVal = sel.value;
|
||||
sel.innerHTML = '<option value="">All</option>';
|
||||
sorted.forEach(c => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = c.id;
|
||||
opt.textContent = c.name;
|
||||
if (String(c.id) === currentVal) opt.selected = true;
|
||||
sel.appendChild(opt);
|
||||
if (currentVal) {
|
||||
fetch('search_clienti.php?id=' + encodeURIComponent(currentVal))
|
||||
.then(r => r.json())
|
||||
.then(json => {
|
||||
const item = (json.results || [])[0];
|
||||
if (item) {
|
||||
sel.innerHTML = '<option value="">All</option><option value="' + item.id + '" selected>' + item.text + '</option>';
|
||||
}
|
||||
$(sel).select2({
|
||||
placeholder: 'All',
|
||||
allowClear: true,
|
||||
width: '100%',
|
||||
minimumInputLength: 0,
|
||||
ajax: {
|
||||
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
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$(sel).select2({
|
||||
placeholder: 'All',
|
||||
allowClear: true,
|
||||
width: '100%',
|
||||
minimumInputLength: 0,
|
||||
ajax: {
|
||||
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
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Client filter select2 → submit on change
|
||||
$(document).on('change', 'select.client-filter', function() {
|
||||
submitFilters();
|
||||
});
|
||||
|
||||
// Modal close handlers
|
||||
|
||||
110
public/userarea/warm_cache.php
Normal file
110
public/userarea/warm_cache.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
// Cache warm-up script.
|
||||
// Run via CLI: php warm_cache.php
|
||||
// Run via cron: every 30 min: php /path/to/warm_cache.php
|
||||
// Run via HTTP: http://localhost:8000/public/userarea/warm_cache.php (requires auth)
|
||||
|
||||
$startTime = microtime(true);
|
||||
$isCli = (php_sapi_name() === 'cli');
|
||||
|
||||
if (!$isCli) {
|
||||
// When called via HTTP, require auth
|
||||
include('include/headscript.php');
|
||||
if (!Auth::check()) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
header('Content-Type: application/json');
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
||||
require_once __DIR__ . '/class/db-functions.php';
|
||||
|
||||
$cacheDir = __DIR__ . '/cache';
|
||||
if (!is_dir($cacheDir)) mkdir($cacheDir, 0777, true);
|
||||
|
||||
$log = [];
|
||||
function warmLog(string $msg, bool $isCli) {
|
||||
global $log;
|
||||
$line = date('H:i:s') . " $msg";
|
||||
$log[] = $line;
|
||||
if ($isCli) echo $line . PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||
|
||||
// 1. Clients
|
||||
warmLog('[clients] Fetching...', $isCli);
|
||||
$params = ['$select' => 'IdCliente,Nominativo,CodiceCliente', '$orderby' => 'Nominativo asc'];
|
||||
$clientData = $api->get("Cliente?" . http_build_query($params));
|
||||
file_put_contents($cacheDir . '/clienti.json', json_encode($clientData));
|
||||
$clientCount = count($clientData['value'] ?? []);
|
||||
warmLog("[clients] Cached $clientCount clients", $isCli);
|
||||
|
||||
// 2. Fixed fields: MoltiplicatorePrezzo, AnagraficaCertestObject, AnagraficaCertestService
|
||||
$fixedFields = [
|
||||
'MoltiplicatorePrezzo' => ['endpoint' => 'MoltiplicatorePrezzi', 'file' => 'moltiplicatori_prezzo.json'],
|
||||
'AnagraficaCertestObject' => ['endpoint' => 'AnagraficaCertestObject', 'file' => 'anagrafica_certest_object.json'],
|
||||
'AnagraficaCertestService' => ['endpoint' => 'AnagraficaCertestService', 'file' => 'anagrafica_certest_service.json'],
|
||||
];
|
||||
|
||||
foreach ($fixedFields as $name => $cfg) {
|
||||
warmLog("[$name] Fetching...", $isCli);
|
||||
$data = $api->get($cfg['endpoint']);
|
||||
file_put_contents($cacheDir . '/' . $cfg['file'], json_encode($data, JSON_PRETTY_PRINT));
|
||||
$count = count($data['value'] ?? $data ?? []);
|
||||
warmLog("[$name] Cached $count items", $isCli);
|
||||
}
|
||||
|
||||
// 3. ClienteResponsabile — for each unique client in datadb
|
||||
$stmt = $pdo->query("SELECT DISTINCT idclient FROM datadb WHERE idclient IS NOT NULL AND idclient > 0 ORDER BY idclient ASC");
|
||||
$clientIds = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
warmLog("[ClienteResponsabile] Fetching for " . count($clientIds) . " clients...", $isCli);
|
||||
|
||||
foreach ($clientIds as $cid) {
|
||||
$cid = (int)$cid;
|
||||
$endpoint = "Cliente($cid)?\$expand=Responsabili";
|
||||
$cacheFile = $cacheDir . '/cliente_responsabili_' . $cid . '.json';
|
||||
try {
|
||||
$data = $api->get($endpoint);
|
||||
file_put_contents($cacheFile, json_encode($data, JSON_PRETTY_PRINT));
|
||||
$count = count($data['Responsabili'] ?? []);
|
||||
warmLog("[ClienteResponsabile] Client $cid: $count responsabili", $isCli);
|
||||
} catch (Exception $e) {
|
||||
warmLog("[ClienteResponsabile] Client $cid: ERROR " . $e->getMessage(), $isCli);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. CustomField values — all SceltaMultipla field_ids from template_mapping
|
||||
$stmt = $pdo->query("SELECT DISTINCT field_id FROM template_mapping WHERE data_type = 'SceltaMultipla' AND is_visible_import = 1 AND field_id IS NOT NULL ORDER BY field_id");
|
||||
$fieldIds = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
warmLog("[CustomFields] Fetching " . count($fieldIds) . " fields...", $isCli);
|
||||
|
||||
foreach ($fieldIds as $fid) {
|
||||
$fid = (int)$fid;
|
||||
$cacheFile = $cacheDir . '/customfield_' . $fid . '.json';
|
||||
try {
|
||||
$endpoint = "CustomField($fid)?\$expand=CustomFieldsValues";
|
||||
$data = $api->get($endpoint);
|
||||
$values = $data['CustomFieldsValues'] ?? [];
|
||||
file_put_contents($cacheFile, json_encode($values));
|
||||
warmLog("[CustomField $fid] Cached " . count($values) . " values", $isCli);
|
||||
} catch (Exception $e) {
|
||||
warmLog("[CustomField $fid] ERROR " . $e->getMessage(), $isCli);
|
||||
}
|
||||
}
|
||||
|
||||
$elapsed = round(microtime(true) - $startTime, 1);
|
||||
warmLog("Done in {$elapsed}s", $isCli);
|
||||
|
||||
} catch (Exception $e) {
|
||||
warmLog("FATAL: " . $e->getMessage(), $isCli);
|
||||
}
|
||||
|
||||
if (!$isCli) {
|
||||
echo json_encode(['success' => true, 'log' => $log]);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user