netgescon-day0/resources/views/admin/gescon-import/partials/tabelle-field-mapping.blade.php

783 lines
40 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@php
$existingMap = collect(($tabelleFieldMapping ?? []))->keyBy('legacy');
$legacyCode = request('legacy_code');
$importConn = \App\Models\UserSetting::get('gescon.import_conn', 'gescon_import');
$humanize = static function ($value) {
$value = trim((string) $value);
if ($value === '') {
return '';
}
$value = str_replace(['_', '-', '.'], ' ', $value);
$value = preg_replace('/\s+/', ' ', $value ?? '') ?? '';
return (string) \Illuminate\Support\Str::of($value)->squish()->title();
};
$legacyFields = [];
$seenKeys = [];
$stagingHeaders = [];
try {
$connection = $importConn ?: config('database.default');
if ($connection) {
$schema = \Illuminate\Support\Facades\Schema::connection($connection);
$probeTables = [
'dett_tab',
'tabelle_millesimali_righe',
'tabelle_millesimali',
'millesimi',
'milles',
];
foreach ($probeTables as $table) {
if ($schema->hasTable($table)) {
$stagingHeaders = $schema->getColumnListing($table);
if (!empty($stagingHeaders)) {
break;
}
}
}
}
} catch (\Throwable $e) {
$stagingHeaders = [];
}
$preferredFields = [
[
'key' => 'tabella',
'label' => 'Codice tabella',
'group' => 'Identificativi',
'description' => 'Codice tabella millesimale (campo TAB in dett_tab).',
'default_target' => 'tabella',
],
[
'key' => 'voce',
'label' => 'Descrizione voce',
'group' => 'Identificativi',
'description' => 'Descrizione o voce della riga millesimale.',
'default_target' => 'voce',
],
[
'key' => 'millesimi',
'label' => 'Millesimi',
'group' => 'Valori millesimali',
'description' => 'Valore millesimale principale (campo MILLESIMI).',
'default_target' => 'valore',
],
[
'key' => 'valore_prev',
'label' => 'Valore previsto',
'group' => 'Valori millesimali',
'description' => 'Valore previsto o preventivo della tabella.',
'default_target' => 'valore_prev',
],
[
'key' => 'valore_cons',
'label' => 'Valore consuntivo',
'group' => 'Valori millesimali',
'description' => 'Valore consuntivo della tabella.',
'default_target' => 'valore_cons',
],
[
'key' => 'importo_prev',
'label' => 'Importo previsto (€)',
'group' => 'Importi',
'description' => 'Importo previsto espresso in euro.',
'default_target' => 'importo_prev',
],
[
'key' => 'importo_cons',
'label' => 'Importo consuntivo (€)',
'group' => 'Importi',
'description' => 'Importo consuntivo espresso in euro.',
'default_target' => 'importo_cons',
],
[
'key' => 'nord',
'label' => 'NORD / Ordine',
'group' => 'Coordinate',
'description' => 'Numero dordine della riga (NORD).',
'default_target' => 'nord',
],
[
'key' => 'scala',
'label' => 'Scala',
'group' => 'Coordinate',
'description' => 'Indicazione scala o blocco di riferimento.',
'default_target' => 'scala',
],
[
'key' => 'piano',
'label' => 'Piano',
'group' => 'Coordinate',
'description' => 'Piano dellunità associata alla tabella.',
'default_target' => 'piano',
],
[
'key' => 'unita_codice',
'label' => 'Codice unità',
'group' => 'Coordinate',
'description' => 'Codice unità/condòmino legato alla riga.',
'default_target' => 'unita_codice',
],
[
'key' => 'note',
'label' => 'Note',
'group' => 'Metadati',
'description' => 'Annotazioni e commenti legacy.',
'default_target' => 'note',
],
[
'key' => 'flag_unico',
'label' => 'Flag unico',
'group' => 'Metadati',
'description' => 'Indicatore legacy importato per logiche speciali.',
'default_target' => 'flag_unico',
],
[
'key' => 'provenienza',
'label' => 'Provenienza',
'group' => 'Metadati',
'description' => 'Origine o file di provenienza della riga.',
'default_target' => 'provenienza',
],
[
'key' => 'straordinaria_codice',
'label' => 'Codice straordinaria',
'group' => 'Metadati',
'description' => 'Identifica riparti straordinari legacy.',
'default_target' => 'straordinaria_codice',
],
[
'key' => 'ruolo_quota',
'label' => 'Ruolo / quota',
'group' => 'Metadati',
'description' => 'Ruolo, quota o profilo legacy collegato.',
'default_target' => 'ruolo_quota',
],
];
foreach ($preferredFields as $item) {
$key = (string) ($item['key'] ?? '');
if ($key === '' || isset($seenKeys[$key])) {
continue;
}
if (empty($item['label'])) {
$item['label'] = $humanize($key);
}
$legacyFields[] = $item;
$seenKeys[$key] = true;
}
foreach ($stagingHeaders as $header) {
$key = (string) $header;
if ($key === '' || isset($seenKeys[$key])) {
continue;
}
$legacyFields[] = [
'key' => $key,
'label' => $humanize($key),
'group' => null,
'description' => null,
'default_target' => null,
];
$seenKeys[$key] = true;
}
foreach ($existingMap->keys() as $legacyKey) {
$key = (string) $legacyKey;
if ($key === '' || isset($seenKeys[$key])) {
continue;
}
$legacyFields[] = [
'key' => $key,
'label' => $humanize($key),
'group' => null,
'description' => null,
'default_target' => null,
];
$seenKeys[$key] = true;
}
$legacyFieldMeta = [];
foreach ($legacyFields as $field) {
$legacyFieldMeta[$field['key']] = [
'label' => $field['label'],
'group' => $field['group'] ?? null,
'description' => $field['description'] ?? null,
'default_target' => $field['default_target'] ?? null,
];
}
$targetFields = [
'voce',
'tabella',
'valore',
'valore_prev',
'valore_cons',
'importo_prev',
'importo_cons',
'nord',
'scala',
'piano',
'unita_codice',
'note',
'flag_unico',
'provenienza',
'straordinaria_codice',
'ruolo_quota',
];
@endphp
<div class="netgescon-card mt-6" id="tabelle-field-mapping-card">
<div class="netgescon-card-header flex items-center justify-between">
<h4 class="card-title mb-0 text-base">Mapping Campi Tabelle Millesimali (Legacy Archivi)</h4>
<button type="button" class="netgescon-btn netgescon-btn-sm netgescon-btn-outline-primary" onclick="window.saveTabelleFieldMapping && window.saveTabelleFieldMapping()">Salva Mapping</button>
</div>
<div class="netgescon-card-body p-0 overflow-visible">
<div class="flex flex-col md:flex-row">
<div class="md:w-1/2 border-r">
<div class="p-3">
<div class="flex items-center justify-between mb-2">
<h5 class="font-semibold text-sm">Campi Legacy (tabelle)</h5>
<input type="text" id="filter-legacy-tabelle" placeholder="Filtra..." class="netgescon-input netgescon-input-sm w-40" />
</div>
<div class="flex items-center gap-3 text-xs text-slate-500 mb-2">
<span>Tabella: <span class="font-semibold text-slate-700" id="legacy-tabelle-table"></span></span>
<span>Righe trovate: <span class="font-semibold text-slate-700" id="legacy-tabelle-count">0</span></span>
</div>
<div class="max-h-none overflow-visible" id="legacy-tabelle-list">
@php $currentGroup = null; @endphp
@forelse($legacyFields as $field)
@if(($field['group'] ?? null) && $field['group'] !== $currentGroup)
<div class="text-[10px] uppercase tracking-wide text-slate-400 px-2 pt-3">
{{ $field['group'] }}
</div>
@php $currentGroup = $field['group']; @endphp
@endif
<div class="legacy-tab-row px-2 py-2 text-sm cursor-pointer hover:bg-slate-100 flex justify-between gap-3" data-field="{{ $field['key'] }}" data-group="{{ $field['group'] ?? '' }}">
<div>
<div class="font-semibold text-sm">{{ $field['label'] }}</div>
<div class="text-[11px] text-slate-500">{{ $field['key'] }}</div>
@if(!empty($field['description']))
<div class="text-[11px] text-slate-400">{{ $field['description'] }}</div>
@endif
</div>
@if(!empty($field['default_target']))
<div class="text-[11px] text-emerald-600 whitespace-nowrap">Suggerito {{ $field['default_target'] }}</div>
@endif
</div>
@empty
<div class="text-xs text-slate-500 py-2">Nessun campo legacy rilevato.</div>
@endforelse
</div>
<div class="mt-4">
<div class="flex flex-wrap items-center justify-between gap-2 mb-2">
<div>
<h6 class="text-[11px] uppercase tracking-wide text-slate-500">Anteprima dati legacy</h6>
<span class="text-[11px] text-slate-500" id="legacy-tabelle-preview-meta">In attesa selezione…</span>
</div>
<div class="flex items-center gap-2 flex-wrap">
<button type="button" class="netgescon-btn netgescon-btn-xs netgescon-btn-outline-secondary" onclick="window.reloadTabelleColumns && window.reloadTabelleColumns()">
<i class="mdi mdi-refresh"></i> Sync anteprima
</button>
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="netgescon-btn netgescon-btn-xs netgescon-btn-outline-secondary" onclick="window.browseTabellePreview && window.browseTabellePreview(-1)">Prev</button>
<button type="button" class="netgescon-btn netgescon-btn-xs netgescon-btn-outline-secondary" onclick="window.browseTabellePreview && window.browseTabellePreview(1)">Next</button>
</div>
</div>
</div>
<div class="border rounded bg-white overflow-visible max-h-none" id="legacy-tabelle-preview">
<div class="text-xs text-slate-500 px-3 py-2">Seleziona un codice legacy per vedere le prime righe da <code>dett_tab</code> / <code>milles*</code>.</div>
</div>
<div class="text-[11px] text-slate-500 mt-1" id="legacy-tabelle-preview-index"></div>
</div>
</div>
</div>
<div class="md:w-1/2">
<div class="p-3">
<div class="flex items-center justify-between mb-2">
<h5 class="font-semibold text-sm">Associazione a Campi Target</h5>
<input type="text" id="filter-mapping-tabelle" placeholder="Filtra mapping..." class="netgescon-input netgescon-input-sm w-44" />
</div>
<div class="max-h-none overflow-visible" id="mapping-tabelle-rows">
@forelse($legacyFields as $field)
@php
$legacyKey = $field['key'];
$prefill = $existingMap->get($legacyKey)['target'] ?? ($field['default_target'] ?? null);
@endphp
<div class="mapping-tab-row group border-b last:border-b-0 px-2 py-2 text-xs flex items-start gap-3" data-legacy="{{ $legacyKey }}" data-default-target="{{ $field['default_target'] ?? '' }}">
<div class="w-48 shrink-0">
<div class="font-semibold text-xs" data-legacy-label>{{ $field['label'] }}</div>
<div class="text-[11px] text-slate-500" data-legacy-key>{{ $legacyKey }}</div>
</div>
<div class="flex-1">
<input type="text" class="netgescon-input netgescon-input-sm w-full target-tab-input" placeholder="Seleziona / cerca campo target" list="tabelle-target-fields" value="{{ $prefill ?? '' }}" data-legacy="{{ $legacyKey }}" />
<div class="text-[11px] text-slate-500 mt-1">Anteprima <span class="font-mono" data-preview-value></span></div>
<div class="mt-1 hidden inline-create-ui">
<div class="flex items-center gap-2 flex-wrap">
<input type="text" class="netgescon-input netgescon-input-sm new-col-name" placeholder="Nome nuovo campo" style="max-width: 16rem;">
<select class="netgescon-input netgescon-input-sm new-col-type" style="max-width: 10rem;">
<option value="string">string</option>
<option value="text">text</option>
<option value="integer">integer</option>
<option value="decimal">decimal</option>
<option value="boolean">boolean</option>
<option value="date">date</option>
<option value="datetime">datetime</option>
</select>
<label class="flex items-center gap-1 text-xs"><input type="checkbox" class="new-col-nullable" checked> null</label>
<button type="button" class="netgescon-btn netgescon-btn-xs netgescon-btn-outline-success" onclick="window.__createTabellaField && window.__createTabellaField(this)">Crea e associa</button>
<button type="button" class="netgescon-btn netgescon-btn-xs netgescon-btn-outline-secondary" onclick="window.__toggleInlineCreate && window.__toggleInlineCreate(this, false)">Annulla</button>
</div>
</div>
</div>
<div class="flex flex-col items-end gap-1">
<button type="button" class="opacity-0 group-hover:opacity-100 transition text-red-500" onclick="this.closest('.mapping-tab-row').querySelector('.target-tab-input').value='';" title="Rimuovi"><i class="mdi mdi-close"></i></button>
<button type="button" class="text-emerald-600 hover:text-emerald-700 underline decoration-dotted text-[11px]" onclick="window.__toggleInlineCreate && window.__toggleInlineCreate(this, true)">+ Crea campo</button>
</div>
</div>
@empty
<div class="text-xs text-slate-500 py-2">Nessun mapping configurabile.</div>
@endforelse
<datalist id="tabelle-target-fields">
@foreach($targetFields as $tf)
<option value="{{ $tf }}"></option>
@endforeach
</datalist>
</div>
<div class="mt-3 text-right">
<button type="button" class="netgescon-btn netgescon-btn-sm netgescon-btn-outline-primary" onclick="window.saveTabelleFieldMapping && window.saveTabelleFieldMapping()">Salva</button>
</div>
</div>
</div>
</div>
<div class="border-t p-3 bg-slate-50">
<div class="text-xs text-slate-600">Suggerimento: lascia vuoto il campo target se non vuoi importare quel campo legacy. Collega in seguito le UI alla tabella corretta.</div>
</div>
</div>
</div>
<template id="tabelle-legacy-row-template">
<div class="legacy-tab-row px-2 py-2 text-sm cursor-pointer hover:bg-slate-100 flex justify-between gap-3" data-field="">
<div>
<div class="font-semibold text-sm" data-legacy-label></div>
<div class="text-[11px] text-slate-500" data-legacy-key></div>
<div class="text-[11px] text-slate-400" data-legacy-description></div>
</div>
<div class="text-[11px] text-emerald-600" data-legacy-default></div>
</div>
</template>
<template id="tabelle-mapping-row-template">
<div class="mapping-tab-row group border-b last:border-b-0 px-2 py-2 text-xs flex items-start gap-3" data-legacy="">
<div class="w-48 shrink-0">
<div class="font-semibold text-xs" data-legacy-label></div>
<div class="text-[11px] text-slate-500" data-legacy-key></div>
</div>
<div class="flex-1">
<input type="text" class="netgescon-input netgescon-input-sm w-full target-tab-input" placeholder="Seleziona / cerca campo target" list="tabelle-target-fields" data-legacy="" />
<div class="text-[11px] text-slate-500 mt-1">Anteprima <span class="font-mono" data-preview-value></span></div>
<div class="mt-1 hidden inline-create-ui">
<div class="flex items-center gap-2 flex-wrap">
<input type="text" class="netgescon-input netgescon-input-sm new-col-name" placeholder="Nome nuovo campo" style="max-width: 16rem;">
<select class="netgescon-input netgescon-input-sm new-col-type" style="max-width: 10rem;">
<option value="string">string</option>
<option value="text">text</option>
<option value="integer">integer</option>
<option value="decimal">decimal</option>
<option value="boolean">boolean</option>
<option value="date">date</option>
<option value="datetime">datetime</option>
</select>
<label class="flex items-center gap-1 text-xs"><input type="checkbox" class="new-col-nullable" checked> null</label>
<button type="button" class="netgescon-btn netgescon-btn-xs netgescon-btn-outline-success" onclick="window.__createTabellaField && window.__createTabellaField(this)">Crea e associa</button>
<button type="button" class="netgescon-btn netgescon-btn-xs netgescon-btn-outline-secondary" onclick="window.__toggleInlineCreate && window.__toggleInlineCreate(this, false)">Annulla</button>
</div>
</div>
</div>
<div class="flex flex-col items-end gap-1">
<button type="button" class="opacity-0 group-hover:opacity-100 transition text-red-500" onclick="this.closest('.mapping-tab-row').querySelector('.target-tab-input').value='';" title="Rimuovi"><i class="mdi mdi-close"></i></button>
<button type="button" class="text-emerald-600 hover:text-emerald-700 underline decoration-dotted text-[11px]" onclick="window.__toggleInlineCreate && window.__toggleInlineCreate(this, true)">+ Crea campo</button>
</div>
</div>
</template>
<script>
window.GESCON_MAPPING = window.GESCON_MAPPING || {};
window.GESCON_MAPPING.tabelleLegacyMeta = @json($legacyFieldMeta);
</script>
<script>
(function(){
const fl = document.getElementById('filter-legacy-tabelle');
const ll = document.getElementById('legacy-tabelle-list');
const fm = document.getElementById('filter-mapping-tabelle');
const ml = document.getElementById('mapping-tabelle-rows');
const CFG = window.GESCON_MAPPING || {};
const LEGACY = CFG.legacyCode || @json($legacyCode);
const tableLabel = document.getElementById('legacy-tabelle-table');
const countLabel = document.getElementById('legacy-tabelle-count');
const previewBox = document.getElementById('legacy-tabelle-preview');
const previewMeta = document.getElementById('legacy-tabelle-preview-meta');
const previewIndexLabel = document.getElementById('legacy-tabelle-preview-index');
const LEGACY_META = Object.assign({}, CFG.tabelleLegacyMeta || {});
let previewColumns = [];
let previewRows = [];
let previewIndex = 0;
let previewTableName = '—';
let previewCount = 0;
const escapeHtml = window.escapeHtml || function(value){
return String(value ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
};
function applyFilter(container, selector, input){
if(!container || !input) return;
const q = (input.value||'').trim().toLowerCase();
container.querySelectorAll(selector).forEach(el=>{
const txt = (el.getAttribute('data-field')||el.getAttribute('data-legacy')||el.textContent||'').toLowerCase();
el.style.display = !q || txt.includes(q) ? '' : 'none';
});
}
fl && fl.addEventListener('input', ()=>applyFilter(ll, '.legacy-tab-row', fl));
fm && fm.addEventListener('input', ()=>applyFilter(ml, '.mapping-tab-row', fm));
function getLegacyMeta(field){
if(!field) return {};
if(!Object.prototype.hasOwnProperty.call(LEGACY_META, field)){
LEGACY_META[field] = {
label: field,
group: null,
description: null,
default_target: null,
};
}
return LEGACY_META[field];
}
function notifyTabelleColumns(detail){
try {
window.__lastTabelleColumnsPayload = detail;
window.dispatchEvent(new CustomEvent('gescon:tabelleColumnsLoaded', { detail }));
} catch (_) {/* silent */}
}
function getCurrentPreviewRow(){
if (!previewRows.length) return null;
if (previewIndex < 0) previewIndex = 0;
if (previewIndex >= previewRows.length) previewIndex = previewRows.length - 1;
return previewRows[previewIndex] || null;
}
function pickRowValue(row, field){
if (!row || !field) return null;
if (Array.isArray(row)) {
let idx = previewColumns.findIndex(col => col === field);
if (idx < 0) {
const lf = field.toLowerCase();
idx = previewColumns.findIndex(col => (col || '').toLowerCase() === lf);
}
return idx >= 0 ? (row[idx] ?? null) : null;
}
if (Object.prototype.hasOwnProperty.call(row, field)) {
return row[field];
}
const lowerField = field.toLowerCase();
for (const key in row) {
if (!Object.prototype.hasOwnProperty.call(row, key) || typeof key !== 'string') continue;
if (key.toLowerCase() === lowerField) {
return row[key];
}
}
return null;
}
function updateMappingPreviewValues(rowData){
if (!ml) return;
ml.querySelectorAll('.mapping-tab-row').forEach(r => {
const field = r.getAttribute('data-legacy');
const span = r.querySelector('[data-preview-value]');
if (!span) return;
const value = rowData && field ? pickRowValue(rowData, field) : null;
if (value === null || value === undefined || value === '') {
span.textContent = '—';
} else {
span.textContent = String(value).trim() === '' ? '—' : String(value);
}
});
}
function refreshPreviewBox(){
if (!previewBox || !previewMeta) return;
if (tableLabel) tableLabel.textContent = previewTableName || '—';
if (countLabel) countLabel.textContent = previewCount ?? (previewRows.length || 0);
if (previewMeta) {
if (!previewColumns.length) {
previewMeta.textContent = 'Nessuna colonna rilevata';
} else {
const rowsLabel = previewRows.length ? previewRows.length + ' righe' : 'anteprima vuota';
previewMeta.textContent = previewColumns.length + ' colonne · ' + rowsLabel;
}
}
if (previewIndexLabel) {
previewIndexLabel.textContent = previewRows.length ? 'Riga ' + (previewIndex + 1) + ' di ' + previewRows.length : 'Nessuna riga disponibile';
}
if (!previewColumns.length) {
previewBox.innerHTML = '<div class="text-xs text-slate-500 px-3 py-2">Nessuna colonna trovata nelle tabelle millesimali dello staging.</div>';
updateMappingPreviewValues(null);
return;
}
if (!previewRows.length) {
previewBox.innerHTML = '<div class="text-xs text-slate-500 px-3 py-2">Nessuna riga trovata per questo stabile.</div>';
updateMappingPreviewValues(null);
return;
}
const row = getCurrentPreviewRow() || {};
const shownCols = previewColumns.slice(0, 8);
const body = shownCols.map(c => {
const val = pickRowValue(row, c);
return `
<tr>
<th class="px-2 py-1 text-[11px] uppercase text-slate-500 text-left">${escapeHtml(c)}</th>
<td class="px-2 py-1 text-[12px] text-slate-700">${escapeHtml(val ?? '')}</td>
</tr>
`; }).join('');
previewBox.innerHTML = `<table class="table-auto w-full text-xs"><tbody>${body}</tbody></table>`;
updateMappingPreviewValues(row);
}
function renderLegacyPreview(detail){
previewColumns = Array.isArray(detail?.columns) ? detail.columns : [];
previewRows = Array.isArray(detail?.rows) ? detail.rows : [];
previewIndex = 0;
previewTableName = detail?.table || '—';
previewCount = typeof detail?.count === 'number' ? detail.count : (previewRows.length || 0);
refreshPreviewBox();
}
function collectExistingMapping(){
const existing = {};
if (!ml) return existing;
ml.querySelectorAll('.mapping-tab-row').forEach(row => {
const key = row.getAttribute('data-legacy');
const input = row.querySelector('.target-tab-input');
if (!key || !input) return;
const value = input.value;
if (value && value.trim() !== '') {
existing[key] = value.trim();
}
});
return existing;
}
function updateLegacyRowContent(row, field){
const meta = getLegacyMeta(field);
row.setAttribute('data-field', field);
const labelEl = row.querySelector('[data-legacy-label]');
if (labelEl) labelEl.textContent = meta.label || field;
const keyEl = row.querySelector('[data-legacy-key]');
if (keyEl) keyEl.textContent = field;
const descEl = row.querySelector('[data-legacy-description]');
if (descEl) {
if (meta.description) {
descEl.textContent = meta.description;
descEl.classList.remove('hidden');
} else {
descEl.textContent = '';
descEl.classList.add('hidden');
}
}
const defEl = row.querySelector('[data-legacy-default]');
if (defEl) {
if (meta.default_target) {
defEl.textContent = 'Suggerito → ' + meta.default_target;
defEl.classList.remove('hidden');
} else {
defEl.textContent = '';
defEl.classList.add('hidden');
}
}
}
function ensureLegacyRow(field){
if (!field || !ll) return null;
const selector = `.legacy-tab-row[data-field="${CSS.escape(field)}"]`;
let row = ll.querySelector(selector);
if (!row) {
const tpl = document.getElementById('tabelle-legacy-row-template');
if (tpl && tpl.content) {
row = tpl.content.firstElementChild.cloneNode(true);
} else {
row = document.createElement('div');
row.className = 'legacy-tab-row px-2 py-2 text-sm cursor-pointer hover:bg-slate-100 flex justify-between gap-3';
row.innerHTML = '<div><div class="font-semibold text-sm" data-legacy-label></div><div class="text-[11px] text-slate-500" data-legacy-key></div><div class="text-[11px] text-slate-400" data-legacy-description></div></div><div class="text-[11px] text-emerald-600" data-legacy-default></div>';
}
}
updateLegacyRowContent(row, field);
return row;
}
function updateMappingRowContent(row, field, initialValue){
const meta = getLegacyMeta(field);
row.setAttribute('data-legacy', field);
if (meta.default_target) {
row.setAttribute('data-default-target', meta.default_target);
}
const labelEl = row.querySelector('[data-legacy-label]');
if (labelEl) labelEl.textContent = meta.label || field;
const keyEl = row.querySelector('[data-legacy-key]');
if (keyEl) keyEl.textContent = field;
const input = row.querySelector('.target-tab-input');
if (input) {
input.setAttribute('data-legacy', field);
if (initialValue !== null && initialValue !== undefined) {
input.value = initialValue || '';
}
}
}
function ensureMappingRow(field, existingValues){
if (!field || !ml) return null;
const selector = `.mapping-tab-row[data-legacy="${CSS.escape(field)}"]`;
let row = ml.querySelector(selector);
const alreadyPresent = !!row;
if (!row) {
const tpl = document.getElementById('tabelle-mapping-row-template');
if (tpl && tpl.content) {
row = tpl.content.firstElementChild.cloneNode(true);
} else {
row = document.createElement('div');
row.className = 'mapping-tab-row group border-b last:border-b-0 px-2 py-2 text-xs flex items-start gap-3';
row.innerHTML = '<div class="w-48 shrink-0"><div class="font-semibold text-xs" data-legacy-label></div><div class="text-[11px] text-slate-500" data-legacy-key></div></div><div class="flex-1"><input type="text" class="netgescon-input netgescon-input-sm w-full target-tab-input" list="tabelle-target-fields" data-legacy="" /><div class="mt-1 hidden inline-create-ui"><div class="flex items-center gap-2 flex-wrap"><input type="text" class="netgescon-input netgescon-input-sm new-col-name" placeholder="Nome nuovo campo" style="max-width: 16rem;"><select class="netgescon-input netgescon-input-sm new-col-type" style="max-width: 10rem;"><option value="string">string</option><option value="text">text</option><option value="integer">integer</option><option value="decimal">decimal</option><option value="boolean">boolean</option><option value="date">date</option><option value="datetime">datetime</option></select><label class="flex items-center gap-1 text-xs"><input type="checkbox" class="new-col-nullable" checked> null</label><button type="button" class="netgescon-btn netgescon-btn-xs netgescon-btn-outline-success" onclick="window.__createTabellaField && window.__createTabellaField(this)">Crea e associa</button><button type="button" class="netgescon-btn netgescon-btn-xs netgescon-btn-outline-secondary" onclick="window.__toggleInlineCreate && window.__toggleInlineCreate(this, false)">Annulla</button></div></div></div><div class="flex flex-col items-end gap-1"><button type="button" class="opacity-0 group-hover:opacity-100 transition text-red-500" onclick="this.closest('.mapping-tab-row').querySelector('.target-tab-input').value='';" title="Rimuovi"><i class="mdi mdi-close"></i></button><button type="button" class="text-emerald-600 hover:text-emerald-700 underline decoration-dotted text-[11px]" onclick="window.__toggleInlineCreate && window.__toggleInlineCreate(this, true)">+ Crea campo</button></div>';
}
ml.appendChild(row);
}
const initialValue = alreadyPresent ? null : (existingValues[field] ?? getLegacyMeta(field).default_target ?? '');
updateMappingRowContent(row, field, initialValue);
return row;
}
async function loadLegacyTabelleHeaders(){
try {
const route = (CFG.routes && CFG.routes.stagingColumns) ? CFG.routes.stagingColumns : '';
if (!route) return;
const p = new URLSearchParams();
p.set('guess','mille');
if (LEGACY) p.set('legacy_code', LEGACY);
const res = await fetch(route + '?' + p.toString());
const js = await res.json().catch(()=>({}));
if (!res.ok || js.ok === false) {
if (ll) ll.innerHTML = '<div class="text-xs text-danger py-2">Errore caricamento</div>';
notifyTabelleColumns({ columns: [], rows: [], table: null, count: 0, legacy: LEGACY });
return;
}
const cols = Array.isArray(js.columns) ? js.columns : [];
cols.forEach(col => {
if (!col || typeof col !== 'string') return;
if (!LEGACY_META[col]) {
LEGACY_META[col] = { label: col, group: null, description: null, default_target: null };
}
});
const existing = collectExistingMapping();
if (ll) {
ll.innerHTML = '';
if (!cols.length) {
ll.innerHTML = '<div class="text-xs text-slate-500 py-2">Nessuna colonna legacy rilevata.</div>';
} else {
cols.forEach(col => {
const row = ensureLegacyRow(col);
if (row) {
ll.appendChild(row);
}
});
}
}
if (ml) {
ml.innerHTML = '';
cols.forEach(col => ensureMappingRow(col, existing));
}
notifyTabelleColumns({
columns: cols,
rows: Array.isArray(js.rows) ? js.rows : [],
table: js.table || null,
count: js.count ?? null,
legacy: LEGACY,
});
} catch (e) {
if (ll) ll.innerHTML = '<div class="text-xs text-danger py-2">Errore</div>';
notifyTabelleColumns({ columns: [], rows: [], table: null, count: 0, legacy: LEGACY });
}
}
document.addEventListener('DOMContentLoaded', function(){
setTimeout(loadLegacyTabelleHeaders, 200);
if (window.__lastTabelleColumnsPayload) {
renderLegacyPreview(window.__lastTabelleColumnsPayload);
}
});
window.reloadTabelleColumns = function(){
return loadLegacyTabelleHeaders();
};
window.browseTabellePreview = function(step){
if (!previewRows.length) return;
const total = previewRows.length;
previewIndex = (previewIndex + step) % total;
if (previewIndex < 0) previewIndex = total - 1;
refreshPreviewBox();
};
window.addEventListener('gescon:tabelleColumnsLoaded', function(ev){
try { renderLegacyPreview(ev.detail || {}); } catch (_) { /* silent */ }
});
window.saveTabelleFieldMapping = async function(){
const rows = ml ? Array.from(ml.querySelectorAll('.mapping-tab-row')) : [];
const data = rows.map(r=>{
const legacy = r.getAttribute('data-legacy');
const target = r.querySelector('.target-tab-input')?.value.trim();
return {legacy, target: target||null};
}).filter(x=>x.legacy);
try {
const csrf = document.querySelector('meta[name="csrf-token"]').content;
const legacyCodeInput = document.getElementById('import-legacy-code') || document.querySelector('input[name="legacy_code"]') || document.querySelector('input[name="_legacy_code"]');
const legacyCode = legacyCodeInput ? legacyCodeInput.value.trim() : '';
if(!legacyCode){ alert('Prima seleziona un codice legacy stabile.'); return; }
const resp = await fetch('{{ route('admin.gescon-import.mapping.save') }}', {
method:'POST', headers:{'Content-Type':'application/json','X-CSRF-TOKEN':csrf,'Accept':'application/json'},
body: JSON.stringify({ _legacy_code: legacyCode, tabelle_field_mapping: JSON.stringify(data), _mapping_only: true })
});
if(resp.ok){ alert('Mapping Tabelle salvato.'); } else { alert('Errore salvataggio mapping ('+resp.status+').'); }
}catch(e){ alert('Errore: '+e.message); }
};
window.__createTabellaField = async function(btn){
const row = btn.closest('.mapping-tab-row'); if(!row) return;
const ui = row.querySelector('.inline-create-ui'); if(!ui) return;
const name = (ui.querySelector('.new-col-name')?.value||'').trim();
const type = (ui.querySelector('.new-col-type')?.value||'string').trim();
const nullable = !!ui.querySelector('.new-col-nullable')?.checked;
if(!name){ alert('Inserisci un nome per il nuovo campo.'); return; }
const url = CFG.routes && CFG.routes.createTargetColumns;
if(!url){ alert('Endpoint creazione colonne non disponibile.'); return; }
try{
const targetTable = 'tabelle_millesimali_righe';
const resp = await fetch(url, { method:'POST', headers:{'Content-Type':'application/json','X-CSRF-TOKEN':(CFG.csrf||document.querySelector('meta[name="csrf-token"]').content)}, body: JSON.stringify({ table: targetTable, columns:[{ name, type, nullable }] }) });
const js = await resp.json().catch(()=>({}));
if(!resp.ok || js.ok===false){ throw new Error(js.error||('HTTP '+resp.status)); }
const dl = document.getElementById('tabelle-target-fields');
if(dl && !Array.from(dl.options).some(o=>o.value===name)){
const opt=document.createElement('option');
opt.value=name;
dl.appendChild(opt);
}
const input = row.querySelector('.target-tab-input');
if(input){ input.value = name; }
ui.classList.add('hidden');
}catch(e){ alert('Errore creazione campo: '+e.message); }
};
})();
</script>