fixed fields

This commit is contained in:
2026-01-28 11:49:11 +01:00
parent e75be99e43
commit 8838edf3a1
11 changed files with 1812 additions and 22 deletions
+608 -20
View File
@@ -25,6 +25,18 @@ $stmt = $pdo->prepare("SELECT id, field_id, excel_column, is_manual, manual_defa
$stmt->execute([$id]);
$mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Recupera i fixed fields dalla tabella template_fixed_mapping
$stmt = $pdo->prepare("
SELECT id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import
FROM template_fixed_mapping
WHERE template_id = ?
ORDER BY id ASC
");
$stmt->execute([$id]);
$fixedMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
$hasFixedMappings = !empty($fixedMappings);
// Recupera le colonne già associate nel database
$usedColumnsFromDB = array_filter(array_column($mappings, 'excel_column'));
@@ -42,6 +54,8 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
<?php include('cssinclude.php'); ?>
<title>Configure Template <?= htmlspecialchars($template['name'], ENT_QUOTES, 'UTF-8'); ?></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<style>
.dropdown-select {
width: 100%;
@@ -89,6 +103,11 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
width: 100px;
text-align: center;
}
/* fix bootstrap/select2 width */
.select2-container {
width: 100% !important;
}
</style>
</head>
@@ -138,67 +157,215 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
<table id="schemaFieldsTable" class="table table-striped">
<thead>
<tr>
<th>Main</th>
<th>Visible on Import</th>
<th style="width:100px; text-align:center;">Main</th>
<th style="width:120px; text-align:center;">Visible on Import</th>
<th>Title</th>
<th>Type</th>
<th style="width:140px;">Type</th>
<th>Mapping</th>
<th>Default Value</th>
</tr>
</thead>
<tbody id="schemaFieldsBody">
<?php foreach ($mappings as $mapping): ?>
<tr>
<td>
<input type="checkbox" class="main-field-checkbox" data-mapping-id="<?php echo $mapping['id']; ?>" <?php echo $mapping['main_field'] == 1 ? 'checked' : ''; ?>>
<td class="text-center">
<input type="checkbox"
class="main-field-checkbox"
data-mapping-id="<?php echo (int)$mapping['id']; ?>"
<?php echo ((int)$mapping['main_field'] === 1) ? 'checked' : ''; ?>>
</td>
<td>
<input type="checkbox" class="visible-import-checkbox" data-mapping-id="<?php echo $mapping['id']; ?>" <?php echo (isset($mapping['is_visible_import']) ? $mapping['is_visible_import'] : 1) == 1 ? 'checked' : ''; ?>>
<td class="text-center">
<input type="checkbox"
class="visible-import-checkbox"
data-mapping-id="<?php echo (int)$mapping['id']; ?>"
<?php echo ((int)($mapping['is_visible_import'] ?? 1) === 1) ? 'checked' : ''; ?>>
</td>
<td>
<?php echo htmlspecialchars($mapping['field_label'] ?? 'N/A'); ?>
<?php if ($mapping['is_required'] == 1): ?>
<?php if ((int)$mapping['is_required'] === 1): ?>
<span class="badge bg-danger ms-2">Required</span>
<?php endif; ?>
</td>
<td><?php echo htmlspecialchars($mapping['data_type'] ?? 'N/A'); ?></td>
<td>
<?php
$isSceltaMultipla = $mapping['data_type'] === 'SceltaMultipla';
$mappingValue = $isSceltaMultipla ? 'manual' : ($mapping['excel_column'] ? 'xls' : ($mapping['is_manual'] ? 'manual' : ''));
$isSceltaMultipla = ($mapping['data_type'] === 'SceltaMultipla');
$mappingValue = $isSceltaMultipla
? 'manual'
: ($mapping['excel_column'] ? 'xls' : ((int)$mapping['is_manual'] === 1 ? 'manual' : ''));
?>
<select class="form-select mapping-select" data-id="<?php echo $mapping['id']; ?>" data-field-id="<?php echo $mapping['field_id']; ?>" <?php echo $isSceltaMultipla ? 'disabled' : ''; ?>>
<select class="form-select mapping-select"
data-id="<?php echo (int)$mapping['id']; ?>"
data-field-id="<?php echo (int)$mapping['field_id']; ?>"
<?php echo $isSceltaMultipla ? 'disabled' : ''; ?>>
<?php if (!$isSceltaMultipla): ?>
<option value="">Select Option</option>
<option value="xls" <?php echo $mappingValue === 'xls' ? 'selected' : ''; ?>>Map to XLS Column</option>
<option value="xls" <?php echo ($mappingValue === 'xls') ? 'selected' : ''; ?>>Map to XLS Column</option>
<?php endif; ?>
<option value="manual" <?php echo $mappingValue === 'manual' ? 'selected' : ''; ?>>Manual Entry</option>
<option value="manual" <?php echo ($mappingValue === 'manual') ? 'selected' : ''; ?>>Manual Entry</option>
</select>
<select class="form-select xls-columns" style="display:<?php echo $mappingValue === 'xls' ? 'block' : 'none'; ?>" data-id="<?php echo $mapping['id']; ?>" <?php echo $mapping['excel_column'] ? 'data-current-xls="' . htmlspecialchars($mapping['excel_column']) . '"' : ''; ?>></select>
<?php if ($mapping['excel_column']): ?>
<span class="mapped-column" style="margin-left: 5px;"><?php echo htmlspecialchars($mapping['excel_column']); ?></span>
<button class="btn btn-danger btn-sm remove-xls" data-id="<?php echo $mapping['id']; ?>" style="margin-left: 5px;">X</button>
<select class="form-select xls-columns"
style="display:<?php echo ($mappingValue === 'xls') ? 'block' : 'none'; ?>"
data-id="<?php echo (int)$mapping['id']; ?>"
<?php echo $mapping['excel_column'] ? 'data-current-xls="' . htmlspecialchars($mapping['excel_column']) . '"' : ''; ?>>
</select>
<?php if (!empty($mapping['excel_column'])): ?>
<span class="mapped-column" style="margin-left:5px;">
(<?php echo htmlspecialchars($mapping['excel_column']); ?>)
</span>
<button class="btn btn-danger btn-sm remove-xls"
data-id="<?php echo (int)$mapping['id']; ?>"
style="margin-left:5px;">
X
</button>
<?php endif; ?>
</td>
<td>
<?php if ($mapping['data_type'] === 'SceltaMultipla'): ?>
<select class="form-select dropdown-select manual-default" data-id="<?php echo $mapping['id']; ?>" data-field-id="<?php echo $mapping['field_id']; ?>" data-manual-default="<?php echo htmlspecialchars($mapping['manual_default'] ?? ''); ?>" style="display:block;">
<select class="form-select dropdown-select manual-default"
data-id="<?php echo (int)$mapping['id']; ?>"
data-field-id="<?php echo (int)$mapping['field_id']; ?>"
data-manual-default="<?php echo htmlspecialchars($mapping['manual_default'] ?? ''); ?>"
style="display:block;">
<option value="">Seleziona...</option>
</select>
<?php else: ?>
<input type="text" class="form-control manual-default" placeholder="Default value" value="<?php echo htmlspecialchars($mapping['manual_default'] ?? ''); ?>" style="display:<?php echo $mapping['is_manual'] ? 'block' : 'none'; ?>" data-field-id="<?php echo $mapping['field_id']; ?>">
<input type="text"
class="form-control manual-default"
placeholder="Default value"
value="<?php echo htmlspecialchars($mapping['manual_default'] ?? ''); ?>"
style="display:<?php echo ((int)$mapping['is_manual'] === 1) ? 'block' : 'none'; ?>"
data-field-id="<?php echo (int)$mapping['field_id']; ?>">
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- Defaults for fixed fields (not part of XLS mapping) -->
<!-- Fixed fields defaults (saved in DB, autosave) -->
<div class="row mt-4">
<div class="col-12 d-flex align-items-center justify-content-between">
<h5 class="mb-0">Fixed Fields Defaults</h5>
<?php if (!$hasFixedMappings): ?>
<button id="btnAddFixedFields" class="btn btn-outline-primary">
Add Fixed Fields
</button>
<?php endif; ?>
</div>
<div class="col-12">
<small class="text-muted">Defaults are saved automatically when changed</small>
</div>
</div>
<div class="row mt-3" id="fixedFieldsSection" style="display: <?php echo $hasFixedMappings ? 'block' : 'none'; ?>;">
<div class="col-12">
<table class="table table-striped" id="fixedFieldsTable">
<thead>
<tr>
<th style="width:120px; text-align:center;">Visible on Import</th>
<th style="width:120px; text-align:center;">Required</th>
<th>Field</th>
<th style="width:140px;">Type</th>
<th>Default Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($fixedMappings as $fm): ?>
<tr>
<td class="text-center">
<input type="checkbox"
class="fixed-visible-checkbox"
data-fixed-id="<?php echo (int)$fm['id']; ?>"
<?php echo ((int)$fm['is_visible_import'] === 1) ? 'checked' : ''; ?>>
</td>
<td class="text-center">
<input type="checkbox"
class="fixed-required-checkbox"
data-fixed-id="<?php echo (int)$fm['id']; ?>"
<?php echo ((int)$fm['is_required'] === 1) ? 'checked' : ''; ?>>
</td>
<td>
<strong><?php echo htmlspecialchars($fm['fixed_field_key']); ?></strong>
</td>
<td><?php echo htmlspecialchars($fm['data_type']); ?></td>
<td>
<?php if ($fm['data_type'] === 'DATE'): ?>
<input type="date"
class="form-control fixed-default-input"
data-fixed-id="<?php echo (int)$fm['id']; ?>"
value="<?php echo htmlspecialchars($fm['default_value'] ?? ''); ?>">
<?php elseif (in_array($fm['fixed_field_key'], [
'ClienteResponsabile',
'MoltiplicatorePrezzo',
'AnagraficaCertestObject',
'AnagraficaCertestService'
])): ?>
<select class="form-select fixed-default-select"
data-fixed-id="<?php echo (int)$fm['id']; ?>"
data-fixed-key="<?php echo htmlspecialchars($fm['fixed_field_key']); ?>"
data-current-value="<?php echo htmlspecialchars($fm['default_value'] ?? ''); ?>">
<option value="">Loading...</option>
</select>
<?php else: ?>
<input type="text"
class="form-control fixed-default-input"
data-fixed-id="<?php echo (int)$fm['id']; ?>"
value="<?php echo htmlspecialchars($fm['default_value'] ?? ''); ?>">
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div id="fixedSaveStatus" class="small mt-2 text-muted"></div>
</div>
</div>
<div class="mt-4 text-end">
<a href="templates_dashboard.php" class="btn btn-primary"> Back to Template Dashboard</a>
</div>
</div>
</div>
</div>
@@ -209,7 +376,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
</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>
let availableXlsColumns = <?php echo json_encode($xlsHeaders); ?> || [];
let usedColumnsFromDB = <?php echo json_encode($usedColumnsFromDB); ?> || [];
@@ -316,6 +483,80 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
});
}
function initSelect2ForSchemaDropdowns() {
// dropdown-select = SceltaMultipla (manual default)
if (window.jQuery && $.fn.select2) {
$('.dropdown-select').each(function() {
if ($(this).hasClass('select2-hidden-accessible')) return; // già inizializzato
$(this).select2({
width: '100%',
placeholder: 'Seleziona...',
allowClear: true
});
});
}
}
function initSelect2ForFixedDropdowns() {
if (!(window.jQuery && $.fn.select2)) return;
$('.fixed-default-select').each(function() {
const $el = $(this);
// se era già Select2, lo resettiamo (utile quando renderizzi righe nuove)
if ($el.hasClass('select2-hidden-accessible')) {
$el.select2('destroy');
}
$el.select2({
width: '100%',
placeholder: 'Seleziona...',
allowClear: true
});
});
}
function saveFixedDefaultSelect(selectEl) {
const id = selectEl.dataset.fixedId;
const value = selectEl.value;
fetch('update_fixed_field.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id,
field: 'default_value',
value
})
})
.then(r => r.json())
.then(d => {
if (!d.success) throw new Error(d.message || 'Update failed');
// aggiorno anche il dataset così rimane coerente
selectEl.dataset.currentValue = value;
fixedStatus('✅ Saved');
})
.catch(err => {
console.error(err);
fixedStatus('❌ Save error', true);
});
}
// Eventi Select2 (jQuery)
if (window.jQuery) {
$(document).on('select2:select select2:clear', '.fixed-default-select', function() {
saveFixedDefaultSelect(this);
});
// fallback (nel caso cambi senza select2)
$(document).on('change', '.fixed-default-select', function() {
saveFixedDefaultSelect(this);
});
}
document.addEventListener("DOMContentLoaded", function() {
let templateId = <?php echo $id; ?>;
let schemaId = <?php echo json_encode($template['idschema'] ?? 0); ?>;
@@ -403,9 +644,13 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
dropdown.disabled = true;
});
} finally {
// attiva Select2 dopo aver messo le options
initSelect2ForSchemaDropdowns();
console.log('Nascondo overlay di caricamento.');
loadingOverlay.style.display = 'none';
}
}
// Carica i dropdown con overlay
@@ -713,9 +958,352 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
.catch(error => console.error("❌ Fetch error:", error));
}
// =======================
// FIXED FIELDS (DB autosave)
// =======================
function escapeHtml(str) {
return String(str ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#039;');
}
function fixedStatus(msg, isError = false) {
const el = document.getElementById('fixedSaveStatus');
if (!el) return;
el.textContent = msg;
el.classList.toggle('text-danger', isError);
el.classList.toggle('text-muted', !isError);
setTimeout(() => {
el.textContent = '';
}, 2000);
}
function renderFixedRows(rows) {
const tbody = document.querySelector('#fixedFieldsTable tbody');
if (!tbody) return;
const keysWithDropdown = [
'ClienteResponsabile',
'MoltiplicatorePrezzo',
'AnagraficaCertestObject',
'AnagraficaCertestService'
];
tbody.innerHTML = rows.map(r => {
const type = String(r.data_type || '').toUpperCase();
const dv = r.default_value ?? '';
const isDate = type === 'DATE';
const isDropdown = keysWithDropdown.includes(String(r.fixed_field_key || ''));
return `
<tr>
<td class="text-center">
<input type="checkbox"
class="fixed-visible-checkbox"
data-fixed-id="${r.id}"
${parseInt(r.is_visible_import) === 1 ? 'checked' : ''}>
</td>
<td class="text-center">
<input type="checkbox"
class="fixed-required-checkbox"
data-fixed-id="${r.id}"
${parseInt(r.is_required) === 1 ? 'checked' : ''}>
</td>
<td><strong>${escapeHtml(r.fixed_field_key)}</strong></td>
<td>${escapeHtml(r.data_type || '')}</td>
<td>
${
isDate
? `<input type="date"
class="form-control fixed-default-input"
data-fixed-id="${r.id}"
value="${escapeHtml(dv)}">`
: isDropdown
? `<select class="form-select fixed-default-select"
data-fixed-id="${r.id}"
data-fixed-key="${escapeHtml(r.fixed_field_key)}"
data-current-value="${escapeHtml(dv)}">
<option value="">Loading...</option>
</select>`
: `<input type="text"
class="form-control fixed-default-input"
data-fixed-id="${r.id}"
value="${escapeHtml(dv)}"
placeholder="Default value">`
}
</td>
</tr>
`;
}).join('');
}
// Button: Add Fixed Fields
const btnAddFixedFields = document.getElementById('btnAddFixedFields');
if (btnAddFixedFields) {
btnAddFixedFields.addEventListener('click', async () => {
btnAddFixedFields.disabled = true;
btnAddFixedFields.textContent = 'Creating...';
try {
const res = await fetch('create_fixed_fields.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: templateId
})
});
const data = await res.json();
if (!data.success) throw new Error(data.message || 'Create failed');
document.getElementById('fixedFieldsSection').style.display = 'block';
btnAddFixedFields.style.display = 'none';
if (Array.isArray(data.rows)) {
renderFixedRows(data.rows);
await fillFixedDropdowns();
initSelect2ForFixedDropdowns();
}
fixedStatus('✅ Fixed fields created');
} catch (e) {
console.error(e);
fixedStatus('❌ ' + e.message, true);
btnAddFixedFields.disabled = false;
btnAddFixedFields.textContent = 'Add Fixed Fields';
}
});
}
// Autosave: Visible on import
document.addEventListener('change', (e) => {
if (!e.target.classList.contains('fixed-visible-checkbox')) return;
const id = e.target.dataset.fixedId;
const value = e.target.checked ? 1 : 0;
fetch('update_fixed_field.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id,
field: 'is_visible_import',
value
})
})
.then(r => r.json())
.then(d => {
if (!d.success) throw new Error(d.message || 'Update failed');
fixedStatus('✅ Saved');
})
.catch(err => {
console.error(err);
e.target.checked = !e.target.checked;
fixedStatus('❌ Save error', true);
});
});
document.addEventListener('change', (e) => {
if (!e.target.classList.contains('fixed-required-checkbox')) return;
const id = e.target.dataset.fixedId;
const value = e.target.checked ? 1 : 0;
fetch('update_fixed_field.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id,
field: 'is_required',
value
})
})
.then(r => r.json())
.then(d => {
if (!d.success) throw new Error(d.message || 'Update failed');
fixedStatus('✅ Saved');
})
.catch(err => {
console.error(err);
e.target.checked = !e.target.checked;
fixedStatus('❌ Save error', true);
});
});
// Autosave: manual_default (debounce)
let fixedTimer = null;
document.addEventListener('input', (e) => {
if (!e.target.classList.contains('fixed-default-input')) return;
clearTimeout(fixedTimer);
fixedTimer = setTimeout(() => {
const id = e.target.dataset.fixedId;
const value = e.target.value;
fetch('update_fixed_field.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id,
field: 'default_value',
value
})
})
.then(r => r.json())
.then(d => {
if (!d.success) throw new Error(d.message || 'Update failed');
fixedStatus('✅ Saved');
})
.catch(err => {
console.error(err);
fixedStatus('❌ Save error', true);
});
}, 350);
});
if (availableXlsColumns.length) updateXlsDropdowns();
fillFixedDropdowns().then(() => {
initSelect2ForFixedDropdowns();
});
});
async function fillFixedDropdowns() {
const selects = document.querySelectorAll('.fixed-default-select');
console.log('[fillFixedDropdowns] found selects:', selects.length);
if (!selects.length) return;
const clientId = <?php echo (int)($template['idclient'] ?? 0); ?>;
async function fetchJson(url) {
const r = await fetch(url);
const text = await r.text();
if (!r.ok) throw new Error(`HTTP ${r.status} on ${url}: ${text.slice(0, 200)}`);
try {
return JSON.parse(text);
} catch (e) {
throw new Error(`Invalid JSON from ${url}: ${text.slice(0, 200)}`);
}
}
function normalizeList(j, key) {
// Accetta vari formati: {value:[]}, {Responsabili:[]}, {results:[]}, array diretto
if (Array.isArray(j)) return j;
if (j && Array.isArray(j.value)) return j.value;
if (j && Array.isArray(j.Responsabili)) return j.Responsabili;
if (j && Array.isArray(j.results)) return j.results;
console.warn('[fillFixedDropdowns] unknown JSON shape for', key, j);
return [];
}
async function loadData(key) {
if (key === 'MoltiplicatorePrezzo') {
const j = await fetchJson('get_moltiplicatoreprezzo.php');
const list = normalizeList(j, key);
return list.map(x => ({
id: x.IdMoltiplicatorePrezzo,
label: `${x.Descrizione} (x${x.Fattore})`
}));
}
if (key === 'AnagraficaCertestObject') {
const j = await fetchJson('get_anagrafica_certest_object.php');
const list = normalizeList(j, key);
return list.map(x => ({
id: x.IdAnagrafica,
label: `${x.Codice} - ${x.NomeAnagrafica}`
}));
}
if (key === 'AnagraficaCertestService') {
const j = await fetchJson('get_anagrafica_certest_service.php');
const list = normalizeList(j, key);
return list.map(x => ({
id: x.IdAnagrafica,
label: `${x.Codice} - ${x.NomeAnagrafica}`
}));
}
if (key === 'ClienteResponsabile') {
if (!clientId) return [];
const j = await fetchJson('get_cliente_responsabile.php?id_cliente=' + clientId);
const list = normalizeList(j, key);
// supporto due possibili campi:
// - {IdClienteResponsabile, Nominativo}
// - {Id, Nome} o simili
return list.map(x => ({
id: x.IdClienteResponsabile ?? x.Id ?? x.id,
label: x.Nominativo ?? x.Nome ?? x.name ?? ''
})).filter(x => x.id != null && x.label);
}
return [];
}
for (const select of selects) {
const key = select.dataset.fixedKey;
const current = select.dataset.currentValue;
// IMPORTANT: togli "Loading..." subito (così se crasha non resta bloccato)
select.innerHTML = '<option value="">Loading...</option>';
try {
console.log('[fillFixedDropdowns] loading', key);
const items = await loadData(key);
if (!items.length) {
select.innerHTML = '<option value="">No data</option>';
continue;
}
select.innerHTML = '<option value=""></option>';
for (const it of items) {
const opt = document.createElement('option');
opt.value = it.id;
opt.textContent = it.label;
select.appendChild(opt);
}
if (current) select.value = current;
} catch (err) {
console.error('[fillFixedDropdowns] error for', key, err);
select.innerHTML = '<option value="">Error loading data</option>';
}
}
}
</script>
</body>
</html>