783 lines
40 KiB
PHP
783 lines
40 KiB
PHP
@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 d’ordine 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 dell’unità 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, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''');
|
||
};
|
||
|
||
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>
|