wuic-framework-lib 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/{wuic-framework-lib-chart-list.component-B1V8Q-Qq.mjs → wuic-framework-lib-chart-list.component-C02jeFGy.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-code-editor.component-4N95qt3D.mjs → wuic-framework-lib-code-editor.component-BZNLr0Jj.mjs} +59 -2
- package/fesm2022/{wuic-framework-lib-designer.component-pW1YK-oY.mjs → wuic-framework-lib-designer.component-N_CfbGnr.mjs} +986 -17
- package/fesm2022/{wuic-framework-lib-first-run-wizard.component-CaGePYLi.mjs → wuic-framework-lib-first-run-wizard.component-lW7-xMm3.mjs} +7 -61
- package/fesm2022/{wuic-framework-lib-framework-docs.component-m0IlS0Sl.mjs → wuic-framework-lib-framework-docs.component-CGQbmSkv.mjs} +3716 -347
- package/fesm2022/{wuic-framework-lib-pivot-builder.component-VIs0CIH_.mjs → wuic-framework-lib-pivot-builder.component-0gJz9RWr.mjs} +5 -5
- package/fesm2022/{wuic-framework-lib-prompt-dialog-fallback-host.component-Dm9GTZ07.mjs → wuic-framework-lib-prompt-dialog-fallback-host.component-C9LvmFsL.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-report-designer.component-FiAwNkPM.mjs → wuic-framework-lib-report-designer.component-DcpdzFjh.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-report-viewer.component-DY0dGMrt.mjs → wuic-framework-lib-report-viewer.component-BHpbGpp9.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-scheduler-list.component-3E5m-9Qd.mjs → wuic-framework-lib-scheduler-list.component-DHoukjf-.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-spreadsheet-list-sf.component-D9P9WbDl.mjs → wuic-framework-lib-spreadsheet-list-sf.component-VsaXepUC.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-workflow-designer.component-4RC2EkGA.mjs → wuic-framework-lib-workflow-designer.component-epj3oKq3.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-wuic-framework-lib-BSyGhpM1.mjs → wuic-framework-lib-wuic-framework-lib-Deudd4MY.mjs} +2186 -144
- package/fesm2022/wuic-framework-lib.mjs +1 -1
- package/package.json +1 -1
- package/types/wuic-framework-lib.d.ts +829 -11
|
@@ -50,7 +50,7 @@ import { Title } from '@angular/platform-browser';
|
|
|
50
50
|
import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'primeng/tabs';
|
|
51
51
|
import * as i5$1 from 'primeng/menubar';
|
|
52
52
|
import { MenubarModule } from 'primeng/menubar';
|
|
53
|
-
import { distinctUntilChanged, catchError as catchError$1 } from 'rxjs/operators';
|
|
53
|
+
import { distinctUntilChanged, filter, catchError as catchError$1 } from 'rxjs/operators';
|
|
54
54
|
import * as i11 from 'primeng/progressbar';
|
|
55
55
|
import { ProgressBarModule } from 'primeng/progressbar';
|
|
56
56
|
import { MarkerClusterer } from '@googlemaps/markerclusterer';
|
|
@@ -90,9 +90,9 @@ import * as i4$3 from 'primeng/popover';
|
|
|
90
90
|
import { PopoverModule } from 'primeng/popover';
|
|
91
91
|
import * as i5$4 from '@angular/cdk/drag-drop';
|
|
92
92
|
import { moveItemInArray, transferArrayItem, DragDropModule } from '@angular/cdk/drag-drop';
|
|
93
|
-
import * as
|
|
93
|
+
import * as i10$1 from 'primeng/badge';
|
|
94
94
|
import { BadgeModule } from 'primeng/badge';
|
|
95
|
-
import * as
|
|
95
|
+
import * as i11$1 from 'primeng/chip';
|
|
96
96
|
import { ChipModule } from 'primeng/chip';
|
|
97
97
|
import { ScrollPanelModule } from 'primeng/scrollpanel';
|
|
98
98
|
import * as i5$5 from 'primeng/fieldset';
|
|
@@ -2707,7 +2707,7 @@ class WtoolboxService {
|
|
|
2707
2707
|
const appRef = resolvedInjector.get(ApplicationRef);
|
|
2708
2708
|
const environmentInjector = resolvedInjector.get(EnvironmentInjector);
|
|
2709
2709
|
const close$ = new Subject();
|
|
2710
|
-
const { PromptDialogFallbackHostComponent } = await import('./wuic-framework-lib-prompt-dialog-fallback-host.component-
|
|
2710
|
+
const { PromptDialogFallbackHostComponent } = await import('./wuic-framework-lib-prompt-dialog-fallback-host.component-C9LvmFsL.mjs');
|
|
2711
2711
|
const hostRef = createComponent(PromptDialogFallbackHostComponent, { environmentInjector });
|
|
2712
2712
|
hostRef.instance.datasource = dialogConfig?.data?.datasource;
|
|
2713
2713
|
hostRef.instance.header = String(dialogConfig?.header || '');
|
|
@@ -7283,6 +7283,83 @@ class MetadataProviderService {
|
|
|
7283
7283
|
clearMenuAdminMethodsCache(userId) {
|
|
7284
7284
|
localStorage.removeItem(this.getMenuAdminMethodsCacheKey(userId));
|
|
7285
7285
|
}
|
|
7286
|
+
/**
|
|
7287
|
+
* Cancella dalla Dexie locale SOLO i record collegati alla route data, lasciando
|
|
7288
|
+
* intatto il resto del database multi-tenant. Necessario dopo che un'operazione
|
|
7289
|
+
* server-side (es. RAG chatbot Apply, metadata-editor save) modifica i metadati
|
|
7290
|
+
* di una singola route senza invalidare l'intera cache: chiamare
|
|
7291
|
+
* <c>Dexie.delete()</c> per tutto il DB causa una riapertura completa al primo
|
|
7292
|
+
* F5 (costosa: ~MB di re-fetch dal backend per le route che non hanno cambiato);
|
|
7293
|
+
* con questo metodo si esegue un flush mirato sulle 7 tabelle Dexie correlate.
|
|
7294
|
+
*
|
|
7295
|
+
* Tabelle gestite (vedi <c>generateLocalDB</c>):
|
|
7296
|
+
* - MetadatiTabella (filter: md_route_name)
|
|
7297
|
+
* - MetadatiColonna (filter: md_id derivato)
|
|
7298
|
+
* - MetadatiCustomActionTabella (filter: md_id)
|
|
7299
|
+
* - MetadatiUiStiliTabella (filter: md_id)
|
|
7300
|
+
* - MetadatiUiStiliColonna (filter: mc_id ∈ colonne della route)
|
|
7301
|
+
* - MetadatiUtentiAutorizzazioniColonna (filter: mc_id ∈ colonne della route)
|
|
7302
|
+
* - MetadatiConditionGroup (filter: md_id)
|
|
7303
|
+
*
|
|
7304
|
+
* No-op se: route vuota, Dexie non inizializzata, o la route non e' presente in
|
|
7305
|
+
* cache (gia' "lazy" - verra' fetched dal backend al primo accesso).
|
|
7306
|
+
* Best-effort: errori dei singoli delete non bloccano gli altri.
|
|
7307
|
+
*
|
|
7308
|
+
* @param route Nome route metadata (es. "cities", "stockitems") — corrisponde
|
|
7309
|
+
* a <c>MetadatiTabella.md_route_name</c>.
|
|
7310
|
+
*/
|
|
7311
|
+
static async deleteRouteFromCache(route) {
|
|
7312
|
+
if (!route || typeof route !== 'string')
|
|
7313
|
+
return;
|
|
7314
|
+
const db = MetadataProviderService.MetaDB;
|
|
7315
|
+
if (!db || typeof db.table !== 'function')
|
|
7316
|
+
return;
|
|
7317
|
+
const safe = async (fn) => { try {
|
|
7318
|
+
await fn();
|
|
7319
|
+
}
|
|
7320
|
+
catch { /* best-effort */ } };
|
|
7321
|
+
// Step 1: trova md_id della route via MetadatiTabella (l'indice
|
|
7322
|
+
// [md_id+__user_id] e' composito; cerchiamo direttamente per md_route_name).
|
|
7323
|
+
let mdIds = [];
|
|
7324
|
+
try {
|
|
7325
|
+
const rows = await db.table('MetadatiTabella').where('md_route_name').equals(route).toArray();
|
|
7326
|
+
mdIds = (rows || []).map((r) => r?.md_id).filter((x) => x !== undefined && x !== null);
|
|
7327
|
+
}
|
|
7328
|
+
catch {
|
|
7329
|
+
return; /* tabella non presente o store mancante */
|
|
7330
|
+
}
|
|
7331
|
+
if (mdIds.length === 0)
|
|
7332
|
+
return; // route non in cache locale
|
|
7333
|
+
// Step 2: trova i mc_id (colonne) appartenenti a quegli md_id PRIMA di
|
|
7334
|
+
// cancellarli, cosi' possiamo flushare in cascata stili/autorizzazioni
|
|
7335
|
+
// colonna che hanno solo mc_id come FK.
|
|
7336
|
+
let mcIds = [];
|
|
7337
|
+
try {
|
|
7338
|
+
const cols = await db.table('MetadatiColonna').where('md_id').anyOf(mdIds).toArray();
|
|
7339
|
+
mcIds = (cols || []).map((c) => c?.mc_id).filter((x) => x !== undefined && x !== null);
|
|
7340
|
+
}
|
|
7341
|
+
catch { }
|
|
7342
|
+
// Step 3: trova i CG_Id (condition groups) per cancellare in cascata anche
|
|
7343
|
+
// MetadatiConditionGroupAction (che ha FK_CG_Id come FK).
|
|
7344
|
+
let cgIds = [];
|
|
7345
|
+
try {
|
|
7346
|
+
const cgs = await db.table('MetadatiConditionGroup').where('md_id').anyOf(mdIds).toArray();
|
|
7347
|
+
cgIds = (cgs || []).map((g) => g?.CG_Id).filter((x) => x !== undefined && x !== null);
|
|
7348
|
+
}
|
|
7349
|
+
catch { }
|
|
7350
|
+
// Step 4: cancella tutte e 8 le tabelle in parallelo (le primary key sono
|
|
7351
|
+
// disgiunte tra le tabelle, niente race condition).
|
|
7352
|
+
await Promise.all([
|
|
7353
|
+
safe(() => db.table('MetadatiTabella').where('md_route_name').equals(route).delete()),
|
|
7354
|
+
safe(() => db.table('MetadatiColonna').where('md_id').anyOf(mdIds).delete()),
|
|
7355
|
+
safe(() => db.table('MetadatiCustomActionTabella').where('md_id').anyOf(mdIds).delete()),
|
|
7356
|
+
safe(() => db.table('MetadatiUiStiliTabella').where('md_id').anyOf(mdIds).delete()),
|
|
7357
|
+
safe(() => mcIds.length ? db.table('MetadatiUiStiliColonna').where('mc_id').anyOf(mcIds).delete() : Promise.resolve()),
|
|
7358
|
+
safe(() => mcIds.length ? db.table('MetadatiUtentiAutorizzazioniColonna').where('mc_id').anyOf(mcIds).delete() : Promise.resolve()),
|
|
7359
|
+
safe(() => db.table('MetadatiConditionGroup').where('md_id').anyOf(mdIds).delete()),
|
|
7360
|
+
safe(() => cgIds.length ? db.table('MetadatiConditionGroupAction').where('FK_CG_Id').anyOf(cgIds).delete() : Promise.resolve()),
|
|
7361
|
+
]);
|
|
7362
|
+
}
|
|
7286
7363
|
/**
|
|
7287
7364
|
* Invalida la cache menu dell'utente corrente (o dell'utente passato) rimuovendo la voce `menu_{userId}` da localStorage.
|
|
7288
7365
|
* Se richiesto elimina anche la cache dei metodi amministrativi `menu_admin_methods_{userId}`.
|
|
@@ -7377,6 +7454,93 @@ class MetadataProviderService {
|
|
|
7377
7454
|
async getMetadatiById(md_id) {
|
|
7378
7455
|
return await this.getMetadati('', md_id);
|
|
7379
7456
|
}
|
|
7457
|
+
// Cache process-wide della lista route metadata non-system disponibili
|
|
7458
|
+
// all'utente corrente. Server-side e' gia' cached in
|
|
7459
|
+
// `HttpContext.Current.Application["availableRoutes"]` (vedi
|
|
7460
|
+
// _Metadati_suggestions_base.cs:1641), qui evitiamo round-trip HTTP
|
|
7461
|
+
// ripetuti durante la sessione (la lista cambia raramente: solo dopo
|
|
7462
|
+
// crea/elimina route in scaffolding, scenari controllati).
|
|
7463
|
+
//
|
|
7464
|
+
// La cache e' shared per istanza di MetadataProviderService (Angular
|
|
7465
|
+
// providedIn:'root' singleton). Per forzare refresh dopo scaffolding,
|
|
7466
|
+
// chiamare `clearAvailableRoutesCache()`.
|
|
7467
|
+
static _availableRoutesCache = null;
|
|
7468
|
+
static _availableRoutesInflight = null;
|
|
7469
|
+
/**
|
|
7470
|
+
* Lista delle route metadata utente (md_route_name + md_display_string).
|
|
7471
|
+
* Backend: `MetaService.getAvailableRoutes` (admin-only, cached server-side).
|
|
7472
|
+
*
|
|
7473
|
+
* Idempotente + de-duplicato in-flight: chiamate concorrenti condividono
|
|
7474
|
+
* la stessa Promise per evitare race.
|
|
7475
|
+
*
|
|
7476
|
+
* Usata dal chatbot RAG (designer state provider) per:
|
|
7477
|
+
* 1) fornire al LLM la whitelist di route valide,
|
|
7478
|
+
* 2) permettere fuzzy-match semantico ("provincie" -> "provinces"),
|
|
7479
|
+
* 3) fare follow-up question quando l'utente menziona una route ambigua.
|
|
7480
|
+
*/
|
|
7481
|
+
async getAvailableRoutesAsync() {
|
|
7482
|
+
if (MetadataProviderService._availableRoutesCache) {
|
|
7483
|
+
return MetadataProviderService._availableRoutesCache;
|
|
7484
|
+
}
|
|
7485
|
+
if (MetadataProviderService._availableRoutesInflight) {
|
|
7486
|
+
return MetadataProviderService._availableRoutesInflight;
|
|
7487
|
+
}
|
|
7488
|
+
// URL canonico hardcoded (no concat con global_root_url che varia tra
|
|
7489
|
+
// dev/prod e non sempre include il prefisso AsmxProxy). Stesso pattern
|
|
7490
|
+
// di spreadsheet-list-sf.component.ts:3684.
|
|
7491
|
+
const url = '/api/Meta/AsmxProxy/MetaService.getAvailableRoutes';
|
|
7492
|
+
// user_id e' parametro legacy ignorato dal backend (usa sempre
|
|
7493
|
+
// l'utente autenticato via cookie k-user). Lo passiamo per
|
|
7494
|
+
// compatibilita' di schema ma puo' anche essere stringa vuota.
|
|
7495
|
+
const userId = String(this.userInfo.getuserInfo()?.user_id ?? '');
|
|
7496
|
+
MetadataProviderService._availableRoutesInflight = new Promise((resolve) => {
|
|
7497
|
+
this.http.post(url, { user_id: userId }).subscribe({
|
|
7498
|
+
next: (resp) => {
|
|
7499
|
+
// Il backend ritorna `{ results: [...], TotalRecords: N }` (rawPagedResult).
|
|
7500
|
+
// serialize via RawHelpers.serialize ritorna JSON come stringa, ma il
|
|
7501
|
+
// controller AsmxProxy fa il parse — quindi qui arriva oggetto.
|
|
7502
|
+
let raw = resp;
|
|
7503
|
+
if (typeof raw === 'string') {
|
|
7504
|
+
try {
|
|
7505
|
+
raw = JSON.parse(raw);
|
|
7506
|
+
}
|
|
7507
|
+
catch {
|
|
7508
|
+
raw = null;
|
|
7509
|
+
}
|
|
7510
|
+
}
|
|
7511
|
+
const list = Array.isArray(raw?.results) ? raw.results : (Array.isArray(raw) ? raw : []);
|
|
7512
|
+
const normalized = list
|
|
7513
|
+
.map((r) => ({
|
|
7514
|
+
md_id: Number(r?.md_id ?? 0),
|
|
7515
|
+
md_route_name: String(r?.md_route_name ?? '').trim(),
|
|
7516
|
+
md_display_string: String(r?.md_display_string ?? '').trim(),
|
|
7517
|
+
}))
|
|
7518
|
+
.filter((r) => r.md_route_name.length > 0);
|
|
7519
|
+
MetadataProviderService._availableRoutesCache = normalized;
|
|
7520
|
+
MetadataProviderService._availableRoutesInflight = null;
|
|
7521
|
+
resolve(normalized);
|
|
7522
|
+
},
|
|
7523
|
+
error: () => {
|
|
7524
|
+
// Best-effort: in caso di errore (utente non admin, backend giu',
|
|
7525
|
+
// ecc.) ritorna lista vuota. Il chiamante (state provider chatbot)
|
|
7526
|
+
// gestira' graceful con messaggio "lista non disponibile".
|
|
7527
|
+
MetadataProviderService._availableRoutesInflight = null;
|
|
7528
|
+
resolve([]);
|
|
7529
|
+
}
|
|
7530
|
+
});
|
|
7531
|
+
});
|
|
7532
|
+
return MetadataProviderService._availableRoutesInflight;
|
|
7533
|
+
}
|
|
7534
|
+
/**
|
|
7535
|
+
* Invalida la cache process-wide delle route disponibili. Chiamare dopo
|
|
7536
|
+
* scaffolding (crea/elimina route) per forzare un re-fetch al prossimo
|
|
7537
|
+
* accesso. La cache server-side va invalidata separatamente (boot di
|
|
7538
|
+
* un nuovo worker o gesture esplicita).
|
|
7539
|
+
*/
|
|
7540
|
+
clearAvailableRoutesCache() {
|
|
7541
|
+
MetadataProviderService._availableRoutesCache = null;
|
|
7542
|
+
MetadataProviderService._availableRoutesInflight = null;
|
|
7543
|
+
}
|
|
7380
7544
|
/**
|
|
7381
7545
|
* Restituisce i metadata di una route (o `md_id`) da cache Dexie quando validi; in caso contrario ricarica da backend.
|
|
7382
7546
|
* Gestisce invalidazione per versioning metadata progetto e bypass cache in modalita client-side CRUD.
|
|
@@ -7949,11 +8113,14 @@ class MetadataProviderService {
|
|
|
7949
8113
|
const resolve = (doSync) => beforeSync(doSync);
|
|
7950
8114
|
const prevent = () => beforeSync(false);
|
|
7951
8115
|
const record = savingData;
|
|
8116
|
+
const metaInfo = dataSource.metaInfo;
|
|
7952
8117
|
${table_metadata.md_before_save}
|
|
7953
8118
|
`) : async () => true;
|
|
7954
8119
|
table_metadata.md_after_save_fn = table_metadata.md_after_save ? new AsyncFunction('dataSource, savingData, syncedData, isInsert, isClone, isDelete, event, wtoolbox', `
|
|
7955
8120
|
const record = savingData;
|
|
7956
8121
|
const result = syncedData;
|
|
8122
|
+
const metaInfo = dataSource.metaInfo;
|
|
8123
|
+
|
|
7957
8124
|
${table_metadata.md_after_save}
|
|
7958
8125
|
`) : async () => { };
|
|
7959
8126
|
table_metadata.md_after_load_fn = table_metadata.md_after_load ? new AsyncFunction('dataSource, originalEvent, result, loadedRecords, isInsert, wtoolbox', `
|
|
@@ -9564,6 +9731,77 @@ class UpdateInfo {
|
|
|
9564
9731
|
}
|
|
9565
9732
|
}
|
|
9566
9733
|
|
|
9734
|
+
/**
|
|
9735
|
+
* Helper centralizzato per ripulire `localStorage` post-save metadata
|
|
9736
|
+
* preservando le chiavi runtime user-state che NON dipendono dai metadati.
|
|
9737
|
+
*
|
|
9738
|
+
* Background: il framework, dopo un save metadata editor, fa `localStorage.clear()`
|
|
9739
|
+
* per invalidare le cache (schema, lookup, ecc.). Senza whitelist, ogni save
|
|
9740
|
+
* azzera anche stato che dovrebbe sopravvivere fino al logout, degradando UX:
|
|
9741
|
+
*
|
|
9742
|
+
* - `wuic_rag_last_session_id` (chatbot RAG): id della sessione corrente. Senza
|
|
9743
|
+
* preservarlo, ogni modifica metadata fa perdere la cronologia chat (anche
|
|
9744
|
+
* se i messaggi sono salvati in DB).
|
|
9745
|
+
* - `wuic-selected-language` / `wuic-selected-theme` / `wuic-theme-mode`:
|
|
9746
|
+
* preferenze UI utente — il cambio metadati NON dovrebbe resettarle.
|
|
9747
|
+
* - `translation` / `translation_<locale>`: cache traduzioni; senza, le label
|
|
9748
|
+
* tornano alle chiavi raw per ~500ms-2s.
|
|
9749
|
+
* - `wuic_auth_redirect_reason`: marker auth flow.
|
|
9750
|
+
*
|
|
9751
|
+
* Per aggiungere altre chiavi protette: estendere `LOCALSTORAGE_PRESERVE_KEYS`
|
|
9752
|
+
* (match esatto) o `LOCALSTORAGE_PRESERVE_PREFIXES` (match per prefisso).
|
|
9753
|
+
*
|
|
9754
|
+
* **Logout**: `auth-session.service.ts:clearBrowserStorage` fa `localStorage.clear()`
|
|
9755
|
+
* DIRETTO (senza whitelist) — by design. Il logout deve rimuovere tutto,
|
|
9756
|
+
* incluse le chiavi protette qui.
|
|
9757
|
+
*/
|
|
9758
|
+
/** Chiavi esatte da preservare (match `===`). */
|
|
9759
|
+
const LOCALSTORAGE_PRESERVE_KEYS = [
|
|
9760
|
+
'translation',
|
|
9761
|
+
'wuic_rag_last_session_id',
|
|
9762
|
+
'wuic-selected-language',
|
|
9763
|
+
'wuic-selected-theme',
|
|
9764
|
+
'wuic-theme-mode',
|
|
9765
|
+
'wuic_auth_redirect_reason',
|
|
9766
|
+
];
|
|
9767
|
+
/** Prefissi da preservare (match `startsWith`). */
|
|
9768
|
+
const LOCALSTORAGE_PRESERVE_PREFIXES = [
|
|
9769
|
+
'translation_', // cache traduzioni per locale (it-IT, en-US, ...)
|
|
9770
|
+
];
|
|
9771
|
+
/**
|
|
9772
|
+
* Equivalente di `localStorage.clear()` ma rispetta la whitelist sopra.
|
|
9773
|
+
* Idempotente, no-op in private mode / storage non disponibile.
|
|
9774
|
+
*/
|
|
9775
|
+
function clearLocalStoragePreservingUserState() {
|
|
9776
|
+
try {
|
|
9777
|
+
if (typeof localStorage === 'undefined')
|
|
9778
|
+
return;
|
|
9779
|
+
// Snapshot delle chiavi protette PRIMA del clear (l'iterazione su
|
|
9780
|
+
// localStorage.key(i) viene invalidata dal clear).
|
|
9781
|
+
const preserved = {};
|
|
9782
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
9783
|
+
const key = localStorage.key(i);
|
|
9784
|
+
if (!key)
|
|
9785
|
+
continue;
|
|
9786
|
+
const exactMatch = LOCALSTORAGE_PRESERVE_KEYS.includes(key);
|
|
9787
|
+
const prefixMatch = !exactMatch
|
|
9788
|
+
&& LOCALSTORAGE_PRESERVE_PREFIXES.some((p) => key.startsWith(p));
|
|
9789
|
+
if (exactMatch || prefixMatch) {
|
|
9790
|
+
const val = localStorage.getItem(key);
|
|
9791
|
+
if (val !== null)
|
|
9792
|
+
preserved[key] = val;
|
|
9793
|
+
}
|
|
9794
|
+
}
|
|
9795
|
+
localStorage.clear();
|
|
9796
|
+
for (const [k, v] of Object.entries(preserved)) {
|
|
9797
|
+
localStorage.setItem(k, v);
|
|
9798
|
+
}
|
|
9799
|
+
}
|
|
9800
|
+
catch {
|
|
9801
|
+
/* private mode / quota / disabled storage: best-effort, no-throw */
|
|
9802
|
+
}
|
|
9803
|
+
}
|
|
9804
|
+
|
|
9567
9805
|
// import { config, EntityManager, EntityQuery } from 'breeze-client';
|
|
9568
9806
|
// import { OData4DataService, OData4UriBuilder } from 'breeze-odata4';
|
|
9569
9807
|
class DataProviderOdataService {
|
|
@@ -9943,7 +10181,7 @@ class DataProviderOdataService {
|
|
|
9943
10181
|
}).toPromise();
|
|
9944
10182
|
debugger;
|
|
9945
10183
|
if (MetadataProviderService.isMetaRoute(route)) {
|
|
9946
|
-
|
|
10184
|
+
clearLocalStoragePreservingUserState();
|
|
9947
10185
|
const metaDb = await MetadataProviderService.getMetaDB();
|
|
9948
10186
|
metaDb.tables.forEach(function (table) {
|
|
9949
10187
|
table.clear();
|
|
@@ -10223,15 +10461,13 @@ class DataProviderMetaService {
|
|
|
10223
10461
|
return out;
|
|
10224
10462
|
}
|
|
10225
10463
|
/**
|
|
10226
|
-
* Pulisce la cache localStorage preservando le
|
|
10227
|
-
*
|
|
10464
|
+
* Pulisce la cache localStorage preservando le chiavi user-state della
|
|
10465
|
+
* whitelist centralizzata in `util/wuic-storage.util.ts` (translation cache,
|
|
10466
|
+
* id sessione chatbot RAG, preferenze tema/lingua, ecc.). Vedi i commenti in
|
|
10467
|
+
* quel file per la lista completa e il razionale.
|
|
10228
10468
|
*/
|
|
10229
10469
|
clearLocalStoragePreservingTranslation() {
|
|
10230
|
-
|
|
10231
|
-
localStorage.clear();
|
|
10232
|
-
if (translationSnapshot !== null && translationSnapshot !== undefined) {
|
|
10233
|
-
localStorage.setItem('translation', translationSnapshot);
|
|
10234
|
-
}
|
|
10470
|
+
clearLocalStoragePreservingUserState();
|
|
10235
10471
|
}
|
|
10236
10472
|
buildRouteContext(route, action, source) {
|
|
10237
10473
|
return WtoolboxService.buildCrudRouteContext(route, action, source);
|
|
@@ -11288,7 +11524,7 @@ class DataProviderWebserviceService {
|
|
|
11288
11524
|
entity: clonedEntity || entity
|
|
11289
11525
|
}).toPromise();
|
|
11290
11526
|
if (MetadataProviderService.isMetaRoute(route)) {
|
|
11291
|
-
|
|
11527
|
+
clearLocalStoragePreservingUserState();
|
|
11292
11528
|
const metaDb = await MetadataProviderService.getMetaDB();
|
|
11293
11529
|
metaDb.tables.forEach(function (table) {
|
|
11294
11530
|
table.clear();
|
|
@@ -11330,7 +11566,7 @@ class DataProviderWebserviceService {
|
|
|
11330
11566
|
}
|
|
11331
11567
|
}
|
|
11332
11568
|
if (MetadataProviderService.isMetaRoute(route)) {
|
|
11333
|
-
|
|
11569
|
+
clearLocalStoragePreservingUserState();
|
|
11334
11570
|
scope.getSchemaAndData();
|
|
11335
11571
|
}
|
|
11336
11572
|
else {
|
|
@@ -11362,7 +11598,7 @@ class DataProviderWebserviceService {
|
|
|
11362
11598
|
}).toPromise();
|
|
11363
11599
|
debugger;
|
|
11364
11600
|
if (MetadataProviderService.isMetaRoute(route)) {
|
|
11365
|
-
|
|
11601
|
+
clearLocalStoragePreservingUserState();
|
|
11366
11602
|
const metaDb = await MetadataProviderService.getMetaDB();
|
|
11367
11603
|
metaDb.tables.forEach(function (table) {
|
|
11368
11604
|
table.clear();
|
|
@@ -11394,7 +11630,7 @@ class DataProviderWebserviceService {
|
|
|
11394
11630
|
entity: clonedEntity || entity
|
|
11395
11631
|
}).toPromise();
|
|
11396
11632
|
if (MetadataProviderService.isMetaRoute(scope.metaInfo.tableMetadata.md_route_name)) {
|
|
11397
|
-
|
|
11633
|
+
clearLocalStoragePreservingUserState();
|
|
11398
11634
|
const metaDb = await MetadataProviderService.getMetaDB();
|
|
11399
11635
|
metaDb.tables.forEach(function (table) {
|
|
11400
11636
|
table.clear();
|
|
@@ -12726,6 +12962,15 @@ class TranslationManagerService {
|
|
|
12726
12962
|
* del missing key).
|
|
12727
12963
|
*/
|
|
12728
12964
|
_langMismatchReloadInFlight = false;
|
|
12965
|
+
/**
|
|
12966
|
+
* Ultimo locale risolto in modo AFFIDABILE (da override localStorage o da
|
|
12967
|
+
* `userInfo`). Usato come fallback stabile in `resolveCurrentLocale()` quando
|
|
12968
|
+
* `getuserInfo()` torna momentaneamente vuoto (succede durante navigazione/
|
|
12969
|
+
* edit/reload metadata): senza, il locale driftarebbe a `navigator.language`/
|
|
12970
|
+
* default, cambiando la `translationCacheKey` e innescando il self-heal che
|
|
12971
|
+
* faceva sparire le traduzioni fino al refresh.
|
|
12972
|
+
*/
|
|
12973
|
+
_stableLocale = null;
|
|
12729
12974
|
/**
|
|
12730
12975
|
* Bundle minimo di traduzioni hardcoded per il SOLO scenario firstRun (DB
|
|
12731
12976
|
* metadati ancora non inizializzato → tabella `_wuic_translations` non
|
|
@@ -13389,8 +13634,10 @@ class TranslationManagerService {
|
|
|
13389
13634
|
*/
|
|
13390
13635
|
resolveCurrentLocale() {
|
|
13391
13636
|
const override = this.readLanguageOverride();
|
|
13392
|
-
if (override)
|
|
13637
|
+
if (override) {
|
|
13638
|
+
this._stableLocale = override;
|
|
13393
13639
|
return override;
|
|
13640
|
+
}
|
|
13394
13641
|
// The login response shape can vary: classic shape exposes
|
|
13395
13642
|
// `lingua: { id: "it-IT" }` (object); newer/lean shape exposes
|
|
13396
13643
|
// just `language: "it-IT"` (string). Read both — the framework
|
|
@@ -13401,8 +13648,17 @@ class TranslationManagerService {
|
|
|
13401
13648
|
// loaded for en-US instead of the user's it-IT default.
|
|
13402
13649
|
const userInfo = this.authSrv.getuserInfo() || {};
|
|
13403
13650
|
const userLang = userInfo?.lingua?.id || userInfo?.language;
|
|
13404
|
-
if (userLang && typeof userLang === 'string')
|
|
13405
|
-
|
|
13651
|
+
if (userLang && typeof userLang === 'string') {
|
|
13652
|
+
const v = String(userLang).trim();
|
|
13653
|
+
this._stableLocale = v;
|
|
13654
|
+
return v;
|
|
13655
|
+
}
|
|
13656
|
+
// `getuserInfo()` momentaneamente vuoto (navigazione/edit/reload metadata):
|
|
13657
|
+
// NON driftare a navigator/default, altrimenti la translationCacheKey cambia
|
|
13658
|
+
// e il self-heal di getTranslation() rifetcha inutilmente (o, prima del fix,
|
|
13659
|
+
// azzerava la tabella). Riusa l'ultimo locale risolto in modo affidabile.
|
|
13660
|
+
if (this._stableLocale)
|
|
13661
|
+
return this._stableLocale;
|
|
13406
13662
|
if (typeof navigator !== 'undefined' && navigator.language)
|
|
13407
13663
|
return String(navigator.language).trim();
|
|
13408
13664
|
return 'it-IT';
|
|
@@ -13541,9 +13797,25 @@ class TranslationManagerService {
|
|
|
13541
13797
|
getTranslation(value) {
|
|
13542
13798
|
const self = this;
|
|
13543
13799
|
if (self.translationTable?.length > 0 && !localStorage.getItem(this.translationCacheKey)) {
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13800
|
+
// La tabella in memoria e' VALIDA ma la chiave localStorage e' assente
|
|
13801
|
+
// (key derivata da un locale momentaneamente diverso, oppure cache rimossa
|
|
13802
|
+
// da updateTranslations/forceReload/altra tab).
|
|
13803
|
+
//
|
|
13804
|
+
// BUG STORICO (sintomo: traduzioni che spariscono dopo navigazione/edit e
|
|
13805
|
+
// tornano solo col refresh): qui si faceva `translationTable = []` +
|
|
13806
|
+
// `translationsLoaded$.next(false)` + `ensureTranslationsLoaded()`. Ma:
|
|
13807
|
+
// 1) azzerare la tabella faceva collassare TUTTI i binding sulla chiave
|
|
13808
|
+
// grezza ad ogni ciclo di ChangeDetection;
|
|
13809
|
+
// 2) `ensureTranslationsLoaded()` (senza force) era un NO-OP perche'
|
|
13810
|
+
// `loadPromise` resta settato dopo il primo load riuscito -> la tabella
|
|
13811
|
+
// restava vuota fino al refresh manuale (che ricrea il singleton).
|
|
13812
|
+
//
|
|
13813
|
+
// Fix: NON azzerare e NON rifetchare dal server — la tabella in RAM e' gia'
|
|
13814
|
+
// corretta. Basta RI-PERSISTERE la tabella corrente sotto la chiave attuale
|
|
13815
|
+
// (operazione sincrona, zero rete): al prossimo ciclo `getItem(key)` esiste
|
|
13816
|
+
// e la condizione non scatta piu'. La propagazione di eventuali modifiche
|
|
13817
|
+
// server-side e' gia' coperta dalla revalidate al boot/cache-hit.
|
|
13818
|
+
self.persistCacheIfComplete();
|
|
13547
13819
|
}
|
|
13548
13820
|
if ((!self.translationTable || self.translationTable.length === 0) && !self.translationsLoaded$.value) {
|
|
13549
13821
|
void self.ensureTranslationsLoaded();
|
|
@@ -13681,13 +13953,28 @@ class DataSourceComponent {
|
|
|
13681
13953
|
* DI) invece che da costruttore per non rompere i call site manuali tipo
|
|
13682
13954
|
* `new DataSourceComponent(metaSrv, dataSrv, ...)` in designer.component.ts
|
|
13683
13955
|
* e metadata-editor.service.ts (che mantengono i 7 params posizionali storici).
|
|
13684
|
-
*
|
|
13685
|
-
*
|
|
13686
|
-
*
|
|
13956
|
+
*
|
|
13957
|
+
* ATTENZIONE: `{ optional: true }` rende il field `null` solo se il TOKEN non e'
|
|
13958
|
+
* risolvibile, NON quando si e' fuori da un injection context. Fuori contesto
|
|
13959
|
+
* (istanziazione manuale via `new`, es. metadata-editor.service che crea un
|
|
13960
|
+
* DataSource headless per fetch schema/dati) `inject()` lancia SEMPRE `NG0203`
|
|
13961
|
+
* a prescindere da `optional`. Per questo lo avvolgiamo in try/catch: nel path
|
|
13962
|
+
* Angular normale ritorna l'ElementRef host; nei call site manuali ritorna
|
|
13963
|
+
* `null` (il consumo in ngOnInit usa `?.nativeElement`, quindi entrambi i path
|
|
13964
|
+
* sono supportati). Senza questo guard, un doppio-click su una label del
|
|
13965
|
+
* parametric dialog (che passa per metadata-editor.service -> `new DataSource`)
|
|
13966
|
+
* crashava con NG0203.
|
|
13687
13967
|
* Scopo: esporre `this` su `host.__wuicCmp` per E2E introspection senza
|
|
13688
13968
|
* dipendere da `window.ng.getComponent` (tree-shaken in prod build).
|
|
13689
13969
|
*/
|
|
13690
|
-
elementRef =
|
|
13970
|
+
elementRef = (() => {
|
|
13971
|
+
try {
|
|
13972
|
+
return inject(ElementRef, { optional: true });
|
|
13973
|
+
}
|
|
13974
|
+
catch {
|
|
13975
|
+
return null;
|
|
13976
|
+
}
|
|
13977
|
+
})();
|
|
13691
13978
|
static getLiveInstances() {
|
|
13692
13979
|
return Array.from(DataSourceComponent.liveInstances);
|
|
13693
13980
|
}
|
|
@@ -14465,6 +14752,31 @@ class DataSourceComponent {
|
|
|
14465
14752
|
clearSelectedRows() {
|
|
14466
14753
|
this.selectedRows = [];
|
|
14467
14754
|
}
|
|
14755
|
+
/**
|
|
14756
|
+
* Ritorna i record correntemente selezionati nella `<wuic-list-grid>` (checkbox
|
|
14757
|
+
* di selezione multipla). Usato nelle **toolbar action bulk** per recuperare le
|
|
14758
|
+
* righe su cui agire prima di chiamare un endpoint server.
|
|
14759
|
+
*
|
|
14760
|
+
* Forma canonica nei callback (firma `(datasource, metaInfo, record, event, wtoolbox)`):
|
|
14761
|
+
* ```js
|
|
14762
|
+
* const selected = (datasource.getSelectedRows && datasource.getSelectedRows()) || [];
|
|
14763
|
+
* if (!selected.length) {
|
|
14764
|
+
* wtoolbox.messageNotificationService.add({ severity:'warn', summary:'Selezione vuota', detail:'Seleziona almeno una riga' });
|
|
14765
|
+
* return;
|
|
14766
|
+
* }
|
|
14767
|
+
* const ids = selected.map(r => Number(r.id?.value ?? r.id ?? r.Id));
|
|
14768
|
+
* ```
|
|
14769
|
+
*
|
|
14770
|
+
* **Importante**: il parametro `record` passato al callback toolbar è un SINGOLO
|
|
14771
|
+
* record (la riga "corrente"), NON l'array dei selezionati. Per il bulk usare
|
|
14772
|
+
* SEMPRE `datasource.getSelectedRows()` (oggetti completi) o
|
|
14773
|
+
* `datasource.getSelectedKeys()` (solo PK).
|
|
14774
|
+
*
|
|
14775
|
+
* Pre-requisito: la tabella deve avere `mdmultipleselection=1` su
|
|
14776
|
+
* `_metadati__tabelle` (vedi skill `table-actions`).
|
|
14777
|
+
*
|
|
14778
|
+
* @returns Array di record selezionati (vuoto se nulla e' selezionato).
|
|
14779
|
+
*/
|
|
14468
14780
|
getSelectedRows() {
|
|
14469
14781
|
return this.selectedRows;
|
|
14470
14782
|
}
|
|
@@ -20482,11 +20794,31 @@ class DynamicRowTemplateComponent {
|
|
|
20482
20794
|
* Gestisce la logica operativa di `classes` usando metadati di tabella/colonna allineati al modello server `_Metadati_*`.
|
|
20483
20795
|
* @returns Valore calcolato dinamicamente a partire dallo stato corrente del componente.
|
|
20484
20796
|
*/
|
|
20797
|
+
/**
|
|
20798
|
+
* Inline style cumulativo applicato alla `<tr>` quando una entry di
|
|
20799
|
+
* `_metadati__u_i__stili__tabelle` ha `must_attribute_name` con prefisso `style:`
|
|
20800
|
+
* (es. `style:background-color:#9b59b6;color:white`). Permette al chatbot RAG
|
|
20801
|
+
* (tool `propose_table_style`) di proporre colori arbitrari fuori dalla palette
|
|
20802
|
+
* predefinita (`row-danger/warning/info/success`) senza dover aggiungere CSS
|
|
20803
|
+
* lato app. Sintassi: prefisso `style:` letterale + dichiarazioni standard CSS
|
|
20804
|
+
* separate da `;`. Backwards-compatible: entries senza prefisso `style:` restano
|
|
20805
|
+
* trattate come classe CSS (vedi `getTableStyleClasses`).
|
|
20806
|
+
*/
|
|
20807
|
+
get inlineStyle() {
|
|
20808
|
+
return this.getTableInlineStyle();
|
|
20809
|
+
}
|
|
20485
20810
|
get classes() {
|
|
20486
20811
|
const classList = [];
|
|
20487
20812
|
const tableStyleClasses = this.getTableStyleClasses();
|
|
20488
20813
|
classList.push(...tableStyleClasses);
|
|
20489
|
-
|
|
20814
|
+
// `wuic-row-style-applied` e' la sentinella usata da list-grid.component.scss
|
|
20815
|
+
// per neutralizzare il `background` dei <td> child (altrimenti coprirebbero
|
|
20816
|
+
// il colore impostato sulla <tr> dalle stripe odd/even). Va aggiunta sia
|
|
20817
|
+
// per le classi che per gli inline-style — entrambi devono "vincere" sui td.
|
|
20818
|
+
// Nota: getTableInlineStyle e' qui re-invocato (l'altro hit e' in
|
|
20819
|
+
// @HostBinding('attr.style')); costo trascurabile, gli output sono cached
|
|
20820
|
+
// sui predicate. Tenere il check semplice e' preferibile a memoizzare.
|
|
20821
|
+
if (tableStyleClasses.length || this.getTableInlineStyle() !== null) {
|
|
20490
20822
|
classList.push('wuic-row-style-applied');
|
|
20491
20823
|
}
|
|
20492
20824
|
if ((this.metaInfo.tableMetadata.md_ui_grid_conditional_template || this.metaInfo.tableMetadata.md_ui_grid_conditional_alt_template) && this.metaInfo.tableMetadata.md_ui_grid_conditional_template_condition) {
|
|
@@ -20515,6 +20847,11 @@ class DynamicRowTemplateComponent {
|
|
|
20515
20847
|
if (!cssClass) {
|
|
20516
20848
|
return;
|
|
20517
20849
|
}
|
|
20850
|
+
// Entries con prefisso `style:` sono inline-style, non classi.
|
|
20851
|
+
// Vengono gestite separatamente da `getTableInlineStyle`.
|
|
20852
|
+
if (cssClass.startsWith('style:')) {
|
|
20853
|
+
return;
|
|
20854
|
+
}
|
|
20518
20855
|
const conditionCode = String(style?.must_attribute_value || '').trim();
|
|
20519
20856
|
if (!conditionCode) {
|
|
20520
20857
|
classes.push(cssClass);
|
|
@@ -20537,6 +20874,66 @@ class DynamicRowTemplateComponent {
|
|
|
20537
20874
|
return classes;
|
|
20538
20875
|
}
|
|
20539
20876
|
/**
|
|
20877
|
+
* Inline-style speculare a `getTableStyleClasses`. Filtra le entries di
|
|
20878
|
+
* `_Metadati_UI_Stili_Tabelles` il cui `must_attribute_name` ha prefisso
|
|
20879
|
+
* `style:` letterale; valuta il predicate (`must_attribute_value`) con
|
|
20880
|
+
* stessa semantica e stessa cache (skip su throw); concatena le dichiarazioni
|
|
20881
|
+
* CSS in una stringa unica idonea all'attributo HTML `style`. Restituisce
|
|
20882
|
+
* `null` se nessuna entry matcha — Angular rimuove l'attributo, lasciando
|
|
20883
|
+
* intatto eventuale styling dal CSS.
|
|
20884
|
+
*/
|
|
20885
|
+
getTableInlineStyle() {
|
|
20886
|
+
const styles = (this.metaInfo?.tableMetadata?._Metadati_UI_Stili_Tabelles || []);
|
|
20887
|
+
if (!Array.isArray(styles) || !styles.length) {
|
|
20888
|
+
return null;
|
|
20889
|
+
}
|
|
20890
|
+
const decls = [];
|
|
20891
|
+
styles.forEach((style) => {
|
|
20892
|
+
const raw = String(style?.must_attribute_name || '').trim();
|
|
20893
|
+
if (!raw.startsWith('style:')) {
|
|
20894
|
+
return;
|
|
20895
|
+
}
|
|
20896
|
+
const cssDecl = raw.slice('style:'.length).trim();
|
|
20897
|
+
if (!cssDecl) {
|
|
20898
|
+
return;
|
|
20899
|
+
}
|
|
20900
|
+
const conditionCode = String(style?.must_attribute_value || '').trim();
|
|
20901
|
+
if (conditionCode) {
|
|
20902
|
+
const cacheKey = `${String(style?.must_id ?? '')}|${conditionCode}`;
|
|
20903
|
+
let predicate = DynamicRowTemplateComponent.tableStyleConditionCache.get(cacheKey);
|
|
20904
|
+
if (!predicate) {
|
|
20905
|
+
predicate = this.buildTableStylePredicate(conditionCode);
|
|
20906
|
+
DynamicRowTemplateComponent.tableStyleConditionCache.set(cacheKey, predicate);
|
|
20907
|
+
}
|
|
20908
|
+
try {
|
|
20909
|
+
if (!predicate(this.metaInfo, this.rowData, WtoolboxService)) {
|
|
20910
|
+
return;
|
|
20911
|
+
}
|
|
20912
|
+
}
|
|
20913
|
+
catch {
|
|
20914
|
+
return;
|
|
20915
|
+
}
|
|
20916
|
+
}
|
|
20917
|
+
// PrimeNG p-datatable / p-treetable applicano background/color/border alle
|
|
20918
|
+
// <tr> via regole con `!important` (striped rows, hover, selected, focus).
|
|
20919
|
+
// Inline style senza `!important` perderebbe la specificity → niente colore
|
|
20920
|
+
// visibile (caso reale 2026-06-07: `background-color:#4a148c` ignorato,
|
|
20921
|
+
// `color:red` invece passa perche' non c'e' contender esplicito). Iniettiamo
|
|
20922
|
+
// `!important` automaticamente su ogni dichiarazione che non ce l'ha gia',
|
|
20923
|
+
// cosi' il chatbot non deve istruire il LLM su questo dettaglio framework.
|
|
20924
|
+
const decorated = cssDecl
|
|
20925
|
+
.split(';')
|
|
20926
|
+
.map((d) => d.trim())
|
|
20927
|
+
.filter((d) => d.length > 0)
|
|
20928
|
+
.map((d) => (/!\s*important\b/i.test(d) ? d : `${d} !important`))
|
|
20929
|
+
.join(';');
|
|
20930
|
+
if (decorated) {
|
|
20931
|
+
decls.push(decorated);
|
|
20932
|
+
}
|
|
20933
|
+
});
|
|
20934
|
+
return decls.length ? decls.join(';') : null;
|
|
20935
|
+
}
|
|
20936
|
+
/**
|
|
20540
20937
|
* Costruisce una struttura di output a partire dal contesto corrente usando i metadati per determinare campi, chiavi e comportamento runtime, allineando i record al formato atteso dal framework.
|
|
20541
20938
|
* @param conditionCode Valore testuale usato come chiave, campo, route o parametro di configurazione.
|
|
20542
20939
|
* @returns Valore di tipo `(metaInfo: MetaInfo, record: any, wtoolbox: typeof WtoolboxService) => boolean` costruito dal metodo per i passaggi successivi del flusso.
|
|
@@ -20592,6 +20989,14 @@ class DynamicRowTemplateComponent {
|
|
|
20592
20989
|
if (!cssClass) {
|
|
20593
20990
|
return;
|
|
20594
20991
|
}
|
|
20992
|
+
// Skip entries con prefisso `style:` — sono inline-style, non classi.
|
|
20993
|
+
// Verranno gestite da `getCellInlineStyle` speculare a
|
|
20994
|
+
// `getTableInlineStyle` per le row-styles. Senza questo skip, il LLM
|
|
20995
|
+
// del chatbot RAG che emette `css_class:'style:background-color:blue'`
|
|
20996
|
+
// produrrebbe una classe CSS literale invalida e fragile.
|
|
20997
|
+
if (cssClass.startsWith('style:')) {
|
|
20998
|
+
return;
|
|
20999
|
+
}
|
|
20595
21000
|
const conditionCode = String(style?.musc_attribute_value || '').trim();
|
|
20596
21001
|
const callbackCode = String(style?.musc_attribute_value_callback || '').trim();
|
|
20597
21002
|
const effectiveConditionCode = callbackCode || conditionCode;
|
|
@@ -20644,13 +21049,72 @@ class DynamicRowTemplateComponent {
|
|
|
20644
21049
|
}
|
|
20645
21050
|
}
|
|
20646
21051
|
}
|
|
20647
|
-
|
|
21052
|
+
// `wuic-cell-style-applied` va aggiunta anche quando la cella ha
|
|
21053
|
+
// inline-style (entries `style:`-prefixed), cosi' i CSS list-grid che
|
|
21054
|
+
// neutralizzano il bg odd/even sui td styled possono matchare.
|
|
21055
|
+
// Speculare al pattern row-style `wuic-row-style-applied`.
|
|
21056
|
+
const hasInlineCellStyle = this.getCellInlineStyle(metaColumn, rowData) !== null;
|
|
21057
|
+
if (classes.length || hasInlineCellStyle) {
|
|
20648
21058
|
classes.push('wuic-cell-style-applied');
|
|
20649
|
-
return classes.join(' ');
|
|
21059
|
+
return classes.length ? classes.join(' ') : 'wuic-cell-style-applied';
|
|
20650
21060
|
}
|
|
20651
21061
|
return null;
|
|
20652
21062
|
}
|
|
20653
21063
|
/**
|
|
21064
|
+
* Inline-style speculare a `getCellInlineStyle` per le celle. Filtra le
|
|
21065
|
+
* entries di `_Metadati_UI_Stili_Colonnes` il cui `musc_attribute_name` ha
|
|
21066
|
+
* prefisso `style:`; valuta il predicate (`musc_attribute_value` o
|
|
21067
|
+
* `musc_attribute_value_callback`) e concatena le dichiarazioni CSS in una
|
|
21068
|
+
* stringa unica idonea all'attributo HTML `style`. Iniezione automatica di
|
|
21069
|
+
* `!important` su ogni dichiarazione che non ce l'ha gia' (PrimeNG p-table
|
|
21070
|
+
* applica `background` ai td via regole con `!important` per striped/hover —
|
|
21071
|
+
* senza, l'inline style perderebbe la specificity).
|
|
21072
|
+
*/
|
|
21073
|
+
getCellInlineStyle(metaColumn, rowData) {
|
|
21074
|
+
const column = metaColumn;
|
|
21075
|
+
if (!column)
|
|
21076
|
+
return null;
|
|
21077
|
+
const styles = (column._Metadati_UI_Stili_Colonnes || []);
|
|
21078
|
+
if (!Array.isArray(styles) || !styles.length)
|
|
21079
|
+
return null;
|
|
21080
|
+
const decls = [];
|
|
21081
|
+
styles.forEach((style) => {
|
|
21082
|
+
const raw = String(style?.musc_attribute_name || '').trim();
|
|
21083
|
+
if (!raw.startsWith('style:'))
|
|
21084
|
+
return;
|
|
21085
|
+
const cssDecl = raw.slice('style:'.length).trim();
|
|
21086
|
+
if (!cssDecl)
|
|
21087
|
+
return;
|
|
21088
|
+
const conditionCode = String(style?.musc_attribute_value || '').trim();
|
|
21089
|
+
const callbackCode = String(style?.musc_attribute_value_callback || '').trim();
|
|
21090
|
+
const effectiveConditionCode = callbackCode || conditionCode;
|
|
21091
|
+
if (effectiveConditionCode) {
|
|
21092
|
+
const cacheKey = `${String(style?.musc_id ?? '')}|${effectiveConditionCode}`;
|
|
21093
|
+
let predicate = DynamicRowTemplateComponent.columnStyleConditionCache.get(cacheKey);
|
|
21094
|
+
if (!predicate) {
|
|
21095
|
+
predicate = this.buildTableStylePredicate(effectiveConditionCode);
|
|
21096
|
+
DynamicRowTemplateComponent.columnStyleConditionCache.set(cacheKey, predicate);
|
|
21097
|
+
}
|
|
21098
|
+
try {
|
|
21099
|
+
if (!predicate(this.metaInfo, rowData, WtoolboxService))
|
|
21100
|
+
return;
|
|
21101
|
+
}
|
|
21102
|
+
catch {
|
|
21103
|
+
return;
|
|
21104
|
+
}
|
|
21105
|
+
}
|
|
21106
|
+
const decorated = cssDecl
|
|
21107
|
+
.split(';')
|
|
21108
|
+
.map((d) => d.trim())
|
|
21109
|
+
.filter((d) => d.length > 0)
|
|
21110
|
+
.map((d) => (/!\s*important\b/i.test(d) ? d : `${d} !important`))
|
|
21111
|
+
.join(';');
|
|
21112
|
+
if (decorated)
|
|
21113
|
+
decls.push(decorated);
|
|
21114
|
+
});
|
|
21115
|
+
return decls.length ? decls.join(';') : null;
|
|
21116
|
+
}
|
|
21117
|
+
/**
|
|
20654
21118
|
* Recupera e prepara i dati richiesti dal chiamante usando i metadati per determinare campi, chiavi e comportamento runtime.
|
|
20655
21119
|
* @param fieldName Valore testuale usato come chiave, campo, route o parametro di configurazione.
|
|
20656
21120
|
* @returns Valore di tipo `MetadatiColonna | null` costruito dal metodo per i passaggi successivi del flusso.
|
|
@@ -20795,7 +21259,7 @@ class DynamicRowTemplateComponent {
|
|
|
20795
21259
|
return a === b;
|
|
20796
21260
|
}
|
|
20797
21261
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DynamicRowTemplateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
20798
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: DynamicRowTemplateComponent, isStandalone: true, selector: "wuic-dynamic-row-template", inputs: { rowData: "rowData", rowIndex: "rowIndex", actionButtonRowIsVisible: "actionButtonRowIsVisible", isListVirtualizationEnabled: "isListVirtualizationEnabled", columns: "columns", expanded: "expanded", metaInfo: "metaInfo", datasource: "datasource", dt: "dt", toggleRow: "toggleRow", rowSelect: "rowSelect", rowCustomSelect: "rowCustomSelect", inlineCellEditingEnabled: "inlineCellEditingEnabled", onInlineCellEditorBlur: "onInlineCellEditorBlur", onInlineCellEditorValueChange: "onInlineCellEditorValueChange", getRuntimeGridFieldMeta: "getRuntimeGridFieldMeta", MetadatiColonna: "MetadatiColonna" }, host: { properties: { "class": "this.classes" } }, ngImport: i0, template: '', isInline: true });
|
|
21262
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: DynamicRowTemplateComponent, isStandalone: true, selector: "wuic-dynamic-row-template", inputs: { rowData: "rowData", rowIndex: "rowIndex", actionButtonRowIsVisible: "actionButtonRowIsVisible", isListVirtualizationEnabled: "isListVirtualizationEnabled", columns: "columns", expanded: "expanded", metaInfo: "metaInfo", datasource: "datasource", dt: "dt", toggleRow: "toggleRow", rowSelect: "rowSelect", rowCustomSelect: "rowCustomSelect", inlineCellEditingEnabled: "inlineCellEditingEnabled", onInlineCellEditorBlur: "onInlineCellEditorBlur", onInlineCellEditorValueChange: "onInlineCellEditorValueChange", getRuntimeGridFieldMeta: "getRuntimeGridFieldMeta", MetadatiColonna: "MetadatiColonna" }, host: { properties: { "attr.style": "this.inlineStyle", "class": "this.classes" } }, ngImport: i0, template: '', isInline: true });
|
|
20799
21263
|
}
|
|
20800
21264
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DynamicRowTemplateComponent, decorators: [{
|
|
20801
21265
|
type: Component,
|
|
@@ -20838,6 +21302,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
20838
21302
|
type: Input
|
|
20839
21303
|
}], MetadatiColonna: [{
|
|
20840
21304
|
type: Input
|
|
21305
|
+
}], inlineStyle: [{
|
|
21306
|
+
type: HostBinding,
|
|
21307
|
+
args: ['attr.style']
|
|
20841
21308
|
}], classes: [{
|
|
20842
21309
|
type: HostBinding,
|
|
20843
21310
|
args: ['class']
|
|
@@ -21196,7 +21663,7 @@ class LazySchedulerListComponent {
|
|
|
21196
21663
|
* Inizializza il componente preparando stato, sottoscrizioni e primi caricamenti richiesti.
|
|
21197
21664
|
*/
|
|
21198
21665
|
async ngOnInit() {
|
|
21199
|
-
schedulerListComponentPromise ??= import('./wuic-framework-lib-scheduler-list.component-
|
|
21666
|
+
schedulerListComponentPromise ??= import('./wuic-framework-lib-scheduler-list.component-DHoukjf-.mjs').then((m) => m.SchedulerListComponent);
|
|
21200
21667
|
this.loadedComponent = await schedulerListComponentPromise;
|
|
21201
21668
|
this.cdr.markForCheck();
|
|
21202
21669
|
}
|
|
@@ -21404,7 +21871,7 @@ class LazySpreadsheetListSfComponent {
|
|
|
21404
21871
|
this.activatedRoute = activatedRoute;
|
|
21405
21872
|
}
|
|
21406
21873
|
async ngOnInit() {
|
|
21407
|
-
spreadsheetListSfComponentPromise ??= import('./wuic-framework-lib-spreadsheet-list-sf.component-
|
|
21874
|
+
spreadsheetListSfComponentPromise ??= import('./wuic-framework-lib-spreadsheet-list-sf.component-VsaXepUC.mjs').then((m) => m.SpreadsheetListSfComponent);
|
|
21408
21875
|
this.loadedComponent = await spreadsheetListSfComponentPromise;
|
|
21409
21876
|
// Quando la promise e' gia' risolta dal cache module-scope (secondo mount
|
|
21410
21877
|
// post-refresh), il microtask che risolve `await` non scatena sempre CD
|
|
@@ -21682,7 +22149,7 @@ class LazyChartListComponent {
|
|
|
21682
22149
|
* Inizializza il componente preparando stato, sottoscrizioni e primi caricamenti richiesti.
|
|
21683
22150
|
*/
|
|
21684
22151
|
async ngOnInit() {
|
|
21685
|
-
chartListComponentPromise ??= import('./wuic-framework-lib-chart-list.component-
|
|
22152
|
+
chartListComponentPromise ??= import('./wuic-framework-lib-chart-list.component-C02jeFGy.mjs').then((m) => m.ChartListComponent);
|
|
21686
22153
|
this.loadedComponent = await chartListComponentPromise;
|
|
21687
22154
|
this.cdr.markForCheck();
|
|
21688
22155
|
}
|
|
@@ -31397,6 +31864,22 @@ class ListGridComponent {
|
|
|
31397
31864
|
releaseGridStateUrlPushTimer;
|
|
31398
31865
|
navigationTriggeredByPopstate = false;
|
|
31399
31866
|
suppressNextPageOnlyQuerySync = false;
|
|
31867
|
+
// PrimeNG p-table emette per ogni sort/filter/paging DUE (a volte 3)
|
|
31868
|
+
// eventi consecutivi (separati da ~30-60ms, NON nello stesso microtask).
|
|
31869
|
+
// Il fetchData a runtime fa 1 round-trip server completo (GetFlatData su
|
|
31870
|
+
// tabelle 200k+ righe -> facilmente >1s). I duplicati raddoppiano il
|
|
31871
|
+
// carico DB.
|
|
31872
|
+
//
|
|
31873
|
+
// Strategia in 2 layer:
|
|
31874
|
+
// 1. setTimeout(0) coalesce ravvicinato (rapid-fire stesso microtask)
|
|
31875
|
+
// 2. dedup-by-payload-hash (skip se sortInfo+pageInfo+filterInfo
|
|
31876
|
+
// identici al last-fetched). Cattura il pattern p-table dove gli
|
|
31877
|
+
// eventi sono separati da 30-60ms ma producono payload identico.
|
|
31878
|
+
// Verificato live 2026-06-04 via MCP Chrome (Order Lines / 231k righe,
|
|
31879
|
+
// sort su OrderID -> 2 POST identici a 39ms di distanza).
|
|
31880
|
+
pendingFilterChangeEvent = null;
|
|
31881
|
+
pendingFilterChangeTimer = null;
|
|
31882
|
+
lastFetchedPayloadHash = null;
|
|
31400
31883
|
clientSideCrudToggleBusy = false;
|
|
31401
31884
|
persistedColumnWidthsByRoute = {};
|
|
31402
31885
|
persistedColumnLayoutByRoute = {};
|
|
@@ -32220,7 +32703,7 @@ class ListGridComponent {
|
|
|
32220
32703
|
</div>
|
|
32221
32704
|
<div class="wuic-mobile-card-body">
|
|
32222
32705
|
<ng-container *ngFor="let col of columns | visibleFieldList">
|
|
32223
|
-
<div class="wuic-mobile-card-row" [ngClass]="getCellClasses(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)">
|
|
32706
|
+
<div class="wuic-mobile-card-row" [ngClass]="getCellClasses(col.metaColumn, rowData)" [attr.style]="getCellInlineStyle(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)">
|
|
32224
32707
|
<div class="wuic-mobile-card-label">{{ col.header }}</div>
|
|
32225
32708
|
<div class="wuic-mobile-card-value">
|
|
32226
32709
|
<ng-container [ngSwitch]="col.metaColumn.mc_nome_colonna">
|
|
@@ -32282,7 +32765,7 @@ class ListGridComponent {
|
|
|
32282
32765
|
</div>
|
|
32283
32766
|
<div class="wuic-mobile-card-body">
|
|
32284
32767
|
<ng-container *ngFor="let col of columns | visibleFieldList">
|
|
32285
|
-
<div class="wuic-mobile-card-row" [ngClass]="getCellClasses(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)">
|
|
32768
|
+
<div class="wuic-mobile-card-row" [ngClass]="getCellClasses(col.metaColumn, rowData)" [attr.style]="getCellInlineStyle(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)">
|
|
32286
32769
|
<div class="wuic-mobile-card-label">{{ col.header }}</div>
|
|
32287
32770
|
<div class="wuic-mobile-card-value">
|
|
32288
32771
|
<ng-container [ngSwitch]="col.metaColumn.mc_nome_colonna">
|
|
@@ -32337,7 +32820,7 @@ class ListGridComponent {
|
|
|
32337
32820
|
</ng-container>
|
|
32338
32821
|
</td>
|
|
32339
32822
|
<ng-container *ngFor="let col of columns | visibleFieldList">
|
|
32340
|
-
<td *ngIf="!col?.frozen" [ngClass]="getCellClasses(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)" (focusout)="onCellFocusOut($event, rowData, col.metaColumn)">
|
|
32823
|
+
<td *ngIf="!col?.frozen" [ngClass]="getCellClasses(col.metaColumn, rowData)" [attr.style]="getCellInlineStyle(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)" (focusout)="onCellFocusOut($event, rowData, col.metaColumn)">
|
|
32341
32824
|
<wuic-field-editor-lazy *ngIf="rowData.__is_editing && !col.metaColumn.mc_hide_in_edit" [record]="rowData.__observable" [field]="getRuntimeGridFieldMeta(col.metaColumn, rowData)" [metaInfo]="metaInfo" [datasource]="datasource" [hideLabel]="metaInfo.tableMetadata.md_inline_edit" [onInlineCellValueChange]="onInlineCellEditorValueChange"></wuic-field-editor-lazy>
|
|
32342
32825
|
<ng-container *ngIf="!rowData.__is_editing">
|
|
32343
32826
|
<ng-container [ngSwitch]="col.metaColumn.mc_nome_colonna">
|
|
@@ -32358,7 +32841,7 @@ class ListGridComponent {
|
|
|
32358
32841
|
</ng-container>
|
|
32359
32842
|
</ng-container>
|
|
32360
32843
|
</td>
|
|
32361
|
-
<td *ngIf="col?.frozen" [wuicFrozenColumn]="true" [alignFrozen]="col?.alignFrozen || 'left'" [ngClass]="getCellClasses(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)" (focusout)="onCellFocusOut($event, rowData, col.metaColumn)">
|
|
32844
|
+
<td *ngIf="col?.frozen" [wuicFrozenColumn]="true" [alignFrozen]="col?.alignFrozen || 'left'" [ngClass]="getCellClasses(col.metaColumn, rowData)" [attr.style]="getCellInlineStyle(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)" (focusout)="onCellFocusOut($event, rowData, col.metaColumn)">
|
|
32362
32845
|
<wuic-field-editor-lazy *ngIf="rowData.__is_editing && !col.metaColumn.mc_hide_in_edit" [record]="rowData.__observable" [field]="getRuntimeGridFieldMeta(col.metaColumn, rowData)" [metaInfo]="metaInfo" [datasource]="datasource" [hideLabel]="metaInfo.tableMetadata.md_inline_edit" [onInlineCellValueChange]="onInlineCellEditorValueChange"></wuic-field-editor-lazy>
|
|
32363
32846
|
<ng-container *ngIf="!rowData.__is_editing">
|
|
32364
32847
|
<ng-container [ngSwitch]="col.metaColumn.mc_nome_colonna">
|
|
@@ -33281,6 +33764,32 @@ class ListGridComponent {
|
|
|
33281
33764
|
async pageFilterChange(event) {
|
|
33282
33765
|
// Applying a saved state updates sort/filter bindings programmatically.
|
|
33283
33766
|
// Ignore transient table events to avoid duplicate fetches.
|
|
33767
|
+
if (this.applyingSavedState) {
|
|
33768
|
+
return;
|
|
33769
|
+
}
|
|
33770
|
+
// Coalesce rapid-fire events (sort emette 2-3 eventi in 0ms):
|
|
33771
|
+
// sovrascrivi il queued event e schedula un solo processing al next tick.
|
|
33772
|
+
// Eventi cross-tick legittimi (sort poi paging dopo click utente) sono
|
|
33773
|
+
// separati da almeno N ms del browser idle -> ognuno schedula proprio.
|
|
33774
|
+
this.pendingFilterChangeEvent = event;
|
|
33775
|
+
if (this.pendingFilterChangeTimer !== null) {
|
|
33776
|
+
return; // gia' schedulato, l'ultimo evento sara' processato
|
|
33777
|
+
}
|
|
33778
|
+
this.pendingFilterChangeTimer = setTimeout(() => {
|
|
33779
|
+
const ev = this.pendingFilterChangeEvent;
|
|
33780
|
+
this.pendingFilterChangeEvent = null;
|
|
33781
|
+
this.pendingFilterChangeTimer = null;
|
|
33782
|
+
if (ev) {
|
|
33783
|
+
void this._processPageFilterEvent(ev);
|
|
33784
|
+
}
|
|
33785
|
+
}, 0);
|
|
33786
|
+
}
|
|
33787
|
+
/**
|
|
33788
|
+
* Processore reale dell'evento PrimeNG (sort/filter/paging). Chiamato
|
|
33789
|
+
* tail-call da `pageFilterChange` via setTimeout(0) per coalesce.
|
|
33790
|
+
* NON chiamare direttamente: la coalesce e' la guard anti-duplicate-fetch.
|
|
33791
|
+
*/
|
|
33792
|
+
async _processPageFilterEvent(event) {
|
|
33284
33793
|
if (this.applyingSavedState) {
|
|
33285
33794
|
return;
|
|
33286
33795
|
}
|
|
@@ -33411,6 +33920,22 @@ class ListGridComponent {
|
|
|
33411
33920
|
else {
|
|
33412
33921
|
this.datasource.value.sortInfo = [];
|
|
33413
33922
|
}
|
|
33923
|
+
// Dedup-by-payload-hash: PrimeNG emette spesso DUE eventi consecutivi
|
|
33924
|
+
// (sort + page-only follow-up) separati da ~30-60ms con payload
|
|
33925
|
+
// IDENTICO. Skip il fetch se sortInfo+pageInfo+filterInfo coincidono
|
|
33926
|
+
// col last-fetched. Verificato live 2026-06-04 via MCP Chrome.
|
|
33927
|
+
try {
|
|
33928
|
+
const payloadKey = JSON.stringify({
|
|
33929
|
+
s: this.datasource.value.sortInfo || [],
|
|
33930
|
+
p: { c: this.pageIndex, sz: this.pageSize },
|
|
33931
|
+
f: this.datasource.value.filterInfo || null,
|
|
33932
|
+
});
|
|
33933
|
+
if (payloadKey === this.lastFetchedPayloadHash) {
|
|
33934
|
+
return; // identico al fetch precedente: salta
|
|
33935
|
+
}
|
|
33936
|
+
this.lastFetchedPayloadHash = payloadKey;
|
|
33937
|
+
}
|
|
33938
|
+
catch { /* hash non bloccante: meglio re-fetch che bloccare */ }
|
|
33414
33939
|
await this.datasource.value.fetchData();
|
|
33415
33940
|
this.scheduleGridViewportHeightSync();
|
|
33416
33941
|
// Emit @Output events DOPO fetchData: a questo punto TUTTO lo stato
|
|
@@ -37466,11 +37991,11 @@ class ListGridComponent {
|
|
|
37466
37991
|
return Number.isFinite(width) ? width : NaN;
|
|
37467
37992
|
}
|
|
37468
37993
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ListGridComponent, deps: [{ token: MetadataProviderService }, { token: i1$5.DomSanitizer }, { token: i1$2.ActivatedRoute }, { token: i1$2.Router }, { token: i1$3.Location }, { token: i1$5.Title }, { token: TranslationManagerService }, { token: i0.ChangeDetectorRef }, { token: UserInfoService }, { token: i0.ElementRef }, { token: DeviceAwarenessService }, { token: CustomAttributesService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
37469
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ListGridComponent, isStandalone: true, selector: "wuic-list-grid", inputs: { hardcodedRoute: "hardcodedRoute", parentRecord: "parentRecord", parentMetaInfo: "parentMetaInfo", datasource: "datasource", hardcodedDatasource: "hardcodedDatasource", rowCustomSelect: "rowCustomSelect", hideToolbar: "hideToolbar" }, outputs: { onBeforeRowRender: "onBeforeRowRender", onAfterRowRender: "onAfterRowRender", onAfterRender: "onAfterRender", onPaging: "onPaging", onSorting: "onSorting", onFiltering: "onFiltering", onRefresh: "onRefresh", onPTableSelectionChange: "onPTableSelectionChange", onPTableRowExpand: "onPTableRowExpand", onPTableRowCollapse: "onPTableRowCollapse", onPTableColumnResize: "onPTableColumnResize", onPTableColumnReorder: "onPTableColumnReorder" }, host: { listeners: { "window:resize": "onWindowResizeForGridLayout()" } }, providers: [Title], viewQueries: [{ propertyName: "table", first: true, predicate: ["dt1"], descendants: true }, { propertyName: "columnCtxMenu", first: true, predicate: ["columnCtxMenu"], descendants: true }, { propertyName: "metadataColumnsDatasource", first: true, predicate: ["metadataColumnsDatasource"], descendants: true }, { propertyName: "gridContainerRef", first: true, predicate: ["gridContainer"], descendants: true }, { propertyName: "tableActionsMenu", first: true, predicate: ["tableActionsMenu"], descendants: true }], ngImport: i0, template: "<wuic-data-source #metadataColumnsDatasource [hardcodedRoute]=\"' metadati colonne'\"\r\n style=\"display: none;\"></wuic-data-source>\r\n<p-contextMenu #columnCtxMenu [model]=\"columnContextMenuItems\" appendTo=\"body\"></p-contextMenu>\r\n\r\n<div class=\"list-grid-container\" #gridContainer\r\n [class.wuic-list-grid-mobile]=\"gridLayoutIsMobile\">\r\n @if (rowTemplate) {\r\n <p-table #dt1 [columns]=\"cols\" [value]=\"records\" [scrollable]=\"true\"\r\n [scrollHeight]=\"metaInfo.tableMetadata.md_grid_scroll_height ? metaInfo.tableMetadata.md_grid_scroll_height : 'flex'\"\r\n [virtualScroll]=\"isListVirtualizationEnabled()\" [virtualScrollItemSize]=\"getListVirtualizationItemSize()\"\r\n [resizableColumns]=\"true\" [reorderableColumns]=\"true\" columnResizeMode=\"expand\" [rows]=\"pageSize\"\r\n [first]=\"rowNumber\" [lazy]=\"metaInfo.tableMetadata.md_server_side_operations\" [sortField]=\"orderColumn || null\"\r\n [sortOrder]=\"orderColumn ? (orderDir === 'desc' ? -1 : 1) : 0\" [(selection)]=\"selectedItems\"\r\n [dataKey]=\"metaInfo.pKey?.mc_nome_colonna\" [paginator]=\"metaInfo.tableMetadata.md_pageable\"\r\n [rowsPerPageOptions]=\"metaInfo.rowsPerPageOptions\" [totalRecords]=\"totalRecords\"\r\n [showPageLinks]=\"!datasource?.value?.cursorMode\" [showFirstLastIcon]=\"!datasource?.value?.cursorMode\"\r\n [showCurrentPageReport]=\"metaInfo.tableMetadata.md_show_record_count && !datasource?.value?.cursorMode\"\r\n [loading]=\"datasource?.value?.loading | async\"\r\n [currentPageReportTemplate]=\"'Showing {first} to {last} of {totalRecords} entries' | translate\"\r\n (onPage)=\"pageFilterChange($event)\" (onFilter)=\"pageFilterChange($event)\" (onSort)=\"pageFilterChange($event)\"\r\n (selectionChange)=\"onSelectionChange($event)\" (onColResize)=\"onColumnResize($event)\"\r\n (onColReorder)=\"onColumnReorder($event)\" [expandedRowKeys]=\"expandedRows\" (onRowExpand)=\"onRowExpand($event)\"\r\n (onRowCollapse)=\"onRowCollapse($event)\" [selectionMode]=\"rowCustomSelect ? 'single' : undefined\"\r\n [tableStyleClass]=\"getTableStyleClass()\">\r\n <ng-template pTemplate=\"colgroup\" let-columns>\r\n <colgroup>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <col class=\"nested-grid-column\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <col class=\"multiple-selection-grid-column\" />\r\n }\r\n @if (showActionColumn()) {\r\n <col class=\"command-grid-column\" />\r\n }\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n <col [style.width]=\"getColumnWidthCss(col)\" [style.min-width]=\"columnMinWidthCss\" />\r\n }\r\n </colgroup>\r\n </ng-template>\r\n <ng-template pTemplate=\"caption\">\r\n @if (!hideToolbar) {\r\n <div class=\"grid-caption-bar\">\r\n <div class=\"grid-caption-left\">\r\n @if (!metaInfo.tableMetadata.md_hide_refresh) {\r\n <p-button icon=\"pi pi-refresh\" label=\"{{'refresh_grid' | translate}}\" (onClick)=\"refresh()\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_insertable) {\r\n <p-button icon=\"pi pi-plus\" label=\"{{'add_grid' | translate}}\" (onClick)=\"addRecord()\" />\r\n }\r\n @if (tableActionMenuItems?.length) {\r\n <p-button icon=\"pi pi-bars\" [label]=\"'actions' | translate\" (onClick)=\"tableActionsMenu.toggle($event)\" />\r\n <p-menu #tableActionsMenu [popup]=\"true\" [model]=\"tableActionMenuItems\" appendTo=\"body\"\r\n (onShow)=\"reEvaluateActionEnabledStates()\"></p-menu>\r\n }\r\n <wuic-import-export-button [routeName]=\"routeName\"\r\n [tableMetadata]=\"metaInfo?.tableMetadata\"\r\n [filterInfoProvider]=\"getCurrentFilterInfoForExport\"></wuic-import-export-button>\r\n @if (reportMenuItems?.length) {\r\n <p-splitButton icon=\"pi pi-chart-bar\" [label]=\"'list_grid.reports' | translate\" [model]=\"reportMenuItems\"\r\n [menuButtonProps]=\"{ ariaLabel: ('list_grid.reports' | translate) }\"\r\n (onClick)=\"reportMenuItems[0]?.command({})\" />\r\n }\r\n @if (canToggleClientSideCrud()) {\r\n @if (!isClientSideCrudActive()) {\r\n <p-button icon=\"pi pi-database\" [label]=\"'client_side_crud_enable' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"toggleClientSideCrud()\" />\r\n } @else {\r\n <p-button icon=\"pi pi-cloud-upload\" [label]=\"'client_side_crud_sync' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"syncAndDisableClientSideCrud()\" />\r\n <p-button icon=\"pi pi-ban\" [label]=\"'client_side_crud_disable_without_sync' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"clientSideCrudToggleBusy\"\r\n (onClick)=\"discardLocalAndDisableClientSideCrud()\" />\r\n }\r\n }\r\n @if (isInlineBatchSaveEnabled() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <p-button icon=\"pi pi-save\" [label]=\"'list_grid.save_changes' | translate\"\r\n [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\" (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button icon=\"pi pi-times\" [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n }\r\n @if (hasDatasourceChanges() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <button type=\"button\" class=\"grid-changes-indicator\"\r\n [title]=\"'Changes in memory: ' + getDatasourceChangesCount()\" (click)=\"openChangesDialog()\">\r\n <i class=\"pi pi-pencil\"></i>\r\n <span class=\"grid-changes-count\">{{ getDatasourceChangesCount() }}</span>\r\n </button>\r\n }\r\n </div>\r\n <div class=\"grid-caption-right\">\r\n @if (!metaInfo?.tableMetadata?.extraProps?.toolbar?.hideManageState) {\r\n @if (currentRouteSavedStates?.length) {\r\n <select class=\"p-inputtext p-component wuic-grid-state-select\" [(ngModel)]=\"selectedSavedStateId\"\r\n (change)=\"onSavedStateDropdownChange($event)\">\r\n <option value=\"\"></option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n }\r\n <p-button [label]=\"'list_grid.manage_state' | translate\" icon=\"pi pi-bookmark\" iconPos=\"left\"\r\n (onClick)=\"onSaveStateMenuToggle($event, saveStateMenu)\" />\r\n <p-menu #saveStateMenu [popup]=\"true\" [model]=\"saveStateMenuItems\" appendTo=\"body\"></p-menu>\r\n }\r\n @if (!isAdvancedFilterModeEnabled()) {\r\n <p-button [label]=\"'clear_filters' | translate\" icon=\"pi pi-filter-slash\" (onClick)=\"clearFilter(dt1)\" />\r\n }\r\n <!-- <i class=\"pi pi-search\"></i>\r\n <input type=\"text\" pInputText [(ngModel)]=\"searchValue\"\r\n class=\"p-inputtext p-component p-element ng-pristine ng-valid ng-touched\"\r\n (input)=\"dt1.filterGlobal(getTgtVal($event.target), 'contains')\"\r\n [placeholder]=\"'search_keyword' | translate\" /> -->\r\n </div>\r\n </div>\r\n }\r\n </ng-template>\r\n <ng-template pTemplate=\"header\" let-columns>\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <th class=\"nested-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <th class=\"multiple-selection-grid-column\" role=\"presentation\" pFrozenColumn [frozen]=\"true\" alignFrozen=\"left\"\r\n class=\"wuic-selection-th\">\r\n <p-tableHeaderCheckbox [ariaLabel]=\"'Seleziona tutte le righe'\" />\r\n </th>\r\n }\r\n @if (showActionColumn()) {\r\n <th class=\"command-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n <!-- @for (col of columns | visibleFieldList; track col.metaColumn.mc_id) { NON FUNZIONA si spaccano i filter template !!! -->\r\n <!-- *ngFor NON FUNGE SE USI IL trackBy -> si spaccano i filter template -->\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n @if (!!col?.frozen) {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n pFrozenColumn [frozen]=\"true\" [alignFrozen]=\"col?.alignFrozen || 'left'\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n }\r\n </div>\r\n </th>\r\n } @else {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <!-- <ng-container [ngSwitch]=\"col.metaColumn.mc_ui_column_type\"> -->\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n <!-- <p-columnFilter *ngSwitchDefault type=\"text\" [field]=\"col.field\" display=\"menu\" /> -->\r\n <!-- </ng-container> -->\r\n }\r\n </div>\r\n </th>\r\n }\r\n }\r\n <!-- } -->\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"body\" let-rowData let-columns=\"columns\" let-expanded=\"expanded\" let-rowIndex=\"rowIndex\">\r\n <ng-container pTemplate=\"body\"\r\n *ngComponentOutlet=\"rowTemplate; inputs: { columns: columns, rowData: rowData, expanded: expanded, rowIndex: rowIndex, actionButtonRowIsVisible: isActionButtonRowVisible, isListVirtualizationEnabled: isListVirtualizationEnabled.bind(this), inlineCellEditingEnabled: isInlineCellEditingEnabled(), onInlineCellEditorBlur: onInlineCellEditorBlur.bind(this), onInlineCellEditorValueChange: onInlineCellEditorValueChange.bind(this), getRuntimeGridFieldMeta: getRuntimeGridFieldMetaFn, metaInfo: metaInfo, datasource: datasource, MetadatiColonna: MetadatiColonna, dt: dt1, toggleRow: toggleRow, rowSelect: rowSelect, rowCustomSelect: rowCustomSelect }\"></ng-container>\r\n </ng-template>\r\n <ng-template pTemplate=\"expandedrow\" let-rowData let-columns=\"columns\">\r\n <tr>\r\n <td\r\n [attr.colspan]=\"(columns | visibleFieldList).length + (metaInfo.tableMetadata.md_nested_grid_routes ? 1 : 0) + (metaInfo.tableMetadata.md_multiple_selection ? 1 : 0) + (showActionColumn() ? 1 : 0)\">\r\n @if (metaInfo.nestedRoutes?.length == 1) {\r\n <div [ngClass]=\"metaInfo.nestedRoutes[0].nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"metaInfo.nestedRoutes[0].route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <div class=\"list-grid-long-description-container\"\r\n [innerHTML]=\"getNestedLongDescriptionSafeHtml(nestedSource)\">\r\n </div>\r\n\r\n <wuic-data-repeater\r\n [hardcodedAction]=\"metaInfo.nestedRoutes[0].action == '' ? 'list' : metaInfo.nestedRoutes[0].action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n }\r\n @if (metaInfo.nestedRoutes?.length > 1) {\r\n <div>\r\n <p-tabs [value]=\"0\">\r\n <p-tablist>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tab [value]=\"i\">{{ nestedRoute.nestedTabCaption }}</p-tab>\r\n }\r\n </p-tablist>\r\n <p-tabpanels>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tabpanel [value]=\"i\">\r\n <div [ngClass]=\"nestedRoute.nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"nestedRoute.route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <wuic-data-repeater [hardcodedAction]=\"nestedRoute.action == '' ? 'list' : nestedRoute.action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n </p-tabpanel>\r\n }\r\n </p-tabpanels>\r\n </p-tabs>\r\n </div>\r\n }\r\n </td>\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"footer\">\r\n @if (metaInfo.hasFooter) {\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n @if (showActionColumn()) {\r\n <td></td>\r\n }\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <td></td>\r\n }\r\n <td></td>\r\n @for (col of metaInfo.columnMetadata | visibleFieldList; track col.mc_id) {\r\n <td>\r\n @if (col.mc_aggregation) {\r\n <table>\r\n @for (agg of getAggregations(col); track agg.field + '#' + agg.aggregation) {\r\n <tr>\r\n <td>\r\n {{agg.aggregation | translate}}:\r\n </td>\r\n <td>\r\n {{agg.aggregateValue}}\r\n </td>\r\n </tr>\r\n }\r\n </table>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </ng-template>\r\n </p-table>\r\n }\r\n\r\n <!--\r\n Mobile: la list-grid usa la STESSA <p-table> del desktop, sopra.\r\n Il row template (compilato via `DynamicRowTemplateComponent.\r\n getComponentFromTemplate`) cambia contenuto in base a\r\n `deviceAwareness.isMobile` \u2014 vedi `getEffectiveGridRowTemplate` in\r\n list-grid.component.ts: quando mobile, emette una singola <td colspan>\r\n con layout card-style invece di <td> per ogni colonna. CSS\r\n `@media (max-width: 768px)` nel file .scss nasconde <thead>, rende\r\n `display: block` le <tr>/<td> per liberarsi della semantica table.\r\n Vantaggio: un solo code path (riusa il JIT compile del row template\r\n gia' testato) eliminando il bug `*ngComponentOutlet [inputs]` che\r\n affliggeva il percorso mobile-card separato.\r\n -->\r\n</div>\r\n\r\n<p-dialog [header]=\"'list_grid.save_state_dialog_header' | translate\" [(visible)]=\"saveGridStateDialogVisible\"\r\n [modal]=\"true\" [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-save-state-select\">{{ 'list_grid.state' | translate }}</label>\r\n <select id=\"wuic-save-state-select\" class=\"p-inputtext p-component\" [(ngModel)]=\"saveGridStateDialogSelectedId\"\r\n (change)=\"onSaveGridStateDialogSelectionChange()\">\r\n <option [value]=\"NEW_GRID_STATE_OPTION_ID\">{{ 'new' | translate }}</option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n\r\n @if (saveGridStateDialogSelectedId === NEW_GRID_STATE_OPTION_ID) {\r\n <label for=\"wuic-save-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-save-state-name\" type=\"text\" pInputText [(ngModel)]=\"saveGridStateDialogNewName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n }\r\n\r\n <label for=\"wuic-save-state-default\" class=\"wuic-column-layout-check\">\r\n <input id=\"wuic-save-state-default\" type=\"checkbox\" [(ngModel)]=\"saveGridStateDialogSetAsDefault\" />\r\n <span>{{ 'list_grid.set_as_default' | translate }}</span>\r\n </label>\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"saveGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveCurrentRouteGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.rename_state' | translate\" [(visible)]=\"renameGridStateDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '30rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-rename-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-rename-state-name\" type=\"text\" pInputText [(ngModel)]=\"renameGridStateDialogName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"renameGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"renameSelectedGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.configure_columns' | translate\" [(visible)]=\"columnLayoutDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '36rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-column-layout-dialog\">\r\n @for (item of columnLayoutDraft; track item.field + '#' + $index; let i = $index) {\r\n <div class=\"wuic-column-layout-row\">\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.visible\" />\r\n <span>{{ item.header }}</span>\r\n </label>\r\n <div class=\"wuic-column-layout-actions\">\r\n <p-button icon=\"pi pi-arrow-up\" [outlined]=\"true\" [disabled]=\"i === 0\"\r\n (onClick)=\"moveColumnLayoutDraft(i, -1)\" />\r\n <p-button icon=\"pi pi-arrow-down\" [outlined]=\"true\" [disabled]=\"i === (columnLayoutDraft.length - 1)\"\r\n (onClick)=\"moveColumnLayoutDraft(i, 1)\" />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"columnLayoutDialogVisible = false\" />\r\n <p-button [label]=\"'list_grid.reset_metadata' | translate\" [outlined]=\"true\" icon=\"pi pi-refresh\"\r\n (onClick)=\"resetColumnLayoutDraftAndWidths()\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveColumnLayoutFromDialog()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.report_parameters' | translate\" [(visible)]=\"reportVariableDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\" (onHide)=\"cancelReportVariableDialog()\">\r\n <div class=\"wuic-save-state-dialog\">\r\n @for (variable of selectedReportVariables; track variable.name; let i = $index) {\r\n <label [for]=\"'wuic-report-var-' + i\">{{ variable.alias || variable.name }}</label>\r\n <input [id]=\"'wuic-report-var-' + i\" [name]=\"'wuic-report-var-name-' + i\" type=\"text\" pInputText\r\n [(ngModel)]=\"variable.value\" />\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"cancelReportVariableDialog()\" />\r\n <p-button [label]=\"'list_grid.open_report' | translate\" (onClick)=\"applyReportVariablesAndOpenViewer()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.pending_changes' | translate\" [(visible)]=\"changesDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '44rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-changes-dialog\">\r\n @if (!pendingChangeItems.length) {\r\n <div>{{ 'list_grid.no_pending_changes' | translate }}</div>\r\n } @else {\r\n @for (item of pendingChangeItems; track item.id) {\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.selected\" />\r\n <span><strong>{{ item.label }}</strong> - {{ item.details }}</span>\r\n </label>\r\n }\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'list_grid.save_all' | translate\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.save_selected' | translate\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !getSelectedPendingChangesCount() || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveSelectedPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n </ng-template>\r\n</p-dialog>", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;max-height:100%;min-height:0;overflow:hidden;--wuic-grid-row-odd: #ffffff;--wuic-grid-row-even: #f6f8fc;--wuic-grid-row-hover: #edf2ff;--wuic-grid-row-selected-bg: #dbe8ff;--wuic-grid-row-selected-text: #0f2348;--wuic-grid-row-selected-ring: rgba(59, 130, 246, .55);--wuic-grid-accent: var(--p-primary-color, #3b82f6);--wuic-grid-base: var(--p-surface-0, #ffffff)}:host-context(.theme-dark){--wuic-grid-row-odd: #1f2430;--wuic-grid-row-even: #252b38;--wuic-grid-row-hover: #31394b;--wuic-grid-row-selected-bg: #284774;--wuic-grid-row-selected-text: #eaf2ff;--wuic-grid-row-selected-ring: rgba(120, 170, 255, .65);--wuic-grid-base: var(--p-surface-900, #1f2430)}@supports (color: color-mix(in srgb,white,black)){:host{--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 96%, var(--wuic-grid-accent) 4%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 90%, var(--wuic-grid-accent) 10%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 82%, var(--wuic-grid-accent) 18%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 56%, var(--wuic-grid-accent) 44%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 72%, transparent)}:host-context(.theme-dark){--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 92%, var(--wuic-grid-accent) 8%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 84%, var(--wuic-grid-accent) 16%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 72%, var(--wuic-grid-accent) 28%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 36%, var(--wuic-grid-accent) 64%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 82%, transparent)}}.list-grid-container{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-view{flex:1 1 auto;min-height:0}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;height:auto}:host ::ng-deep .p-datatable-table-container:has(>table.wuic-nested-grid),:host ::ng-deep .p-datatable:has(>.p-datatable-table-container>table.wuic-nested-grid),:host ::ng-deep .list-grid-container:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid-lazy:has(table.wuic-nested-grid),:host ::ng-deep .data-repeater-body:has(table.wuic-nested-grid),:host ::ng-deep td:has(wuic-data-repeater table.wuic-nested-grid){overflow:visible!important;max-height:none!important}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead{position:sticky!important;top:var(--wuic-outer-thead-h, 0px);z-index:3}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead>tr>th{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(odd)>td{background:var(--wuic-grid-row-odd)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(2n)>td{background:var(--wuic-grid-row-even)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):hover>td{background:var(--wuic-grid-row-hover)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td{background:var(--wuic-grid-row-selected-bg)!important;color:var(--wuic-grid-row-selected-text);box-shadow:inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td:first-child{box-shadow:inset 4px 0 0 var(--wuic-grid-accent),inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected:hover>td{background:var(--wuic-grid-row-selected-bg)!important}:host ::ng-deep td.p-datatable-frozen-column,:host ::ng-deep th.p-datatable-frozen-column{border-right:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep td.wuic-action-cell,:host ::ng-deep th.command-grid-column{width:64px!important;min-width:64px!important;max-width:64px!important;box-sizing:border-box;text-align:center;vertical-align:middle}:host ::ng-deep td.wuic-action-cell wuic-data-action-button-lazy,:host ::ng-deep td.wuic-action-cell wuic-data-action-button{display:inline-flex;align-items:center;justify-content:center}:host ::ng-deep .p-datatable-tbody>tr>td:first-child,:host ::ng-deep .p-datatable-thead>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-box,:host ::ng-deep .p-datatable-tbody>tr>td input.p-checkbox-box{width:18px;height:18px;border:1px solid #9ca3af;border-radius:4px;background:#fff;box-sizing:border-box;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th.wuic-selection-th{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td>input.p-checkbox-box{margin-left:auto!important;margin-right:auto!important;display:block}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-icon{font-size:.72rem}:host ::ng-deep .p-datatable-table.width_defined{display:table}:host ::ng-deep .p-datatable-table{display:table}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table{width:100%!important;min-width:100%}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>td,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>th{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>td:first-child,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td{min-width:0;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable>.p-paginator{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator{border-top:2px solid currentColor;background:var(--p-content-background, #fff);font-weight:600;padding:.5rem .75rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page{min-width:2.25rem;min-height:2.25rem;border:2px solid currentColor!important;border-radius:4px;margin:0 2px;font-weight:700;background:transparent;color:currentColor}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled){background:currentColor;color:var(--p-content-background, #fff)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible{outline:3px solid currentColor;outline-offset:2px}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled{opacity:.45;cursor:not-allowed}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:var(--p-primary-color, #2563eb)!important;color:var(--p-primary-contrast-color, #fff)!important;border-color:var(--p-primary-color, #2563eb)!important;border-width:3px!important;font-weight:800}@media(forced-colors:active){:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:Highlight!important;color:HighlightText!important;border-color:Highlight!important;forced-color-adjust:none}}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current{font-weight:700;padding:0 .5rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select{border:2px solid currentColor!important;font-weight:700;min-height:2.25rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within{outline:3px solid currentColor;outline-offset:2px}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td .list-grid-cell-text-content,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td>a{display:block;width:0;min-width:100%;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap!important;overflow-wrap:anywhere}.wuic-save-state-dialog{display:flex;flex-direction:column;gap:.6rem}.wuic-save-state-dialog select,.wuic-save-state-dialog input{width:100%}.wuic-column-layout-dialog{display:flex;flex-direction:column;gap:.45rem;max-height:55vh;overflow:auto}.wuic-column-layout-row{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.35rem 0}.wuic-column-layout-check{display:inline-flex;align-items:center;gap:.5rem}.wuic-column-layout-actions{display:inline-flex;gap:.35rem}.wuic-changes-dialog{display:flex;flex-direction:column;gap:.5rem;max-height:55vh;overflow:auto}.wuic-export-progress-dialog{display:flex;flex-direction:column;gap:.65rem}::ng-deep .wuic-progress-dialog .p-progressbar{--p-progressbar-value-transition: none}::ng-deep .wuic-progress-dialog .p-progressbar .p-progressbar-value,::ng-deep .wuic-progress-dialog .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar-value{transition:none!important;animation:none!important}.wuic-export-progress-detail{font-size:.9rem;font-weight:600;text-align:right}.wuic-import-dialog{display:flex;flex-direction:column;gap:.75rem}.wuic-import-grid{display:grid;grid-template-columns:1fr;gap:.35rem}.wuic-import-file-row{display:flex;align-items:center;gap:.65rem}.grid-caption-bar{display:flex;align-items:stretch;justify-content:space-between;gap:.75rem;flex-wrap:nowrap}.grid-caption-left,.grid-caption-right{display:flex;align-items:stretch;gap:.5rem;flex-wrap:nowrap;min-height:2.25rem;height:2.25rem}.grid-caption-right{margin-left:auto}.grid-caption-left>*,.grid-caption-right>*{margin:0;align-self:stretch}.grid-changes-indicator{display:inline-flex;align-items:center;gap:.3rem;padding:0 .55rem;border:1px solid #f59e0b;border-radius:999px;background:#fffbeb;color:#b45309;font-size:.82rem;font-weight:700;cursor:pointer;appearance:none}.grid-changes-count{min-width:1.2rem;text-align:center}.wuic-grid-state-select{min-width:220px}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button,:host ::ng-deep .grid-caption-bar .p-inputtext{min-height:2.25rem;height:2.25rem;padding-top:.45rem;padding-bottom:.45rem;margin:0;box-sizing:border-box;line-height:1.1rem;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button,:host ::ng-deep .grid-caption-bar p-splitbutton{display:inline-flex;align-items:stretch;margin:0;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button>.p-button{min-height:2.25rem;height:2.25rem;display:inline-flex;align-items:center}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{padding-left:.75rem;padding-right:.75rem}:host ::ng-deep .grid-caption-bar .p-splitbutton{display:inline-flex;align-items:stretch;margin:0}:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{height:100%}:host ::ng-deep .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .grid-caption-bar .p-button .p-button-icon{line-height:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td{padding-top:.35rem;padding-bottom:.35rem;vertical-align:middle;position:relative}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within{overflow:visible!important;z-index:50}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper>div{overflow:visible!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{position:relative;z-index:100;min-width:360px!important;max-width:80vw!important;background:var(--p-content-background, var(--p-surface-0, #fff));box-shadow:0 4px 12px #0000002e;border:1px solid var(--p-primary-color, #3b82f6);border-radius:4px}:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{background:var(--p-surface-900, #1f2430)}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{display:block;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor{display:flex!important;align-items:center!important;min-height:2.2rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper{margin:0!important;padding:0!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .caption-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{width:100%!important;max-width:100%!important;min-width:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper{display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{min-height:2.2rem;height:2.2rem;line-height:1.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-autocomplete .p-inputtext{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .disabled-textfield{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete>svg[data-p-icon]{display:none!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete .p-autocomplete-input{padding-right:.5rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within p-autocomplete>svg[data-p-icon]{z-index:101!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker{display:flex;align-items:stretch}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-input,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-dropdown{min-height:2.2rem;height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-disabled .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext:disabled{opacity:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor{display:flex;align-items:center;min-height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper{margin:0!important;padding:0!important;width:100%!important;max-width:100%!important;min-width:0!important;display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important;width:100%!important;max-width:100%!important;min-width:0!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}.command-grid-column{width:65px}.multiple-selection-grid-column{width:50px}.nested-grid-column{width:30px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-thead{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tfoot,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-footer,:host ::ng-deep .wuic-list-grid-mobile tfoot{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-resizable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table{display:block}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody{display:block;width:100%;padding-top:12px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr{display:block;width:100%;box-sizing:border-box;margin-bottom:12px;border-radius:12px;overflow:hidden;background:var(--p-content-background, var(--wuic-grid-row-odd));border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 80%, var(--wuic-grid-accent) 20%))}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr:last-child{margin-bottom:0}:host ::ng-deep .wuic-list-grid-mobile td.wuic-mobile-card-cell{display:block;width:100%;padding:14px;border-bottom:1px solid var(--surface-border, #e5e7eb);box-sizing:border-box}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table{width:100%!important;min-width:unset!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-splitbutton .p-button .p-button-label{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button{padding-left:.5rem!important;padding-right:.5rem!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-icon{margin-right:0!important;margin-left:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator{display:flex!important;flex-wrap:nowrap;justify-content:center;align-items:center;gap:4px;padding:8px 4px}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-current{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-pages{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-first,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-prev,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-next,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-last{min-width:36px!important;height:36px!important;margin:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-rpp-options,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-select{min-width:56px!important;height:36px!important;margin-left:4px!important}.wuic-mobile-card-list{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:18px;padding:14px;overflow:auto}.wuic-mobile-card-slot{padding-bottom:18px;box-sizing:border-box}.wuic-mobile-card-toolbar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:4px 0;border-bottom:1px solid color-mix(in srgb,var(--wuic-grid-base) 80%,var(--wuic-grid-accent) 20%);margin-bottom:4px}.wuic-mobile-card{display:flex;flex-direction:column;gap:6px;padding:14px 16px;border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 75%, var(--wuic-grid-accent) 25%));border-radius:12px;background:var(--p-content-background, var(--wuic-grid-row-odd));color:var(--p-text-color, inherit);box-shadow:0 4px 14px #0f172a14}:host-context(.theme-dark) .wuic-mobile-card{box-shadow:0 2px 8px #00000059}.wuic-mobile-card.wuic-row-selected{background:var(--wuic-grid-row-selected-bg);color:var(--wuic-grid-row-selected-text);outline:2px solid var(--wuic-grid-row-selected-ring)}:host ::ng-deep .wuic-mobile-card-actions{display:flex;justify-content:flex-end;align-items:center;margin:0 -4px 4px;padding-bottom:6px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-body{display:flex;flex-direction:column;gap:0}:host ::ng-deep .wuic-mobile-card-row{display:grid;grid-template-columns:minmax(110px,38%) 1fr;column-gap:12px;align-items:baseline;padding:8px 2px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-row:last-child{border-bottom:none}:host ::ng-deep .wuic-mobile-card-label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:currentColor;opacity:.75;line-height:1.3;word-break:break-word}:host ::ng-deep .wuic-mobile-card-value{font-size:.95rem;font-weight:500;text-align:right;word-break:break-word;min-width:0;color:currentColor}:host ::ng-deep .wuic-mobile-card-value a{color:var(--p-primary-color, #3b82f6);text-decoration:underline}.wuic-mobile-card-empty{padding:24px 12px;text-align:center;color:#6b7280;font-style:italic}p-menu:not(.p-menu-overlay-visible){display:none}@media(max-width:420px){:host ::ng-deep .wuic-mobile-card-row{grid-template-columns:1fr;row-gap:2px}:host ::ng-deep .wuic-mobile-card-value{text-align:left}}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => DataSourceComponent), selector: "wuic-data-source", inputs: ["route", "routeFromRouting", "hardcodedRoute", "autoload", "loading", "changeTracking", "parentRecord", "parentMetaInfo", "parentDatasource", "useComboEndpoint", "comboFormulaLookup", "comboMcId", "comboField", "comboExtraFields", "comboCurrentRecord", "componentRef"] }, { kind: "component", type: i0.forwardRef(() => DataRepeaterComponent), selector: "wuic-data-repeater", inputs: ["datasource", "hardcodedDatasource", "action", "hardcodedAction", "field", "record", "rowCustomSelect", "hideToolbar", "isEditForm", "fillHeight"], outputs: ["templateReady", "archetypeInstanceReady", "archetypeEvent", "archetypeDataBound"] }, { kind: "ngmodule", type: i0.forwardRef(() => TableModule) }, { kind: "component", type: i0.forwardRef(() => i9$1.Table), selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i0.forwardRef(() => i3.PrimeTemplate), selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.SortableColumn), selector: "[pSortableColumn]", inputs: ["pSortableColumn", "pSortableColumnDisabled"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.FrozenColumn), selector: "[pFrozenColumn]", inputs: ["frozen", "alignFrozen"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.ResizableColumn), selector: "[pResizableColumn]", inputs: ["pResizableColumnDisabled"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.ReorderableColumn), selector: "[pReorderableColumn]", inputs: ["pReorderableColumnDisabled"] }, { kind: "component", type: i0.forwardRef(() => i9$1.SortIcon), selector: "p-sortIcon", inputs: ["field"] }, { kind: "component", type: i0.forwardRef(() => i9$1.TableHeaderCheckbox), selector: "p-tableHeaderCheckbox", inputs: ["disabled", "inputId", "name", "ariaLabel"] }, { kind: "component", type: i0.forwardRef(() => i9$1.ColumnFilter), selector: "p-columnFilter, p-column-filter, p-columnfilter", inputs: ["field", "type", "display", "showMenu", "matchMode", "operator", "showOperator", "showClearButton", "showApplyButton", "showMatchModes", "showAddButton", "hideOnClear", "placeholder", "matchModeOptions", "maxConstraints", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "locale", "localeMatcher", "currency", "currencyDisplay", "filterOn", "useGrouping", "showButtons", "ariaLabel", "filterButtonProps", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: i0.forwardRef(() => ButtonModule) }, { kind: "component", type: i0.forwardRef(() => i4.Button), selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: i0.forwardRef(() => SplitButtonModule) }, { kind: "component", type: i0.forwardRef(() => i12.SplitButton), selector: "p-splitbutton, p-splitButton, p-split-button", inputs: ["model", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "icon", "iconPos", "label", "tooltip", "tooltipOptions", "styleClass", "menuStyle", "menuStyleClass", "dropdownIcon", "appendTo", "dir", "expandAriaLabel", "showTransitionOptions", "hideTransitionOptions", "motionOptions", "buttonProps", "menuButtonProps", "autofocus", "disabled", "tabindex", "menuButtonDisabled", "buttonDisabled"], outputs: ["onClick", "onMenuHide", "onMenuShow", "onDropdownClick"] }, { kind: "ngmodule", type: i0.forwardRef(() => DialogModule) }, { kind: "component", type: i0.forwardRef(() => i2.Dialog), selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: i0.forwardRef(() => ContextMenuModule) }, { kind: "component", type: i0.forwardRef(() => i14.ContextMenu), selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: i0.forwardRef(() => MenuModule) }, { kind: "component", type: i0.forwardRef(() => i9.Menu), selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex", "appendTo", "motionOptions"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }, { kind: "ngmodule", type: i0.forwardRef(() => PaginatorModule) }, { kind: "ngmodule", type: i0.forwardRef(() => ScrollerModule) }, { kind: "component", type: i0.forwardRef(() => Tabs), selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i0.forwardRef(() => TabList), selector: "p-tablist" }, { kind: "component", type: i0.forwardRef(() => Tab), selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: i0.forwardRef(() => TabPanels), selector: "p-tabpanels" }, { kind: "component", type: i0.forwardRef(() => TabPanel), selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "directive", type: i0.forwardRef(() => NgClass), selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: i0.forwardRef(() => FormsModule) }, { kind: "directive", type: i0.forwardRef(() => i1$4.NgSelectOption), selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.ɵNgSelectMultipleOption), selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.DefaultValueAccessor), selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i0.forwardRef(() => i1$4.CheckboxControlValueAccessor), selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i0.forwardRef(() => i1$4.SelectControlValueAccessor), selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.NgControlStatus), selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i0.forwardRef(() => i1$4.NgModel), selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i0.forwardRef(() => InputTextModule) }, { kind: "directive", type: i0.forwardRef(() => i13.InputText), selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i0.forwardRef(() => FieldFilterComponent), selector: "wuic-field-filter", inputs: ["record", "field", "metaInfo", "readOnly", "hideLabel", "operator", "datasource"] }, { kind: "directive", type: i0.forwardRef(() => NgComponentOutlet), selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: i0.forwardRef(() => TranslateModule) }, { kind: "component", type: i0.forwardRef(() => MetadataEditorComponent), selector: "wuic-metadata-editor", inputs: ["datasource", "hardcodedDatasource", "saveCallback", "hideReports", "hideRelatedTableActions", "hideRelatedColumnActions"] }, { kind: "component", type: i0.forwardRef(() => ImportExportButtonComponent), selector: "wuic-import-export-button", inputs: ["routeName", "tableMetadata", "filterInfoProvider"] }, { kind: "pipe", type: i0.forwardRef(() => AsyncPipe), name: "async" }, { kind: "pipe", type: i0.forwardRef(() => i8.TranslatePipe), name: "translate" }, { kind: "pipe", type: i0.forwardRef(() => VisibleFieldListPipe), name: "visibleFieldList" }] });
|
|
37994
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ListGridComponent, isStandalone: true, selector: "wuic-list-grid", inputs: { hardcodedRoute: "hardcodedRoute", parentRecord: "parentRecord", parentMetaInfo: "parentMetaInfo", datasource: "datasource", hardcodedDatasource: "hardcodedDatasource", rowCustomSelect: "rowCustomSelect", hideToolbar: "hideToolbar" }, outputs: { onBeforeRowRender: "onBeforeRowRender", onAfterRowRender: "onAfterRowRender", onAfterRender: "onAfterRender", onPaging: "onPaging", onSorting: "onSorting", onFiltering: "onFiltering", onRefresh: "onRefresh", onPTableSelectionChange: "onPTableSelectionChange", onPTableRowExpand: "onPTableRowExpand", onPTableRowCollapse: "onPTableRowCollapse", onPTableColumnResize: "onPTableColumnResize", onPTableColumnReorder: "onPTableColumnReorder" }, host: { listeners: { "window:resize": "onWindowResizeForGridLayout()" } }, providers: [Title], viewQueries: [{ propertyName: "table", first: true, predicate: ["dt1"], descendants: true }, { propertyName: "columnCtxMenu", first: true, predicate: ["columnCtxMenu"], descendants: true }, { propertyName: "metadataColumnsDatasource", first: true, predicate: ["metadataColumnsDatasource"], descendants: true }, { propertyName: "gridContainerRef", first: true, predicate: ["gridContainer"], descendants: true }, { propertyName: "tableActionsMenu", first: true, predicate: ["tableActionsMenu"], descendants: true }], ngImport: i0, template: "<wuic-data-source #metadataColumnsDatasource [hardcodedRoute]=\"' metadati colonne'\"\r\n style=\"display: none;\"></wuic-data-source>\r\n<p-contextMenu #columnCtxMenu [model]=\"columnContextMenuItems\" appendTo=\"body\"></p-contextMenu>\r\n\r\n<div class=\"list-grid-container\" #gridContainer\r\n [class.wuic-list-grid-mobile]=\"gridLayoutIsMobile\">\r\n @if (rowTemplate) {\r\n <p-table #dt1 [columns]=\"cols\" [value]=\"records\" [scrollable]=\"true\"\r\n [scrollHeight]=\"metaInfo.tableMetadata.md_grid_scroll_height ? metaInfo.tableMetadata.md_grid_scroll_height : 'flex'\"\r\n [virtualScroll]=\"isListVirtualizationEnabled()\" [virtualScrollItemSize]=\"getListVirtualizationItemSize()\"\r\n [resizableColumns]=\"true\" [reorderableColumns]=\"true\" columnResizeMode=\"expand\" [rows]=\"pageSize\"\r\n [first]=\"rowNumber\" [lazy]=\"metaInfo.tableMetadata.md_server_side_operations\" [sortField]=\"orderColumn || null\"\r\n [sortOrder]=\"orderColumn ? (orderDir === 'desc' ? -1 : 1) : 0\" [(selection)]=\"selectedItems\"\r\n [dataKey]=\"metaInfo.pKey?.mc_nome_colonna\" [paginator]=\"metaInfo.tableMetadata.md_pageable\"\r\n [rowsPerPageOptions]=\"metaInfo.rowsPerPageOptions\" [totalRecords]=\"totalRecords\"\r\n [showPageLinks]=\"!datasource?.value?.cursorMode\" [showFirstLastIcon]=\"!datasource?.value?.cursorMode\"\r\n [showCurrentPageReport]=\"metaInfo.tableMetadata.md_show_record_count && !datasource?.value?.cursorMode\"\r\n [loading]=\"datasource?.value?.loading | async\"\r\n [currentPageReportTemplate]=\"'Showing {first} to {last} of {totalRecords} entries' | translate\"\r\n (onPage)=\"pageFilterChange($event)\" (onFilter)=\"pageFilterChange($event)\" (onSort)=\"pageFilterChange($event)\"\r\n (selectionChange)=\"onSelectionChange($event)\" (onColResize)=\"onColumnResize($event)\"\r\n (onColReorder)=\"onColumnReorder($event)\" [expandedRowKeys]=\"expandedRows\" (onRowExpand)=\"onRowExpand($event)\"\r\n (onRowCollapse)=\"onRowCollapse($event)\" [selectionMode]=\"rowCustomSelect ? 'single' : undefined\"\r\n [tableStyleClass]=\"getTableStyleClass()\">\r\n <ng-template pTemplate=\"colgroup\" let-columns>\r\n <colgroup>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <col class=\"nested-grid-column\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <col class=\"multiple-selection-grid-column\" />\r\n }\r\n @if (showActionColumn()) {\r\n <col class=\"command-grid-column\" />\r\n }\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n <col [style.width]=\"getColumnWidthCss(col)\" [style.min-width]=\"columnMinWidthCss\" />\r\n }\r\n </colgroup>\r\n </ng-template>\r\n <ng-template pTemplate=\"caption\">\r\n @if (!hideToolbar) {\r\n <div class=\"grid-caption-bar\">\r\n <div class=\"grid-caption-left\">\r\n @if (!metaInfo.tableMetadata.md_hide_refresh) {\r\n <p-button icon=\"pi pi-refresh\" label=\"{{'refresh_grid' | translate}}\" (onClick)=\"refresh()\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_insertable) {\r\n <p-button icon=\"pi pi-plus\" label=\"{{'add_grid' | translate}}\" (onClick)=\"addRecord()\" />\r\n }\r\n @if (tableActionMenuItems?.length) {\r\n <p-button icon=\"pi pi-bars\" [label]=\"'actions' | translate\" (onClick)=\"tableActionsMenu.toggle($event)\" />\r\n <p-menu #tableActionsMenu [popup]=\"true\" [model]=\"tableActionMenuItems\" appendTo=\"body\"\r\n (onShow)=\"reEvaluateActionEnabledStates()\"></p-menu>\r\n }\r\n <wuic-import-export-button [routeName]=\"routeName\"\r\n [tableMetadata]=\"metaInfo?.tableMetadata\"\r\n [filterInfoProvider]=\"getCurrentFilterInfoForExport\"></wuic-import-export-button>\r\n @if (reportMenuItems?.length) {\r\n <p-splitButton icon=\"pi pi-chart-bar\" [label]=\"'list_grid.reports' | translate\" [model]=\"reportMenuItems\"\r\n [menuButtonProps]=\"{ ariaLabel: ('list_grid.reports' | translate) }\"\r\n (onClick)=\"reportMenuItems[0]?.command({})\" />\r\n }\r\n @if (canToggleClientSideCrud()) {\r\n @if (!isClientSideCrudActive()) {\r\n <p-button icon=\"pi pi-database\" [label]=\"'client_side_crud_enable' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"toggleClientSideCrud()\" />\r\n } @else {\r\n <p-button icon=\"pi pi-cloud-upload\" [label]=\"'client_side_crud_sync' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"syncAndDisableClientSideCrud()\" />\r\n <p-button icon=\"pi pi-ban\" [label]=\"'client_side_crud_disable_without_sync' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"clientSideCrudToggleBusy\"\r\n (onClick)=\"discardLocalAndDisableClientSideCrud()\" />\r\n }\r\n }\r\n @if (isInlineBatchSaveEnabled() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <p-button icon=\"pi pi-save\" [label]=\"'list_grid.save_changes' | translate\"\r\n [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\" (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button icon=\"pi pi-times\" [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n }\r\n @if (hasDatasourceChanges() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <button type=\"button\" class=\"grid-changes-indicator\"\r\n [title]=\"'Changes in memory: ' + getDatasourceChangesCount()\" (click)=\"openChangesDialog()\">\r\n <i class=\"pi pi-pencil\"></i>\r\n <span class=\"grid-changes-count\">{{ getDatasourceChangesCount() }}</span>\r\n </button>\r\n }\r\n </div>\r\n <div class=\"grid-caption-right\">\r\n @if (!metaInfo?.tableMetadata?.extraProps?.toolbar?.hideManageState) {\r\n @if (currentRouteSavedStates?.length) {\r\n <select class=\"p-inputtext p-component wuic-grid-state-select\" [(ngModel)]=\"selectedSavedStateId\"\r\n (change)=\"onSavedStateDropdownChange($event)\">\r\n <option value=\"\"></option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n }\r\n <p-button [label]=\"'list_grid.manage_state' | translate\" icon=\"pi pi-bookmark\" iconPos=\"left\"\r\n (onClick)=\"onSaveStateMenuToggle($event, saveStateMenu)\" />\r\n <p-menu #saveStateMenu [popup]=\"true\" [model]=\"saveStateMenuItems\" appendTo=\"body\"></p-menu>\r\n }\r\n @if (!isAdvancedFilterModeEnabled()) {\r\n <p-button [label]=\"'clear_filters' | translate\" icon=\"pi pi-filter-slash\" (onClick)=\"clearFilter(dt1)\" />\r\n }\r\n <!-- <i class=\"pi pi-search\"></i>\r\n <input type=\"text\" pInputText [(ngModel)]=\"searchValue\"\r\n class=\"p-inputtext p-component p-element ng-pristine ng-valid ng-touched\"\r\n (input)=\"dt1.filterGlobal(getTgtVal($event.target), 'contains')\"\r\n [placeholder]=\"'search_keyword' | translate\" /> -->\r\n </div>\r\n </div>\r\n }\r\n </ng-template>\r\n <ng-template pTemplate=\"header\" let-columns>\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <th class=\"nested-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <th class=\"multiple-selection-grid-column\" role=\"presentation\" pFrozenColumn [frozen]=\"true\" alignFrozen=\"left\"\r\n class=\"wuic-selection-th\">\r\n <p-tableHeaderCheckbox [ariaLabel]=\"'Seleziona tutte le righe'\" />\r\n </th>\r\n }\r\n @if (showActionColumn()) {\r\n <th class=\"command-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n <!-- @for (col of columns | visibleFieldList; track col.metaColumn.mc_id) { NON FUNZIONA si spaccano i filter template !!! -->\r\n <!-- *ngFor NON FUNGE SE USI IL trackBy -> si spaccano i filter template -->\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n @if (!!col?.frozen) {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n pFrozenColumn [frozen]=\"true\" [alignFrozen]=\"col?.alignFrozen || 'left'\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n }\r\n </div>\r\n </th>\r\n } @else {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <!-- <ng-container [ngSwitch]=\"col.metaColumn.mc_ui_column_type\"> -->\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n <!-- <p-columnFilter *ngSwitchDefault type=\"text\" [field]=\"col.field\" display=\"menu\" /> -->\r\n <!-- </ng-container> -->\r\n }\r\n </div>\r\n </th>\r\n }\r\n }\r\n <!-- } -->\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"body\" let-rowData let-columns=\"columns\" let-expanded=\"expanded\" let-rowIndex=\"rowIndex\">\r\n <ng-container pTemplate=\"body\"\r\n *ngComponentOutlet=\"rowTemplate; inputs: { columns: columns, rowData: rowData, expanded: expanded, rowIndex: rowIndex, actionButtonRowIsVisible: isActionButtonRowVisible, isListVirtualizationEnabled: isListVirtualizationEnabled.bind(this), inlineCellEditingEnabled: isInlineCellEditingEnabled(), onInlineCellEditorBlur: onInlineCellEditorBlur.bind(this), onInlineCellEditorValueChange: onInlineCellEditorValueChange.bind(this), getRuntimeGridFieldMeta: getRuntimeGridFieldMetaFn, metaInfo: metaInfo, datasource: datasource, MetadatiColonna: MetadatiColonna, dt: dt1, toggleRow: toggleRow, rowSelect: rowSelect, rowCustomSelect: rowCustomSelect }\"></ng-container>\r\n </ng-template>\r\n <ng-template pTemplate=\"expandedrow\" let-rowData let-columns=\"columns\">\r\n <tr>\r\n <td\r\n [attr.colspan]=\"(columns | visibleFieldList).length + (metaInfo.tableMetadata.md_nested_grid_routes ? 1 : 0) + (metaInfo.tableMetadata.md_multiple_selection ? 1 : 0) + (showActionColumn() ? 1 : 0)\">\r\n @if (metaInfo.nestedRoutes?.length == 1) {\r\n <div [ngClass]=\"metaInfo.nestedRoutes[0].nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"metaInfo.nestedRoutes[0].route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <div class=\"list-grid-long-description-container\"\r\n [innerHTML]=\"getNestedLongDescriptionSafeHtml(nestedSource)\">\r\n </div>\r\n\r\n <wuic-data-repeater\r\n [hardcodedAction]=\"metaInfo.nestedRoutes[0].action == '' ? 'list' : metaInfo.nestedRoutes[0].action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n }\r\n @if (metaInfo.nestedRoutes?.length > 1) {\r\n <div>\r\n <p-tabs [value]=\"0\">\r\n <p-tablist>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tab [value]=\"i\">{{ nestedRoute.nestedTabCaption }}</p-tab>\r\n }\r\n </p-tablist>\r\n <p-tabpanels>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tabpanel [value]=\"i\">\r\n <div [ngClass]=\"nestedRoute.nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"nestedRoute.route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <wuic-data-repeater [hardcodedAction]=\"nestedRoute.action == '' ? 'list' : nestedRoute.action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n </p-tabpanel>\r\n }\r\n </p-tabpanels>\r\n </p-tabs>\r\n </div>\r\n }\r\n </td>\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"footer\">\r\n @if (metaInfo.hasFooter) {\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n @if (showActionColumn()) {\r\n <td></td>\r\n }\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <td></td>\r\n }\r\n <td></td>\r\n @for (col of metaInfo.columnMetadata | visibleFieldList; track col.mc_id) {\r\n <td>\r\n @if (col.mc_aggregation) {\r\n <table>\r\n @for (agg of getAggregations(col); track agg.field + '#' + agg.aggregation) {\r\n <tr>\r\n <td>\r\n {{agg.aggregation | translate}}:\r\n </td>\r\n <td>\r\n {{agg.aggregateValue}}\r\n </td>\r\n </tr>\r\n }\r\n </table>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </ng-template>\r\n </p-table>\r\n }\r\n\r\n <!--\r\n Mobile: la list-grid usa la STESSA <p-table> del desktop, sopra.\r\n Il row template (compilato via `DynamicRowTemplateComponent.\r\n getComponentFromTemplate`) cambia contenuto in base a\r\n `deviceAwareness.isMobile` \u2014 vedi `getEffectiveGridRowTemplate` in\r\n list-grid.component.ts: quando mobile, emette una singola <td colspan>\r\n con layout card-style invece di <td> per ogni colonna. CSS\r\n `@media (max-width: 768px)` nel file .scss nasconde <thead>, rende\r\n `display: block` le <tr>/<td> per liberarsi della semantica table.\r\n Vantaggio: un solo code path (riusa il JIT compile del row template\r\n gia' testato) eliminando il bug `*ngComponentOutlet [inputs]` che\r\n affliggeva il percorso mobile-card separato.\r\n -->\r\n</div>\r\n\r\n<p-dialog [header]=\"'list_grid.save_state_dialog_header' | translate\" [(visible)]=\"saveGridStateDialogVisible\"\r\n [modal]=\"true\" [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-save-state-select\">{{ 'list_grid.state' | translate }}</label>\r\n <select id=\"wuic-save-state-select\" class=\"p-inputtext p-component\" [(ngModel)]=\"saveGridStateDialogSelectedId\"\r\n (change)=\"onSaveGridStateDialogSelectionChange()\">\r\n <option [value]=\"NEW_GRID_STATE_OPTION_ID\">{{ 'new' | translate }}</option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n\r\n @if (saveGridStateDialogSelectedId === NEW_GRID_STATE_OPTION_ID) {\r\n <label for=\"wuic-save-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-save-state-name\" type=\"text\" pInputText [(ngModel)]=\"saveGridStateDialogNewName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n }\r\n\r\n <label for=\"wuic-save-state-default\" class=\"wuic-column-layout-check\">\r\n <input id=\"wuic-save-state-default\" type=\"checkbox\" [(ngModel)]=\"saveGridStateDialogSetAsDefault\" />\r\n <span>{{ 'list_grid.set_as_default' | translate }}</span>\r\n </label>\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"saveGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveCurrentRouteGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.rename_state' | translate\" [(visible)]=\"renameGridStateDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '30rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-rename-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-rename-state-name\" type=\"text\" pInputText [(ngModel)]=\"renameGridStateDialogName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"renameGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"renameSelectedGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.configure_columns' | translate\" [(visible)]=\"columnLayoutDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '36rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-column-layout-dialog\">\r\n @for (item of columnLayoutDraft; track item.field + '#' + $index; let i = $index) {\r\n <div class=\"wuic-column-layout-row\">\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.visible\" />\r\n <span>{{ item.header }}</span>\r\n </label>\r\n <div class=\"wuic-column-layout-actions\">\r\n <p-button icon=\"pi pi-arrow-up\" [outlined]=\"true\" [disabled]=\"i === 0\"\r\n (onClick)=\"moveColumnLayoutDraft(i, -1)\" />\r\n <p-button icon=\"pi pi-arrow-down\" [outlined]=\"true\" [disabled]=\"i === (columnLayoutDraft.length - 1)\"\r\n (onClick)=\"moveColumnLayoutDraft(i, 1)\" />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"columnLayoutDialogVisible = false\" />\r\n <p-button [label]=\"'list_grid.reset_metadata' | translate\" [outlined]=\"true\" icon=\"pi pi-refresh\"\r\n (onClick)=\"resetColumnLayoutDraftAndWidths()\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveColumnLayoutFromDialog()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.report_parameters' | translate\" [(visible)]=\"reportVariableDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\" (onHide)=\"cancelReportVariableDialog()\">\r\n <div class=\"wuic-save-state-dialog\">\r\n @for (variable of selectedReportVariables; track variable.name; let i = $index) {\r\n <label [for]=\"'wuic-report-var-' + i\">{{ variable.alias || variable.name }}</label>\r\n <input [id]=\"'wuic-report-var-' + i\" [name]=\"'wuic-report-var-name-' + i\" type=\"text\" pInputText\r\n [(ngModel)]=\"variable.value\" />\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"cancelReportVariableDialog()\" />\r\n <p-button [label]=\"'list_grid.open_report' | translate\" (onClick)=\"applyReportVariablesAndOpenViewer()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.pending_changes' | translate\" [(visible)]=\"changesDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '44rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-changes-dialog\">\r\n @if (!pendingChangeItems.length) {\r\n <div>{{ 'list_grid.no_pending_changes' | translate }}</div>\r\n } @else {\r\n @for (item of pendingChangeItems; track item.id) {\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.selected\" />\r\n <span><strong>{{ item.label }}</strong> - {{ item.details }}</span>\r\n </label>\r\n }\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'list_grid.save_all' | translate\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.save_selected' | translate\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !getSelectedPendingChangesCount() || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveSelectedPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n </ng-template>\r\n</p-dialog>", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;max-height:100%;min-height:0;overflow:hidden;--wuic-grid-row-odd: #ffffff;--wuic-grid-row-even: #f6f8fc;--wuic-grid-row-hover: #edf2ff;--wuic-grid-row-selected-bg: #dbe8ff;--wuic-grid-row-selected-text: #0f2348;--wuic-grid-row-selected-ring: rgba(59, 130, 246, .55);--wuic-grid-accent: var(--p-primary-color, #3b82f6);--wuic-grid-base: var(--p-surface-0, #ffffff)}:host-context(.theme-dark){--wuic-grid-row-odd: #1f2430;--wuic-grid-row-even: #252b38;--wuic-grid-row-hover: #31394b;--wuic-grid-row-selected-bg: #284774;--wuic-grid-row-selected-text: #eaf2ff;--wuic-grid-row-selected-ring: rgba(120, 170, 255, .65);--wuic-grid-base: var(--p-surface-900, #1f2430)}@supports (color: color-mix(in srgb,white,black)){:host{--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 96%, var(--wuic-grid-accent) 4%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 90%, var(--wuic-grid-accent) 10%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 82%, var(--wuic-grid-accent) 18%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 56%, var(--wuic-grid-accent) 44%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 72%, transparent)}:host-context(.theme-dark){--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 92%, var(--wuic-grid-accent) 8%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 84%, var(--wuic-grid-accent) 16%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 72%, var(--wuic-grid-accent) 28%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 36%, var(--wuic-grid-accent) 64%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 82%, transparent)}}.list-grid-container{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-view{flex:1 1 auto;min-height:0}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;height:auto}:host ::ng-deep .p-datatable-table-container:has(>table.wuic-nested-grid),:host ::ng-deep .p-datatable:has(>.p-datatable-table-container>table.wuic-nested-grid),:host ::ng-deep .list-grid-container:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid-lazy:has(table.wuic-nested-grid),:host ::ng-deep .data-repeater-body:has(table.wuic-nested-grid),:host ::ng-deep td:has(wuic-data-repeater table.wuic-nested-grid){overflow:visible!important;max-height:none!important}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead{position:sticky!important;top:var(--wuic-outer-thead-h, 0px);z-index:3}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead>tr>th{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(odd)>td{background:var(--wuic-grid-row-odd)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(2n)>td{background:var(--wuic-grid-row-even)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):hover>td{background:var(--wuic-grid-row-hover)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-style-applied:not(.p-highlight):not(.wuic-row-selected)>td{background:transparent!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):not(.wuic-row-selected)>td.wuic-cell-style-applied{background:transparent}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td{background:var(--wuic-grid-row-selected-bg)!important;color:var(--wuic-grid-row-selected-text);box-shadow:inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td:first-child{box-shadow:inset 4px 0 0 var(--wuic-grid-accent),inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected:hover>td{background:var(--wuic-grid-row-selected-bg)!important}:host ::ng-deep td.p-datatable-frozen-column,:host ::ng-deep th.p-datatable-frozen-column{border-right:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep td.wuic-action-cell,:host ::ng-deep th.command-grid-column{width:64px!important;min-width:64px!important;max-width:64px!important;box-sizing:border-box;text-align:center;vertical-align:middle}:host ::ng-deep td.wuic-action-cell wuic-data-action-button-lazy,:host ::ng-deep td.wuic-action-cell wuic-data-action-button{display:inline-flex;align-items:center;justify-content:center}:host ::ng-deep .p-datatable-tbody>tr>td:first-child,:host ::ng-deep .p-datatable-thead>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-box,:host ::ng-deep .p-datatable-tbody>tr>td input.p-checkbox-box{width:18px;height:18px;border:1px solid #9ca3af;border-radius:4px;background:#fff;box-sizing:border-box;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th.wuic-selection-th{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td>input.p-checkbox-box{margin-left:auto!important;margin-right:auto!important;display:block}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-icon{font-size:.72rem}:host ::ng-deep .p-datatable-table.width_defined{display:table}:host ::ng-deep .p-datatable-table{display:table}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table{width:100%!important;min-width:100%}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>td,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>th{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>td:first-child,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td{min-width:0;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable>.p-paginator{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator{border-top:2px solid currentColor;background:var(--p-content-background, #fff);font-weight:600;padding:.5rem .75rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page{min-width:2.25rem;min-height:2.25rem;border:2px solid currentColor!important;border-radius:4px;margin:0 2px;font-weight:700;background:transparent;color:currentColor}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled){background:currentColor;color:var(--p-content-background, #fff)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible{outline:3px solid currentColor;outline-offset:2px}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled{opacity:.45;cursor:not-allowed}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:var(--p-primary-color, #2563eb)!important;color:var(--p-primary-contrast-color, #fff)!important;border-color:var(--p-primary-color, #2563eb)!important;border-width:3px!important;font-weight:800}@media(forced-colors:active){:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:Highlight!important;color:HighlightText!important;border-color:Highlight!important;forced-color-adjust:none}}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current{font-weight:700;padding:0 .5rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select{border:2px solid currentColor!important;font-weight:700;min-height:2.25rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within{outline:3px solid currentColor;outline-offset:2px}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td .list-grid-cell-text-content,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td>a{display:block;width:0;min-width:100%;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap!important;overflow-wrap:anywhere}.wuic-save-state-dialog{display:flex;flex-direction:column;gap:.6rem}.wuic-save-state-dialog select,.wuic-save-state-dialog input{width:100%}.wuic-column-layout-dialog{display:flex;flex-direction:column;gap:.45rem;max-height:55vh;overflow:auto}.wuic-column-layout-row{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.35rem 0}.wuic-column-layout-check{display:inline-flex;align-items:center;gap:.5rem}.wuic-column-layout-actions{display:inline-flex;gap:.35rem}.wuic-changes-dialog{display:flex;flex-direction:column;gap:.5rem;max-height:55vh;overflow:auto}.wuic-export-progress-dialog{display:flex;flex-direction:column;gap:.65rem}::ng-deep .wuic-progress-dialog .p-progressbar{--p-progressbar-value-transition: none}::ng-deep .wuic-progress-dialog .p-progressbar .p-progressbar-value,::ng-deep .wuic-progress-dialog .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar-value{transition:none!important;animation:none!important}.wuic-export-progress-detail{font-size:.9rem;font-weight:600;text-align:right}.wuic-import-dialog{display:flex;flex-direction:column;gap:.75rem}.wuic-import-grid{display:grid;grid-template-columns:1fr;gap:.35rem}.wuic-import-file-row{display:flex;align-items:center;gap:.65rem}.grid-caption-bar{display:flex;align-items:stretch;justify-content:space-between;gap:.75rem;flex-wrap:nowrap}.grid-caption-left,.grid-caption-right{display:flex;align-items:stretch;gap:.5rem;flex-wrap:nowrap;min-height:2.25rem;height:2.25rem}.grid-caption-right{margin-left:auto}.grid-caption-left>*,.grid-caption-right>*{margin:0;align-self:stretch}.grid-changes-indicator{display:inline-flex;align-items:center;gap:.3rem;padding:0 .55rem;border:1px solid #f59e0b;border-radius:999px;background:#fffbeb;color:#b45309;font-size:.82rem;font-weight:700;cursor:pointer;appearance:none}.grid-changes-count{min-width:1.2rem;text-align:center}.wuic-grid-state-select{min-width:220px}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button,:host ::ng-deep .grid-caption-bar .p-inputtext{min-height:2.25rem;height:2.25rem;padding-top:.45rem;padding-bottom:.45rem;margin:0;box-sizing:border-box;line-height:1.1rem;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button,:host ::ng-deep .grid-caption-bar p-splitbutton{display:inline-flex;align-items:stretch;margin:0;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button>.p-button{min-height:2.25rem;height:2.25rem;display:inline-flex;align-items:center}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{padding-left:.75rem;padding-right:.75rem}:host ::ng-deep .grid-caption-bar .p-splitbutton{display:inline-flex;align-items:stretch;margin:0}:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{height:100%}:host ::ng-deep .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .grid-caption-bar .p-button .p-button-icon{line-height:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td{padding-top:.35rem;padding-bottom:.35rem;vertical-align:middle;position:relative}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within{overflow:visible!important;z-index:50}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper>div{overflow:visible!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{position:relative;z-index:100;min-width:360px!important;max-width:80vw!important;background:var(--p-content-background, var(--p-surface-0, #fff));box-shadow:0 4px 12px #0000002e;border:1px solid var(--p-primary-color, #3b82f6);border-radius:4px}:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{background:var(--p-surface-900, #1f2430)}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{display:block;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor{display:flex!important;align-items:center!important;min-height:2.2rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper{margin:0!important;padding:0!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .caption-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{width:100%!important;max-width:100%!important;min-width:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper{display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{min-height:2.2rem;height:2.2rem;line-height:1.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-autocomplete .p-inputtext{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .disabled-textfield{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete>svg[data-p-icon]{display:none!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete .p-autocomplete-input{padding-right:.5rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within p-autocomplete>svg[data-p-icon]{z-index:101!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker{display:flex;align-items:stretch}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-input,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-dropdown{min-height:2.2rem;height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-disabled .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext:disabled{opacity:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor{display:flex;align-items:center;min-height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper{margin:0!important;padding:0!important;width:100%!important;max-width:100%!important;min-width:0!important;display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important;width:100%!important;max-width:100%!important;min-width:0!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}.command-grid-column{width:65px}.multiple-selection-grid-column{width:50px}.nested-grid-column{width:30px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-thead{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tfoot,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-footer,:host ::ng-deep .wuic-list-grid-mobile tfoot{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-resizable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table{display:block}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody{display:block;width:100%;padding-top:12px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr{display:block;width:100%;box-sizing:border-box;margin-bottom:12px;border-radius:12px;overflow:hidden;background:var(--p-content-background, var(--wuic-grid-row-odd));border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 80%, var(--wuic-grid-accent) 20%))}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr:last-child{margin-bottom:0}:host ::ng-deep .wuic-list-grid-mobile td.wuic-mobile-card-cell{display:block;width:100%;padding:14px;border-bottom:1px solid var(--surface-border, #e5e7eb);box-sizing:border-box}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table{width:100%!important;min-width:unset!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-splitbutton .p-button .p-button-label{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button{padding-left:.5rem!important;padding-right:.5rem!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-icon{margin-right:0!important;margin-left:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator{display:flex!important;flex-wrap:nowrap;justify-content:center;align-items:center;gap:4px;padding:8px 4px}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-current{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-pages{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-first,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-prev,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-next,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-last{min-width:36px!important;height:36px!important;margin:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-rpp-options,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-select{min-width:56px!important;height:36px!important;margin-left:4px!important}.wuic-mobile-card-list{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:18px;padding:14px;overflow:auto}.wuic-mobile-card-slot{padding-bottom:18px;box-sizing:border-box}.wuic-mobile-card-toolbar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:4px 0;border-bottom:1px solid color-mix(in srgb,var(--wuic-grid-base) 80%,var(--wuic-grid-accent) 20%);margin-bottom:4px}.wuic-mobile-card{display:flex;flex-direction:column;gap:6px;padding:14px 16px;border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 75%, var(--wuic-grid-accent) 25%));border-radius:12px;background:var(--p-content-background, var(--wuic-grid-row-odd));color:var(--p-text-color, inherit);box-shadow:0 4px 14px #0f172a14}:host-context(.theme-dark) .wuic-mobile-card{box-shadow:0 2px 8px #00000059}.wuic-mobile-card.wuic-row-selected{background:var(--wuic-grid-row-selected-bg);color:var(--wuic-grid-row-selected-text);outline:2px solid var(--wuic-grid-row-selected-ring)}:host ::ng-deep .wuic-mobile-card-actions{display:flex;justify-content:flex-end;align-items:center;margin:0 -4px 4px;padding-bottom:6px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-body{display:flex;flex-direction:column;gap:0}:host ::ng-deep .wuic-mobile-card-row{display:grid;grid-template-columns:minmax(110px,38%) 1fr;column-gap:12px;align-items:baseline;padding:8px 2px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-row:last-child{border-bottom:none}:host ::ng-deep .wuic-mobile-card-label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:currentColor;opacity:.75;line-height:1.3;word-break:break-word}:host ::ng-deep .wuic-mobile-card-value{font-size:.95rem;font-weight:500;text-align:right;word-break:break-word;min-width:0;color:currentColor}:host ::ng-deep .wuic-mobile-card-value a{color:var(--p-primary-color, #3b82f6);text-decoration:underline}.wuic-mobile-card-empty{padding:24px 12px;text-align:center;color:#6b7280;font-style:italic}p-menu:not(.p-menu-overlay-visible){display:none}@media(max-width:420px){:host ::ng-deep .wuic-mobile-card-row{grid-template-columns:1fr;row-gap:2px}:host ::ng-deep .wuic-mobile-card-value{text-align:left}}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => DataSourceComponent), selector: "wuic-data-source", inputs: ["route", "routeFromRouting", "hardcodedRoute", "autoload", "loading", "changeTracking", "parentRecord", "parentMetaInfo", "parentDatasource", "useComboEndpoint", "comboFormulaLookup", "comboMcId", "comboField", "comboExtraFields", "comboCurrentRecord", "componentRef"] }, { kind: "component", type: i0.forwardRef(() => DataRepeaterComponent), selector: "wuic-data-repeater", inputs: ["datasource", "hardcodedDatasource", "action", "hardcodedAction", "field", "record", "rowCustomSelect", "hideToolbar", "isEditForm", "fillHeight"], outputs: ["templateReady", "archetypeInstanceReady", "archetypeEvent", "archetypeDataBound"] }, { kind: "ngmodule", type: i0.forwardRef(() => TableModule) }, { kind: "component", type: i0.forwardRef(() => i9$1.Table), selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i0.forwardRef(() => i3.PrimeTemplate), selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.SortableColumn), selector: "[pSortableColumn]", inputs: ["pSortableColumn", "pSortableColumnDisabled"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.FrozenColumn), selector: "[pFrozenColumn]", inputs: ["frozen", "alignFrozen"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.ResizableColumn), selector: "[pResizableColumn]", inputs: ["pResizableColumnDisabled"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.ReorderableColumn), selector: "[pReorderableColumn]", inputs: ["pReorderableColumnDisabled"] }, { kind: "component", type: i0.forwardRef(() => i9$1.SortIcon), selector: "p-sortIcon", inputs: ["field"] }, { kind: "component", type: i0.forwardRef(() => i9$1.TableHeaderCheckbox), selector: "p-tableHeaderCheckbox", inputs: ["disabled", "inputId", "name", "ariaLabel"] }, { kind: "component", type: i0.forwardRef(() => i9$1.ColumnFilter), selector: "p-columnFilter, p-column-filter, p-columnfilter", inputs: ["field", "type", "display", "showMenu", "matchMode", "operator", "showOperator", "showClearButton", "showApplyButton", "showMatchModes", "showAddButton", "hideOnClear", "placeholder", "matchModeOptions", "maxConstraints", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "locale", "localeMatcher", "currency", "currencyDisplay", "filterOn", "useGrouping", "showButtons", "ariaLabel", "filterButtonProps", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: i0.forwardRef(() => ButtonModule) }, { kind: "component", type: i0.forwardRef(() => i4.Button), selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: i0.forwardRef(() => SplitButtonModule) }, { kind: "component", type: i0.forwardRef(() => i12.SplitButton), selector: "p-splitbutton, p-splitButton, p-split-button", inputs: ["model", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "icon", "iconPos", "label", "tooltip", "tooltipOptions", "styleClass", "menuStyle", "menuStyleClass", "dropdownIcon", "appendTo", "dir", "expandAriaLabel", "showTransitionOptions", "hideTransitionOptions", "motionOptions", "buttonProps", "menuButtonProps", "autofocus", "disabled", "tabindex", "menuButtonDisabled", "buttonDisabled"], outputs: ["onClick", "onMenuHide", "onMenuShow", "onDropdownClick"] }, { kind: "ngmodule", type: i0.forwardRef(() => DialogModule) }, { kind: "component", type: i0.forwardRef(() => i2.Dialog), selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: i0.forwardRef(() => ContextMenuModule) }, { kind: "component", type: i0.forwardRef(() => i14.ContextMenu), selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: i0.forwardRef(() => MenuModule) }, { kind: "component", type: i0.forwardRef(() => i9.Menu), selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex", "appendTo", "motionOptions"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }, { kind: "ngmodule", type: i0.forwardRef(() => PaginatorModule) }, { kind: "ngmodule", type: i0.forwardRef(() => ScrollerModule) }, { kind: "component", type: i0.forwardRef(() => Tabs), selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i0.forwardRef(() => TabList), selector: "p-tablist" }, { kind: "component", type: i0.forwardRef(() => Tab), selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: i0.forwardRef(() => TabPanels), selector: "p-tabpanels" }, { kind: "component", type: i0.forwardRef(() => TabPanel), selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "directive", type: i0.forwardRef(() => NgClass), selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: i0.forwardRef(() => FormsModule) }, { kind: "directive", type: i0.forwardRef(() => i1$4.NgSelectOption), selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.ɵNgSelectMultipleOption), selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.DefaultValueAccessor), selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i0.forwardRef(() => i1$4.CheckboxControlValueAccessor), selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i0.forwardRef(() => i1$4.SelectControlValueAccessor), selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.NgControlStatus), selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i0.forwardRef(() => i1$4.NgModel), selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i0.forwardRef(() => InputTextModule) }, { kind: "directive", type: i0.forwardRef(() => i13.InputText), selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i0.forwardRef(() => FieldFilterComponent), selector: "wuic-field-filter", inputs: ["record", "field", "metaInfo", "readOnly", "hideLabel", "operator", "datasource"] }, { kind: "directive", type: i0.forwardRef(() => NgComponentOutlet), selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: i0.forwardRef(() => TranslateModule) }, { kind: "component", type: i0.forwardRef(() => MetadataEditorComponent), selector: "wuic-metadata-editor", inputs: ["datasource", "hardcodedDatasource", "saveCallback", "hideReports", "hideRelatedTableActions", "hideRelatedColumnActions"] }, { kind: "component", type: i0.forwardRef(() => ImportExportButtonComponent), selector: "wuic-import-export-button", inputs: ["routeName", "tableMetadata", "filterInfoProvider"] }, { kind: "pipe", type: i0.forwardRef(() => AsyncPipe), name: "async" }, { kind: "pipe", type: i0.forwardRef(() => i8.TranslatePipe), name: "translate" }, { kind: "pipe", type: i0.forwardRef(() => VisibleFieldListPipe), name: "visibleFieldList" }] });
|
|
37470
37995
|
}
|
|
37471
37996
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ListGridComponent, decorators: [{
|
|
37472
37997
|
type: Component,
|
|
37473
|
-
args: [{ selector: 'wuic-list-grid', imports: [DataSourceComponent, forwardRef(() => DataRepeaterComponent), TableModule, ButtonModule, SplitButtonModule, DialogModule, ContextMenuModule, MenuModule, PaginatorModule, ScrollerModule, Tabs, TabList, Tab, TabPanels, TabPanel, NgClass, AsyncPipe, FormsModule, InputTextModule, FieldFilterComponent, NgComponentOutlet, TranslateModule, VisibleFieldListPipe, MetadataEditorComponent, ImportExportButtonComponent], providers: [Title], template: "<wuic-data-source #metadataColumnsDatasource [hardcodedRoute]=\"' metadati colonne'\"\r\n style=\"display: none;\"></wuic-data-source>\r\n<p-contextMenu #columnCtxMenu [model]=\"columnContextMenuItems\" appendTo=\"body\"></p-contextMenu>\r\n\r\n<div class=\"list-grid-container\" #gridContainer\r\n [class.wuic-list-grid-mobile]=\"gridLayoutIsMobile\">\r\n @if (rowTemplate) {\r\n <p-table #dt1 [columns]=\"cols\" [value]=\"records\" [scrollable]=\"true\"\r\n [scrollHeight]=\"metaInfo.tableMetadata.md_grid_scroll_height ? metaInfo.tableMetadata.md_grid_scroll_height : 'flex'\"\r\n [virtualScroll]=\"isListVirtualizationEnabled()\" [virtualScrollItemSize]=\"getListVirtualizationItemSize()\"\r\n [resizableColumns]=\"true\" [reorderableColumns]=\"true\" columnResizeMode=\"expand\" [rows]=\"pageSize\"\r\n [first]=\"rowNumber\" [lazy]=\"metaInfo.tableMetadata.md_server_side_operations\" [sortField]=\"orderColumn || null\"\r\n [sortOrder]=\"orderColumn ? (orderDir === 'desc' ? -1 : 1) : 0\" [(selection)]=\"selectedItems\"\r\n [dataKey]=\"metaInfo.pKey?.mc_nome_colonna\" [paginator]=\"metaInfo.tableMetadata.md_pageable\"\r\n [rowsPerPageOptions]=\"metaInfo.rowsPerPageOptions\" [totalRecords]=\"totalRecords\"\r\n [showPageLinks]=\"!datasource?.value?.cursorMode\" [showFirstLastIcon]=\"!datasource?.value?.cursorMode\"\r\n [showCurrentPageReport]=\"metaInfo.tableMetadata.md_show_record_count && !datasource?.value?.cursorMode\"\r\n [loading]=\"datasource?.value?.loading | async\"\r\n [currentPageReportTemplate]=\"'Showing {first} to {last} of {totalRecords} entries' | translate\"\r\n (onPage)=\"pageFilterChange($event)\" (onFilter)=\"pageFilterChange($event)\" (onSort)=\"pageFilterChange($event)\"\r\n (selectionChange)=\"onSelectionChange($event)\" (onColResize)=\"onColumnResize($event)\"\r\n (onColReorder)=\"onColumnReorder($event)\" [expandedRowKeys]=\"expandedRows\" (onRowExpand)=\"onRowExpand($event)\"\r\n (onRowCollapse)=\"onRowCollapse($event)\" [selectionMode]=\"rowCustomSelect ? 'single' : undefined\"\r\n [tableStyleClass]=\"getTableStyleClass()\">\r\n <ng-template pTemplate=\"colgroup\" let-columns>\r\n <colgroup>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <col class=\"nested-grid-column\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <col class=\"multiple-selection-grid-column\" />\r\n }\r\n @if (showActionColumn()) {\r\n <col class=\"command-grid-column\" />\r\n }\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n <col [style.width]=\"getColumnWidthCss(col)\" [style.min-width]=\"columnMinWidthCss\" />\r\n }\r\n </colgroup>\r\n </ng-template>\r\n <ng-template pTemplate=\"caption\">\r\n @if (!hideToolbar) {\r\n <div class=\"grid-caption-bar\">\r\n <div class=\"grid-caption-left\">\r\n @if (!metaInfo.tableMetadata.md_hide_refresh) {\r\n <p-button icon=\"pi pi-refresh\" label=\"{{'refresh_grid' | translate}}\" (onClick)=\"refresh()\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_insertable) {\r\n <p-button icon=\"pi pi-plus\" label=\"{{'add_grid' | translate}}\" (onClick)=\"addRecord()\" />\r\n }\r\n @if (tableActionMenuItems?.length) {\r\n <p-button icon=\"pi pi-bars\" [label]=\"'actions' | translate\" (onClick)=\"tableActionsMenu.toggle($event)\" />\r\n <p-menu #tableActionsMenu [popup]=\"true\" [model]=\"tableActionMenuItems\" appendTo=\"body\"\r\n (onShow)=\"reEvaluateActionEnabledStates()\"></p-menu>\r\n }\r\n <wuic-import-export-button [routeName]=\"routeName\"\r\n [tableMetadata]=\"metaInfo?.tableMetadata\"\r\n [filterInfoProvider]=\"getCurrentFilterInfoForExport\"></wuic-import-export-button>\r\n @if (reportMenuItems?.length) {\r\n <p-splitButton icon=\"pi pi-chart-bar\" [label]=\"'list_grid.reports' | translate\" [model]=\"reportMenuItems\"\r\n [menuButtonProps]=\"{ ariaLabel: ('list_grid.reports' | translate) }\"\r\n (onClick)=\"reportMenuItems[0]?.command({})\" />\r\n }\r\n @if (canToggleClientSideCrud()) {\r\n @if (!isClientSideCrudActive()) {\r\n <p-button icon=\"pi pi-database\" [label]=\"'client_side_crud_enable' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"toggleClientSideCrud()\" />\r\n } @else {\r\n <p-button icon=\"pi pi-cloud-upload\" [label]=\"'client_side_crud_sync' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"syncAndDisableClientSideCrud()\" />\r\n <p-button icon=\"pi pi-ban\" [label]=\"'client_side_crud_disable_without_sync' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"clientSideCrudToggleBusy\"\r\n (onClick)=\"discardLocalAndDisableClientSideCrud()\" />\r\n }\r\n }\r\n @if (isInlineBatchSaveEnabled() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <p-button icon=\"pi pi-save\" [label]=\"'list_grid.save_changes' | translate\"\r\n [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\" (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button icon=\"pi pi-times\" [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n }\r\n @if (hasDatasourceChanges() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <button type=\"button\" class=\"grid-changes-indicator\"\r\n [title]=\"'Changes in memory: ' + getDatasourceChangesCount()\" (click)=\"openChangesDialog()\">\r\n <i class=\"pi pi-pencil\"></i>\r\n <span class=\"grid-changes-count\">{{ getDatasourceChangesCount() }}</span>\r\n </button>\r\n }\r\n </div>\r\n <div class=\"grid-caption-right\">\r\n @if (!metaInfo?.tableMetadata?.extraProps?.toolbar?.hideManageState) {\r\n @if (currentRouteSavedStates?.length) {\r\n <select class=\"p-inputtext p-component wuic-grid-state-select\" [(ngModel)]=\"selectedSavedStateId\"\r\n (change)=\"onSavedStateDropdownChange($event)\">\r\n <option value=\"\"></option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n }\r\n <p-button [label]=\"'list_grid.manage_state' | translate\" icon=\"pi pi-bookmark\" iconPos=\"left\"\r\n (onClick)=\"onSaveStateMenuToggle($event, saveStateMenu)\" />\r\n <p-menu #saveStateMenu [popup]=\"true\" [model]=\"saveStateMenuItems\" appendTo=\"body\"></p-menu>\r\n }\r\n @if (!isAdvancedFilterModeEnabled()) {\r\n <p-button [label]=\"'clear_filters' | translate\" icon=\"pi pi-filter-slash\" (onClick)=\"clearFilter(dt1)\" />\r\n }\r\n <!-- <i class=\"pi pi-search\"></i>\r\n <input type=\"text\" pInputText [(ngModel)]=\"searchValue\"\r\n class=\"p-inputtext p-component p-element ng-pristine ng-valid ng-touched\"\r\n (input)=\"dt1.filterGlobal(getTgtVal($event.target), 'contains')\"\r\n [placeholder]=\"'search_keyword' | translate\" /> -->\r\n </div>\r\n </div>\r\n }\r\n </ng-template>\r\n <ng-template pTemplate=\"header\" let-columns>\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <th class=\"nested-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <th class=\"multiple-selection-grid-column\" role=\"presentation\" pFrozenColumn [frozen]=\"true\" alignFrozen=\"left\"\r\n class=\"wuic-selection-th\">\r\n <p-tableHeaderCheckbox [ariaLabel]=\"'Seleziona tutte le righe'\" />\r\n </th>\r\n }\r\n @if (showActionColumn()) {\r\n <th class=\"command-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n <!-- @for (col of columns | visibleFieldList; track col.metaColumn.mc_id) { NON FUNZIONA si spaccano i filter template !!! -->\r\n <!-- *ngFor NON FUNGE SE USI IL trackBy -> si spaccano i filter template -->\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n @if (!!col?.frozen) {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n pFrozenColumn [frozen]=\"true\" [alignFrozen]=\"col?.alignFrozen || 'left'\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n }\r\n </div>\r\n </th>\r\n } @else {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <!-- <ng-container [ngSwitch]=\"col.metaColumn.mc_ui_column_type\"> -->\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n <!-- <p-columnFilter *ngSwitchDefault type=\"text\" [field]=\"col.field\" display=\"menu\" /> -->\r\n <!-- </ng-container> -->\r\n }\r\n </div>\r\n </th>\r\n }\r\n }\r\n <!-- } -->\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"body\" let-rowData let-columns=\"columns\" let-expanded=\"expanded\" let-rowIndex=\"rowIndex\">\r\n <ng-container pTemplate=\"body\"\r\n *ngComponentOutlet=\"rowTemplate; inputs: { columns: columns, rowData: rowData, expanded: expanded, rowIndex: rowIndex, actionButtonRowIsVisible: isActionButtonRowVisible, isListVirtualizationEnabled: isListVirtualizationEnabled.bind(this), inlineCellEditingEnabled: isInlineCellEditingEnabled(), onInlineCellEditorBlur: onInlineCellEditorBlur.bind(this), onInlineCellEditorValueChange: onInlineCellEditorValueChange.bind(this), getRuntimeGridFieldMeta: getRuntimeGridFieldMetaFn, metaInfo: metaInfo, datasource: datasource, MetadatiColonna: MetadatiColonna, dt: dt1, toggleRow: toggleRow, rowSelect: rowSelect, rowCustomSelect: rowCustomSelect }\"></ng-container>\r\n </ng-template>\r\n <ng-template pTemplate=\"expandedrow\" let-rowData let-columns=\"columns\">\r\n <tr>\r\n <td\r\n [attr.colspan]=\"(columns | visibleFieldList).length + (metaInfo.tableMetadata.md_nested_grid_routes ? 1 : 0) + (metaInfo.tableMetadata.md_multiple_selection ? 1 : 0) + (showActionColumn() ? 1 : 0)\">\r\n @if (metaInfo.nestedRoutes?.length == 1) {\r\n <div [ngClass]=\"metaInfo.nestedRoutes[0].nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"metaInfo.nestedRoutes[0].route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <div class=\"list-grid-long-description-container\"\r\n [innerHTML]=\"getNestedLongDescriptionSafeHtml(nestedSource)\">\r\n </div>\r\n\r\n <wuic-data-repeater\r\n [hardcodedAction]=\"metaInfo.nestedRoutes[0].action == '' ? 'list' : metaInfo.nestedRoutes[0].action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n }\r\n @if (metaInfo.nestedRoutes?.length > 1) {\r\n <div>\r\n <p-tabs [value]=\"0\">\r\n <p-tablist>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tab [value]=\"i\">{{ nestedRoute.nestedTabCaption }}</p-tab>\r\n }\r\n </p-tablist>\r\n <p-tabpanels>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tabpanel [value]=\"i\">\r\n <div [ngClass]=\"nestedRoute.nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"nestedRoute.route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <wuic-data-repeater [hardcodedAction]=\"nestedRoute.action == '' ? 'list' : nestedRoute.action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n </p-tabpanel>\r\n }\r\n </p-tabpanels>\r\n </p-tabs>\r\n </div>\r\n }\r\n </td>\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"footer\">\r\n @if (metaInfo.hasFooter) {\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n @if (showActionColumn()) {\r\n <td></td>\r\n }\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <td></td>\r\n }\r\n <td></td>\r\n @for (col of metaInfo.columnMetadata | visibleFieldList; track col.mc_id) {\r\n <td>\r\n @if (col.mc_aggregation) {\r\n <table>\r\n @for (agg of getAggregations(col); track agg.field + '#' + agg.aggregation) {\r\n <tr>\r\n <td>\r\n {{agg.aggregation | translate}}:\r\n </td>\r\n <td>\r\n {{agg.aggregateValue}}\r\n </td>\r\n </tr>\r\n }\r\n </table>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </ng-template>\r\n </p-table>\r\n }\r\n\r\n <!--\r\n Mobile: la list-grid usa la STESSA <p-table> del desktop, sopra.\r\n Il row template (compilato via `DynamicRowTemplateComponent.\r\n getComponentFromTemplate`) cambia contenuto in base a\r\n `deviceAwareness.isMobile` \u2014 vedi `getEffectiveGridRowTemplate` in\r\n list-grid.component.ts: quando mobile, emette una singola <td colspan>\r\n con layout card-style invece di <td> per ogni colonna. CSS\r\n `@media (max-width: 768px)` nel file .scss nasconde <thead>, rende\r\n `display: block` le <tr>/<td> per liberarsi della semantica table.\r\n Vantaggio: un solo code path (riusa il JIT compile del row template\r\n gia' testato) eliminando il bug `*ngComponentOutlet [inputs]` che\r\n affliggeva il percorso mobile-card separato.\r\n -->\r\n</div>\r\n\r\n<p-dialog [header]=\"'list_grid.save_state_dialog_header' | translate\" [(visible)]=\"saveGridStateDialogVisible\"\r\n [modal]=\"true\" [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-save-state-select\">{{ 'list_grid.state' | translate }}</label>\r\n <select id=\"wuic-save-state-select\" class=\"p-inputtext p-component\" [(ngModel)]=\"saveGridStateDialogSelectedId\"\r\n (change)=\"onSaveGridStateDialogSelectionChange()\">\r\n <option [value]=\"NEW_GRID_STATE_OPTION_ID\">{{ 'new' | translate }}</option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n\r\n @if (saveGridStateDialogSelectedId === NEW_GRID_STATE_OPTION_ID) {\r\n <label for=\"wuic-save-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-save-state-name\" type=\"text\" pInputText [(ngModel)]=\"saveGridStateDialogNewName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n }\r\n\r\n <label for=\"wuic-save-state-default\" class=\"wuic-column-layout-check\">\r\n <input id=\"wuic-save-state-default\" type=\"checkbox\" [(ngModel)]=\"saveGridStateDialogSetAsDefault\" />\r\n <span>{{ 'list_grid.set_as_default' | translate }}</span>\r\n </label>\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"saveGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveCurrentRouteGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.rename_state' | translate\" [(visible)]=\"renameGridStateDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '30rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-rename-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-rename-state-name\" type=\"text\" pInputText [(ngModel)]=\"renameGridStateDialogName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"renameGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"renameSelectedGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.configure_columns' | translate\" [(visible)]=\"columnLayoutDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '36rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-column-layout-dialog\">\r\n @for (item of columnLayoutDraft; track item.field + '#' + $index; let i = $index) {\r\n <div class=\"wuic-column-layout-row\">\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.visible\" />\r\n <span>{{ item.header }}</span>\r\n </label>\r\n <div class=\"wuic-column-layout-actions\">\r\n <p-button icon=\"pi pi-arrow-up\" [outlined]=\"true\" [disabled]=\"i === 0\"\r\n (onClick)=\"moveColumnLayoutDraft(i, -1)\" />\r\n <p-button icon=\"pi pi-arrow-down\" [outlined]=\"true\" [disabled]=\"i === (columnLayoutDraft.length - 1)\"\r\n (onClick)=\"moveColumnLayoutDraft(i, 1)\" />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"columnLayoutDialogVisible = false\" />\r\n <p-button [label]=\"'list_grid.reset_metadata' | translate\" [outlined]=\"true\" icon=\"pi pi-refresh\"\r\n (onClick)=\"resetColumnLayoutDraftAndWidths()\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveColumnLayoutFromDialog()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.report_parameters' | translate\" [(visible)]=\"reportVariableDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\" (onHide)=\"cancelReportVariableDialog()\">\r\n <div class=\"wuic-save-state-dialog\">\r\n @for (variable of selectedReportVariables; track variable.name; let i = $index) {\r\n <label [for]=\"'wuic-report-var-' + i\">{{ variable.alias || variable.name }}</label>\r\n <input [id]=\"'wuic-report-var-' + i\" [name]=\"'wuic-report-var-name-' + i\" type=\"text\" pInputText\r\n [(ngModel)]=\"variable.value\" />\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"cancelReportVariableDialog()\" />\r\n <p-button [label]=\"'list_grid.open_report' | translate\" (onClick)=\"applyReportVariablesAndOpenViewer()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.pending_changes' | translate\" [(visible)]=\"changesDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '44rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-changes-dialog\">\r\n @if (!pendingChangeItems.length) {\r\n <div>{{ 'list_grid.no_pending_changes' | translate }}</div>\r\n } @else {\r\n @for (item of pendingChangeItems; track item.id) {\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.selected\" />\r\n <span><strong>{{ item.label }}</strong> - {{ item.details }}</span>\r\n </label>\r\n }\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'list_grid.save_all' | translate\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.save_selected' | translate\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !getSelectedPendingChangesCount() || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveSelectedPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n </ng-template>\r\n</p-dialog>", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;max-height:100%;min-height:0;overflow:hidden;--wuic-grid-row-odd: #ffffff;--wuic-grid-row-even: #f6f8fc;--wuic-grid-row-hover: #edf2ff;--wuic-grid-row-selected-bg: #dbe8ff;--wuic-grid-row-selected-text: #0f2348;--wuic-grid-row-selected-ring: rgba(59, 130, 246, .55);--wuic-grid-accent: var(--p-primary-color, #3b82f6);--wuic-grid-base: var(--p-surface-0, #ffffff)}:host-context(.theme-dark){--wuic-grid-row-odd: #1f2430;--wuic-grid-row-even: #252b38;--wuic-grid-row-hover: #31394b;--wuic-grid-row-selected-bg: #284774;--wuic-grid-row-selected-text: #eaf2ff;--wuic-grid-row-selected-ring: rgba(120, 170, 255, .65);--wuic-grid-base: var(--p-surface-900, #1f2430)}@supports (color: color-mix(in srgb,white,black)){:host{--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 96%, var(--wuic-grid-accent) 4%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 90%, var(--wuic-grid-accent) 10%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 82%, var(--wuic-grid-accent) 18%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 56%, var(--wuic-grid-accent) 44%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 72%, transparent)}:host-context(.theme-dark){--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 92%, var(--wuic-grid-accent) 8%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 84%, var(--wuic-grid-accent) 16%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 72%, var(--wuic-grid-accent) 28%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 36%, var(--wuic-grid-accent) 64%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 82%, transparent)}}.list-grid-container{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-view{flex:1 1 auto;min-height:0}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;height:auto}:host ::ng-deep .p-datatable-table-container:has(>table.wuic-nested-grid),:host ::ng-deep .p-datatable:has(>.p-datatable-table-container>table.wuic-nested-grid),:host ::ng-deep .list-grid-container:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid-lazy:has(table.wuic-nested-grid),:host ::ng-deep .data-repeater-body:has(table.wuic-nested-grid),:host ::ng-deep td:has(wuic-data-repeater table.wuic-nested-grid){overflow:visible!important;max-height:none!important}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead{position:sticky!important;top:var(--wuic-outer-thead-h, 0px);z-index:3}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead>tr>th{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(odd)>td{background:var(--wuic-grid-row-odd)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(2n)>td{background:var(--wuic-grid-row-even)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):hover>td{background:var(--wuic-grid-row-hover)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td{background:var(--wuic-grid-row-selected-bg)!important;color:var(--wuic-grid-row-selected-text);box-shadow:inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td:first-child{box-shadow:inset 4px 0 0 var(--wuic-grid-accent),inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected:hover>td{background:var(--wuic-grid-row-selected-bg)!important}:host ::ng-deep td.p-datatable-frozen-column,:host ::ng-deep th.p-datatable-frozen-column{border-right:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep td.wuic-action-cell,:host ::ng-deep th.command-grid-column{width:64px!important;min-width:64px!important;max-width:64px!important;box-sizing:border-box;text-align:center;vertical-align:middle}:host ::ng-deep td.wuic-action-cell wuic-data-action-button-lazy,:host ::ng-deep td.wuic-action-cell wuic-data-action-button{display:inline-flex;align-items:center;justify-content:center}:host ::ng-deep .p-datatable-tbody>tr>td:first-child,:host ::ng-deep .p-datatable-thead>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-box,:host ::ng-deep .p-datatable-tbody>tr>td input.p-checkbox-box{width:18px;height:18px;border:1px solid #9ca3af;border-radius:4px;background:#fff;box-sizing:border-box;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th.wuic-selection-th{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td>input.p-checkbox-box{margin-left:auto!important;margin-right:auto!important;display:block}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-icon{font-size:.72rem}:host ::ng-deep .p-datatable-table.width_defined{display:table}:host ::ng-deep .p-datatable-table{display:table}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table{width:100%!important;min-width:100%}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>td,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>th{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>td:first-child,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td{min-width:0;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable>.p-paginator{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator{border-top:2px solid currentColor;background:var(--p-content-background, #fff);font-weight:600;padding:.5rem .75rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page{min-width:2.25rem;min-height:2.25rem;border:2px solid currentColor!important;border-radius:4px;margin:0 2px;font-weight:700;background:transparent;color:currentColor}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled){background:currentColor;color:var(--p-content-background, #fff)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible{outline:3px solid currentColor;outline-offset:2px}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled{opacity:.45;cursor:not-allowed}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:var(--p-primary-color, #2563eb)!important;color:var(--p-primary-contrast-color, #fff)!important;border-color:var(--p-primary-color, #2563eb)!important;border-width:3px!important;font-weight:800}@media(forced-colors:active){:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:Highlight!important;color:HighlightText!important;border-color:Highlight!important;forced-color-adjust:none}}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current{font-weight:700;padding:0 .5rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select{border:2px solid currentColor!important;font-weight:700;min-height:2.25rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within{outline:3px solid currentColor;outline-offset:2px}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td .list-grid-cell-text-content,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td>a{display:block;width:0;min-width:100%;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap!important;overflow-wrap:anywhere}.wuic-save-state-dialog{display:flex;flex-direction:column;gap:.6rem}.wuic-save-state-dialog select,.wuic-save-state-dialog input{width:100%}.wuic-column-layout-dialog{display:flex;flex-direction:column;gap:.45rem;max-height:55vh;overflow:auto}.wuic-column-layout-row{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.35rem 0}.wuic-column-layout-check{display:inline-flex;align-items:center;gap:.5rem}.wuic-column-layout-actions{display:inline-flex;gap:.35rem}.wuic-changes-dialog{display:flex;flex-direction:column;gap:.5rem;max-height:55vh;overflow:auto}.wuic-export-progress-dialog{display:flex;flex-direction:column;gap:.65rem}::ng-deep .wuic-progress-dialog .p-progressbar{--p-progressbar-value-transition: none}::ng-deep .wuic-progress-dialog .p-progressbar .p-progressbar-value,::ng-deep .wuic-progress-dialog .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar-value{transition:none!important;animation:none!important}.wuic-export-progress-detail{font-size:.9rem;font-weight:600;text-align:right}.wuic-import-dialog{display:flex;flex-direction:column;gap:.75rem}.wuic-import-grid{display:grid;grid-template-columns:1fr;gap:.35rem}.wuic-import-file-row{display:flex;align-items:center;gap:.65rem}.grid-caption-bar{display:flex;align-items:stretch;justify-content:space-between;gap:.75rem;flex-wrap:nowrap}.grid-caption-left,.grid-caption-right{display:flex;align-items:stretch;gap:.5rem;flex-wrap:nowrap;min-height:2.25rem;height:2.25rem}.grid-caption-right{margin-left:auto}.grid-caption-left>*,.grid-caption-right>*{margin:0;align-self:stretch}.grid-changes-indicator{display:inline-flex;align-items:center;gap:.3rem;padding:0 .55rem;border:1px solid #f59e0b;border-radius:999px;background:#fffbeb;color:#b45309;font-size:.82rem;font-weight:700;cursor:pointer;appearance:none}.grid-changes-count{min-width:1.2rem;text-align:center}.wuic-grid-state-select{min-width:220px}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button,:host ::ng-deep .grid-caption-bar .p-inputtext{min-height:2.25rem;height:2.25rem;padding-top:.45rem;padding-bottom:.45rem;margin:0;box-sizing:border-box;line-height:1.1rem;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button,:host ::ng-deep .grid-caption-bar p-splitbutton{display:inline-flex;align-items:stretch;margin:0;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button>.p-button{min-height:2.25rem;height:2.25rem;display:inline-flex;align-items:center}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{padding-left:.75rem;padding-right:.75rem}:host ::ng-deep .grid-caption-bar .p-splitbutton{display:inline-flex;align-items:stretch;margin:0}:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{height:100%}:host ::ng-deep .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .grid-caption-bar .p-button .p-button-icon{line-height:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td{padding-top:.35rem;padding-bottom:.35rem;vertical-align:middle;position:relative}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within{overflow:visible!important;z-index:50}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper>div{overflow:visible!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{position:relative;z-index:100;min-width:360px!important;max-width:80vw!important;background:var(--p-content-background, var(--p-surface-0, #fff));box-shadow:0 4px 12px #0000002e;border:1px solid var(--p-primary-color, #3b82f6);border-radius:4px}:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{background:var(--p-surface-900, #1f2430)}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{display:block;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor{display:flex!important;align-items:center!important;min-height:2.2rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper{margin:0!important;padding:0!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .caption-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{width:100%!important;max-width:100%!important;min-width:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper{display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{min-height:2.2rem;height:2.2rem;line-height:1.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-autocomplete .p-inputtext{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .disabled-textfield{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete>svg[data-p-icon]{display:none!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete .p-autocomplete-input{padding-right:.5rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within p-autocomplete>svg[data-p-icon]{z-index:101!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker{display:flex;align-items:stretch}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-input,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-dropdown{min-height:2.2rem;height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-disabled .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext:disabled{opacity:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor{display:flex;align-items:center;min-height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper{margin:0!important;padding:0!important;width:100%!important;max-width:100%!important;min-width:0!important;display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important;width:100%!important;max-width:100%!important;min-width:0!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}.command-grid-column{width:65px}.multiple-selection-grid-column{width:50px}.nested-grid-column{width:30px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-thead{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tfoot,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-footer,:host ::ng-deep .wuic-list-grid-mobile tfoot{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-resizable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table{display:block}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody{display:block;width:100%;padding-top:12px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr{display:block;width:100%;box-sizing:border-box;margin-bottom:12px;border-radius:12px;overflow:hidden;background:var(--p-content-background, var(--wuic-grid-row-odd));border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 80%, var(--wuic-grid-accent) 20%))}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr:last-child{margin-bottom:0}:host ::ng-deep .wuic-list-grid-mobile td.wuic-mobile-card-cell{display:block;width:100%;padding:14px;border-bottom:1px solid var(--surface-border, #e5e7eb);box-sizing:border-box}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table{width:100%!important;min-width:unset!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-splitbutton .p-button .p-button-label{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button{padding-left:.5rem!important;padding-right:.5rem!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-icon{margin-right:0!important;margin-left:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator{display:flex!important;flex-wrap:nowrap;justify-content:center;align-items:center;gap:4px;padding:8px 4px}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-current{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-pages{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-first,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-prev,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-next,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-last{min-width:36px!important;height:36px!important;margin:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-rpp-options,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-select{min-width:56px!important;height:36px!important;margin-left:4px!important}.wuic-mobile-card-list{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:18px;padding:14px;overflow:auto}.wuic-mobile-card-slot{padding-bottom:18px;box-sizing:border-box}.wuic-mobile-card-toolbar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:4px 0;border-bottom:1px solid color-mix(in srgb,var(--wuic-grid-base) 80%,var(--wuic-grid-accent) 20%);margin-bottom:4px}.wuic-mobile-card{display:flex;flex-direction:column;gap:6px;padding:14px 16px;border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 75%, var(--wuic-grid-accent) 25%));border-radius:12px;background:var(--p-content-background, var(--wuic-grid-row-odd));color:var(--p-text-color, inherit);box-shadow:0 4px 14px #0f172a14}:host-context(.theme-dark) .wuic-mobile-card{box-shadow:0 2px 8px #00000059}.wuic-mobile-card.wuic-row-selected{background:var(--wuic-grid-row-selected-bg);color:var(--wuic-grid-row-selected-text);outline:2px solid var(--wuic-grid-row-selected-ring)}:host ::ng-deep .wuic-mobile-card-actions{display:flex;justify-content:flex-end;align-items:center;margin:0 -4px 4px;padding-bottom:6px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-body{display:flex;flex-direction:column;gap:0}:host ::ng-deep .wuic-mobile-card-row{display:grid;grid-template-columns:minmax(110px,38%) 1fr;column-gap:12px;align-items:baseline;padding:8px 2px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-row:last-child{border-bottom:none}:host ::ng-deep .wuic-mobile-card-label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:currentColor;opacity:.75;line-height:1.3;word-break:break-word}:host ::ng-deep .wuic-mobile-card-value{font-size:.95rem;font-weight:500;text-align:right;word-break:break-word;min-width:0;color:currentColor}:host ::ng-deep .wuic-mobile-card-value a{color:var(--p-primary-color, #3b82f6);text-decoration:underline}.wuic-mobile-card-empty{padding:24px 12px;text-align:center;color:#6b7280;font-style:italic}p-menu:not(.p-menu-overlay-visible){display:none}@media(max-width:420px){:host ::ng-deep .wuic-mobile-card-row{grid-template-columns:1fr;row-gap:2px}:host ::ng-deep .wuic-mobile-card-value{text-align:left}}\n"] }]
|
|
37998
|
+
args: [{ selector: 'wuic-list-grid', imports: [DataSourceComponent, forwardRef(() => DataRepeaterComponent), TableModule, ButtonModule, SplitButtonModule, DialogModule, ContextMenuModule, MenuModule, PaginatorModule, ScrollerModule, Tabs, TabList, Tab, TabPanels, TabPanel, NgClass, AsyncPipe, FormsModule, InputTextModule, FieldFilterComponent, NgComponentOutlet, TranslateModule, VisibleFieldListPipe, MetadataEditorComponent, ImportExportButtonComponent], providers: [Title], template: "<wuic-data-source #metadataColumnsDatasource [hardcodedRoute]=\"' metadati colonne'\"\r\n style=\"display: none;\"></wuic-data-source>\r\n<p-contextMenu #columnCtxMenu [model]=\"columnContextMenuItems\" appendTo=\"body\"></p-contextMenu>\r\n\r\n<div class=\"list-grid-container\" #gridContainer\r\n [class.wuic-list-grid-mobile]=\"gridLayoutIsMobile\">\r\n @if (rowTemplate) {\r\n <p-table #dt1 [columns]=\"cols\" [value]=\"records\" [scrollable]=\"true\"\r\n [scrollHeight]=\"metaInfo.tableMetadata.md_grid_scroll_height ? metaInfo.tableMetadata.md_grid_scroll_height : 'flex'\"\r\n [virtualScroll]=\"isListVirtualizationEnabled()\" [virtualScrollItemSize]=\"getListVirtualizationItemSize()\"\r\n [resizableColumns]=\"true\" [reorderableColumns]=\"true\" columnResizeMode=\"expand\" [rows]=\"pageSize\"\r\n [first]=\"rowNumber\" [lazy]=\"metaInfo.tableMetadata.md_server_side_operations\" [sortField]=\"orderColumn || null\"\r\n [sortOrder]=\"orderColumn ? (orderDir === 'desc' ? -1 : 1) : 0\" [(selection)]=\"selectedItems\"\r\n [dataKey]=\"metaInfo.pKey?.mc_nome_colonna\" [paginator]=\"metaInfo.tableMetadata.md_pageable\"\r\n [rowsPerPageOptions]=\"metaInfo.rowsPerPageOptions\" [totalRecords]=\"totalRecords\"\r\n [showPageLinks]=\"!datasource?.value?.cursorMode\" [showFirstLastIcon]=\"!datasource?.value?.cursorMode\"\r\n [showCurrentPageReport]=\"metaInfo.tableMetadata.md_show_record_count && !datasource?.value?.cursorMode\"\r\n [loading]=\"datasource?.value?.loading | async\"\r\n [currentPageReportTemplate]=\"'Showing {first} to {last} of {totalRecords} entries' | translate\"\r\n (onPage)=\"pageFilterChange($event)\" (onFilter)=\"pageFilterChange($event)\" (onSort)=\"pageFilterChange($event)\"\r\n (selectionChange)=\"onSelectionChange($event)\" (onColResize)=\"onColumnResize($event)\"\r\n (onColReorder)=\"onColumnReorder($event)\" [expandedRowKeys]=\"expandedRows\" (onRowExpand)=\"onRowExpand($event)\"\r\n (onRowCollapse)=\"onRowCollapse($event)\" [selectionMode]=\"rowCustomSelect ? 'single' : undefined\"\r\n [tableStyleClass]=\"getTableStyleClass()\">\r\n <ng-template pTemplate=\"colgroup\" let-columns>\r\n <colgroup>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <col class=\"nested-grid-column\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <col class=\"multiple-selection-grid-column\" />\r\n }\r\n @if (showActionColumn()) {\r\n <col class=\"command-grid-column\" />\r\n }\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n <col [style.width]=\"getColumnWidthCss(col)\" [style.min-width]=\"columnMinWidthCss\" />\r\n }\r\n </colgroup>\r\n </ng-template>\r\n <ng-template pTemplate=\"caption\">\r\n @if (!hideToolbar) {\r\n <div class=\"grid-caption-bar\">\r\n <div class=\"grid-caption-left\">\r\n @if (!metaInfo.tableMetadata.md_hide_refresh) {\r\n <p-button icon=\"pi pi-refresh\" label=\"{{'refresh_grid' | translate}}\" (onClick)=\"refresh()\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_insertable) {\r\n <p-button icon=\"pi pi-plus\" label=\"{{'add_grid' | translate}}\" (onClick)=\"addRecord()\" />\r\n }\r\n @if (tableActionMenuItems?.length) {\r\n <p-button icon=\"pi pi-bars\" [label]=\"'actions' | translate\" (onClick)=\"tableActionsMenu.toggle($event)\" />\r\n <p-menu #tableActionsMenu [popup]=\"true\" [model]=\"tableActionMenuItems\" appendTo=\"body\"\r\n (onShow)=\"reEvaluateActionEnabledStates()\"></p-menu>\r\n }\r\n <wuic-import-export-button [routeName]=\"routeName\"\r\n [tableMetadata]=\"metaInfo?.tableMetadata\"\r\n [filterInfoProvider]=\"getCurrentFilterInfoForExport\"></wuic-import-export-button>\r\n @if (reportMenuItems?.length) {\r\n <p-splitButton icon=\"pi pi-chart-bar\" [label]=\"'list_grid.reports' | translate\" [model]=\"reportMenuItems\"\r\n [menuButtonProps]=\"{ ariaLabel: ('list_grid.reports' | translate) }\"\r\n (onClick)=\"reportMenuItems[0]?.command({})\" />\r\n }\r\n @if (canToggleClientSideCrud()) {\r\n @if (!isClientSideCrudActive()) {\r\n <p-button icon=\"pi pi-database\" [label]=\"'client_side_crud_enable' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"toggleClientSideCrud()\" />\r\n } @else {\r\n <p-button icon=\"pi pi-cloud-upload\" [label]=\"'client_side_crud_sync' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"syncAndDisableClientSideCrud()\" />\r\n <p-button icon=\"pi pi-ban\" [label]=\"'client_side_crud_disable_without_sync' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"clientSideCrudToggleBusy\"\r\n (onClick)=\"discardLocalAndDisableClientSideCrud()\" />\r\n }\r\n }\r\n @if (isInlineBatchSaveEnabled() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <p-button icon=\"pi pi-save\" [label]=\"'list_grid.save_changes' | translate\"\r\n [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\" (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button icon=\"pi pi-times\" [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n }\r\n @if (hasDatasourceChanges() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <button type=\"button\" class=\"grid-changes-indicator\"\r\n [title]=\"'Changes in memory: ' + getDatasourceChangesCount()\" (click)=\"openChangesDialog()\">\r\n <i class=\"pi pi-pencil\"></i>\r\n <span class=\"grid-changes-count\">{{ getDatasourceChangesCount() }}</span>\r\n </button>\r\n }\r\n </div>\r\n <div class=\"grid-caption-right\">\r\n @if (!metaInfo?.tableMetadata?.extraProps?.toolbar?.hideManageState) {\r\n @if (currentRouteSavedStates?.length) {\r\n <select class=\"p-inputtext p-component wuic-grid-state-select\" [(ngModel)]=\"selectedSavedStateId\"\r\n (change)=\"onSavedStateDropdownChange($event)\">\r\n <option value=\"\"></option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n }\r\n <p-button [label]=\"'list_grid.manage_state' | translate\" icon=\"pi pi-bookmark\" iconPos=\"left\"\r\n (onClick)=\"onSaveStateMenuToggle($event, saveStateMenu)\" />\r\n <p-menu #saveStateMenu [popup]=\"true\" [model]=\"saveStateMenuItems\" appendTo=\"body\"></p-menu>\r\n }\r\n @if (!isAdvancedFilterModeEnabled()) {\r\n <p-button [label]=\"'clear_filters' | translate\" icon=\"pi pi-filter-slash\" (onClick)=\"clearFilter(dt1)\" />\r\n }\r\n <!-- <i class=\"pi pi-search\"></i>\r\n <input type=\"text\" pInputText [(ngModel)]=\"searchValue\"\r\n class=\"p-inputtext p-component p-element ng-pristine ng-valid ng-touched\"\r\n (input)=\"dt1.filterGlobal(getTgtVal($event.target), 'contains')\"\r\n [placeholder]=\"'search_keyword' | translate\" /> -->\r\n </div>\r\n </div>\r\n }\r\n </ng-template>\r\n <ng-template pTemplate=\"header\" let-columns>\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <th class=\"nested-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <th class=\"multiple-selection-grid-column\" role=\"presentation\" pFrozenColumn [frozen]=\"true\" alignFrozen=\"left\"\r\n class=\"wuic-selection-th\">\r\n <p-tableHeaderCheckbox [ariaLabel]=\"'Seleziona tutte le righe'\" />\r\n </th>\r\n }\r\n @if (showActionColumn()) {\r\n <th class=\"command-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n <!-- @for (col of columns | visibleFieldList; track col.metaColumn.mc_id) { NON FUNZIONA si spaccano i filter template !!! -->\r\n <!-- *ngFor NON FUNGE SE USI IL trackBy -> si spaccano i filter template -->\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n @if (!!col?.frozen) {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n pFrozenColumn [frozen]=\"true\" [alignFrozen]=\"col?.alignFrozen || 'left'\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n }\r\n </div>\r\n </th>\r\n } @else {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <!-- <ng-container [ngSwitch]=\"col.metaColumn.mc_ui_column_type\"> -->\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n <!-- <p-columnFilter *ngSwitchDefault type=\"text\" [field]=\"col.field\" display=\"menu\" /> -->\r\n <!-- </ng-container> -->\r\n }\r\n </div>\r\n </th>\r\n }\r\n }\r\n <!-- } -->\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"body\" let-rowData let-columns=\"columns\" let-expanded=\"expanded\" let-rowIndex=\"rowIndex\">\r\n <ng-container pTemplate=\"body\"\r\n *ngComponentOutlet=\"rowTemplate; inputs: { columns: columns, rowData: rowData, expanded: expanded, rowIndex: rowIndex, actionButtonRowIsVisible: isActionButtonRowVisible, isListVirtualizationEnabled: isListVirtualizationEnabled.bind(this), inlineCellEditingEnabled: isInlineCellEditingEnabled(), onInlineCellEditorBlur: onInlineCellEditorBlur.bind(this), onInlineCellEditorValueChange: onInlineCellEditorValueChange.bind(this), getRuntimeGridFieldMeta: getRuntimeGridFieldMetaFn, metaInfo: metaInfo, datasource: datasource, MetadatiColonna: MetadatiColonna, dt: dt1, toggleRow: toggleRow, rowSelect: rowSelect, rowCustomSelect: rowCustomSelect }\"></ng-container>\r\n </ng-template>\r\n <ng-template pTemplate=\"expandedrow\" let-rowData let-columns=\"columns\">\r\n <tr>\r\n <td\r\n [attr.colspan]=\"(columns | visibleFieldList).length + (metaInfo.tableMetadata.md_nested_grid_routes ? 1 : 0) + (metaInfo.tableMetadata.md_multiple_selection ? 1 : 0) + (showActionColumn() ? 1 : 0)\">\r\n @if (metaInfo.nestedRoutes?.length == 1) {\r\n <div [ngClass]=\"metaInfo.nestedRoutes[0].nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"metaInfo.nestedRoutes[0].route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <div class=\"list-grid-long-description-container\"\r\n [innerHTML]=\"getNestedLongDescriptionSafeHtml(nestedSource)\">\r\n </div>\r\n\r\n <wuic-data-repeater\r\n [hardcodedAction]=\"metaInfo.nestedRoutes[0].action == '' ? 'list' : metaInfo.nestedRoutes[0].action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n }\r\n @if (metaInfo.nestedRoutes?.length > 1) {\r\n <div>\r\n <p-tabs [value]=\"0\">\r\n <p-tablist>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tab [value]=\"i\">{{ nestedRoute.nestedTabCaption }}</p-tab>\r\n }\r\n </p-tablist>\r\n <p-tabpanels>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tabpanel [value]=\"i\">\r\n <div [ngClass]=\"nestedRoute.nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"nestedRoute.route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <wuic-data-repeater [hardcodedAction]=\"nestedRoute.action == '' ? 'list' : nestedRoute.action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n </p-tabpanel>\r\n }\r\n </p-tabpanels>\r\n </p-tabs>\r\n </div>\r\n }\r\n </td>\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"footer\">\r\n @if (metaInfo.hasFooter) {\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n @if (showActionColumn()) {\r\n <td></td>\r\n }\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <td></td>\r\n }\r\n <td></td>\r\n @for (col of metaInfo.columnMetadata | visibleFieldList; track col.mc_id) {\r\n <td>\r\n @if (col.mc_aggregation) {\r\n <table>\r\n @for (agg of getAggregations(col); track agg.field + '#' + agg.aggregation) {\r\n <tr>\r\n <td>\r\n {{agg.aggregation | translate}}:\r\n </td>\r\n <td>\r\n {{agg.aggregateValue}}\r\n </td>\r\n </tr>\r\n }\r\n </table>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </ng-template>\r\n </p-table>\r\n }\r\n\r\n <!--\r\n Mobile: la list-grid usa la STESSA <p-table> del desktop, sopra.\r\n Il row template (compilato via `DynamicRowTemplateComponent.\r\n getComponentFromTemplate`) cambia contenuto in base a\r\n `deviceAwareness.isMobile` \u2014 vedi `getEffectiveGridRowTemplate` in\r\n list-grid.component.ts: quando mobile, emette una singola <td colspan>\r\n con layout card-style invece di <td> per ogni colonna. CSS\r\n `@media (max-width: 768px)` nel file .scss nasconde <thead>, rende\r\n `display: block` le <tr>/<td> per liberarsi della semantica table.\r\n Vantaggio: un solo code path (riusa il JIT compile del row template\r\n gia' testato) eliminando il bug `*ngComponentOutlet [inputs]` che\r\n affliggeva il percorso mobile-card separato.\r\n -->\r\n</div>\r\n\r\n<p-dialog [header]=\"'list_grid.save_state_dialog_header' | translate\" [(visible)]=\"saveGridStateDialogVisible\"\r\n [modal]=\"true\" [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-save-state-select\">{{ 'list_grid.state' | translate }}</label>\r\n <select id=\"wuic-save-state-select\" class=\"p-inputtext p-component\" [(ngModel)]=\"saveGridStateDialogSelectedId\"\r\n (change)=\"onSaveGridStateDialogSelectionChange()\">\r\n <option [value]=\"NEW_GRID_STATE_OPTION_ID\">{{ 'new' | translate }}</option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n\r\n @if (saveGridStateDialogSelectedId === NEW_GRID_STATE_OPTION_ID) {\r\n <label for=\"wuic-save-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-save-state-name\" type=\"text\" pInputText [(ngModel)]=\"saveGridStateDialogNewName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n }\r\n\r\n <label for=\"wuic-save-state-default\" class=\"wuic-column-layout-check\">\r\n <input id=\"wuic-save-state-default\" type=\"checkbox\" [(ngModel)]=\"saveGridStateDialogSetAsDefault\" />\r\n <span>{{ 'list_grid.set_as_default' | translate }}</span>\r\n </label>\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"saveGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveCurrentRouteGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.rename_state' | translate\" [(visible)]=\"renameGridStateDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '30rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-rename-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-rename-state-name\" type=\"text\" pInputText [(ngModel)]=\"renameGridStateDialogName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"renameGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"renameSelectedGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.configure_columns' | translate\" [(visible)]=\"columnLayoutDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '36rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-column-layout-dialog\">\r\n @for (item of columnLayoutDraft; track item.field + '#' + $index; let i = $index) {\r\n <div class=\"wuic-column-layout-row\">\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.visible\" />\r\n <span>{{ item.header }}</span>\r\n </label>\r\n <div class=\"wuic-column-layout-actions\">\r\n <p-button icon=\"pi pi-arrow-up\" [outlined]=\"true\" [disabled]=\"i === 0\"\r\n (onClick)=\"moveColumnLayoutDraft(i, -1)\" />\r\n <p-button icon=\"pi pi-arrow-down\" [outlined]=\"true\" [disabled]=\"i === (columnLayoutDraft.length - 1)\"\r\n (onClick)=\"moveColumnLayoutDraft(i, 1)\" />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"columnLayoutDialogVisible = false\" />\r\n <p-button [label]=\"'list_grid.reset_metadata' | translate\" [outlined]=\"true\" icon=\"pi pi-refresh\"\r\n (onClick)=\"resetColumnLayoutDraftAndWidths()\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveColumnLayoutFromDialog()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.report_parameters' | translate\" [(visible)]=\"reportVariableDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\" (onHide)=\"cancelReportVariableDialog()\">\r\n <div class=\"wuic-save-state-dialog\">\r\n @for (variable of selectedReportVariables; track variable.name; let i = $index) {\r\n <label [for]=\"'wuic-report-var-' + i\">{{ variable.alias || variable.name }}</label>\r\n <input [id]=\"'wuic-report-var-' + i\" [name]=\"'wuic-report-var-name-' + i\" type=\"text\" pInputText\r\n [(ngModel)]=\"variable.value\" />\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"cancelReportVariableDialog()\" />\r\n <p-button [label]=\"'list_grid.open_report' | translate\" (onClick)=\"applyReportVariablesAndOpenViewer()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.pending_changes' | translate\" [(visible)]=\"changesDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '44rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-changes-dialog\">\r\n @if (!pendingChangeItems.length) {\r\n <div>{{ 'list_grid.no_pending_changes' | translate }}</div>\r\n } @else {\r\n @for (item of pendingChangeItems; track item.id) {\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.selected\" />\r\n <span><strong>{{ item.label }}</strong> - {{ item.details }}</span>\r\n </label>\r\n }\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'list_grid.save_all' | translate\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.save_selected' | translate\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !getSelectedPendingChangesCount() || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveSelectedPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n </ng-template>\r\n</p-dialog>", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;max-height:100%;min-height:0;overflow:hidden;--wuic-grid-row-odd: #ffffff;--wuic-grid-row-even: #f6f8fc;--wuic-grid-row-hover: #edf2ff;--wuic-grid-row-selected-bg: #dbe8ff;--wuic-grid-row-selected-text: #0f2348;--wuic-grid-row-selected-ring: rgba(59, 130, 246, .55);--wuic-grid-accent: var(--p-primary-color, #3b82f6);--wuic-grid-base: var(--p-surface-0, #ffffff)}:host-context(.theme-dark){--wuic-grid-row-odd: #1f2430;--wuic-grid-row-even: #252b38;--wuic-grid-row-hover: #31394b;--wuic-grid-row-selected-bg: #284774;--wuic-grid-row-selected-text: #eaf2ff;--wuic-grid-row-selected-ring: rgba(120, 170, 255, .65);--wuic-grid-base: var(--p-surface-900, #1f2430)}@supports (color: color-mix(in srgb,white,black)){:host{--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 96%, var(--wuic-grid-accent) 4%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 90%, var(--wuic-grid-accent) 10%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 82%, var(--wuic-grid-accent) 18%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 56%, var(--wuic-grid-accent) 44%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 72%, transparent)}:host-context(.theme-dark){--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 92%, var(--wuic-grid-accent) 8%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 84%, var(--wuic-grid-accent) 16%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 72%, var(--wuic-grid-accent) 28%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 36%, var(--wuic-grid-accent) 64%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 82%, transparent)}}.list-grid-container{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-view{flex:1 1 auto;min-height:0}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;height:auto}:host ::ng-deep .p-datatable-table-container:has(>table.wuic-nested-grid),:host ::ng-deep .p-datatable:has(>.p-datatable-table-container>table.wuic-nested-grid),:host ::ng-deep .list-grid-container:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid-lazy:has(table.wuic-nested-grid),:host ::ng-deep .data-repeater-body:has(table.wuic-nested-grid),:host ::ng-deep td:has(wuic-data-repeater table.wuic-nested-grid){overflow:visible!important;max-height:none!important}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead{position:sticky!important;top:var(--wuic-outer-thead-h, 0px);z-index:3}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead>tr>th{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(odd)>td{background:var(--wuic-grid-row-odd)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(2n)>td{background:var(--wuic-grid-row-even)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):hover>td{background:var(--wuic-grid-row-hover)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-style-applied:not(.p-highlight):not(.wuic-row-selected)>td{background:transparent!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):not(.wuic-row-selected)>td.wuic-cell-style-applied{background:transparent}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td{background:var(--wuic-grid-row-selected-bg)!important;color:var(--wuic-grid-row-selected-text);box-shadow:inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td:first-child{box-shadow:inset 4px 0 0 var(--wuic-grid-accent),inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected:hover>td{background:var(--wuic-grid-row-selected-bg)!important}:host ::ng-deep td.p-datatable-frozen-column,:host ::ng-deep th.p-datatable-frozen-column{border-right:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep td.wuic-action-cell,:host ::ng-deep th.command-grid-column{width:64px!important;min-width:64px!important;max-width:64px!important;box-sizing:border-box;text-align:center;vertical-align:middle}:host ::ng-deep td.wuic-action-cell wuic-data-action-button-lazy,:host ::ng-deep td.wuic-action-cell wuic-data-action-button{display:inline-flex;align-items:center;justify-content:center}:host ::ng-deep .p-datatable-tbody>tr>td:first-child,:host ::ng-deep .p-datatable-thead>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-box,:host ::ng-deep .p-datatable-tbody>tr>td input.p-checkbox-box{width:18px;height:18px;border:1px solid #9ca3af;border-radius:4px;background:#fff;box-sizing:border-box;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th.wuic-selection-th{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td>input.p-checkbox-box{margin-left:auto!important;margin-right:auto!important;display:block}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-icon{font-size:.72rem}:host ::ng-deep .p-datatable-table.width_defined{display:table}:host ::ng-deep .p-datatable-table{display:table}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table{width:100%!important;min-width:100%}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>td,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>th{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>td:first-child,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td{min-width:0;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable>.p-paginator{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator{border-top:2px solid currentColor;background:var(--p-content-background, #fff);font-weight:600;padding:.5rem .75rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page{min-width:2.25rem;min-height:2.25rem;border:2px solid currentColor!important;border-radius:4px;margin:0 2px;font-weight:700;background:transparent;color:currentColor}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled){background:currentColor;color:var(--p-content-background, #fff)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible{outline:3px solid currentColor;outline-offset:2px}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled{opacity:.45;cursor:not-allowed}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:var(--p-primary-color, #2563eb)!important;color:var(--p-primary-contrast-color, #fff)!important;border-color:var(--p-primary-color, #2563eb)!important;border-width:3px!important;font-weight:800}@media(forced-colors:active){:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:Highlight!important;color:HighlightText!important;border-color:Highlight!important;forced-color-adjust:none}}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current{font-weight:700;padding:0 .5rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select{border:2px solid currentColor!important;font-weight:700;min-height:2.25rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within{outline:3px solid currentColor;outline-offset:2px}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td .list-grid-cell-text-content,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td>a{display:block;width:0;min-width:100%;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap!important;overflow-wrap:anywhere}.wuic-save-state-dialog{display:flex;flex-direction:column;gap:.6rem}.wuic-save-state-dialog select,.wuic-save-state-dialog input{width:100%}.wuic-column-layout-dialog{display:flex;flex-direction:column;gap:.45rem;max-height:55vh;overflow:auto}.wuic-column-layout-row{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.35rem 0}.wuic-column-layout-check{display:inline-flex;align-items:center;gap:.5rem}.wuic-column-layout-actions{display:inline-flex;gap:.35rem}.wuic-changes-dialog{display:flex;flex-direction:column;gap:.5rem;max-height:55vh;overflow:auto}.wuic-export-progress-dialog{display:flex;flex-direction:column;gap:.65rem}::ng-deep .wuic-progress-dialog .p-progressbar{--p-progressbar-value-transition: none}::ng-deep .wuic-progress-dialog .p-progressbar .p-progressbar-value,::ng-deep .wuic-progress-dialog .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar-value{transition:none!important;animation:none!important}.wuic-export-progress-detail{font-size:.9rem;font-weight:600;text-align:right}.wuic-import-dialog{display:flex;flex-direction:column;gap:.75rem}.wuic-import-grid{display:grid;grid-template-columns:1fr;gap:.35rem}.wuic-import-file-row{display:flex;align-items:center;gap:.65rem}.grid-caption-bar{display:flex;align-items:stretch;justify-content:space-between;gap:.75rem;flex-wrap:nowrap}.grid-caption-left,.grid-caption-right{display:flex;align-items:stretch;gap:.5rem;flex-wrap:nowrap;min-height:2.25rem;height:2.25rem}.grid-caption-right{margin-left:auto}.grid-caption-left>*,.grid-caption-right>*{margin:0;align-self:stretch}.grid-changes-indicator{display:inline-flex;align-items:center;gap:.3rem;padding:0 .55rem;border:1px solid #f59e0b;border-radius:999px;background:#fffbeb;color:#b45309;font-size:.82rem;font-weight:700;cursor:pointer;appearance:none}.grid-changes-count{min-width:1.2rem;text-align:center}.wuic-grid-state-select{min-width:220px}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button,:host ::ng-deep .grid-caption-bar .p-inputtext{min-height:2.25rem;height:2.25rem;padding-top:.45rem;padding-bottom:.45rem;margin:0;box-sizing:border-box;line-height:1.1rem;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button,:host ::ng-deep .grid-caption-bar p-splitbutton{display:inline-flex;align-items:stretch;margin:0;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button>.p-button{min-height:2.25rem;height:2.25rem;display:inline-flex;align-items:center}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{padding-left:.75rem;padding-right:.75rem}:host ::ng-deep .grid-caption-bar .p-splitbutton{display:inline-flex;align-items:stretch;margin:0}:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{height:100%}:host ::ng-deep .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .grid-caption-bar .p-button .p-button-icon{line-height:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td{padding-top:.35rem;padding-bottom:.35rem;vertical-align:middle;position:relative}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within{overflow:visible!important;z-index:50}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper>div{overflow:visible!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{position:relative;z-index:100;min-width:360px!important;max-width:80vw!important;background:var(--p-content-background, var(--p-surface-0, #fff));box-shadow:0 4px 12px #0000002e;border:1px solid var(--p-primary-color, #3b82f6);border-radius:4px}:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{background:var(--p-surface-900, #1f2430)}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{display:block;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor{display:flex!important;align-items:center!important;min-height:2.2rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper{margin:0!important;padding:0!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .caption-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{width:100%!important;max-width:100%!important;min-width:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper{display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{min-height:2.2rem;height:2.2rem;line-height:1.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-autocomplete .p-inputtext{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .disabled-textfield{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete>svg[data-p-icon]{display:none!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete .p-autocomplete-input{padding-right:.5rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within p-autocomplete>svg[data-p-icon]{z-index:101!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker{display:flex;align-items:stretch}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-input,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-dropdown{min-height:2.2rem;height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-disabled .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext:disabled{opacity:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor{display:flex;align-items:center;min-height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper{margin:0!important;padding:0!important;width:100%!important;max-width:100%!important;min-width:0!important;display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important;width:100%!important;max-width:100%!important;min-width:0!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}.command-grid-column{width:65px}.multiple-selection-grid-column{width:50px}.nested-grid-column{width:30px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-thead{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tfoot,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-footer,:host ::ng-deep .wuic-list-grid-mobile tfoot{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-resizable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table{display:block}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody{display:block;width:100%;padding-top:12px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr{display:block;width:100%;box-sizing:border-box;margin-bottom:12px;border-radius:12px;overflow:hidden;background:var(--p-content-background, var(--wuic-grid-row-odd));border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 80%, var(--wuic-grid-accent) 20%))}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr:last-child{margin-bottom:0}:host ::ng-deep .wuic-list-grid-mobile td.wuic-mobile-card-cell{display:block;width:100%;padding:14px;border-bottom:1px solid var(--surface-border, #e5e7eb);box-sizing:border-box}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table{width:100%!important;min-width:unset!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-splitbutton .p-button .p-button-label{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button{padding-left:.5rem!important;padding-right:.5rem!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-icon{margin-right:0!important;margin-left:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator{display:flex!important;flex-wrap:nowrap;justify-content:center;align-items:center;gap:4px;padding:8px 4px}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-current{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-pages{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-first,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-prev,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-next,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-last{min-width:36px!important;height:36px!important;margin:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-rpp-options,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-select{min-width:56px!important;height:36px!important;margin-left:4px!important}.wuic-mobile-card-list{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:18px;padding:14px;overflow:auto}.wuic-mobile-card-slot{padding-bottom:18px;box-sizing:border-box}.wuic-mobile-card-toolbar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:4px 0;border-bottom:1px solid color-mix(in srgb,var(--wuic-grid-base) 80%,var(--wuic-grid-accent) 20%);margin-bottom:4px}.wuic-mobile-card{display:flex;flex-direction:column;gap:6px;padding:14px 16px;border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 75%, var(--wuic-grid-accent) 25%));border-radius:12px;background:var(--p-content-background, var(--wuic-grid-row-odd));color:var(--p-text-color, inherit);box-shadow:0 4px 14px #0f172a14}:host-context(.theme-dark) .wuic-mobile-card{box-shadow:0 2px 8px #00000059}.wuic-mobile-card.wuic-row-selected{background:var(--wuic-grid-row-selected-bg);color:var(--wuic-grid-row-selected-text);outline:2px solid var(--wuic-grid-row-selected-ring)}:host ::ng-deep .wuic-mobile-card-actions{display:flex;justify-content:flex-end;align-items:center;margin:0 -4px 4px;padding-bottom:6px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-body{display:flex;flex-direction:column;gap:0}:host ::ng-deep .wuic-mobile-card-row{display:grid;grid-template-columns:minmax(110px,38%) 1fr;column-gap:12px;align-items:baseline;padding:8px 2px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-row:last-child{border-bottom:none}:host ::ng-deep .wuic-mobile-card-label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:currentColor;opacity:.75;line-height:1.3;word-break:break-word}:host ::ng-deep .wuic-mobile-card-value{font-size:.95rem;font-weight:500;text-align:right;word-break:break-word;min-width:0;color:currentColor}:host ::ng-deep .wuic-mobile-card-value a{color:var(--p-primary-color, #3b82f6);text-decoration:underline}.wuic-mobile-card-empty{padding:24px 12px;text-align:center;color:#6b7280;font-style:italic}p-menu:not(.p-menu-overlay-visible){display:none}@media(max-width:420px){:host ::ng-deep .wuic-mobile-card-row{grid-template-columns:1fr;row-gap:2px}:host ::ng-deep .wuic-mobile-card-value{text-align:left}}\n"] }]
|
|
37474
37999
|
}], ctorParameters: () => [{ type: MetadataProviderService }, { type: i1$5.DomSanitizer }, { type: i1$2.ActivatedRoute }, { type: i1$2.Router }, { type: i1$3.Location }, { type: i1$5.Title }, { type: TranslationManagerService }, { type: i0.ChangeDetectorRef }, { type: UserInfoService }, { type: i0.ElementRef }, { type: DeviceAwarenessService }, { type: CustomAttributesService, decorators: [{
|
|
37475
38000
|
type: Optional
|
|
37476
38001
|
}] }], propDecorators: { hardcodedRoute: [{
|
|
@@ -41916,6 +42441,9 @@ class AppSettingsEditorComponent {
|
|
|
41916
42441
|
this.sections.set(resp.sections || []);
|
|
41917
42442
|
this.filePath.set(resp.filePath || '');
|
|
41918
42443
|
this.rebuildMaps(resp.sections || []);
|
|
42444
|
+
// Popola le dropdown a opzioni dinamiche (es. model list in base al provider).
|
|
42445
|
+
// Fire-and-forget: lo schema renderizza subito, le options arrivano async.
|
|
42446
|
+
this.refreshDynamicOptions();
|
|
41919
42447
|
// Ripristina il tab attivo dal localStorage (per `name`, non index).
|
|
41920
42448
|
// Senza questa logica ogni reload (incluso quello post-save+restart)
|
|
41921
42449
|
// resetta a tab 0, perdendo il contesto dell'utente che stava editando
|
|
@@ -42000,10 +42528,54 @@ class AppSettingsEditorComponent {
|
|
|
42000
42528
|
onFieldChange(key, newValue) {
|
|
42001
42529
|
this.current.set(key, newValue);
|
|
42002
42530
|
this.changeTick.update((t) => t + 1);
|
|
42531
|
+
// Se questo campo guida opzioni dinamiche di un altro (es. provider -> model list),
|
|
42532
|
+
// ri-fetcha le options di quei campi col nuovo valore.
|
|
42533
|
+
const shortKey = key.includes(':') ? key.substring(key.indexOf(':') + 1) : key;
|
|
42534
|
+
for (const sec of this.sections())
|
|
42535
|
+
for (const f of sec.fields)
|
|
42536
|
+
if (f.optionsEndpoint && f.optionsDependsOn === shortKey)
|
|
42537
|
+
this.fetchFieldOptions(f, String(newValue ?? ''));
|
|
42003
42538
|
}
|
|
42004
42539
|
fieldValue(key) {
|
|
42005
42540
|
return this.current.get(key);
|
|
42006
42541
|
}
|
|
42542
|
+
/** Per ogni field con `optionsEndpoint`, fetcha le options usando il valore corrente
|
|
42543
|
+
* del campo `optionsDependsOn`. Chiamato dopo ogni loadSchema. */
|
|
42544
|
+
refreshDynamicOptions() {
|
|
42545
|
+
for (const sec of this.sections()) {
|
|
42546
|
+
for (const f of sec.fields) {
|
|
42547
|
+
if (!f.optionsEndpoint || !f.optionsDependsOn)
|
|
42548
|
+
continue;
|
|
42549
|
+
this.fetchFieldOptions(f, this.dependsOnValue(f.optionsDependsOn));
|
|
42550
|
+
}
|
|
42551
|
+
}
|
|
42552
|
+
}
|
|
42553
|
+
/** Valore corrente del campo identificato da `dependsOnKey` (con/senza prefisso AppSettings:). */
|
|
42554
|
+
dependsOnValue(dependsOnKey) {
|
|
42555
|
+
for (const sec of this.sections())
|
|
42556
|
+
for (const f of sec.fields)
|
|
42557
|
+
if (f.key === dependsOnKey || f.key.endsWith(':' + dependsOnKey))
|
|
42558
|
+
return String(this.fieldValue(f.key) ?? '');
|
|
42559
|
+
return '';
|
|
42560
|
+
}
|
|
42561
|
+
/** GET `<endpoint>?<dependsOn>=<depVal>` -> popola `f.options`. Best-effort: su errore
|
|
42562
|
+
* lascia il campo come testo libero (options resta vuoto). */
|
|
42563
|
+
fetchFieldOptions(f, depVal) {
|
|
42564
|
+
if (!f.optionsEndpoint || !f.optionsDependsOn)
|
|
42565
|
+
return;
|
|
42566
|
+
const ep = f.optionsEndpoint.replace(/^\/?api\//, ''); // base gia' include /api/
|
|
42567
|
+
const url = `${this.buildApiBaseUrl()}${ep}?${encodeURIComponent(f.optionsDependsOn)}=${encodeURIComponent(depVal)}`;
|
|
42568
|
+
this.http.get(url, { withCredentials: true }).subscribe({
|
|
42569
|
+
next: (opts) => {
|
|
42570
|
+
if (Array.isArray(opts)) {
|
|
42571
|
+
f.options = opts;
|
|
42572
|
+
// bump del signal sections cosi' il <p-select> ri-legge f.options (CD signal-safe)
|
|
42573
|
+
this.sections.update((arr) => [...arr]);
|
|
42574
|
+
}
|
|
42575
|
+
},
|
|
42576
|
+
error: () => { },
|
|
42577
|
+
});
|
|
42578
|
+
}
|
|
42007
42579
|
/**
|
|
42008
42580
|
* Ritorna `true` se almeno uno dei changes coinvolge una chiave licenza
|
|
42009
42581
|
* (`license-email` / `license-payload` / `license-signature`). Usato dal save
|
|
@@ -45350,7 +45922,7 @@ class LazyFirstRunWizardComponent {
|
|
|
45350
45922
|
this.cdr = cdr;
|
|
45351
45923
|
}
|
|
45352
45924
|
async ngOnInit() {
|
|
45353
|
-
firstRunWizardComponentPromise ??= import('./wuic-framework-lib-first-run-wizard.component-
|
|
45925
|
+
firstRunWizardComponentPromise ??= import('./wuic-framework-lib-first-run-wizard.component-lW7-xMm3.mjs').then((m) => m.WuicFirstRunWizardComponent);
|
|
45354
45926
|
const componentClass = await firstRunWizardComponentPromise;
|
|
45355
45927
|
this.componentRef = this.vcr.createComponent(componentClass);
|
|
45356
45928
|
// Forward output del componente reale al wrapper. Type assertion necessaria
|
|
@@ -56994,7 +57566,7 @@ class LazyCodeEditorComponent {
|
|
|
56994
57566
|
this.cdr = cdr;
|
|
56995
57567
|
}
|
|
56996
57568
|
async ngOnInit() {
|
|
56997
|
-
codeEditorComponentPromise ??= import('./wuic-framework-lib-code-editor.component-
|
|
57569
|
+
codeEditorComponentPromise ??= import('./wuic-framework-lib-code-editor.component-BZNLr0Jj.mjs').then((m) => m.CodeEditorComponent);
|
|
56998
57570
|
this.loadedComponent = await codeEditorComponentPromise;
|
|
56999
57571
|
this.cdr.markForCheck();
|
|
57000
57572
|
}
|
|
@@ -58781,7 +59353,7 @@ class DesignerRouteComponent {
|
|
|
58781
59353
|
* Inizializza il componente preparando stato, sottoscrizioni e primi caricamenti richiesti.
|
|
58782
59354
|
*/
|
|
58783
59355
|
async ngOnInit() {
|
|
58784
|
-
designerComponentPromise ??= import('./wuic-framework-lib-designer.component-
|
|
59356
|
+
designerComponentPromise ??= import('./wuic-framework-lib-designer.component-N_CfbGnr.mjs').then((m) => m.DesignerComponent);
|
|
58785
59357
|
this.loadedComponent = await designerComponentPromise;
|
|
58786
59358
|
}
|
|
58787
59359
|
/**
|
|
@@ -58858,7 +59430,7 @@ class LazyPivotBuilderComponent {
|
|
|
58858
59430
|
this.cdr = cdr;
|
|
58859
59431
|
}
|
|
58860
59432
|
async ngOnInit() {
|
|
58861
|
-
pivotBuilderComponentPromise ??= import('./wuic-framework-lib-pivot-builder.component-
|
|
59433
|
+
pivotBuilderComponentPromise ??= import('./wuic-framework-lib-pivot-builder.component-0gJz9RWr.mjs').then((m) => m.PivotBuilderComponent);
|
|
58862
59434
|
this.loadedComponent = await pivotBuilderComponentPromise;
|
|
58863
59435
|
this.cdr.markForCheck();
|
|
58864
59436
|
}
|
|
@@ -58897,7 +59469,7 @@ class WorkflowDesignerRouteComponent {
|
|
|
58897
59469
|
* Inizializza il componente preparando stato, sottoscrizioni e primi caricamenti richiesti.
|
|
58898
59470
|
*/
|
|
58899
59471
|
async ngOnInit() {
|
|
58900
|
-
workflowDesignerComponentPromise ??= import('./wuic-framework-lib-workflow-designer.component-
|
|
59472
|
+
workflowDesignerComponentPromise ??= import('./wuic-framework-lib-workflow-designer.component-epj3oKq3.mjs').then((m) => m.WorkflowDesignerComponent);
|
|
58901
59473
|
this.loadedComponent = await workflowDesignerComponentPromise;
|
|
58902
59474
|
}
|
|
58903
59475
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WorkflowDesignerRouteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
@@ -58954,7 +59526,7 @@ class LazyReportDesignerComponent {
|
|
|
58954
59526
|
this.cdr = cdr;
|
|
58955
59527
|
}
|
|
58956
59528
|
async ngOnInit() {
|
|
58957
|
-
reportDesignerComponentPromise ??= import('./wuic-framework-lib-report-designer.component-
|
|
59529
|
+
reportDesignerComponentPromise ??= import('./wuic-framework-lib-report-designer.component-DcpdzFjh.mjs').then((m) => m.ReportDesignerComponent);
|
|
58958
59530
|
this.loadedComponent = await reportDesignerComponentPromise;
|
|
58959
59531
|
this.cdr.markForCheck();
|
|
58960
59532
|
}
|
|
@@ -59009,7 +59581,7 @@ class LazyReportViewerComponent {
|
|
|
59009
59581
|
this.cdr = cdr;
|
|
59010
59582
|
}
|
|
59011
59583
|
async ngOnInit() {
|
|
59012
|
-
reportViewerComponentPromise ??= import('./wuic-framework-lib-report-viewer.component-
|
|
59584
|
+
reportViewerComponentPromise ??= import('./wuic-framework-lib-report-viewer.component-BHpbGpp9.mjs').then((m) => m.ReportViewerComponent);
|
|
59013
59585
|
this.loadedComponent = await reportViewerComponentPromise;
|
|
59014
59586
|
this.cdr.markForCheck();
|
|
59015
59587
|
}
|
|
@@ -60713,6 +61285,7 @@ class WuicRagService {
|
|
|
60713
61285
|
query,
|
|
60714
61286
|
top_k: options.topK ?? 8,
|
|
60715
61287
|
use_lora: options.useLora ?? true,
|
|
61288
|
+
profile: options.profile,
|
|
60716
61289
|
};
|
|
60717
61290
|
return this.http.post(this.buildUrl('Query'), payload, { withCredentials: true });
|
|
60718
61291
|
}
|
|
@@ -60734,7 +61307,10 @@ class WuicRagService {
|
|
|
60734
61307
|
query,
|
|
60735
61308
|
history,
|
|
60736
61309
|
top_k: options.topK ?? 5,
|
|
60737
|
-
|
|
61310
|
+
// Vuoto = il backend usa il model configurato (`rag-llm-default-chat-model`),
|
|
61311
|
+
// provider-appropriate. Non forzare un model Anthropic hardcoded.
|
|
61312
|
+
model: options.model ?? '',
|
|
61313
|
+
profile: options.profile,
|
|
60738
61314
|
};
|
|
60739
61315
|
return this.http.post(this.buildUrl('Chat'), payload, { withCredentials: true });
|
|
60740
61316
|
}
|
|
@@ -60818,6 +61394,55 @@ class WuicRagService {
|
|
|
60818
61394
|
askAsync(req) {
|
|
60819
61395
|
return firstValueFrom(this.ask(req));
|
|
60820
61396
|
}
|
|
61397
|
+
/**
|
|
61398
|
+
* Applica la proposta d'azione precedentemente emessa dall'LLM sul messaggio
|
|
61399
|
+
* indicato. Server-side: re-legge la `proposed_action_json` dal DB (non dal
|
|
61400
|
+
* client: prevenzione tampering del `callback_js`), valida, fa INSERT sul
|
|
61401
|
+
* metadata corrispondente + `InvalidateMetadataCachesAndSetVersion`.
|
|
61402
|
+
*
|
|
61403
|
+
* Gating: solo `isSuperAdmin` (server torna `403 admin-required` in caso).
|
|
61404
|
+
* Esiti tipici (`error`): `not-authenticated` | `admin-required` |
|
|
61405
|
+
* `message-not-found` | `no-proposed-action-on-message` | `already-applied` |
|
|
61406
|
+
* `unsupported-kind: <x>` | `apply-failed`.
|
|
61407
|
+
*/
|
|
61408
|
+
applyProposedAction(req) {
|
|
61409
|
+
return this.http.post(this.buildUrl('ApplyAction'), req, { withCredentials: true });
|
|
61410
|
+
}
|
|
61411
|
+
applyProposedActionAsync(req) {
|
|
61412
|
+
return firstValueFrom(this.applyProposedAction(req));
|
|
61413
|
+
}
|
|
61414
|
+
/**
|
|
61415
|
+
* Sovrascrive il `proposed_action_json` di un messaggio assistant con il
|
|
61416
|
+
* payload editato dall'utente in UI (campo "Classe/Stile" + textarea callback).
|
|
61417
|
+
* Va chiamato PRIMA di `applyProposedActionAsync` quando l'utente modifica
|
|
61418
|
+
* qualcosa, cosi' che il backend ApplyAction rilegga dal DB il payload giusto.
|
|
61419
|
+
* Filtri di sicurezza nel backend: ownership user_id + non gia' applicata.
|
|
61420
|
+
*/
|
|
61421
|
+
updateProposedAction(req) {
|
|
61422
|
+
return this.http.post(this.buildUrl('UpdateProposedAction'), req, { withCredentials: true });
|
|
61423
|
+
}
|
|
61424
|
+
updateProposedActionAsync(req) {
|
|
61425
|
+
return firstValueFrom(this.updateProposedAction(req));
|
|
61426
|
+
}
|
|
61427
|
+
/**
|
|
61428
|
+
* Compatta la sessione: chiama il backend che a sua volta chiama il rag_server
|
|
61429
|
+
* per produrre un summary della history. Persiste su `_rag_chat_sessions`
|
|
61430
|
+
* + avanza `summary_up_to_message_id`. Le future call /Ask passeranno il
|
|
61431
|
+
* summary nel system prompt al posto dei turn coperti.
|
|
61432
|
+
*/
|
|
61433
|
+
compactSession(req) {
|
|
61434
|
+
return this.http.post(this.buildUrl('Compact'), req, { withCredentials: true });
|
|
61435
|
+
}
|
|
61436
|
+
compactSessionAsync(req) {
|
|
61437
|
+
return firstValueFrom(this.compactSession(req));
|
|
61438
|
+
}
|
|
61439
|
+
/** Rinomina il titolo di una session (mostrato nella sessions list). */
|
|
61440
|
+
renameSession(req) {
|
|
61441
|
+
return this.http.post(this.buildUrl('RenameSession'), req, { withCredentials: true });
|
|
61442
|
+
}
|
|
61443
|
+
renameSessionAsync(req) {
|
|
61444
|
+
return firstValueFrom(this.renameSession(req));
|
|
61445
|
+
}
|
|
60821
61446
|
// ----- private helpers -----
|
|
60822
61447
|
/**
|
|
60823
61448
|
* Costruisce l'URL `<api_base>Rag/<action>` rispettando l'eventuale
|
|
@@ -60846,6 +61471,134 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
60846
61471
|
args: [{ providedIn: 'root' }]
|
|
60847
61472
|
}], ctorParameters: () => [{ type: i1$1.HttpClient }] });
|
|
60848
61473
|
|
|
61474
|
+
class ChatbotHostRegistryService {
|
|
61475
|
+
handlers = new Map();
|
|
61476
|
+
stateProviders = new Map();
|
|
61477
|
+
/**
|
|
61478
|
+
* Registra (o sostituisce) il handler per un dato `kind`. L'ultima
|
|
61479
|
+
* registrazione vince - utile per scenari edge in cui due viste
|
|
61480
|
+
* concorrenti gestiscono lo stesso kind (raro, ma documentato).
|
|
61481
|
+
*/
|
|
61482
|
+
register(kind, handler) {
|
|
61483
|
+
if (!kind || typeof handler !== 'function')
|
|
61484
|
+
return;
|
|
61485
|
+
this.handlers.set(kind, handler);
|
|
61486
|
+
}
|
|
61487
|
+
/**
|
|
61488
|
+
* Rimuove il handler per un dato `kind`. No-op se non registrato.
|
|
61489
|
+
* Da chiamare in ngOnDestroy dell'host per evitare retain.
|
|
61490
|
+
*/
|
|
61491
|
+
unregister(kind) {
|
|
61492
|
+
if (!kind)
|
|
61493
|
+
return;
|
|
61494
|
+
this.handlers.delete(kind);
|
|
61495
|
+
}
|
|
61496
|
+
/**
|
|
61497
|
+
* Restituisce il handler registrato per `kind`, o `null` se nessuno
|
|
61498
|
+
* lo gestisce client-side. Il chatbot usa questo per decidere se
|
|
61499
|
+
* bypassare il backend o no.
|
|
61500
|
+
*/
|
|
61501
|
+
get(kind) {
|
|
61502
|
+
if (!kind)
|
|
61503
|
+
return null;
|
|
61504
|
+
return this.handlers.get(kind) || null;
|
|
61505
|
+
}
|
|
61506
|
+
/**
|
|
61507
|
+
* Helper diagnostico: lista tutti i kind attualmente registrati.
|
|
61508
|
+
* Usato dai test e dal debug panel.
|
|
61509
|
+
*/
|
|
61510
|
+
registeredKinds() {
|
|
61511
|
+
return Array.from(this.handlers.keys());
|
|
61512
|
+
}
|
|
61513
|
+
// ===========================================================================
|
|
61514
|
+
// State Providers - per iniettare snapshot state nel route_context del LLM
|
|
61515
|
+
// ===========================================================================
|
|
61516
|
+
/**
|
|
61517
|
+
* Registra (o sostituisce) un state provider per un dato `kind`.
|
|
61518
|
+
* Il provider verra' chiamato dal chatbot in `buildRouteContext()` per
|
|
61519
|
+
* arricchire il system prompt del LLM con lo state corrente dell'host
|
|
61520
|
+
* (es. designer.dashboardElements serializzati come tree compatto).
|
|
61521
|
+
*
|
|
61522
|
+
* Esempio (designer.component):
|
|
61523
|
+
* this.chatbotHostRegistry.registerStateProvider(
|
|
61524
|
+
* 'designer_inject',
|
|
61525
|
+
* () => this.buildCompactStateDump()
|
|
61526
|
+
* );
|
|
61527
|
+
*/
|
|
61528
|
+
registerStateProvider(kind, provider) {
|
|
61529
|
+
if (!kind || typeof provider !== 'function')
|
|
61530
|
+
return;
|
|
61531
|
+
this.stateProviders.set(kind, provider);
|
|
61532
|
+
}
|
|
61533
|
+
/**
|
|
61534
|
+
* Rimuove il state provider. Da chiamare in ngOnDestroy.
|
|
61535
|
+
*/
|
|
61536
|
+
unregisterStateProvider(kind) {
|
|
61537
|
+
if (!kind)
|
|
61538
|
+
return;
|
|
61539
|
+
this.stateProviders.delete(kind);
|
|
61540
|
+
}
|
|
61541
|
+
/**
|
|
61542
|
+
* Invoca il state provider registrato per `kind` e ritorna il dump.
|
|
61543
|
+
* Se non registrato, ritorna `null`. Errori nel provider sono
|
|
61544
|
+
* swallowed e logged (best-effort: il chatbot deve sempre funzionare
|
|
61545
|
+
* anche se l'host crasha durante il dump).
|
|
61546
|
+
*/
|
|
61547
|
+
getStateDump(kind) {
|
|
61548
|
+
if (!kind)
|
|
61549
|
+
return null;
|
|
61550
|
+
const provider = this.stateProviders.get(kind);
|
|
61551
|
+
if (!provider)
|
|
61552
|
+
return null;
|
|
61553
|
+
try {
|
|
61554
|
+
const dump = provider();
|
|
61555
|
+
return (typeof dump === 'string' && dump.length > 0) ? dump : null;
|
|
61556
|
+
}
|
|
61557
|
+
catch (err) {
|
|
61558
|
+
try {
|
|
61559
|
+
console.warn('[ChatbotHostRegistry] stateProvider failed for', kind, err);
|
|
61560
|
+
}
|
|
61561
|
+
catch { /* ignore */ }
|
|
61562
|
+
return null;
|
|
61563
|
+
}
|
|
61564
|
+
}
|
|
61565
|
+
/**
|
|
61566
|
+
* Helper diagnostico: lista tutti i kind con state provider attivo.
|
|
61567
|
+
*/
|
|
61568
|
+
registeredStateKinds() {
|
|
61569
|
+
return Array.from(this.stateProviders.keys());
|
|
61570
|
+
}
|
|
61571
|
+
/**
|
|
61572
|
+
* Invoca SYNC tutti gli state provider registrati ignorandone il risultato.
|
|
61573
|
+
*
|
|
61574
|
+
* Scopo: dare ai provider la possibilita' di triggerare i loro fetch lazy
|
|
61575
|
+
* (es. il designer prefetcha `getAvailableRoutes` solo quando lo state
|
|
61576
|
+
* provider viene invocato — altrimenti spreca chiamate al mount). Chiamato
|
|
61577
|
+
* dal chatbot in ngOnInit: la finestra "apertura chatbot" → "primo prompt
|
|
61578
|
+
* inviato" e' di almeno 1-2 secondi (l'utente deve digitare), tempo
|
|
61579
|
+
* sufficiente perche' la cache sia pronta al primo Ask. Senza warm-up,
|
|
61580
|
+
* il primo prompt vedrebbe state dump 'caricamento' e il LLM
|
|
61581
|
+
* potrebbe hallucinare nomi alternativi inventati.
|
|
61582
|
+
*
|
|
61583
|
+
* Errori dei provider vengono swallowed (lo stesso pattern di getStateDump).
|
|
61584
|
+
*/
|
|
61585
|
+
warmUpAllStateProviders() {
|
|
61586
|
+
const kinds = this.registeredStateKinds();
|
|
61587
|
+
for (const k of kinds) {
|
|
61588
|
+
try {
|
|
61589
|
+
this.getStateDump(k);
|
|
61590
|
+
}
|
|
61591
|
+
catch { /* ignore */ }
|
|
61592
|
+
}
|
|
61593
|
+
}
|
|
61594
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChatbotHostRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
61595
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChatbotHostRegistryService, providedIn: 'root' });
|
|
61596
|
+
}
|
|
61597
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChatbotHostRegistryService, decorators: [{
|
|
61598
|
+
type: Injectable,
|
|
61599
|
+
args: [{ providedIn: 'root' }]
|
|
61600
|
+
}] });
|
|
61601
|
+
|
|
60849
61602
|
/**
|
|
60850
61603
|
* Componente standalone che espone una chat sopra il RAG del codebase WUIC.
|
|
60851
61604
|
*
|
|
@@ -60869,9 +61622,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
60869
61622
|
class WuicRagChatbotComponent {
|
|
60870
61623
|
ragService;
|
|
60871
61624
|
cdr;
|
|
61625
|
+
chatbotHostRegistry;
|
|
60872
61626
|
route;
|
|
60873
61627
|
router;
|
|
60874
61628
|
notificationRT;
|
|
61629
|
+
metaSrv;
|
|
60875
61630
|
/** Titolo mostrato nell'header del chatbot. */
|
|
60876
61631
|
title = 'Assistente codebase WUIC';
|
|
60877
61632
|
/** Modalita' operativa: vedi nota nella classDoc. */
|
|
@@ -60884,12 +61639,52 @@ class WuicRagChatbotComponent {
|
|
|
60884
61639
|
maxHistory = 50;
|
|
60885
61640
|
/** Placeholder dell'input. */
|
|
60886
61641
|
placeholder = 'Chiedi qualcosa sul codebase WUIC (italiano o inglese)...';
|
|
60887
|
-
/** Modello
|
|
60888
|
-
|
|
61642
|
+
/** Modello LLM in modalita' chat. Vuoto (default) = usa il model configurato lato
|
|
61643
|
+
* backend (`rag-llm-default-chat-model`), provider-appropriate. Non hardcodare un
|
|
61644
|
+
* model Anthropic: su provider openrouter/openai manderebbe un ID invalido (HTTP 400
|
|
61645
|
+
* -> retrieval-only). Valorizzare solo per forzare un model specifico. */
|
|
61646
|
+
model = '';
|
|
60889
61647
|
/** Altezza fissa dell'area chat in CSS units (default `420px`). */
|
|
60890
61648
|
chatHeight = '420px';
|
|
60891
61649
|
/** Mostra il bottone "Svuota cronologia" nell'header. */
|
|
60892
61650
|
showClearButton = true;
|
|
61651
|
+
/**
|
|
61652
|
+
* Persiste l'id della sessione corrente in `localStorage` e lo ripristina al
|
|
61653
|
+
* boot del componente (es. refresh della pagina) quando NON arriva un
|
|
61654
|
+
* `?sessionId` dalla route. La cronologia completa viene poi ricaricata dal DB
|
|
61655
|
+
* via `/api/Rag/History` (che ritorna TUTTI i messaggi della sessione, non un
|
|
61656
|
+
* sottoinsieme) -> il refresh non svuota piu' la chat. Default true.
|
|
61657
|
+
*/
|
|
61658
|
+
persistSession = true;
|
|
61659
|
+
/**
|
|
61660
|
+
* Se `true` (default), prima di ogni `Ask` il componente costruisce un
|
|
61661
|
+
* **route context** sintetico (route corrente + tabella metadata + colonne)
|
|
61662
|
+
* dal `MetadataProviderService` e lo invia al rag_server come `routeContext`
|
|
61663
|
+
* nel payload. Il server lo inietta nel system prompt dell'LLM cosi' che le
|
|
61664
|
+
* risposte usino i NOMI REALI delle colonne della pagina corrente invece di
|
|
61665
|
+
* placeholder generici.
|
|
61666
|
+
*
|
|
61667
|
+
* Disabilitare a `false` per la route full-page `/rag-chatbot` (il chatbot
|
|
61668
|
+
* stesso non e' una "business route" — niente context utile da iniettare) o
|
|
61669
|
+
* per UI di privacy avanzata.
|
|
61670
|
+
*/
|
|
61671
|
+
sendRouteContext = true;
|
|
61672
|
+
/** Chiave `localStorage` dell'ultima sessione (scoped per browser). */
|
|
61673
|
+
static LAST_SESSION_KEY = 'wuic_rag_last_session_id';
|
|
61674
|
+
/**
|
|
61675
|
+
* Mostra il toggle "Vista Dev / Vista Cliente" nell'header (default OFF).
|
|
61676
|
+
* Da abilitare SOLO nelle pagine dev/demo: la chat spedita al cliente NON
|
|
61677
|
+
* deve esporlo. E' comunque leak-safe (il server applica strictest-of: il
|
|
61678
|
+
* toggle puo' solo aumentare la redazione, mai diminuirla).
|
|
61679
|
+
*/
|
|
61680
|
+
allowProfileToggle = false;
|
|
61681
|
+
/**
|
|
61682
|
+
* Profilo di redazione corrente scelto dal toggle UI.
|
|
61683
|
+
* - 'internal' = Vista Dev (full source);
|
|
61684
|
+
* - 'release' = Vista Cliente (firme + niente skill, come on-prem).
|
|
61685
|
+
* Inviato al server solo se {@link allowProfileToggle} e' true.
|
|
61686
|
+
*/
|
|
61687
|
+
profileOverride = 'internal';
|
|
60893
61688
|
// ----- Outputs -----
|
|
60894
61689
|
/** Emette quando l'utente clicca su un source chip. */
|
|
60895
61690
|
resultSelected = new EventEmitter();
|
|
@@ -60920,6 +61715,35 @@ class WuicRagChatbotComponent {
|
|
|
60920
61715
|
processedNotificationIds = new Set();
|
|
60921
61716
|
/** Sub a notificationRT.notifications$ + ActivatedRoute.queryParams. */
|
|
60922
61717
|
subs = new Subscription();
|
|
61718
|
+
/**
|
|
61719
|
+
* Context window usage del modello corrente (ultima stima dal backend, 0-100%).
|
|
61720
|
+
* Aggiornato a ogni completion. Driver del cerchio colorato in header:
|
|
61721
|
+
* verde <60% / giallo 60-80% / arancione 80-90% / rosso >90%.
|
|
61722
|
+
*/
|
|
61723
|
+
contextUsagePercent = 0;
|
|
61724
|
+
/** Token usati (dall'ultima call) / context window max. Mostrati in tooltip. */
|
|
61725
|
+
contextUsedTokens = null;
|
|
61726
|
+
contextWindowMax = null;
|
|
61727
|
+
/** True quando il dialog "context-overflow" e' aperto: l'ultima call ha
|
|
61728
|
+
* fallito perche' la history sfora il context. UX: 2 bottoni (Compatta / Nuova). */
|
|
61729
|
+
contextOverflowDialogOpen = false;
|
|
61730
|
+
/** True mentre il /compact e' in-flight (loading nel bottone). */
|
|
61731
|
+
compactingSession = false;
|
|
61732
|
+
/**
|
|
61733
|
+
* True per tutto il flow `onClearHistory`: apertura prompt di conferma
|
|
61734
|
+
* (PrimeNG Dialog lazy-load, ~300-800ms al primo click) + DELETE backend +
|
|
61735
|
+
* reset stato locale. Driver dello spinner sul bottone cestino in header.
|
|
61736
|
+
* Re-entry guard: blocca click multipli mentre il flow e' in corso.
|
|
61737
|
+
*/
|
|
61738
|
+
clearingHistory = false;
|
|
61739
|
+
/** True quando il dialog "Sessioni" e' aperto. Lazy-load lista al toggle. */
|
|
61740
|
+
sessionsDialogOpen = false;
|
|
61741
|
+
sessionsList = [];
|
|
61742
|
+
sessionsLoading = false;
|
|
61743
|
+
/** Map sessionId -> bool: true se quella riga e' in modalita' rinomina. */
|
|
61744
|
+
sessionRenameEditing = {};
|
|
61745
|
+
/** Map sessionId -> string: valore temporaneo del titolo durante l'edit inline. */
|
|
61746
|
+
sessionRenameDraft = {};
|
|
60923
61747
|
/**
|
|
60924
61748
|
* Source selezionato per il dialog preview. Null = dialog chiuso.
|
|
60925
61749
|
* Il dialog mostra SOLO lo snippet embedded in RagSource (max 500 char dal
|
|
@@ -60929,12 +61753,14 @@ class WuicRagChatbotComponent {
|
|
|
60929
61753
|
selectedSource = null;
|
|
60930
61754
|
inputTextarea;
|
|
60931
61755
|
scrollContainer;
|
|
60932
|
-
constructor(ragService, cdr, route, router, notificationRT) {
|
|
61756
|
+
constructor(ragService, cdr, chatbotHostRegistry, route, router, notificationRT, metaSrv) {
|
|
60933
61757
|
this.ragService = ragService;
|
|
60934
61758
|
this.cdr = cdr;
|
|
61759
|
+
this.chatbotHostRegistry = chatbotHostRegistry;
|
|
60935
61760
|
this.route = route;
|
|
60936
61761
|
this.router = router;
|
|
60937
61762
|
this.notificationRT = notificationRT;
|
|
61763
|
+
this.metaSrv = metaSrv;
|
|
60938
61764
|
}
|
|
60939
61765
|
ngOnInit() {
|
|
60940
61766
|
// 1. Probe health del backend (esistente)
|
|
@@ -60944,17 +61770,47 @@ class WuicRagChatbotComponent {
|
|
|
60944
61770
|
next: (info) => { this.healthInfo = info; this.cdr.markForCheck(); },
|
|
60945
61771
|
error: (err) => { this.healthError = this.extractErrorMessage(err); this.cdr.markForCheck(); },
|
|
60946
61772
|
});
|
|
61773
|
+
// Warm-up degli state provider degli host (es. designer): da' loro la
|
|
61774
|
+
// possibilita' di triggerare i loro fetch lazy (es. lista route metadata)
|
|
61775
|
+
// PRIMA del primo prompt dell'utente. Senza warm-up, il primo prompt
|
|
61776
|
+
// vedrebbe state dump 'caricamento' e il LLM potrebbe hallucinare nomi
|
|
61777
|
+
// alternativi inventati (visto in produzione: utente chiede "grid bindata
|
|
61778
|
+
// a provincie", LLM elenca "provinces/states/regions" che NON sono nella
|
|
61779
|
+
// whitelist reale -> utente sceglie "provinces", al secondo prompt il LLM
|
|
61780
|
+
// vede la whitelist vera che contiene "stateprovinces" e fa fuzzy match
|
|
61781
|
+
// silenzioso provinces->stateprovinces, confondendo l'utente).
|
|
61782
|
+
try {
|
|
61783
|
+
this.chatbotHostRegistry.warmUpAllStateProviders();
|
|
61784
|
+
}
|
|
61785
|
+
catch { /* ignore */ }
|
|
60947
61786
|
// 2. Deep-link da notifica: ?sessionId=X(&messageId=Y) -> carica history.
|
|
60948
|
-
//
|
|
61787
|
+
// Refresh pagina senza query param -> ripristina l'ultima sessione da
|
|
61788
|
+
// localStorage (se persistSession). Se non c'e' nulla, lazy: la sessione
|
|
61789
|
+
// viene creata al primo Ask.
|
|
61790
|
+
let restoreAttempted = false;
|
|
60949
61791
|
if (this.route) {
|
|
60950
61792
|
this.subs.add(this.route.queryParamMap.subscribe((qp) => {
|
|
60951
61793
|
const sid = Number(qp.get('sessionId') || 0);
|
|
60952
61794
|
const mid = Number(qp.get('messageId') || 0);
|
|
60953
61795
|
if (sid > 0 && sid !== this.currentSessionId) {
|
|
61796
|
+
restoreAttempted = true;
|
|
60954
61797
|
this.loadSession(sid, mid > 0 ? mid : null);
|
|
60955
61798
|
}
|
|
61799
|
+
else if (sid <= 0 && !restoreAttempted && this.persistSession && this.currentSessionId == null) {
|
|
61800
|
+
// Nessun sessionId in URL (refresh diretto): recupera l'ultima sessione.
|
|
61801
|
+
restoreAttempted = true;
|
|
61802
|
+
const saved = this.readLastSession();
|
|
61803
|
+
if (saved)
|
|
61804
|
+
this.loadSession(saved, null);
|
|
61805
|
+
}
|
|
60956
61806
|
}));
|
|
60957
61807
|
}
|
|
61808
|
+
else if (this.persistSession && this.currentSessionId == null) {
|
|
61809
|
+
// Componente usato fuori da contesto routing: restore best-effort.
|
|
61810
|
+
const saved = this.readLastSession();
|
|
61811
|
+
if (saved)
|
|
61812
|
+
this.loadSession(saved, null);
|
|
61813
|
+
}
|
|
60958
61814
|
// 3. Subscribe alle notifiche real-time. Filtriamo type rag.message.* +
|
|
60959
61815
|
// payload.sessionId match + dedup via processedNotificationIds.
|
|
60960
61816
|
// handleNotification -> refreshSingleMessage -> applyMessageToTurn
|
|
@@ -60972,6 +61828,13 @@ class WuicRagChatbotComponent {
|
|
|
60972
61828
|
}
|
|
60973
61829
|
ngOnDestroy() {
|
|
60974
61830
|
this.subs.unsubscribe();
|
|
61831
|
+
// Cleanup delle subscription al BehaviorSubject del Monaco editor per
|
|
61832
|
+
// ogni proposta (vedi getProposedActionField). Senza, ogni messageId
|
|
61833
|
+
// tenuto in cache lascerebbe un listener orfano sul record subject.
|
|
61834
|
+
for (const cached of this.proposedActionEditorCache.values()) {
|
|
61835
|
+
cached.bodySubscription?.unsubscribe();
|
|
61836
|
+
}
|
|
61837
|
+
this.proposedActionEditorCache.clear();
|
|
60975
61838
|
}
|
|
60976
61839
|
// ----- Computed getters per template -----
|
|
60977
61840
|
get effectiveMode() {
|
|
@@ -60999,6 +61862,19 @@ class WuicRagChatbotComponent {
|
|
|
60999
61862
|
get sendDisabled() {
|
|
61000
61863
|
return this.loading || !this.inputValue.trim() || !!this.healthError;
|
|
61001
61864
|
}
|
|
61865
|
+
/** Label del toggle profilo. */
|
|
61866
|
+
get profileToggleLabel() {
|
|
61867
|
+
return this.profileOverride === 'release' ? 'Vista Cliente' : 'Vista Dev';
|
|
61868
|
+
}
|
|
61869
|
+
/**
|
|
61870
|
+
* Flippa il profilo di redazione (Dev <-> Cliente). Le risposte gia' in
|
|
61871
|
+
* cronologia restano com'erano (il profilo e' per-richiesta); i prossimi
|
|
61872
|
+
* invii usano il nuovo profilo.
|
|
61873
|
+
*/
|
|
61874
|
+
toggleProfile() {
|
|
61875
|
+
this.profileOverride = this.profileOverride === 'release' ? 'internal' : 'release';
|
|
61876
|
+
this.cdr.markForCheck();
|
|
61877
|
+
}
|
|
61002
61878
|
// ----- Azioni utente -----
|
|
61003
61879
|
/**
|
|
61004
61880
|
* Invia il prompt corrente via flow async:
|
|
@@ -61012,6 +61888,14 @@ class WuicRagChatbotComponent {
|
|
|
61012
61888
|
const text = this.inputValue.trim();
|
|
61013
61889
|
if (!text || this.loading || this.healthError)
|
|
61014
61890
|
return;
|
|
61891
|
+
// Slash command `/compact`: intercetta PRIMA del send. Comportamento identico
|
|
61892
|
+
// al click sul context cue. Niente call Ask -> niente turn in history.
|
|
61893
|
+
if (text === '/compact' || text.toLowerCase() === '/compatta') {
|
|
61894
|
+
this.inputValue = '';
|
|
61895
|
+
this.cdr.markForCheck();
|
|
61896
|
+
await this.onContextCueClick();
|
|
61897
|
+
return;
|
|
61898
|
+
}
|
|
61015
61899
|
const userTurn = { role: 'user', content: text };
|
|
61016
61900
|
const assistantTurn = {
|
|
61017
61901
|
role: 'assistant',
|
|
@@ -61026,6 +61910,16 @@ class WuicRagChatbotComponent {
|
|
|
61026
61910
|
this.loading = true;
|
|
61027
61911
|
this.cdr.markForCheck();
|
|
61028
61912
|
this.scheduleScrollToBottom();
|
|
61913
|
+
// Costruisce il route context PRIMA del send (best-effort: errore => null).
|
|
61914
|
+
// Il server lo inietta nel system prompt dell'LLM cosi' che le risposte
|
|
61915
|
+
// possano usare i nomi REALI delle colonne della pagina corrente.
|
|
61916
|
+
let routeContext = null;
|
|
61917
|
+
if (this.sendRouteContext) {
|
|
61918
|
+
try {
|
|
61919
|
+
routeContext = await this.buildRouteContext();
|
|
61920
|
+
}
|
|
61921
|
+
catch { /* best-effort: niente context, ma il send va lo stesso */ }
|
|
61922
|
+
}
|
|
61029
61923
|
try {
|
|
61030
61924
|
const ack = await this.ragService.askAsync({
|
|
61031
61925
|
prompt: text,
|
|
@@ -61033,9 +61927,12 @@ class WuicRagChatbotComponent {
|
|
|
61033
61927
|
mode: this.mode,
|
|
61034
61928
|
topK: this.topK,
|
|
61035
61929
|
model: this.model,
|
|
61930
|
+
profile: this.allowProfileToggle ? this.profileOverride : undefined,
|
|
61931
|
+
routeContext: routeContext,
|
|
61036
61932
|
});
|
|
61037
61933
|
// Salva la sessione corrente (puo' essere stata appena creata)
|
|
61038
61934
|
this.currentSessionId = ack.sessionId;
|
|
61935
|
+
this.saveLastSession(ack.sessionId);
|
|
61039
61936
|
userTurn.messageId = ack.userMessageId;
|
|
61040
61937
|
assistantTurn.messageId = ack.assistantMessageId;
|
|
61041
61938
|
assistantTurn.correlationId = ack.correlationId;
|
|
@@ -61060,37 +61957,57 @@ class WuicRagChatbotComponent {
|
|
|
61060
61957
|
/**
|
|
61061
61958
|
* Svuota la cronologia chat (soft-delete della sessione corrente sul DB) +
|
|
61062
61959
|
* reset stato locale.
|
|
61960
|
+
*
|
|
61961
|
+
* Il flag `clearingHistory` resta `true` per tutto il flow (dall'apertura
|
|
61962
|
+
* del dialog di conferma fino al completamento DELETE+reset). Driver dello
|
|
61963
|
+
* spinner sul bottone cestino in header — necessario perche' il PRIMO click
|
|
61964
|
+
* subisce ~300-800ms di lazy-load del bundle PrimeNG Dialog
|
|
61965
|
+
* (`promptDialog` apre un `p-dialog` dinamico, non gia' istanziato). Senza
|
|
61966
|
+
* feedback l'utente cliccava 2-3 volte pensando che non rispondesse.
|
|
61063
61967
|
*/
|
|
61064
61968
|
async onClearHistory() {
|
|
61065
61969
|
if (this.history.length === 0 && !this.currentSessionId)
|
|
61066
61970
|
return;
|
|
61067
|
-
|
|
61971
|
+
if (this.clearingHistory)
|
|
61972
|
+
return; // re-entry guard
|
|
61973
|
+
this.clearingHistory = true;
|
|
61974
|
+
this.cdr.markForCheck();
|
|
61068
61975
|
try {
|
|
61069
|
-
|
|
61070
|
-
if (typeof tools.promptDialog === 'function') {
|
|
61071
|
-
const res = await tools.promptDialog({
|
|
61072
|
-
title: 'Svuotare cronologia?',
|
|
61073
|
-
message: 'Vuoi cancellare definitivamente questa sessione di chat?',
|
|
61074
|
-
confirmLabel: 'Svuota',
|
|
61075
|
-
cancelLabel: 'Annulla',
|
|
61076
|
-
});
|
|
61077
|
-
confirmed = !!res && res.confirmed !== false;
|
|
61078
|
-
}
|
|
61079
|
-
}
|
|
61080
|
-
catch {
|
|
61081
|
-
confirmed = true;
|
|
61082
|
-
}
|
|
61083
|
-
if (!confirmed)
|
|
61084
|
-
return;
|
|
61085
|
-
if (this.currentSessionId) {
|
|
61976
|
+
let confirmed = true;
|
|
61086
61977
|
try {
|
|
61087
|
-
|
|
61978
|
+
const tools = WtoolboxService;
|
|
61979
|
+
if (typeof tools.promptDialog === 'function') {
|
|
61980
|
+
const ts = WtoolboxService.translationService;
|
|
61981
|
+
const tr = (k, fallback) => (ts && typeof ts.instant === 'function' ? ts.instant(k) : fallback);
|
|
61982
|
+
const res = await tools.promptDialog({
|
|
61983
|
+
title: tr('rag_chatbot.confirm.delete_session.title', 'Svuota cronologia'),
|
|
61984
|
+
message: tr('rag_chatbot.confirm.delete_session', 'Eliminare la cronologia di questa chat?'),
|
|
61985
|
+
confirmLabel: tr('rag_chatbot.confirm.delete_session.confirm', 'Elimina'),
|
|
61986
|
+
cancelLabel: tr('rag_chatbot.confirm.cancel', 'Annulla'),
|
|
61987
|
+
});
|
|
61988
|
+
confirmed = !!res && res.confirmed !== false;
|
|
61989
|
+
}
|
|
61088
61990
|
}
|
|
61089
|
-
catch {
|
|
61991
|
+
catch {
|
|
61992
|
+
confirmed = true;
|
|
61993
|
+
}
|
|
61994
|
+
if (!confirmed)
|
|
61995
|
+
return;
|
|
61996
|
+
if (this.currentSessionId) {
|
|
61997
|
+
try {
|
|
61998
|
+
await this.ragService.deleteSessionAsync(this.currentSessionId);
|
|
61999
|
+
}
|
|
62000
|
+
catch { /* best effort */ }
|
|
62001
|
+
}
|
|
62002
|
+
this.history = [];
|
|
62003
|
+
this.pendingMap.clear();
|
|
62004
|
+
this.currentSessionId = null;
|
|
62005
|
+
this.saveLastSession(null);
|
|
62006
|
+
}
|
|
62007
|
+
finally {
|
|
62008
|
+
this.clearingHistory = false;
|
|
62009
|
+
this.cdr.markForCheck();
|
|
61090
62010
|
}
|
|
61091
|
-
this.history = [];
|
|
61092
|
-
this.pendingMap.clear();
|
|
61093
|
-
this.currentSessionId = null;
|
|
61094
62011
|
}
|
|
61095
62012
|
/**
|
|
61096
62013
|
* Click dispatcher:
|
|
@@ -61388,9 +62305,14 @@ class WuicRagChatbotComponent {
|
|
|
61388
62305
|
async loadSession(sessionId, highlightMessageId) {
|
|
61389
62306
|
try {
|
|
61390
62307
|
const snap = await this.ragService.historyAsync(sessionId);
|
|
61391
|
-
if (!snap || !snap.session)
|
|
62308
|
+
if (!snap || !snap.session) {
|
|
62309
|
+
// Sessione salvata non piu' valida (cancellata/altro utente): pulisci il
|
|
62310
|
+
// riferimento persistito cosi' al prossimo boot non si ritenta.
|
|
62311
|
+
this.saveLastSession(null);
|
|
61392
62312
|
return;
|
|
62313
|
+
}
|
|
61393
62314
|
this.currentSessionId = snap.session.id;
|
|
62315
|
+
this.saveLastSession(snap.session.id);
|
|
61394
62316
|
this.history = (snap.messages || []).map((m) => this.dtoToTurn(m));
|
|
61395
62317
|
this.pendingMap.clear();
|
|
61396
62318
|
// Re-popola pendingMap per i messaggi assistant ancora in attesa
|
|
@@ -61399,6 +62321,19 @@ class WuicRagChatbotComponent {
|
|
|
61399
62321
|
this.pendingMap.set(turn.messageId, turn);
|
|
61400
62322
|
}
|
|
61401
62323
|
}
|
|
62324
|
+
// Aggiorna il context cue dal piu' recente assistant turn completato:
|
|
62325
|
+
// riflette lo stato corrente del context window a sessione caricata.
|
|
62326
|
+
const lastCtx = [...(snap.messages || [])].reverse()
|
|
62327
|
+
.find(m => m.role === 'assistant' && m.contextUsedTokens != null && m.contextWindowMax != null);
|
|
62328
|
+
if (lastCtx) {
|
|
62329
|
+
this.updateContextCue(lastCtx.contextUsedTokens, lastCtx.contextWindowMax);
|
|
62330
|
+
}
|
|
62331
|
+
else {
|
|
62332
|
+
// Reset: nuova session o session senza turn assistant
|
|
62333
|
+
this.contextUsagePercent = 0;
|
|
62334
|
+
this.contextUsedTokens = null;
|
|
62335
|
+
this.contextWindowMax = null;
|
|
62336
|
+
}
|
|
61402
62337
|
this.cdr.markForCheck();
|
|
61403
62338
|
this.scheduleScrollToBottom();
|
|
61404
62339
|
if (highlightMessageId) {
|
|
@@ -61407,6 +62342,27 @@ class WuicRagChatbotComponent {
|
|
|
61407
62342
|
}
|
|
61408
62343
|
}
|
|
61409
62344
|
catch (err) {
|
|
62345
|
+
// Caso comune: la session salvata in localStorage punta a un record
|
|
62346
|
+
// che non esiste piu' sul backend (es. cambio DBMS, DB pulito, riga
|
|
62347
|
+
// cancellata o appartenente a un altro tenant). Backend risponde 404
|
|
62348
|
+
// con `{error:"session-not-found"}`. Degradazione gracefully:
|
|
62349
|
+
// - rimuovi la lastSession persistita (evita re-tentativo al prossimo boot)
|
|
62350
|
+
// - resetta lo state interno a "nuova session vuota"
|
|
62351
|
+
// - NIENTE alert/healthError invadente.
|
|
62352
|
+
// Per altri tipi di errore (rete, 500, auth) mostriamo healthError come prima.
|
|
62353
|
+
const isSessionNotFound = (err && typeof err === 'object' && 'status' in err && err.status === 404)
|
|
62354
|
+
|| /session-not-found|404/i.test(this.extractErrorMessage(err) || '');
|
|
62355
|
+
if (isSessionNotFound) {
|
|
62356
|
+
this.saveLastSession(null);
|
|
62357
|
+
this.currentSessionId = null;
|
|
62358
|
+
this.history = [];
|
|
62359
|
+
this.pendingMap.clear();
|
|
62360
|
+
this.contextUsagePercent = 0;
|
|
62361
|
+
this.contextUsedTokens = null;
|
|
62362
|
+
this.contextWindowMax = null;
|
|
62363
|
+
this.cdr.markForCheck();
|
|
62364
|
+
return;
|
|
62365
|
+
}
|
|
61410
62366
|
this.healthError = this.extractErrorMessage(err);
|
|
61411
62367
|
this.cdr.markForCheck();
|
|
61412
62368
|
}
|
|
@@ -61426,10 +62382,13 @@ class WuicRagChatbotComponent {
|
|
|
61426
62382
|
loading: m.status === 'pending',
|
|
61427
62383
|
mode: m.mode || null,
|
|
61428
62384
|
sources,
|
|
61429
|
-
errorMessage: m.status === 'error' ? (m.errorMessage || '
|
|
62385
|
+
errorMessage: m.status === 'error' ? (m.errorMessage || 'rag_chatbot.error.unknown') : null,
|
|
61430
62386
|
tokensIn: m.tokensIn,
|
|
61431
62387
|
tokensOut: m.tokensOut,
|
|
61432
62388
|
correlationId: m.correlationId,
|
|
62389
|
+
proposedAction: this.parseProposedAction(m.proposedActionJson),
|
|
62390
|
+
proposedActionAppliedAt: m.proposedActionAppliedAt || null,
|
|
62391
|
+
followupQuestions: this.parseFollowupQuestions(m.followupQuestionsJson),
|
|
61433
62392
|
};
|
|
61434
62393
|
if (turn.role === 'assistant' && turn.loading && (!turn.content || turn.content === '')) {
|
|
61435
62394
|
turn.content = '';
|
|
@@ -61456,13 +62415,48 @@ class WuicRagChatbotComponent {
|
|
|
61456
62415
|
target.errorMessage = null;
|
|
61457
62416
|
}
|
|
61458
62417
|
else if (m.status === 'error') {
|
|
61459
|
-
target.errorMessage = m.errorMessage || '
|
|
62418
|
+
target.errorMessage = m.errorMessage || 'rag_chatbot.error.unknown';
|
|
62419
|
+
// Errore strutturato `context-overflow`: il backend l'ha settato perche'
|
|
62420
|
+
// la history sfora il context window del modello. Apri il dialog dedicato.
|
|
62421
|
+
if (m.errorMessage === 'context-overflow') {
|
|
62422
|
+
this.contextOverflowDialogOpen = true;
|
|
62423
|
+
}
|
|
61460
62424
|
}
|
|
61461
62425
|
target.tokensIn = m.tokensIn;
|
|
61462
62426
|
target.tokensOut = m.tokensOut;
|
|
62427
|
+
// Aggiorna la proposta (puo' arrivare solo al complete: prima e' null su pending)
|
|
62428
|
+
target.proposedAction = this.parseProposedAction(m.proposedActionJson);
|
|
62429
|
+
target.proposedActionAppliedAt = m.proposedActionAppliedAt || null;
|
|
62430
|
+
target.followupQuestions = this.parseFollowupQuestions(m.followupQuestionsJson);
|
|
62431
|
+
// Aggiorna il context cue (cerchio colorato in header) usando i valori
|
|
62432
|
+
// dell'assistant turn appena ricevuto: e' lo stato corrente del context window.
|
|
62433
|
+
if (m.role === 'assistant' && m.contextUsedTokens != null && m.contextWindowMax != null) {
|
|
62434
|
+
this.updateContextCue(m.contextUsedTokens, m.contextWindowMax);
|
|
62435
|
+
}
|
|
61463
62436
|
if (!target.loading) {
|
|
61464
62437
|
this.pendingMap.delete(m.id);
|
|
61465
62438
|
}
|
|
62439
|
+
// Auto-sync del badge mode (chip 'retrieval' / 'RAG + LLM' in header):
|
|
62440
|
+
// `healthInfo` viene fetchato SOLO in ngOnInit -> dopo restart del
|
|
62441
|
+
// rag_server (rebuild RAG, fresh deploy, crash recovery) il backend
|
|
62442
|
+
// re-pusha la API key e il rag_server passa a `llm_enabled:true`, MA
|
|
62443
|
+
// il componente continua a mostrare 'retrieval' fino al refresh pagina.
|
|
62444
|
+
//
|
|
62445
|
+
// Detection: se l'assistant turn completato ha `target.mode='rag-llm'`
|
|
62446
|
+
// (= il backend ha effettivamente girato LLM) ma `healthInfo.llm_enabled`
|
|
62447
|
+
// e' ancora false, c'e' un mismatch -> rifetcho /health silente.
|
|
62448
|
+
// Simmetricamente per il caso opposto (key rimossa: llm passa a false).
|
|
62449
|
+
// Cost: ~1 call /health per turn quando c'e' divergenza, zero quando OK.
|
|
62450
|
+
if (m.role === 'assistant' && m.status === 'completed' && target.mode) {
|
|
62451
|
+
const turnIsLlm = target.mode === 'rag-llm';
|
|
62452
|
+
const healthIsLlm = !!this.healthInfo?.llm_enabled;
|
|
62453
|
+
if (turnIsLlm !== healthIsLlm) {
|
|
62454
|
+
this.ragService.health().subscribe({
|
|
62455
|
+
next: (info) => { this.healthInfo = info; this.cdr.markForCheck(); },
|
|
62456
|
+
error: () => { }
|
|
62457
|
+
});
|
|
62458
|
+
}
|
|
62459
|
+
}
|
|
61466
62460
|
// markForCheck: la mutation avviene su un oggetto esistente dentro
|
|
61467
62461
|
// `this.history` (nested property), invisibile ad Angular con
|
|
61468
62462
|
// ChangeDetectionStrategy.OnPush dei componenti ancestor (es. quando
|
|
@@ -61481,11 +62475,1006 @@ class WuicRagChatbotComponent {
|
|
|
61481
62475
|
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
61482
62476
|
}
|
|
61483
62477
|
// ----- Helpers (esistenti, riutilizzati) -----
|
|
62478
|
+
/**
|
|
62479
|
+
* Persiste (o cancella se id null) l'ultima sessione in localStorage.
|
|
62480
|
+
* Best-effort: guard SSR + try/catch (private-mode / quota / storage disabled).
|
|
62481
|
+
*/
|
|
62482
|
+
saveLastSession(id) {
|
|
62483
|
+
if (!this.persistSession || typeof window === 'undefined' || !window.localStorage)
|
|
62484
|
+
return;
|
|
62485
|
+
try {
|
|
62486
|
+
const key = WuicRagChatbotComponent.LAST_SESSION_KEY;
|
|
62487
|
+
if (id && id > 0)
|
|
62488
|
+
window.localStorage.setItem(key, String(id));
|
|
62489
|
+
else
|
|
62490
|
+
window.localStorage.removeItem(key);
|
|
62491
|
+
}
|
|
62492
|
+
catch { /* storage non disponibile: la persistenza e' best-effort */ }
|
|
62493
|
+
}
|
|
62494
|
+
/** Legge l'ultima sessione salvata (>0) o null. */
|
|
62495
|
+
readLastSession() {
|
|
62496
|
+
if (typeof window === 'undefined' || !window.localStorage)
|
|
62497
|
+
return null;
|
|
62498
|
+
try {
|
|
62499
|
+
const raw = window.localStorage.getItem(WuicRagChatbotComponent.LAST_SESSION_KEY);
|
|
62500
|
+
const id = Number(raw || 0);
|
|
62501
|
+
return id > 0 ? id : null;
|
|
62502
|
+
}
|
|
62503
|
+
catch {
|
|
62504
|
+
return null;
|
|
62505
|
+
}
|
|
62506
|
+
}
|
|
62507
|
+
// ---------------------------------------------------------------------
|
|
62508
|
+
// Proposed actions (LLM tool-use)
|
|
62509
|
+
// ---------------------------------------------------------------------
|
|
62510
|
+
/** Parse JSON del field `proposed_action_json` -> oggetto tipato (o null). */
|
|
62511
|
+
parseProposedAction(raw) {
|
|
62512
|
+
if (!raw)
|
|
62513
|
+
return null;
|
|
62514
|
+
try {
|
|
62515
|
+
const parsed = JSON.parse(raw);
|
|
62516
|
+
return (parsed && typeof parsed === 'object' && typeof parsed.kind === 'string')
|
|
62517
|
+
? parsed
|
|
62518
|
+
: null;
|
|
62519
|
+
}
|
|
62520
|
+
catch {
|
|
62521
|
+
return null;
|
|
62522
|
+
}
|
|
62523
|
+
}
|
|
62524
|
+
/** Parse JSON del field `followup_questions_json` -> array di stringhe (o null). */
|
|
62525
|
+
parseFollowupQuestions(raw) {
|
|
62526
|
+
if (!raw)
|
|
62527
|
+
return null;
|
|
62528
|
+
try {
|
|
62529
|
+
const parsed = JSON.parse(raw);
|
|
62530
|
+
if (!Array.isArray(parsed))
|
|
62531
|
+
return null;
|
|
62532
|
+
const cleaned = parsed.filter(q => typeof q === 'string' && q.trim().length > 0).slice(0, 3);
|
|
62533
|
+
return cleaned.length > 0 ? cleaned : null;
|
|
62534
|
+
}
|
|
62535
|
+
catch {
|
|
62536
|
+
return null;
|
|
62537
|
+
}
|
|
62538
|
+
}
|
|
62539
|
+
/**
|
|
62540
|
+
* Click su un chip follow-up: popola l'input box con la domanda (NON invia
|
|
62541
|
+
* automaticamente; l'utente puo' editare prima di premere invio). Coerente
|
|
62542
|
+
* con la scelta UX di non lanciare query "per sbaglio".
|
|
62543
|
+
*/
|
|
62544
|
+
applyFollowup(question) {
|
|
62545
|
+
if (!question)
|
|
62546
|
+
return;
|
|
62547
|
+
this.inputValue = question;
|
|
62548
|
+
this.cdr.markForCheck();
|
|
62549
|
+
// focus input cosi' l'utente puo' editare subito o premere invio
|
|
62550
|
+
this.focusInput();
|
|
62551
|
+
}
|
|
62552
|
+
// -------------------------------------------------------------------
|
|
62553
|
+
// Context cue (cerchio colorato in header) + /compact + overflow dialog
|
|
62554
|
+
// -------------------------------------------------------------------
|
|
62555
|
+
/** Aggiorna lo stato del cerchio cue dato (usedTokens, windowMax). */
|
|
62556
|
+
updateContextCue(usedTokens, windowMax) {
|
|
62557
|
+
if (usedTokens == null || windowMax == null || windowMax <= 0)
|
|
62558
|
+
return;
|
|
62559
|
+
this.contextUsedTokens = usedTokens;
|
|
62560
|
+
this.contextWindowMax = windowMax;
|
|
62561
|
+
this.contextUsagePercent = Math.min(100, Math.round((usedTokens / windowMax) * 100));
|
|
62562
|
+
this.cdr.markForCheck();
|
|
62563
|
+
}
|
|
62564
|
+
/** Colore del cerchio basato sul `contextUsagePercent`. */
|
|
62565
|
+
get contextCueColor() {
|
|
62566
|
+
const p = this.contextUsagePercent;
|
|
62567
|
+
if (p < 60)
|
|
62568
|
+
return 'var(--p-green-500, #22c55e)';
|
|
62569
|
+
if (p < 80)
|
|
62570
|
+
return 'var(--p-yellow-500, #eab308)';
|
|
62571
|
+
if (p < 90)
|
|
62572
|
+
return 'var(--p-orange-500, #f97316)';
|
|
62573
|
+
return 'var(--p-red-500, #ef4444)';
|
|
62574
|
+
}
|
|
62575
|
+
get contextCueTooltip() {
|
|
62576
|
+
// Guard: il getter e' chiamato AD OGNI Change Detection (binding di
|
|
62577
|
+
// [pTooltip]). `WtoolboxService.translationService` e' uno static
|
|
62578
|
+
// assegnato in bootstrap; durante una route transition (es. click
|
|
62579
|
+
// su un source chip → router navigation) un CD cycle puo' partire
|
|
62580
|
+
// prima che lo static sia popolato → `undefined.instant(...)` boom
|
|
62581
|
+
// (TypeError "Cannot read properties of undefined (reading 'instant')"
|
|
62582
|
+
// visto in stack trace su WuicRagChatbotComponent_Conditional_10_Template).
|
|
62583
|
+
// Senza guard, l'errore si propaga al global error handler e mostra il
|
|
62584
|
+
// dialog "Si e' verificato un errore inatteso" che blocca l'utente.
|
|
62585
|
+
const ts = WtoolboxService.translationService;
|
|
62586
|
+
if (!ts || typeof ts.instant !== 'function')
|
|
62587
|
+
return '';
|
|
62588
|
+
if (this.contextWindowMax == null || this.contextUsedTokens == null) {
|
|
62589
|
+
return ts.instant('rag_chatbot.context_cue.tooltip_waiting');
|
|
62590
|
+
}
|
|
62591
|
+
const usedK = Math.round(this.contextUsedTokens / 1000);
|
|
62592
|
+
const maxK = Math.round(this.contextWindowMax / 1000);
|
|
62593
|
+
// WtoolboxService.translationService.instant(key) accetta SOLO 1 argomento.
|
|
62594
|
+
// Niente interpolation built-in: replace manuale dei placeholder -> valore.
|
|
62595
|
+
// Regex accetta SIA `{var}` (single brace, format usato nello script
|
|
62596
|
+
// upsert-wuic-translations.ps1) SIA `{{var}}` (double brace, format
|
|
62597
|
+
// tipico Angular template) per robustezza. Storicamente le traduzioni
|
|
62598
|
+
// nel DB hanno usato single brace -> match falliva su double-only.
|
|
62599
|
+
const tpl = ts.instant('rag_chatbot.context_cue.tooltip');
|
|
62600
|
+
return String(tpl)
|
|
62601
|
+
.replace(/\{\{?\s*percent\s*\}?\}/g, String(this.contextUsagePercent))
|
|
62602
|
+
.replace(/\{\{?\s*used\s*\}?\}/g, String(usedK))
|
|
62603
|
+
.replace(/\{\{?\s*max\s*\}?\}/g, String(maxK));
|
|
62604
|
+
}
|
|
62605
|
+
/**
|
|
62606
|
+
* Click sul cerchio context cue: conferma + lancia /compact. Disponibile sempre
|
|
62607
|
+
* (anche <60%): l'utente puo' compattare on-demand.
|
|
62608
|
+
*/
|
|
62609
|
+
async onContextCueClick() {
|
|
62610
|
+
if (!this.currentSessionId || this.compactingSession)
|
|
62611
|
+
return;
|
|
62612
|
+
// Conferma via WtoolboxService.confirm (framework). Fallback su no-op confirm
|
|
62613
|
+
// se non disponibile (es. tests).
|
|
62614
|
+
let confirmed = true;
|
|
62615
|
+
try {
|
|
62616
|
+
const tools = WtoolboxService;
|
|
62617
|
+
if (typeof tools.confirm === 'function') {
|
|
62618
|
+
confirmed = await tools.confirm({
|
|
62619
|
+
header: WtoolboxService.translationService.instant('rag_chatbot.confirm.compact_session.header'),
|
|
62620
|
+
message: WtoolboxService.translationService.instant('rag_chatbot.confirm.compact_session.message'),
|
|
62621
|
+
icon: 'pi pi-exclamation-triangle',
|
|
62622
|
+
});
|
|
62623
|
+
}
|
|
62624
|
+
}
|
|
62625
|
+
catch {
|
|
62626
|
+
confirmed = true;
|
|
62627
|
+
}
|
|
62628
|
+
if (!confirmed)
|
|
62629
|
+
return;
|
|
62630
|
+
await this.doCompactSession();
|
|
62631
|
+
}
|
|
62632
|
+
/**
|
|
62633
|
+
* Chiama l'endpoint /api/Rag/Compact. Aggiorna lo stato dopo successo.
|
|
62634
|
+
* Riusato da: contextCue click, slash-command /compact, dialog overflow "Compatta ora".
|
|
62635
|
+
*/
|
|
62636
|
+
async doCompactSession() {
|
|
62637
|
+
if (!this.currentSessionId || this.compactingSession)
|
|
62638
|
+
return;
|
|
62639
|
+
this.compactingSession = true;
|
|
62640
|
+
this.cdr.markForCheck();
|
|
62641
|
+
try {
|
|
62642
|
+
const resp = await this.ragService.compactSessionAsync({ sessionId: this.currentSessionId });
|
|
62643
|
+
if (resp?.ok && !resp.noop) {
|
|
62644
|
+
WtoolboxService.messageNotificationService?.add?.({
|
|
62645
|
+
severity: 'success',
|
|
62646
|
+
summary: WtoolboxService.translationService.instant('rag_chatbot.toast.compact_success.summary'),
|
|
62647
|
+
detail: `Summary di ${resp.summaryLength} caratteri, fino al messaggio #${resp.summaryUpToMessageId}.`,
|
|
62648
|
+
});
|
|
62649
|
+
// Ricarica history per riflettere il nuovo summary (alcuni client potrebbero
|
|
62650
|
+
// voler mostrare un divider "--- compattato ---" prima dei msg coperti).
|
|
62651
|
+
if (this.currentSessionId)
|
|
62652
|
+
await this.loadSession(this.currentSessionId, null);
|
|
62653
|
+
// Reset overflow dialog se era aperto
|
|
62654
|
+
this.contextOverflowDialogOpen = false;
|
|
62655
|
+
}
|
|
62656
|
+
else if (resp?.noop) {
|
|
62657
|
+
WtoolboxService.messageNotificationService?.add?.({
|
|
62658
|
+
severity: 'info',
|
|
62659
|
+
summary: WtoolboxService.translationService.instant('rag_chatbot.toast.compact_noop.summary'),
|
|
62660
|
+
detail: resp.reason || 'La sessione non ha turn completati da riassumere.',
|
|
62661
|
+
});
|
|
62662
|
+
}
|
|
62663
|
+
else {
|
|
62664
|
+
WtoolboxService.messageNotificationService?.add?.({
|
|
62665
|
+
severity: 'error',
|
|
62666
|
+
summary: WtoolboxService.translationService.instant('rag_chatbot.toast.compact_failed.summary'),
|
|
62667
|
+
detail: resp?.error || WtoolboxService.translationService.instant('rag_chatbot.toast.generic_error'),
|
|
62668
|
+
});
|
|
62669
|
+
}
|
|
62670
|
+
}
|
|
62671
|
+
catch (err) {
|
|
62672
|
+
const msg = this.extractErrorMessage(err);
|
|
62673
|
+
WtoolboxService.messageNotificationService?.add?.({
|
|
62674
|
+
severity: 'error',
|
|
62675
|
+
summary: WtoolboxService.translationService.instant('rag_chatbot.toast.compact_failed.summary'),
|
|
62676
|
+
detail: msg,
|
|
62677
|
+
});
|
|
62678
|
+
}
|
|
62679
|
+
finally {
|
|
62680
|
+
this.compactingSession = false;
|
|
62681
|
+
this.cdr.markForCheck();
|
|
62682
|
+
}
|
|
62683
|
+
}
|
|
62684
|
+
/**
|
|
62685
|
+
* Nuova sessione: chiude la corrente (clear localStorage), resetta history, ricomincia.
|
|
62686
|
+
* Chiamato dal bottone "Nuova sessione" del dialog overflow.
|
|
62687
|
+
*/
|
|
62688
|
+
startNewSession() {
|
|
62689
|
+
this.history = [];
|
|
62690
|
+
this.pendingMap.clear();
|
|
62691
|
+
this.currentSessionId = null;
|
|
62692
|
+
this.saveLastSession(null);
|
|
62693
|
+
this.contextUsagePercent = 0;
|
|
62694
|
+
this.contextUsedTokens = null;
|
|
62695
|
+
this.contextOverflowDialogOpen = false;
|
|
62696
|
+
this.cdr.markForCheck();
|
|
62697
|
+
}
|
|
62698
|
+
// -------------------------------------------------------------------
|
|
62699
|
+
// Sessions popup (lista + rinomina + switch)
|
|
62700
|
+
// -------------------------------------------------------------------
|
|
62701
|
+
async openSessionsDialog() {
|
|
62702
|
+
this.sessionsDialogOpen = true;
|
|
62703
|
+
this.sessionsLoading = true;
|
|
62704
|
+
this.cdr.markForCheck();
|
|
62705
|
+
try {
|
|
62706
|
+
const resp = await this.ragService.sessionsAsync();
|
|
62707
|
+
this.sessionsList = (resp?.sessions || []).slice().sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''));
|
|
62708
|
+
}
|
|
62709
|
+
catch (err) {
|
|
62710
|
+
this.sessionsList = [];
|
|
62711
|
+
WtoolboxService.messageNotificationService?.add?.({
|
|
62712
|
+
severity: 'error',
|
|
62713
|
+
summary: WtoolboxService.translationService.instant('rag_chatbot.toast.sessions_load_failed.summary'),
|
|
62714
|
+
detail: this.extractErrorMessage(err),
|
|
62715
|
+
});
|
|
62716
|
+
}
|
|
62717
|
+
finally {
|
|
62718
|
+
this.sessionsLoading = false;
|
|
62719
|
+
this.cdr.markForCheck();
|
|
62720
|
+
}
|
|
62721
|
+
}
|
|
62722
|
+
closeSessionsDialog() {
|
|
62723
|
+
this.sessionsDialogOpen = false;
|
|
62724
|
+
this.sessionRenameEditing = {};
|
|
62725
|
+
this.sessionRenameDraft = {};
|
|
62726
|
+
}
|
|
62727
|
+
/** Switch alla session selezionata (carica history). */
|
|
62728
|
+
async selectSession(s) {
|
|
62729
|
+
if (!s || !s.id || s.id === this.currentSessionId) {
|
|
62730
|
+
this.closeSessionsDialog();
|
|
62731
|
+
return;
|
|
62732
|
+
}
|
|
62733
|
+
this.closeSessionsDialog();
|
|
62734
|
+
await this.loadSession(s.id, null);
|
|
62735
|
+
}
|
|
62736
|
+
/** Apre l'edit inline del titolo. */
|
|
62737
|
+
startRenameSession(s, event) {
|
|
62738
|
+
if (event) {
|
|
62739
|
+
event.stopPropagation();
|
|
62740
|
+
event.preventDefault();
|
|
62741
|
+
}
|
|
62742
|
+
if (!s?.id)
|
|
62743
|
+
return;
|
|
62744
|
+
this.sessionRenameEditing[s.id] = true;
|
|
62745
|
+
this.sessionRenameDraft[s.id] = s.title || '';
|
|
62746
|
+
this.cdr.markForCheck();
|
|
62747
|
+
}
|
|
62748
|
+
async confirmRenameSession(s, event) {
|
|
62749
|
+
if (event) {
|
|
62750
|
+
event.stopPropagation();
|
|
62751
|
+
event.preventDefault();
|
|
62752
|
+
}
|
|
62753
|
+
if (!s?.id)
|
|
62754
|
+
return;
|
|
62755
|
+
const newTitle = (this.sessionRenameDraft[s.id] || '').trim();
|
|
62756
|
+
if (!newTitle || newTitle === s.title) {
|
|
62757
|
+
this.cancelRenameSession(s);
|
|
62758
|
+
return;
|
|
62759
|
+
}
|
|
62760
|
+
try {
|
|
62761
|
+
const resp = await this.ragService.renameSessionAsync({ sessionId: s.id, title: newTitle });
|
|
62762
|
+
if (resp?.ok) {
|
|
62763
|
+
s.title = resp.title || newTitle;
|
|
62764
|
+
}
|
|
62765
|
+
else {
|
|
62766
|
+
WtoolboxService.messageNotificationService?.add?.({
|
|
62767
|
+
severity: 'error',
|
|
62768
|
+
summary: WtoolboxService.translationService.instant('rag_chatbot.toast.rename_failed.summary'),
|
|
62769
|
+
detail: resp?.error || WtoolboxService.translationService.instant('rag_chatbot.toast.generic_error'),
|
|
62770
|
+
});
|
|
62771
|
+
}
|
|
62772
|
+
}
|
|
62773
|
+
catch (err) {
|
|
62774
|
+
WtoolboxService.messageNotificationService?.add?.({
|
|
62775
|
+
severity: 'error',
|
|
62776
|
+
summary: WtoolboxService.translationService.instant('rag_chatbot.toast.rename_failed.summary'),
|
|
62777
|
+
detail: this.extractErrorMessage(err),
|
|
62778
|
+
});
|
|
62779
|
+
}
|
|
62780
|
+
finally {
|
|
62781
|
+
delete this.sessionRenameEditing[s.id];
|
|
62782
|
+
delete this.sessionRenameDraft[s.id];
|
|
62783
|
+
this.cdr.markForCheck();
|
|
62784
|
+
}
|
|
62785
|
+
}
|
|
62786
|
+
cancelRenameSession(s) {
|
|
62787
|
+
if (!s?.id)
|
|
62788
|
+
return;
|
|
62789
|
+
delete this.sessionRenameEditing[s.id];
|
|
62790
|
+
delete this.sessionRenameDraft[s.id];
|
|
62791
|
+
this.cdr.markForCheck();
|
|
62792
|
+
}
|
|
62793
|
+
/**
|
|
62794
|
+
* Cancella una sessione dalla cronologia (chiamato dall'icona cestino
|
|
62795
|
+
* sulla riga della lista sessioni). Confirm via WtoolboxService.promptDialog
|
|
62796
|
+
* (regola 12 AGENTS: mai window.confirm). Backend soft-delete via
|
|
62797
|
+
* POST /api/Rag/DeleteSession (gia' esistente).
|
|
62798
|
+
*
|
|
62799
|
+
* Se la sessione cancellata e' quella CORRENTE: clear della view (history
|
|
62800
|
+
* + currentSessionId reset) e remove da localStorage. Se non corrente:
|
|
62801
|
+
* solo splice dalla lista locale senza toccare la chat aperta.
|
|
62802
|
+
*
|
|
62803
|
+
* Best-effort: errori backend swallowed (la lista si refresha al prossimo
|
|
62804
|
+
* open del dialog comunque, e l'utente puo' riprovare).
|
|
62805
|
+
*/
|
|
62806
|
+
async onDeleteSessionFromList(s, event) {
|
|
62807
|
+
if (event)
|
|
62808
|
+
event.stopPropagation();
|
|
62809
|
+
if (!s?.id)
|
|
62810
|
+
return;
|
|
62811
|
+
let confirmed = true;
|
|
62812
|
+
try {
|
|
62813
|
+
const tools = WtoolboxService;
|
|
62814
|
+
if (typeof tools.promptDialog === 'function') {
|
|
62815
|
+
const titlePreview = this.sessionPreview(s) || WtoolboxService.translationService.instant('rag_chatbot.sessions_dialog.untitled');
|
|
62816
|
+
const tmpl = WtoolboxService.translationService.instant('rag_chatbot.sessions_dialog.delete_confirm_message');
|
|
62817
|
+
// Accetta sia {title} sia {{title}} per coerenza col resto del codebase.
|
|
62818
|
+
const message = String(tmpl).replace(/\{\{?\s*title\s*\}?\}/g, titlePreview);
|
|
62819
|
+
const res = await tools.promptDialog({
|
|
62820
|
+
title: WtoolboxService.translationService.instant('rag_chatbot.sessions_dialog.delete_confirm_title'),
|
|
62821
|
+
message,
|
|
62822
|
+
confirmLabel: WtoolboxService.translationService.instant('rag_chatbot.sessions_dialog.delete_confirm_yes'),
|
|
62823
|
+
cancelLabel: WtoolboxService.translationService.instant('rag_chatbot.confirm.cancel'),
|
|
62824
|
+
});
|
|
62825
|
+
confirmed = !!res && res.confirmed !== false;
|
|
62826
|
+
}
|
|
62827
|
+
}
|
|
62828
|
+
catch {
|
|
62829
|
+
confirmed = true;
|
|
62830
|
+
}
|
|
62831
|
+
if (!confirmed)
|
|
62832
|
+
return;
|
|
62833
|
+
const sessionId = s.id;
|
|
62834
|
+
try {
|
|
62835
|
+
await this.ragService.deleteSessionAsync(sessionId);
|
|
62836
|
+
}
|
|
62837
|
+
catch { /* best-effort, prosegui col refresh UI */ }
|
|
62838
|
+
// Rimuove dalla lista locale (immediate UI feedback senza re-fetch).
|
|
62839
|
+
const idx = this.sessionsList.findIndex(x => x.id === sessionId);
|
|
62840
|
+
if (idx >= 0) {
|
|
62841
|
+
this.sessionsList = [...this.sessionsList.slice(0, idx), ...this.sessionsList.slice(idx + 1)];
|
|
62842
|
+
}
|
|
62843
|
+
// Se era la sessione corrente: clear view + localStorage.
|
|
62844
|
+
if (this.currentSessionId === sessionId) {
|
|
62845
|
+
this.history = [];
|
|
62846
|
+
this.pendingMap.clear();
|
|
62847
|
+
this.currentSessionId = null;
|
|
62848
|
+
this.saveLastSession(null);
|
|
62849
|
+
}
|
|
62850
|
+
this.cdr.markForCheck();
|
|
62851
|
+
}
|
|
62852
|
+
/** Truncate del primo prompt a 100 char per anteprima list (full su tooltip). */
|
|
62853
|
+
sessionPreview(s) {
|
|
62854
|
+
const t = s?.title || '';
|
|
62855
|
+
if (!t)
|
|
62856
|
+
return '(senza titolo)';
|
|
62857
|
+
return t.length > 100 ? t.substring(0, 100) + '…' : t;
|
|
62858
|
+
}
|
|
62859
|
+
/** Formato data sessione (locale). */
|
|
62860
|
+
sessionDate(s) {
|
|
62861
|
+
const raw = s?.updatedAt || s?.createdAt;
|
|
62862
|
+
if (!raw)
|
|
62863
|
+
return '';
|
|
62864
|
+
try {
|
|
62865
|
+
const d = new Date(raw);
|
|
62866
|
+
return d.toLocaleString();
|
|
62867
|
+
}
|
|
62868
|
+
catch {
|
|
62869
|
+
return raw;
|
|
62870
|
+
}
|
|
62871
|
+
}
|
|
62872
|
+
/** Label umana per il bottone/header in base al `kind`. */
|
|
62873
|
+
proposedActionTitle(a) {
|
|
62874
|
+
if (!a)
|
|
62875
|
+
return '';
|
|
62876
|
+
// Stessa guard di `contextCueTooltip`: chiamato come binding del template,
|
|
62877
|
+
// soggetto al CD durante route transition con WtoolboxService.translationService
|
|
62878
|
+
// potenzialmente undefined. Fallback al kind raw cosi' l'utente vede comunque
|
|
62879
|
+
// qualcosa di sensato anche prima del bootstrap i18n.
|
|
62880
|
+
const ts = WtoolboxService.translationService;
|
|
62881
|
+
if (!ts || typeof ts.instant !== 'function')
|
|
62882
|
+
return String(a.kind ?? '');
|
|
62883
|
+
// Chiave i18n per ognuno dei 10 kind documentati in callback-cookbook.md.
|
|
62884
|
+
// Per kind sconosciuti (futuri o malformati) fallback a una chiave
|
|
62885
|
+
// generica con interpolation {kind}.
|
|
62886
|
+
const knownKinds = new Set([
|
|
62887
|
+
'toolbar_action', 'row_action', 'table_style', 'column_style',
|
|
62888
|
+
'display_formula', 'form_title_formula', 'default_value_callback',
|
|
62889
|
+
'custom_validation', 'selection_changed', 'lifecycle_callback',
|
|
62890
|
+
'simple_metadata_update', 'designer_inject',
|
|
62891
|
+
'sql_metadata_field', 'metadata_column_create',
|
|
62892
|
+
]);
|
|
62893
|
+
if (knownKinds.has(a.kind)) {
|
|
62894
|
+
return ts.instant(`rag_chatbot.action_box.title.${a.kind}`);
|
|
62895
|
+
}
|
|
62896
|
+
// instant(key) accetta SOLO 1 argomento: interpolation manuale.
|
|
62897
|
+
// Regex accetta sia {kind} (single brace, format script upsert)
|
|
62898
|
+
// sia {{kind}} (double brace, format Angular template) per robustezza.
|
|
62899
|
+
const tpl = ts.instant('rag_chatbot.action_box.title.unknown');
|
|
62900
|
+
return String(tpl).replace(/\{\{?\s*kind\s*\}?\}/g, String(a.kind ?? ''));
|
|
62901
|
+
}
|
|
62902
|
+
/**
|
|
62903
|
+
* Estrae il body del code/template della proposta indipendentemente dal kind:
|
|
62904
|
+
* - toolbar_action / row_action / default_value / custom_validation /
|
|
62905
|
+
* selection_changed / lifecycle / form_title → `callback_js` / `formula_js`
|
|
62906
|
+
* - table_style / column_style → `condition_js`
|
|
62907
|
+
* - display_formula → `template_html` (Angular markup)
|
|
62908
|
+
* o `formula_js` (legacy backward-compat)
|
|
62909
|
+
* - sql_metadata_field → `sql_snippet` (SQL fragment)
|
|
62910
|
+
* Ritorna stringa vuota se nessun campo body e' presente.
|
|
62911
|
+
*/
|
|
62912
|
+
proposedActionJsBody(a) {
|
|
62913
|
+
if (!a)
|
|
62914
|
+
return '';
|
|
62915
|
+
return a.callback_js
|
|
62916
|
+
|| a.condition_js
|
|
62917
|
+
|| a.template_html
|
|
62918
|
+
|| a.formula_js
|
|
62919
|
+
|| a.sql_snippet
|
|
62920
|
+
|| '';
|
|
62921
|
+
}
|
|
62922
|
+
/**
|
|
62923
|
+
* Campi NON-body della proposta che e' utile mostrare all'utente accanto
|
|
62924
|
+
* al codice (es. per `table_style` la classe CSS o l'inline-style che verra'
|
|
62925
|
+
* applicato alla `<tr>` quando il callback ritorna true; per `toolbar_action`
|
|
62926
|
+
* la label del bottone; per `sql_metadata_field` il field target). Per ogni
|
|
62927
|
+
* kind ritorna 0..N coppie `{label, value}`. Il template li renderizza in
|
|
62928
|
+
* lettura sopra la textarea del body. Mantenere allineato alla definizione
|
|
62929
|
+
* tool in `rag_tools.json`: quando si aggiunge un campo importante a un kind
|
|
62930
|
+
* (es. `inline_style` su `propose_table_style`), aggiungerlo anche qui per
|
|
62931
|
+
* non far perdere visibilita' all'utente sulla decisione del LLM.
|
|
62932
|
+
*/
|
|
62933
|
+
proposedActionExtraFields(a) {
|
|
62934
|
+
if (!a)
|
|
62935
|
+
return [];
|
|
62936
|
+
const out = [];
|
|
62937
|
+
const get = (k) => a[k];
|
|
62938
|
+
switch (a.kind) {
|
|
62939
|
+
case 'table_style':
|
|
62940
|
+
case 'column_style': {
|
|
62941
|
+
const cssClass = get('css_class');
|
|
62942
|
+
const isInline = !!cssClass && cssClass.startsWith('style:');
|
|
62943
|
+
out.push({
|
|
62944
|
+
label: isInline ? 'Stile inline' : 'Classe CSS',
|
|
62945
|
+
fieldName: 'css_class',
|
|
62946
|
+
value: isInline ? cssClass.substring('style:'.length).trim() : (cssClass || ''),
|
|
62947
|
+
isInlineStyle: isInline,
|
|
62948
|
+
});
|
|
62949
|
+
break;
|
|
62950
|
+
}
|
|
62951
|
+
case 'toolbar_action':
|
|
62952
|
+
case 'row_action': {
|
|
62953
|
+
out.push({ label: 'Etichetta', fieldName: 'label', value: get('label') || '' });
|
|
62954
|
+
out.push({ label: 'Icona', fieldName: 'icon', value: get('icon') || '' });
|
|
62955
|
+
break;
|
|
62956
|
+
}
|
|
62957
|
+
case 'sql_metadata_field': {
|
|
62958
|
+
out.push({ label: 'Campo metadata', fieldName: 'field_name', value: get('field_name') || '' });
|
|
62959
|
+
out.push({ label: 'Tabella', fieldName: 'target_table', value: get('target_table') || '' });
|
|
62960
|
+
break;
|
|
62961
|
+
}
|
|
62962
|
+
case 'metadata_column_create': {
|
|
62963
|
+
out.push({ label: 'Colonna', fieldName: 'column_name', value: get('column_name') || '' });
|
|
62964
|
+
out.push({ label: 'Display', fieldName: 'display_string', value: get('display_string') || '' });
|
|
62965
|
+
out.push({ label: 'Tipo UI', fieldName: 'ui_column_type', value: get('ui_column_type') || '' });
|
|
62966
|
+
break;
|
|
62967
|
+
}
|
|
62968
|
+
}
|
|
62969
|
+
return out;
|
|
62970
|
+
}
|
|
62971
|
+
/**
|
|
62972
|
+
* Nome del campo nella `RagProposedAction` che porta il body editabile della
|
|
62973
|
+
* textarea (callback / template / sql snippet). Speculare a
|
|
62974
|
+
* `proposedActionJsBody` ma ritorna l'identificatore del field, cosi' il
|
|
62975
|
+
* `(ngModelChange)` puo' scrivere su `a[name]`. Default `callback_js` per i
|
|
62976
|
+
* kind che non hanno un body editabile (no-op all'edit).
|
|
62977
|
+
*/
|
|
62978
|
+
proposedActionJsBodyFieldName(a) {
|
|
62979
|
+
if (!a)
|
|
62980
|
+
return 'callback_js';
|
|
62981
|
+
switch (a.kind) {
|
|
62982
|
+
case 'table_style':
|
|
62983
|
+
case 'column_style':
|
|
62984
|
+
return 'condition_js';
|
|
62985
|
+
case 'display_formula':
|
|
62986
|
+
// Backward-compat: template_html nuovo, formula_js legacy.
|
|
62987
|
+
return a.template_html !== undefined ? 'template_html' : 'formula_js';
|
|
62988
|
+
case 'form_title_formula':
|
|
62989
|
+
return 'formula_js';
|
|
62990
|
+
case 'sql_metadata_field':
|
|
62991
|
+
return 'sql_snippet';
|
|
62992
|
+
default:
|
|
62993
|
+
return 'callback_js';
|
|
62994
|
+
}
|
|
62995
|
+
}
|
|
62996
|
+
/**
|
|
62997
|
+
* Setter chiamato da `(ngModelChange)` su un campo extra (es. classe/stile).
|
|
62998
|
+
* Per `css_class` con `isInlineStyle=true` ri-prepende il prefisso `style:`
|
|
62999
|
+
* cosi' la convenzione persistita resta omogenea (vedi
|
|
63000
|
+
* `dynamic-template.component.ts:getTableInlineStyle`). Segna il turno come
|
|
63001
|
+
* `proposedActionDirty=true` cosi' `onApplyAction` chiama il PATCH backend
|
|
63002
|
+
* prima dell'Apply.
|
|
63003
|
+
*/
|
|
63004
|
+
onProposedExtraFieldEdit(turn, extra, newValue) {
|
|
63005
|
+
if (!turn?.proposedAction)
|
|
63006
|
+
return;
|
|
63007
|
+
let stored = newValue;
|
|
63008
|
+
if (extra.fieldName === 'css_class' && extra.isInlineStyle) {
|
|
63009
|
+
stored = 'style:' + (newValue || '').trim();
|
|
63010
|
+
}
|
|
63011
|
+
turn.proposedAction[extra.fieldName] = stored;
|
|
63012
|
+
turn.proposedActionDirty = true;
|
|
63013
|
+
}
|
|
63014
|
+
/**
|
|
63015
|
+
* Setter per la textarea del body (callback_js / condition_js / template_html
|
|
63016
|
+
* / formula_js / sql_snippet). Aggiorna il field corretto in base al kind.
|
|
63017
|
+
*/
|
|
63018
|
+
onProposedBodyEdit(turn, newValue) {
|
|
63019
|
+
if (!turn?.proposedAction)
|
|
63020
|
+
return;
|
|
63021
|
+
const fieldName = this.proposedActionJsBodyFieldName(turn.proposedAction);
|
|
63022
|
+
turn.proposedAction[fieldName] = newValue;
|
|
63023
|
+
turn.proposedActionDirty = true;
|
|
63024
|
+
}
|
|
63025
|
+
/**
|
|
63026
|
+
* Stessa `codeContext` che il metadata-editor usa per editare il callback
|
|
63027
|
+
* sullo stesso campo metadata. Permette al Monaco TS provider di applicare
|
|
63028
|
+
* autocomplete + type-check come se l'utente stesse editando in metadata-editor.
|
|
63029
|
+
* Centralizzato qui per kind: future aggiunte = entry nella map.
|
|
63030
|
+
*/
|
|
63031
|
+
proposedActionCodeContext(a) {
|
|
63032
|
+
if (!a)
|
|
63033
|
+
return '';
|
|
63034
|
+
// Le firme corrispondono a quelle documentate in docs/pages/callback-cookbook.md
|
|
63035
|
+
// (tabella "Contratto dei callback").
|
|
63036
|
+
switch (a.kind) {
|
|
63037
|
+
case 'toolbar_action':
|
|
63038
|
+
// (datasource, metaInfo, record, event, wtoolbox)
|
|
63039
|
+
return 'var datasource: DataSourceComponent; var metaInfo: MetaInfo; var record: {[key: string]: BehaviorSubject<any>}; var event: any; var wtoolbox: typeof WtoolboxService';
|
|
63040
|
+
case 'row_action':
|
|
63041
|
+
// (datasource, record, event, field, wtoolbox)
|
|
63042
|
+
return 'var datasource: DataSourceComponent; var record: {[key: string]: BehaviorSubject<any>}; var event: any; var field: MetadatiColonna; var wtoolbox: typeof WtoolboxService';
|
|
63043
|
+
case 'display_formula':
|
|
63044
|
+
// mc_ui_grid_column_data_template scope: Angular template markup, scope=rowData (oggetto riga raw, NO BehaviorSubject).
|
|
63045
|
+
// Monaco lo renderizza come HTML/Angular template - il "type context" qui e' solo informativo per autocomplete fields.
|
|
63046
|
+
return 'var rowData: {[key: string]: any}';
|
|
63047
|
+
case 'form_title_formula':
|
|
63048
|
+
// mddisplayformula scope: (metaInfo, record, datasource, wtoolbox)
|
|
63049
|
+
return 'var metaInfo: MetaInfo; var record: {[key: string]: BehaviorSubject<any>}; var datasource: DataSourceComponent; var wtoolbox: typeof WtoolboxService';
|
|
63050
|
+
case 'default_value_callback':
|
|
63051
|
+
// mcdefaultvaluecallback scope: (metaInfo, record, datasource, wtoolbox)
|
|
63052
|
+
return 'var metaInfo: MetaInfo; var record: {[key: string]: BehaviorSubject<any>}; var datasource: DataSourceComponent; var wtoolbox: typeof WtoolboxService';
|
|
63053
|
+
case 'custom_validation':
|
|
63054
|
+
// mc_validation_custom_callback scope: (valore, record, validateResult, ...)
|
|
63055
|
+
return 'var valore: any; var record: {[key: string]: BehaviorSubject<any>}; var validateResult: (ok: boolean, message?: string) => void';
|
|
63056
|
+
case 'selection_changed':
|
|
63057
|
+
// mcslctionchangedcustomfunction scope: (record, value, datasource, wtoolbox)
|
|
63058
|
+
return 'var record: {[key: string]: BehaviorSubject<any>}; var value: any; var datasource: DataSourceComponent; var wtoolbox: typeof WtoolboxService';
|
|
63059
|
+
case 'lifecycle_callback':
|
|
63060
|
+
// mdbeforesave/mdaftersave/mdafterload scope: (record, datasource, wtoolbox)
|
|
63061
|
+
return 'var record: {[key: string]: BehaviorSubject<any>}; var datasource: DataSourceComponent; var wtoolbox: typeof WtoolboxService';
|
|
63062
|
+
case 'table_style':
|
|
63063
|
+
case 'column_style':
|
|
63064
|
+
// mustattributevalue / musc_attribute_value scope: solo record (dataItem)
|
|
63065
|
+
return 'var record: any';
|
|
63066
|
+
default:
|
|
63067
|
+
return '';
|
|
63068
|
+
}
|
|
63069
|
+
}
|
|
63070
|
+
/**
|
|
63071
|
+
* Cache field+record sintetici per `<wuic-code-editor>` indicizzata per messageId.
|
|
63072
|
+
* MOTIVATION: il code-editor accetta `field: MetadatiColonna` e
|
|
63073
|
+
* `record: { [k]: BehaviorSubject<any> }`; ricostruirli ad ogni ciclo di CD
|
|
63074
|
+
* causerebbe re-init pesanti del Monaco (riload types, ecc). Cache stabile per
|
|
63075
|
+
* messageId -> stesse referenze -> niente re-init.
|
|
63076
|
+
*/
|
|
63077
|
+
proposedActionEditorCache = new Map();
|
|
63078
|
+
/**
|
|
63079
|
+
* Costruisce (o riusa cached) il `MetadatiColonna` sintetico da passare al
|
|
63080
|
+
* `<wuic-code-editor>` per il box proposta. Inietta il `codeContext` corretto
|
|
63081
|
+
* per il `kind` cosi' il TSProvider Monaco applica autocomplete come nel
|
|
63082
|
+
* metadata-editor stesso. L'editor e' EDITABILE (readOnly:false): le modifiche
|
|
63083
|
+
* vengono propagate a `turn.proposedAction[bodyFieldName]` tramite subscription
|
|
63084
|
+
* al BehaviorSubject del record, e segnano il turno come dirty cosi'
|
|
63085
|
+
* `onApplyAction` chiama il PATCH backend prima dell'Apply.
|
|
63086
|
+
*/
|
|
63087
|
+
getProposedActionField(turn) {
|
|
63088
|
+
if (!turn?.messageId || !turn.proposedAction)
|
|
63089
|
+
return new MetadatiColonna('');
|
|
63090
|
+
const cached = this.proposedActionEditorCache.get(turn.messageId);
|
|
63091
|
+
if (cached)
|
|
63092
|
+
return cached.field;
|
|
63093
|
+
const field = new MetadatiColonna('');
|
|
63094
|
+
field.mc_nome_colonna = 'callback_js';
|
|
63095
|
+
field.mc_ui_column_type = 'code_editor';
|
|
63096
|
+
field.extras = {
|
|
63097
|
+
customEditorConfig: {
|
|
63098
|
+
language: 'typescript',
|
|
63099
|
+
codeContext: this.proposedActionCodeContext(turn.proposedAction),
|
|
63100
|
+
readOnly: false,
|
|
63101
|
+
}
|
|
63102
|
+
};
|
|
63103
|
+
// Body JS unificato: per i kind che usano `condition_js` (table_style)
|
|
63104
|
+
// o `formula_js` (display_formula) il Monaco editor riceve comunque il
|
|
63105
|
+
// contenuto giusto, e il `field.mc_nome_colonna='callback_js'` resta
|
|
63106
|
+
// arbitrario (e' solo l'etichetta della cella nel record).
|
|
63107
|
+
const jsBody = this.proposedActionJsBody(turn.proposedAction);
|
|
63108
|
+
const recordSubj = new BehaviorSubject(jsBody);
|
|
63109
|
+
const record = { callback_js: recordSubj };
|
|
63110
|
+
// Subscription: ogni edit Monaco emette sul BehaviorSubject. Propaghiamo a
|
|
63111
|
+
// `turn.proposedAction[bodyFieldName]` cosi' textarea e Monaco condividono
|
|
63112
|
+
// la stessa fonte di verita'. Skip della prima emit (BehaviorSubject emette
|
|
63113
|
+
// subito il valore iniziale, che e' gia' nel proposedAction).
|
|
63114
|
+
let firstEmit = true;
|
|
63115
|
+
const bodySubscription = recordSubj.subscribe((val) => {
|
|
63116
|
+
if (firstEmit) {
|
|
63117
|
+
firstEmit = false;
|
|
63118
|
+
return;
|
|
63119
|
+
}
|
|
63120
|
+
if (!turn.proposedAction)
|
|
63121
|
+
return;
|
|
63122
|
+
const fieldName = this.proposedActionJsBodyFieldName(turn.proposedAction);
|
|
63123
|
+
const current = turn.proposedAction[fieldName];
|
|
63124
|
+
if (current === val)
|
|
63125
|
+
return; // no-op se non e' cambiato (evita dirty spurio)
|
|
63126
|
+
turn.proposedAction[fieldName] = val;
|
|
63127
|
+
turn.proposedActionDirty = true;
|
|
63128
|
+
this.cdr.markForCheck();
|
|
63129
|
+
});
|
|
63130
|
+
this.proposedActionEditorCache.set(turn.messageId, { field, record, bodySubscription });
|
|
63131
|
+
return field;
|
|
63132
|
+
}
|
|
63133
|
+
/** Compagno di `getProposedActionField`: ritorna lo stesso `record` cached. */
|
|
63134
|
+
getProposedActionRecord(turn) {
|
|
63135
|
+
if (!turn?.messageId || !turn.proposedAction)
|
|
63136
|
+
return {};
|
|
63137
|
+
const cached = this.proposedActionEditorCache.get(turn.messageId);
|
|
63138
|
+
if (cached)
|
|
63139
|
+
return cached.record;
|
|
63140
|
+
// forza inizializzazione tramite getProposedActionField + ri-prende cached
|
|
63141
|
+
this.getProposedActionField(turn);
|
|
63142
|
+
return this.proposedActionEditorCache.get(turn.messageId)?.record || {};
|
|
63143
|
+
}
|
|
63144
|
+
/**
|
|
63145
|
+
* Toggle textarea (default) <-> Monaco editor avanzato per il callback proposto.
|
|
63146
|
+
* Pattern `<lib-code-area-editor>`: textarea istantanea, Monaco on-demand via
|
|
63147
|
+
* `@defer (when turn.editorMode)`. Entrando in Monaco ri-sincronizza il
|
|
63148
|
+
* BehaviorSubject del record cached col valore corrente di `proposedAction`
|
|
63149
|
+
* (potrebbe essere stato editato dalla textarea nel frattempo, vedi
|
|
63150
|
+
* `onProposedBodyEdit`).
|
|
63151
|
+
*/
|
|
63152
|
+
toggleEditorMode(turn) {
|
|
63153
|
+
turn.editorMode = !turn.editorMode;
|
|
63154
|
+
if (turn.editorMode && turn.messageId) {
|
|
63155
|
+
const cached = this.proposedActionEditorCache.get(turn.messageId);
|
|
63156
|
+
if (cached) {
|
|
63157
|
+
const current = this.proposedActionJsBody(turn.proposedAction);
|
|
63158
|
+
// bracket notation (TS4111: index-signature property).
|
|
63159
|
+
const subj = cached.record['callback_js'];
|
|
63160
|
+
if (subj && subj.value !== current) {
|
|
63161
|
+
subj.next(current);
|
|
63162
|
+
}
|
|
63163
|
+
}
|
|
63164
|
+
}
|
|
63165
|
+
this.cdr.markForCheck();
|
|
63166
|
+
}
|
|
63167
|
+
/** True se il turn ha una proposta non ancora applicata (mostra "Applica"). */
|
|
63168
|
+
canApplyAction(turn) {
|
|
63169
|
+
return !!turn?.proposedAction && !turn.proposedActionAppliedAt && !turn.applyingAction;
|
|
63170
|
+
}
|
|
63171
|
+
/**
|
|
63172
|
+
* Bottone "Applica": chiama POST /api/Rag/ApplyAction con conferma. Il server
|
|
63173
|
+
* re-legge la proposta dal DB (anti-tampering del callback_js), checka admin,
|
|
63174
|
+
* INSERT su metadata, InvalidateMetadataCachesAndSetVersion. La proposta viene
|
|
63175
|
+
* marcata come applicata in DB.
|
|
63176
|
+
*/
|
|
63177
|
+
async onApplyAction(turn) {
|
|
63178
|
+
if (!this.canApplyAction(turn) || !turn.messageId)
|
|
63179
|
+
return;
|
|
63180
|
+
const action = turn.proposedAction;
|
|
63181
|
+
// ---- Client-only kinds (host registry) ---------------------------------
|
|
63182
|
+
// Alcuni kind (es. `designer_inject`) NON vengono applicati dal backend
|
|
63183
|
+
// ma da un handler registrato da un componente host (es. designer.component)
|
|
63184
|
+
// tramite `ChatbotHostRegistryService.register(kind, handler)`. In tal
|
|
63185
|
+
// caso bypassiamo `/api/Rag/ApplyAction` e deleghiamo l'esecuzione al
|
|
63186
|
+
// handler client. Il `proposedActionAppliedAt` viene marcato qui localmente
|
|
63187
|
+
// perche' il server non sa che l'azione e' stata applicata.
|
|
63188
|
+
const hostHandler = this.chatbotHostRegistry.get(action.kind);
|
|
63189
|
+
// Conferma (best-effort: se promptDialog framework non disponibile, fallback a confirm).
|
|
63190
|
+
// Per host-handler: messaggio piu' chiaro che la modifica e' visibile SOLO al designer
|
|
63191
|
+
// corrente fino al save esplicito (vs. apply backend che e' globale).
|
|
63192
|
+
let confirmed = true;
|
|
63193
|
+
try {
|
|
63194
|
+
const tools = WtoolboxService;
|
|
63195
|
+
if (typeof tools.promptDialog === 'function') {
|
|
63196
|
+
const confirmMsg = hostHandler
|
|
63197
|
+
? `Applicare '${action.label || action.kind}' al designer corrente? La modifica sara' visibile solo a te fino al save esplicito della dashboard.`
|
|
63198
|
+
: `Applicare '${action.label || action.kind}' sulla route '${action.route || '?'}'? L'azione sara' visibile a tutti gli utenti dopo refresh.`;
|
|
63199
|
+
const r = await tools.promptDialog({
|
|
63200
|
+
title: this.proposedActionTitle(action),
|
|
63201
|
+
message: confirmMsg,
|
|
63202
|
+
confirmLabel: 'Applica',
|
|
63203
|
+
cancelLabel: 'Annulla',
|
|
63204
|
+
});
|
|
63205
|
+
confirmed = !!r && r.confirmed !== false;
|
|
63206
|
+
}
|
|
63207
|
+
}
|
|
63208
|
+
catch {
|
|
63209
|
+
confirmed = true;
|
|
63210
|
+
}
|
|
63211
|
+
if (!confirmed)
|
|
63212
|
+
return;
|
|
63213
|
+
turn.applyingAction = true;
|
|
63214
|
+
turn.applyError = null;
|
|
63215
|
+
this.cdr.markForCheck();
|
|
63216
|
+
try {
|
|
63217
|
+
if (hostHandler) {
|
|
63218
|
+
// ---- Branch CLIENT-ONLY -------------------------------------------
|
|
63219
|
+
const result = await hostHandler(action);
|
|
63220
|
+
if (result?.ok) {
|
|
63221
|
+
turn.proposedActionAppliedAt = new Date().toISOString();
|
|
63222
|
+
try {
|
|
63223
|
+
WtoolboxService.messageNotificationService?.add?.({
|
|
63224
|
+
severity: 'success',
|
|
63225
|
+
summary: 'Azione applicata',
|
|
63226
|
+
detail: result.message || `${action.label || action.kind} applicata al designer.`,
|
|
63227
|
+
});
|
|
63228
|
+
}
|
|
63229
|
+
catch { /* notif opzionale */ }
|
|
63230
|
+
}
|
|
63231
|
+
else {
|
|
63232
|
+
turn.applyError = result?.message || 'host-handler-failed';
|
|
63233
|
+
}
|
|
63234
|
+
}
|
|
63235
|
+
else {
|
|
63236
|
+
// ---- Branch BACKEND (default) -------------------------------------
|
|
63237
|
+
// Pre-flight: se l'utente ha editato classe/stile/callback in UI, sync
|
|
63238
|
+
// il `proposed_action_json` del messaggio sul DB. Il backend ApplyAction
|
|
63239
|
+
// rilegge dal DB (security per evitare tampering, vedi commento in
|
|
63240
|
+
// RagController.ApplyAction). Senza questo PATCH le modifiche utente
|
|
63241
|
+
// andrebbero perse all'apply.
|
|
63242
|
+
if (turn.proposedActionDirty && turn.proposedAction) {
|
|
63243
|
+
try {
|
|
63244
|
+
const upd = await this.ragService.updateProposedActionAsync({
|
|
63245
|
+
messageId: turn.messageId,
|
|
63246
|
+
proposedAction: turn.proposedAction,
|
|
63247
|
+
});
|
|
63248
|
+
if (upd?.ok) {
|
|
63249
|
+
turn.proposedActionDirty = false;
|
|
63250
|
+
}
|
|
63251
|
+
else {
|
|
63252
|
+
turn.applyError = upd?.error || 'proposed-action-update-failed';
|
|
63253
|
+
return;
|
|
63254
|
+
}
|
|
63255
|
+
}
|
|
63256
|
+
catch (patchErr) {
|
|
63257
|
+
turn.applyError = this.extractErrorMessage(patchErr);
|
|
63258
|
+
return;
|
|
63259
|
+
}
|
|
63260
|
+
}
|
|
63261
|
+
const resp = await this.ragService.applyProposedActionAsync({ messageId: turn.messageId });
|
|
63262
|
+
if (resp?.ok) {
|
|
63263
|
+
// Mark applied localmente (il server ha gia' persistito audit)
|
|
63264
|
+
turn.proposedActionAppliedAt = new Date().toISOString();
|
|
63265
|
+
// ─── Invalidate metadata caches (server + client, route-scoped) ─────
|
|
63266
|
+
// Backend RagController.ApplyAction GIA' chiama
|
|
63267
|
+
// RawHelpers.InvalidateMetadataCachesAndSetVersion(true), che bump-a la
|
|
63268
|
+
// projectMetadataVersion server-side. Ma il frontend ha la Dexie
|
|
63269
|
+
// MetadataProviderService.MetaDB persistente che mostra i metadati
|
|
63270
|
+
// vecchi finche' qualcuno non la flusha esplicitamente (bug UX riportato
|
|
63271
|
+
// 2026-06-09: l'utente fa Apply, riapre il dropdown "Metadati correlati"
|
|
63272
|
+
// -> "Stili colonna" mostra "Nessun record trovato" anche se la riga
|
|
63273
|
+
// e' stata salvata server-side).
|
|
63274
|
+
//
|
|
63275
|
+
// SCOPE LIMITATO (richiesta utente):
|
|
63276
|
+
// 1. Eseguiamo il flush SOLO se l'utente sta lavorando in un contesto
|
|
63277
|
+
// route business compatibile con l'action (cioe' buildRouteContext
|
|
63278
|
+
// ha ritornato non-null E action.route corrisponde alla route
|
|
63279
|
+
// corrente). In designer / chatbot fullscreen / route senza
|
|
63280
|
+
// contesto, niente flush: alla prossima navigazione il provider
|
|
63281
|
+
// ricaricherà i metadati comunque via projectMetadataVersion check.
|
|
63282
|
+
// 2. Cancelliamo SOLO le righe Dexie della specifica route impattata
|
|
63283
|
+
// (vedi MetadataProviderService.deleteRouteFromCache: 8 tabelle
|
|
63284
|
+
// filtrate per md_route_name + md_id + mc_id + CG_Id), evitando
|
|
63285
|
+
// il flush totale che e' costoso (~MB di re-fetch su F5).
|
|
63286
|
+
const actionRoute = (action.route || '').trim().toLowerCase();
|
|
63287
|
+
const currentRoute = (() => {
|
|
63288
|
+
try {
|
|
63289
|
+
const url = String(this.router?.url || '').split('?')[0].split('#')[0];
|
|
63290
|
+
return url.replace(/^\/+/, '').split('/')[0].trim().toLowerCase();
|
|
63291
|
+
}
|
|
63292
|
+
catch {
|
|
63293
|
+
return '';
|
|
63294
|
+
}
|
|
63295
|
+
})();
|
|
63296
|
+
const sameRoute = !!actionRoute && actionRoute === currentRoute;
|
|
63297
|
+
if (sameRoute) {
|
|
63298
|
+
// POST invalidateMetadataRuntime: idempotente (server-side ha gia'
|
|
63299
|
+
// bumpato la versione, ritorna OK al volo). Pattern canonico
|
|
63300
|
+
// pivot-builder.component.ts:227.
|
|
63301
|
+
try {
|
|
63302
|
+
await WtoolboxService.http.post(WtoolboxService.appSettings.global_root_url + 'MetaService.invalidateMetadataRuntime', {}, { withCredentials: true }).toPromise();
|
|
63303
|
+
}
|
|
63304
|
+
catch { /* non-fatal: backend ApplyAction l'ha gia' fatto server-side */ }
|
|
63305
|
+
// Flush mirato della Dexie per la sola route impattata.
|
|
63306
|
+
try {
|
|
63307
|
+
await MetadataProviderService.deleteRouteFromCache(actionRoute);
|
|
63308
|
+
}
|
|
63309
|
+
catch { }
|
|
63310
|
+
// Reset cache in-memory custom-attributes (pattern documentato in
|
|
63311
|
+
// custom-attributes.service.ts:199 — "Reset cache chiamato dopo
|
|
63312
|
+
// invalidateMetadataRuntime").
|
|
63313
|
+
try {
|
|
63314
|
+
WtoolboxService.customAttributesService?.clearCache?.();
|
|
63315
|
+
}
|
|
63316
|
+
catch { }
|
|
63317
|
+
}
|
|
63318
|
+
try {
|
|
63319
|
+
WtoolboxService.messageNotificationService?.add?.({
|
|
63320
|
+
severity: 'success',
|
|
63321
|
+
summary: 'Azione applicata',
|
|
63322
|
+
detail: sameRoute
|
|
63323
|
+
? `${action.label || action.kind} creata su '${action.route}'. Ricarica la pagina (F5) per vederla.`
|
|
63324
|
+
: `${action.label || action.kind} creata su '${action.route}'. Naviga alla route per vederla.`,
|
|
63325
|
+
});
|
|
63326
|
+
}
|
|
63327
|
+
catch { /* notif opzionale */ }
|
|
63328
|
+
}
|
|
63329
|
+
else {
|
|
63330
|
+
turn.applyError = resp?.error || 'apply-failed';
|
|
63331
|
+
}
|
|
63332
|
+
}
|
|
63333
|
+
}
|
|
63334
|
+
catch (err) {
|
|
63335
|
+
turn.applyError = this.extractErrorMessage(err);
|
|
63336
|
+
this.errorOccurred.emit({ message: turn.applyError, details: err });
|
|
63337
|
+
}
|
|
63338
|
+
finally {
|
|
63339
|
+
turn.applyingAction = false;
|
|
63340
|
+
this.cdr.markForCheck();
|
|
63341
|
+
}
|
|
63342
|
+
}
|
|
61484
63343
|
trimHistory() {
|
|
61485
63344
|
if (this.history.length > this.maxHistory) {
|
|
61486
63345
|
this.history = this.history.slice(this.history.length - this.maxHistory);
|
|
61487
63346
|
}
|
|
61488
63347
|
}
|
|
63348
|
+
/**
|
|
63349
|
+
* Costruisce un riepilogo testuale della pagina/route corrente che viene
|
|
63350
|
+
* iniettato nel system prompt dell'LLM. Permette di generare snippet che
|
|
63351
|
+
* usano i nomi REALI delle colonne (es. `record.CityName.value`) invece di
|
|
63352
|
+
* placeholder generici.
|
|
63353
|
+
*
|
|
63354
|
+
* Formato (compatto, leggibile dall'LLM):
|
|
63355
|
+
* ```
|
|
63356
|
+
* Route corrente: cities
|
|
63357
|
+
* Tabella: Cities (md_id=42, mdtablename=Cities)
|
|
63358
|
+
* Colonne:
|
|
63359
|
+
* - CityName: type=text, required=yes (real column: CityName)
|
|
63360
|
+
* - StateProvince: type=lookupByID -> StateProvinces
|
|
63361
|
+
* - Location: type=text
|
|
63362
|
+
* - LatestRecordedPopulation: type=number
|
|
63363
|
+
* - btn_send: type=button (row action)
|
|
63364
|
+
* ```
|
|
63365
|
+
*
|
|
63366
|
+
* Ritorna `null` se: router assente, route non resolvibile, route NON e'
|
|
63367
|
+
* una business route (framework-docs/rag-chatbot/auth), o metadata
|
|
63368
|
+
* non disponibile in cache (l'utente probabilmente non e' su una list/edit
|
|
63369
|
+
* della route).
|
|
63370
|
+
*/
|
|
63371
|
+
async buildRouteContext() {
|
|
63372
|
+
if (!this.router)
|
|
63373
|
+
return null;
|
|
63374
|
+
const url = String(this.router.url || '').split('?')[0].split('#')[0];
|
|
63375
|
+
// Path completo della pagina SENZA slash iniziale: /cities/list -> 'cities/list',
|
|
63376
|
+
// /designer -> 'designer'. Il route_name (per metadata) e' il primo segment.
|
|
63377
|
+
const pagePath = url.replace(/^\/+/, '').trim();
|
|
63378
|
+
if (!pagePath)
|
|
63379
|
+
return null;
|
|
63380
|
+
// ---- Sezioni indipendenti, concatenate alla fine -----------------------
|
|
63381
|
+
// 1. PAGE SECTION (MINIMALE): SOLO la route/pagina corrente. Le colonne reali,
|
|
63382
|
+
// l'identita' SQL e i dettagli dei lookup NON sono inlineati (gonfiavano OGNI
|
|
63383
|
+
// richiesta a prescindere dal prompt): il LLM li recupera ON-DEMAND via il tool
|
|
63384
|
+
// `request_metadata_detail` (risolto dal backend, RagController.ResolveMetadataDetail).
|
|
63385
|
+
// Cosi' il contesto resta lean e scala a N dimensioni di metadata senza bloat.
|
|
63386
|
+
// 2. HOST STATE SECTION: dump dello state dei componenti host che hanno registrato
|
|
63387
|
+
// uno state provider in ChatbotHostRegistryService (es. designer.component dumpa il
|
|
63388
|
+
// tree dashboardElements). E' state CLIENT-only -> resta inlineato qui (il backend
|
|
63389
|
+
// non puo' risolverlo via retrieval). Permette i riferimenti deittici dell'utente.
|
|
63390
|
+
const sections = [];
|
|
63391
|
+
const pageSection = this.buildMinimalPageContext(pagePath);
|
|
63392
|
+
if (pageSection)
|
|
63393
|
+
sections.push(pageSection);
|
|
63394
|
+
const hostStateSection = this.buildHostStateContext();
|
|
63395
|
+
if (hostStateSection)
|
|
63396
|
+
sections.push(hostStateSection);
|
|
63397
|
+
return sections.length > 0 ? sections.join('\n\n') : null;
|
|
63398
|
+
}
|
|
63399
|
+
/**
|
|
63400
|
+
* Contesto MINIMALE della pagina: SOLO la route/pagina corrente (es. `cities/list`,
|
|
63401
|
+
* `cities/edit`, `designer`). I dettagli di metadata (elenco colonne reali, identita'
|
|
63402
|
+
* SQL schema/tabella, alias di join dei lookup) NON sono inlineati: il LLM li recupera
|
|
63403
|
+
* ON-DEMAND via il tool `request_metadata_detail` (detail='columns'|'lookup_columns'),
|
|
63404
|
+
* risolto lato backend da `RagController.ResolveMetadataDetail`.
|
|
63405
|
+
*
|
|
63406
|
+
* RAZIONALE: inlinare colonne + SQL identity gonfiava OGNI richiesta a prescindere dal
|
|
63407
|
+
* prompt (context window saturato, token sprecati). Col retrieval agentico il model
|
|
63408
|
+
* recupera SOLO cio' che il prompt richiede, e il meccanismo scala a N dimensioni di
|
|
63409
|
+
* metadata (physical_columns, related_routes, enum_values, ...) senza toccare questo
|
|
63410
|
+
* builder. Vale per qualunque pagina, incluse quelle framework (designer, ...): il
|
|
63411
|
+
* model puo' comunque targetizzare una route business via retrieval.
|
|
63412
|
+
*/
|
|
63413
|
+
buildMinimalPageContext(pagePath) {
|
|
63414
|
+
if (!pagePath)
|
|
63415
|
+
return null;
|
|
63416
|
+
// route_name = primo segment (la route metadata); l'azione (list/edit/dashboard) e' suffisso.
|
|
63417
|
+
const routeName = decodeURIComponent((pagePath.split('/')[0] || '')).trim();
|
|
63418
|
+
const lines = [];
|
|
63419
|
+
lines.push(`Route/pagina corrente: ${pagePath}`);
|
|
63420
|
+
if (routeName && routeName !== pagePath)
|
|
63421
|
+
lines.push(`route_name (per metadata/tool): ${routeName}`);
|
|
63422
|
+
lines.push(`Contesto minimale: l'elenco colonne, l'identita' SQL e i dettagli dei lookup NON sono qui. ` +
|
|
63423
|
+
`Per ottenerli usa il tool request_metadata_detail: {detail:'columns', route:'<route_name>'} per le ` +
|
|
63424
|
+
`colonne reali e l'identita' SQL (schema/tabella + full qualifier); ` +
|
|
63425
|
+
`{detail:'lookup_columns', route:'<route_name>', column:'<col>'} per l'alias di join e le colonne ` +
|
|
63426
|
+
`correlate di una colonna lookupByID.`);
|
|
63427
|
+
return lines.join('\n');
|
|
63428
|
+
}
|
|
63429
|
+
/**
|
|
63430
|
+
* Concatena lo state dump fornito dai componenti host che hanno registrato
|
|
63431
|
+
* un state provider per un proposed_action kind. Tipicamente designer.component
|
|
63432
|
+
* registra `designer_inject` con un tree dei dashboardElements (uniqueName +
|
|
63433
|
+
* inputs significativi), cosi' il LLM puo' identificare componenti via
|
|
63434
|
+
* uniqueName quando l'utente chiede modifiche tipo "cambia background
|
|
63435
|
+
* del riquadro in alto a destra".
|
|
63436
|
+
*
|
|
63437
|
+
* Ritorna `null` se nessun host ha registrato dump per il turn corrente.
|
|
63438
|
+
*/
|
|
63439
|
+
buildHostStateContext() {
|
|
63440
|
+
const kinds = this.chatbotHostRegistry.registeredStateKinds();
|
|
63441
|
+
if (!kinds || kinds.length === 0)
|
|
63442
|
+
return null;
|
|
63443
|
+
const sections = [];
|
|
63444
|
+
for (const kind of kinds) {
|
|
63445
|
+
const dump = this.chatbotHostRegistry.getStateDump(kind);
|
|
63446
|
+
if (dump && dump.trim().length > 0) {
|
|
63447
|
+
// Disambiguation hint: quando il dump contiene componenti del designer
|
|
63448
|
+
// (TABLE_/SPLITTER_/TABVIEW_/ACCORDION_/KPI_/...) prepende una nota
|
|
63449
|
+
// esplicita per il LLM. Senza questa nota, prompt come "aggiungi una
|
|
63450
|
+
// colonna alla tabella" vengono interpretati come "aggiungi una colonna
|
|
63451
|
+
// metadata SQL alla route business corrente" invece che "incrementa
|
|
63452
|
+
// cols sul componente TABLE del canvas designer".
|
|
63453
|
+
const dumpTrim = dump.trim();
|
|
63454
|
+
let header = `HOST STATE (${kind})`;
|
|
63455
|
+
if (kind === 'designer_inject') {
|
|
63456
|
+
header += ':\nNOTA AMBIGUITA\': i componenti elencati sotto sono nel CANVAS DESIGNER corrente. '
|
|
63457
|
+
+ 'Quando l\'utente parla di "tabella", "riga", "colonna", "splitter", "tab", "accordion", '
|
|
63458
|
+
+ '"kpi", "label", "div", ecc. si riferisce SEMPRE a questi uniqueName del canvas, NON a '
|
|
63459
|
+
+ 'tabelle/colonne metadata SQL business. Esempi:\n'
|
|
63460
|
+
+ ' - "aggiungi una colonna alla tabella" => set_property cols=cols+1 sul TABLE__N del canvas (NON propose_simple_metadata_update)\n'
|
|
63461
|
+
+ ' - "aggiungi una riga alla tabella" => set_property rows=rows+1\n'
|
|
63462
|
+
+ ' - "rimuovi una colonna/riga" => set_property cols=cols-1 / rows=rows-1\n'
|
|
63463
|
+
+ ' - "aggiungi un\'area allo splitter" => set_property areas=areas+1\n'
|
|
63464
|
+
+ ' - "aggiungi un tab al tabview" => inject TABPANEL con parent_unique_name=TABVIEW__N\n'
|
|
63465
|
+
+ ' - "aggiungi un item all\'accordion" => set_property items=items+1\n'
|
|
63466
|
+
+ ' - "cambia il colore della tabella" => set_property backgroundColor sul TABLE__N\n'
|
|
63467
|
+
+ 'Se invece l\'utente menziona ESPLICITAMENTE "metadata", "route", "datasource cities", '
|
|
63468
|
+
+ '"colonna SQL", "campo del database" => usa propose_simple_metadata_update.';
|
|
63469
|
+
}
|
|
63470
|
+
else {
|
|
63471
|
+
header += ':';
|
|
63472
|
+
}
|
|
63473
|
+
sections.push(`${header}\n${dumpTrim}`);
|
|
63474
|
+
}
|
|
63475
|
+
}
|
|
63476
|
+
return sections.length > 0 ? sections.join('\n\n') : null;
|
|
63477
|
+
}
|
|
61489
63478
|
scheduleScrollToBottom() {
|
|
61490
63479
|
if (typeof window === 'undefined')
|
|
61491
63480
|
return;
|
|
@@ -61514,67 +63503,86 @@ class WuicRagChatbotComponent {
|
|
|
61514
63503
|
return `${anyErr.status} ${anyErr.statusText}`;
|
|
61515
63504
|
return JSON.stringify(err);
|
|
61516
63505
|
}
|
|
61517
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotComponent, deps: [{ token: WuicRagService }, { token: i0.ChangeDetectorRef }, { token: i1$2.ActivatedRoute, optional: true }, { token: i1$2.Router, optional: true }, { token: NotificationRealtimeService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
61518
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: WuicRagChatbotComponent, isStandalone: true, selector: "wuic-rag-chatbot", inputs: { title: "title", mode: "mode", topK: "topK", showSources: "showSources", maxHistory: "maxHistory", placeholder: "placeholder", model: "model", chatHeight: "chatHeight", showClearButton: "showClearButton" }, outputs: { resultSelected: "resultSelected", errorOccurred: "errorOccurred", turnAdded: "turnAdded" }, viewQueries: [{ propertyName: "inputTextarea", first: true, predicate: ["inputTextarea"], descendants: true }, { propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }], ngImport: i0, template: "<div class=\"wuic-rag-chatbot\" [style.--chat-height]=\"chatHeight\">\n\n <!-- Header -->\n <div class=\"wuic-rag-chatbot__header\">\n <div class=\"wuic-rag-chatbot__title\">\n <i class=\"pi pi-comments wuic-rag-chatbot__title-icon\" aria-hidden=\"true\"></i>\n <span>{{ title }}</span>\n </div>\n <div class=\"wuic-rag-chatbot__header-actions\">\n <p-badge\n [value]=\"modeBadgeLabel\"\n [severity]=\"modeBadgeSeverity\"\n [pTooltip]=\"healthInfo\n ? ('chunks indicizzati: ' + healthInfo.docs_loaded + ' | modello: ' + healthInfo.default_model)\n : (healthError || 'connessione al server RAG...')\"\n tooltipPosition=\"bottom\">\n </p-badge>\n @if (showClearButton) {\n <p-button\n icon=\"pi pi-trash\"\n severity=\"secondary\"\n [text]=\"true\"\n [rounded]=\"true\"\n size=\"small\"\n [disabled]=\"history.length === 0 || loading\"\n (onClick)=\"onClearHistory()\"\n pTooltip=\"Svuota cronologia\"\n tooltipPosition=\"bottom\"\n ariaLabel=\"Svuota cronologia chat\">\n </p-button>\n }\n </div>\n </div>\n\n <!-- Health error banner -->\n @if (healthError) {\n <p-message severity=\"error\" [text]=\"'Server RAG non raggiungibile: ' + healthError\" styleClass=\"wuic-rag-chatbot__error-banner\"></p-message>\n }\n\n <!-- Chat history -->\n <div #scrollContainer class=\"wuic-rag-chatbot__history\" role=\"log\" aria-live=\"polite\">\n @if (history.length === 0) {\n <div class=\"wuic-rag-chatbot__empty\">\n <i class=\"pi pi-info-circle wuic-rag-chatbot__empty-icon\" aria-hidden=\"true\"></i>\n <p>Fai una domanda sul codebase WUIC, ad esempio:</p>\n <ul class=\"wuic-rag-chatbot__suggestions\">\n <li>Invalidare la cache metadata runtime sul server</li>\n <li>Spreadsheet per data entry massivo simile a excel</li>\n <li>Salvataggio di un report dal designer</li>\n </ul>\n </div>\n }\n\n @for (turn of history; track turn.messageId || $index) {\n <div class=\"wuic-rag-chatbot__turn\"\n [attr.data-message-id]=\"turn.messageId || null\"\n [class.wuic-rag-chatbot__turn--user]=\"turn.role === 'user'\"\n [class.wuic-rag-chatbot__turn--assistant]=\"turn.role === 'assistant'\">\n\n <div class=\"wuic-rag-chatbot__turn-header\">\n <i class=\"pi\"\n [class.pi-user]=\"turn.role === 'user'\"\n [class.pi-android]=\"turn.role === 'assistant'\"\n aria-hidden=\"true\"></i>\n <span>{{ turn.role === 'user' ? 'Tu' : 'Assistente' }}</span>\n @if (turn.mode) {\n <span class=\"wuic-rag-chatbot__turn-mode\">[{{ turn.mode === 'rag-llm' ? 'RAG+LLM' : 'retrieval' }}]</span>\n }\n </div>\n\n @if (turn.loading) {\n <div class=\"wuic-rag-chatbot__turn-loading\">\n <p-progressSpinner styleClass=\"wuic-rag-chatbot__spinner\" strokeWidth=\"3\" animationDuration=\".9s\"></p-progressSpinner>\n <span>Sto interrogando il codebase...</span>\n </div>\n } @else if (turn.errorMessage) {\n <p-message severity=\"error\" [text]=\"turn.errorMessage\" styleClass=\"wuic-rag-chatbot__turn-error\"></p-message>\n } @else {\n <div class=\"wuic-rag-chatbot__turn-content\">{{ turn.content }}</div>\n @if (turn.warning) {\n <p-message severity=\"warn\" [text]=\"turn.warning\" styleClass=\"wuic-rag-chatbot__turn-warning\"></p-message>\n }\n @if (showSources && turn.role === 'assistant' && turn.sources && turn.sources.length > 0) {\n @let dedupedSources = dedupSources(turn.sources);\n <div class=\"wuic-rag-chatbot__sources\">\n <div class=\"wuic-rag-chatbot__sources-label\">Fonti:</div>\n <div class=\"wuic-rag-chatbot__sources-list\">\n @for (source of dedupedSources; track source.chunk_id || source.rank) {\n <p-chip\n [label]=\"sourceLabel(source, dedupedSources)\"\n [icon]=\"isDocsSource(source) ? 'pi pi-book' : (isSkillSource(source) ? 'pi pi-wrench' : 'pi pi-file-edit')\"\n [removable]=\"false\"\n [pTooltip]=\"sourceTooltip(source)\"\n tooltipPosition=\"top\"\n [styleClass]=\"isDocsSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--docs'\n : (isSkillSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--skill'\n : 'wuic-rag-chatbot__source-chip')\"\n (click)=\"onSourceClick(source)\">\n </p-chip>\n }\n </div>\n </div>\n }\n @if (turn.tokensIn || turn.tokensOut) {\n <div class=\"wuic-rag-chatbot__tokens\">\n tokens in/out: {{ turn.tokensIn ?? '?' }}/{{ turn.tokensOut ?? '?' }}\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Input area -->\n <div class=\"wuic-rag-chatbot__input-area\">\n <textarea #inputTextarea\n pTextarea\n class=\"wuic-rag-chatbot__input\"\n [(ngModel)]=\"inputValue\"\n (keydown)=\"onKeyDown($event)\"\n [placeholder]=\"placeholder\"\n [disabled]=\"loading || !!healthError\"\n [autoResize]=\"true\"\n rows=\"1\"\n aria-label=\"Domanda per il chatbot codebase\">\n </textarea>\n <p-button\n icon=\"pi pi-send\"\n [disabled]=\"sendDisabled\"\n [loading]=\"loading\"\n (onClick)=\"onSend()\"\n severity=\"primary\"\n ariaLabel=\"Invia messaggio\">\n </p-button>\n </div>\n\n <!-- Dialog preview snippet sorgente.\n Mostra SOLO il contenuto del chunk (gia' embedded nella response RAG).\n NON espone il path del file ne' apre editor esterni: il codebase del\n framework vive sul repo dev e non deve essere linkato all'utente finale. -->\n @if (selectedSource) {\n <p-dialog\n [visible]=\"true\"\n [modal]=\"true\"\n [closable]=\"true\"\n [closeOnEscape]=\"true\"\n [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw' }\"\n [header]=\"sourceDialogTitle(selectedSource)\"\n styleClass=\"wuic-rag-chatbot__source-dialog\"\n (onHide)=\"closeSourcePreview()\">\n <div class=\"wuic-rag-chatbot__source-meta\">\n @if (sourceFilePath(selectedSource); as fpath) {\n <span class=\"wuic-rag-chatbot__source-tag wuic-rag-chatbot__source-tag--file\">\n <i class=\"pi pi-file\" aria-hidden=\"true\"></i>\n {{ fpath }}\n </span>\n }\n @if (selectedSource.symbol_type) {\n <span class=\"wuic-rag-chatbot__source-tag\">{{ selectedSource.symbol_type }}</span>\n }\n @if (selectedSource.start_line && selectedSource.end_line) {\n <span class=\"wuic-rag-chatbot__source-tag\">linee {{ selectedSource.start_line }}-{{ selectedSource.end_line }}</span>\n }\n @if (selectedSource.score_vector !== null && selectedSource.score_vector !== undefined) {\n <span class=\"wuic-rag-chatbot__source-tag\">vec {{ selectedSource.score_vector | number:'1.2-2' }}</span>\n }\n </div>\n <pre class=\"wuic-rag-chatbot__source-snippet\"><code>{{ selectedSource.snippet || '(snippet non disponibile)' }}</code></pre>\n </p-dialog>\n }\n\n</div>\n", styles: [".wuic-rag-chatbot{--wuic-rag-chatbot-radius: .5rem;--wuic-rag-chatbot-padding: .75rem;--wuic-rag-chatbot-gap: .5rem;--chat-height: 420px;display:flex;flex-direction:column;width:100%;height:auto;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:var(--wuic-rag-chatbot-radius);background-color:var(--p-content-background, transparent);color:inherit;overflow:hidden;font-family:inherit}.wuic-rag-chatbot__header{display:flex;align-items:center;justify-content:space-between;padding:var(--wuic-rag-chatbot-padding);border-bottom:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__title{display:flex;align-items:center;gap:.5rem;font-weight:600;color:inherit}.wuic-rag-chatbot__title-icon{color:var(--p-primary-color, #3b82f6)}.wuic-rag-chatbot__header-actions{display:flex;align-items:center;gap:.5rem}.wuic-rag-chatbot__error-banner{margin:var(--wuic-rag-chatbot-padding);margin-bottom:0}.wuic-rag-chatbot__history{flex:1 1 auto;height:var(--chat-height);overflow-y:auto;padding:var(--wuic-rag-chatbot-padding);display:flex;flex-direction:column;gap:var(--wuic-rag-chatbot-gap);background-color:transparent}.wuic-rag-chatbot__empty{margin:auto;text-align:center;color:var(--p-text-muted-color, #6b7280);max-width:80%;padding:1.5rem 0}.wuic-rag-chatbot__empty-icon{font-size:1.5rem;margin-bottom:.5rem;display:block}.wuic-rag-chatbot__suggestions{list-style:none;padding:0;margin:.75rem 0 0;display:flex;flex-direction:column;gap:.25rem;text-align:left}.wuic-rag-chatbot__suggestions li{padding:.35rem .75rem;border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:.375rem;font-size:.875rem}.wuic-rag-chatbot__turn{display:flex;flex-direction:column;gap:.35rem;padding:.65rem .85rem;border-radius:var(--wuic-rag-chatbot-radius);max-width:92%;color:inherit;background-color:transparent;border:1px solid var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__turn--user{align-self:flex-end;background-color:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-200, rgba(59, 130, 246, .25))}.wuic-rag-chatbot__turn--assistant{align-self:flex-start;background-color:var(--p-surface-50, rgba(0, 0, 0, .025))}.wuic-rag-chatbot__turn-header{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__turn-mode{margin-left:auto;font-family:ui-monospace,monospace;font-size:.7rem;opacity:.7}.wuic-rag-chatbot__turn-content{white-space:pre-wrap;word-break:break-word;line-height:1.45;font-size:.93rem;color:inherit}.wuic-rag-chatbot__turn-loading{display:flex;align-items:center;gap:.5rem;color:var(--p-text-muted-color, #6b7280);font-style:italic;font-size:.875rem}.wuic-rag-chatbot__spinner{width:1.25rem!important;height:1.25rem!important}.wuic-rag-chatbot__turn-error,.wuic-rag-chatbot__turn-warning{margin-top:.35rem}.wuic-rag-chatbot__sources{display:flex;flex-direction:column;gap:.35rem;margin-top:.5rem;padding-top:.5rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__sources-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.05em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__sources-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__source-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.wuic-rag-chatbot__source-chip:hover{transform:translateY(-1px);box-shadow:0 1px 4px #0000001f}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:var(--p-blue-50, #eff6ff);color:var(--p-blue-700, #1d4ed8);border:1px solid var(--p-blue-200, #bfdbfe)}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:var(--p-blue-600, #2563eb)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:var(--p-green-50, #f0fdf4);color:var(--p-green-700, #15803d);border:1px solid var(--p-green-200, #bbf7d0)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:var(--p-green-600, #16a34a)}.wuic-rag-chatbot__tokens{margin-top:.4rem;font-size:.7rem;color:var(--p-text-muted-color, #6b7280);text-align:right;font-family:ui-monospace,monospace}.wuic-rag-chatbot__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:var(--wuic-rag-chatbot-padding);border-top:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__input{flex:1 1 auto;min-height:2.25rem;max-height:8rem;resize:none;color:inherit;background-color:transparent}.wuic-rag-chatbot__source-meta{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.75rem}.wuic-rag-chatbot__source-tag{display:inline-flex;align-items:center;gap:.25rem;padding:2px 8px;background:var(--p-surface-100, #f3f4f6);color:var(--p-text-color-secondary, #6b7280);border-radius:10px;font-size:.72rem;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.wuic-rag-chatbot__source-tag--file{background:var(--p-surface-0, #fff);color:var(--p-primary-color, #2563eb);border:1px solid var(--p-primary-200, #bfdbfe);font-weight:500;max-width:100%;word-break:break-all;white-space:normal;line-height:1.3}.wuic-rag-chatbot__source-tag--file i{font-size:.7rem;flex-shrink:0}.wuic-rag-chatbot__source-snippet{margin:0;padding:12px 14px;max-height:420px;overflow:auto;background:var(--p-surface-50, #fafafa);border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.78rem;line-height:1.4;white-space:pre-wrap;word-break:break-word;color:var(--p-text-color, #111827)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i7$1.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: BadgeModule }, { kind: "component", type: i7$2.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "ngmodule", type: ChipModule }, { kind: "component", type: i8$2.Chip, selector: "p-chip", inputs: ["label", "icon", "image", "alt", "styleClass", "disabled", "removable", "removeIcon", "chipProps"], outputs: ["onRemove", "onImageError"] }, { kind: "ngmodule", type: ScrollPanelModule }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i9$2.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i7.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant", "motionOptions"], outputs: ["onClose"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }] });
|
|
63506
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotComponent, deps: [{ token: WuicRagService }, { token: i0.ChangeDetectorRef }, { token: ChatbotHostRegistryService }, { token: i1$2.ActivatedRoute, optional: true }, { token: i1$2.Router, optional: true }, { token: NotificationRealtimeService, optional: true }, { token: MetadataProviderService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
63507
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: WuicRagChatbotComponent, isStandalone: true, selector: "wuic-rag-chatbot", inputs: { title: "title", mode: "mode", topK: "topK", showSources: "showSources", maxHistory: "maxHistory", placeholder: "placeholder", model: "model", chatHeight: "chatHeight", showClearButton: "showClearButton", persistSession: "persistSession", sendRouteContext: "sendRouteContext", allowProfileToggle: "allowProfileToggle" }, outputs: { resultSelected: "resultSelected", errorOccurred: "errorOccurred", turnAdded: "turnAdded" }, viewQueries: [{ propertyName: "inputTextarea", first: true, predicate: ["inputTextarea"], descendants: true }, { propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }], ngImport: i0, template: "<div class=\"wuic-rag-chatbot\" style=\"height: 100%;\">\n\n <!-- Header -->\n <div class=\"wuic-rag-chatbot__header\">\n <div class=\"wuic-rag-chatbot__title\">\n <i class=\"pi pi-comments wuic-rag-chatbot__title-icon\" aria-hidden=\"true\"></i>\n <span>{{ title }}</span>\n </div>\n <div class=\"wuic-rag-chatbot__header-actions\">\n @if (allowProfileToggle) {\n <p-button [label]=\"profileToggleLabel\" [icon]=\"profileOverride === 'release' ? 'pi pi-eye' : 'pi pi-code'\"\n [severity]=\"profileOverride === 'release' ? 'warn' : 'secondary'\" [outlined]=\"true\" [rounded]=\"true\"\n size=\"small\" (onClick)=\"toggleProfile()\" [pTooltip]=\"profileOverride === 'release'\n ? ('rag_chatbot.profile_toggle.tooltip_release' | translate)\n : ('rag_chatbot.profile_toggle.tooltip_dev' | translate)\" tooltipPosition=\"bottom\"\n [ariaLabel]=\"'rag_chatbot.profile_toggle.aria_label' | translate\">\n </p-button>\n }\n <p-badge [value]=\"modeBadgeLabel\" [severity]=\"modeBadgeSeverity\" [pTooltip]=\"healthInfo\n ? ('chunks indicizzati: ' + healthInfo.docs_loaded + ' | modello: ' + healthInfo.default_model)\n : (healthError || ('rag_chatbot.health_indicator.connecting' | translate))\" tooltipPosition=\"bottom\">\n </p-badge>\n <!-- Context cue: cerchio colorato che riflette % context window usato.\n Click + conferma -> /compact. Tooltip mostra k/k token + %. -->\n @if (contextWindowMax) {\n <button type=\"button\"\n class=\"wuic-rag-chatbot__context-cue\"\n [style.background]=\"contextCueColor\"\n [pTooltip]=\"contextCueTooltip\"\n tooltipPosition=\"bottom\"\n [disabled]=\"compactingSession || !currentSessionId\"\n (click)=\"onContextCueClick()\"\n attr.aria-label=\"Compatta sessione (context {{ contextUsagePercent }}%)\">\n @if (compactingSession) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else {\n <span>{{ contextUsagePercent }}%</span>\n }\n </button>\n }\n <!-- Bottone Sessioni: apre popup con lista sessioni ordinate desc per updatedAt. -->\n <p-button icon=\"pi pi-list\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"openSessionsDialog()\" [pTooltip]=\"'rag_chatbot.sessions_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.sessions_button.aria_label' | translate\">\n </p-button>\n @if (showClearButton) {\n <p-button icon=\"pi pi-trash\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n [disabled]=\"history.length === 0 || loading || clearingHistory\" [loading]=\"clearingHistory\"\n (onClick)=\"onClearHistory()\" [pTooltip]=\"'rag_chatbot.clear_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.clear_button.aria_label' | translate\">\n </p-button>\n }\n </div>\n </div>\n\n <!-- Health error banner -->\n @if (healthError) {\n <p-message severity=\"error\" [text]=\"('rag_chatbot.health_error.banner' | translate) + ': ' + healthError\"\n styleClass=\"wuic-rag-chatbot__error-banner\"></p-message>\n }\n\n <!-- Chat history -->\n <div #scrollContainer class=\"wuic-rag-chatbot__history\" role=\"log\" aria-live=\"polite\">\n @if (history.length === 0) {\n <div class=\"wuic-rag-chatbot__empty\">\n <i class=\"pi pi-info-circle wuic-rag-chatbot__empty-icon\" aria-hidden=\"true\"></i>\n <p>{{ 'rag_chatbot.empty.prompt' | translate }}</p>\n <ul class=\"wuic-rag-chatbot__suggestions\">\n <li>{{ 'rag_chatbot.suggestion.invalidate_cache' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.spreadsheet_massive' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.save_report' | translate }}</li>\n </ul>\n </div>\n }\n\n @for (turn of history; track turn.messageId || $index) {\n <div class=\"wuic-rag-chatbot__turn\" [attr.data-message-id]=\"turn.messageId || null\"\n [class.wuic-rag-chatbot__turn--user]=\"turn.role === 'user'\"\n [class.wuic-rag-chatbot__turn--assistant]=\"turn.role === 'assistant'\">\n\n <div class=\"wuic-rag-chatbot__turn-header\">\n <i class=\"pi\" [class.pi-user]=\"turn.role === 'user'\" [class.pi-android]=\"turn.role === 'assistant'\"\n aria-hidden=\"true\"></i>\n <span>{{ (turn.role === 'user' ? 'rag_chatbot.turn.role_user' : 'rag_chatbot.turn.role_assistant') | translate }}</span>\n @if (turn.mode) {\n <span class=\"wuic-rag-chatbot__turn-mode\">[{{ turn.mode === 'rag-llm' ? 'RAG+LLM' : 'retrieval' }}]</span>\n }\n </div>\n\n @if (turn.loading) {\n <div class=\"wuic-rag-chatbot__turn-loading\">\n <p-progressSpinner styleClass=\"wuic-rag-chatbot__spinner\" strokeWidth=\"3\"\n animationDuration=\".9s\"></p-progressSpinner>\n <span>{{ 'rag_chatbot.turn.loading' | translate }}</span>\n </div>\n } @else if (turn.errorMessage) {\n <p-message severity=\"error\" [text]=\"(turn.errorMessage | translate)\" styleClass=\"wuic-rag-chatbot__turn-error\"></p-message>\n } @else {\n <div class=\"wuic-rag-chatbot__turn-content\">{{ turn.content }}</div>\n @if (turn.warning) {\n <p-message severity=\"warn\" [text]=\"turn.warning\" styleClass=\"wuic-rag-chatbot__turn-warning\"></p-message>\n }\n <!-- Box \"Proposta azione\" emessa dall'LLM via tool-use. Renderizzato solo\n per i turn assistant che hanno proposedAction valorizzata. Il monaco\n editor riusa la stessa codeContext del metadata-editor sulla colonna\n equivalente (autocomplete identico, type-check identico). -->\n @if (turn.role === 'assistant' && turn.proposedAction) {\n <div class=\"wuic-rag-chatbot__action-box\"\n [class.wuic-rag-chatbot__action-box--applied]=\"turn.proposedActionAppliedAt\"\n [class.wuic-rag-chatbot__action-box--error]=\"turn.applyError\">\n <div class=\"wuic-rag-chatbot__action-box-header\">\n <i class=\"pi pi-bolt\" aria-hidden=\"true\"></i>\n <span class=\"wuic-rag-chatbot__action-box-title\">\n {{ proposedActionTitle(turn.proposedAction) }}\n @if (turn.proposedAction.label) {\n <span class=\"wuic-rag-chatbot__action-box-label\">\"{{ turn.proposedAction.label }}\"</span>\n }\n @if (turn.proposedAction.route) {\n <span class=\"wuic-rag-chatbot__action-box-route\">{{ 'rag_chatbot.action_box.label_on_route_prefix' | translate }} <code>{{ turn.proposedAction.route }}</code></span>\n }\n </span>\n </div>\n @if (turn.proposedAction.rationale) {\n <div class=\"wuic-rag-chatbot__action-box-rationale\">{{ turn.proposedAction.rationale }}</div>\n }\n <!-- Hint trasparenza UX: l'utente sa in anticipo che, oltre a creare\n la toolbar action, abiliteremo md_multiple_selection sulla route\n (necessario per le bulk operations che leggono getSelectedRows). -->\n @if (turn.proposedAction.requires_multi_selection) {\n <div class=\"wuic-rag-chatbot__action-box-side-effect\">\n <i class=\"pi pi-info-circle\"></i>\n {{ 'rag_chatbot.action_box.side_effect_multi_selection' | translate }}\n </div>\n }\n <!-- Campi NON-body della proposta (es. per table_style: classe/stile;\n per toolbar/row_action: label+icon; per sql_metadata_field: field\n target). Visibili sopra la textarea del body cosi' l'utente capisce\n a colpo d'occhio COSA viene applicato oltre al CODICE del callback.\n Sorgente: proposedActionExtraFields(kind) nel .ts. -->\n @if (proposedActionExtraFields(turn.proposedAction).length > 0) {\n <div class=\"wuic-rag-chatbot__action-box-extra-fields\">\n @for (extra of proposedActionExtraFields(turn.proposedAction); track extra.fieldName) {\n <div class=\"wuic-rag-chatbot__action-box-extra-field\">\n <label class=\"wuic-rag-chatbot__action-box-extra-field-label\">{{ extra.label }}</label>\n <input\n class=\"wuic-rag-chatbot__action-box-extra-field-value\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"extra.value\"\n (ngModelChange)=\"onProposedExtraFieldEdit(turn, extra, $event)\"\n [name]=\"'extra-' + turn.messageId + '-' + extra.fieldName\">\n </div>\n }\n </div>\n }\n @if (proposedActionJsBody(turn.proposedAction)) {\n <!-- Pattern <lib-code-area-editor>: textarea readonly (zero overhead,\n niente flash nero, contenuto immediatamente visibile) di default.\n Toggle \"Apri editor avanzato\" attiva `editorMode` -> @defer carica\n Monaco con codeContext+autocomplete. Doppio livello di lazy:\n @defer in HTML + LazyCodeEditorComponent fa import() dinamico.\n Risultato: Monaco scaricato SOLO se l'utente lo richiede.\n proposedActionJsBody() unifica callback_js / condition_js / formula_js\n in base al kind: stesso UX per tutti i kind che hanno una body JS. -->\n <div class=\"wuic-rag-chatbot__action-box-editor-toolbar\">\n <p-button\n [label]=\"(turn.editorMode ? 'rag_chatbot.action_box.editor_close' : 'rag_chatbot.action_box.editor_open') | translate\"\n [icon]=\"turn.editorMode ? 'pi pi-times' : 'pi pi-code'\"\n severity=\"secondary\"\n [text]=\"true\"\n size=\"small\"\n (onClick)=\"toggleEditorMode(turn)\">\n </p-button>\n </div>\n @if (!turn.editorMode) {\n <textarea\n class=\"wuic-rag-chatbot__action-box-textarea\"\n rows=\"10\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"proposedActionJsBody(turn.proposedAction)\"\n (ngModelChange)=\"onProposedBodyEdit(turn, $event)\"\n [name]=\"'body-' + turn.messageId\">\n </textarea>\n } @else {\n @defer (when turn.editorMode) {\n <div class=\"wuic-rag-chatbot__action-box-editor\">\n <wuic-code-editor-lazy\n [field]=\"getProposedActionField(turn)\"\n [record]=\"getProposedActionRecord(turn)\"\n style=\"display: block; width: 100%; height: 100%;\">\n </wuic-code-editor-lazy>\n </div>\n } @loading (minimum 200ms) {\n <div class=\"wuic-rag-chatbot__action-box-editor-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.editor_loading' | translate }}\n </div>\n }\n }\n }\n <div class=\"wuic-rag-chatbot__action-box-actions\">\n @if (turn.proposedActionAppliedAt) {\n <span class=\"wuic-rag-chatbot__action-box-applied\">\n <i class=\"pi pi-check-circle\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.applied' | translate }}\n </span>\n } @else {\n <p-button\n [label]=\"'rag_chatbot.action_box.apply' | translate\"\n icon=\"pi pi-check\"\n severity=\"primary\"\n size=\"small\"\n [loading]=\"!!turn.applyingAction\"\n (onClick)=\"onApplyAction(turn)\">\n </p-button>\n }\n @if (turn.applyError) {\n <span class=\"wuic-rag-chatbot__action-box-error\">\n <i class=\"pi pi-times-circle\" aria-hidden=\"true\"></i>\n {{ turn.applyError }}\n </span>\n }\n </div>\n </div>\n }\n @if (showSources && turn.role === 'assistant' && turn.sources && turn.sources.length > 0) {\n @let dedupedSources = dedupSources(turn.sources);\n <div class=\"wuic-rag-chatbot__sources\">\n <div class=\"wuic-rag-chatbot__sources-label\">{{ 'rag_chatbot.sources.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__sources-list\">\n @for (source of dedupedSources; track source.chunk_id || source.rank) {\n <p-chip [label]=\"sourceLabel(source, dedupedSources)\"\n [icon]=\"isDocsSource(source) ? 'pi pi-book' : (isSkillSource(source) ? 'pi pi-wrench' : 'pi pi-file-edit')\"\n [removable]=\"false\" [pTooltip]=\"sourceTooltip(source)\" tooltipPosition=\"top\" [styleClass]=\"isDocsSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--docs'\n : (isSkillSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--skill'\n : 'wuic-rag-chatbot__source-chip')\" (click)=\"onSourceClick(source)\">\n </p-chip>\n }\n </div>\n </div>\n }\n <!-- Follow-up questions emesse dall'LLM via tool `suggest_followups`.\n Renderizzate come chip cliccabili sotto la risposta: click popola\n l'input box (NON invia auto, l'utente puo' editare prima). -->\n @if (turn.role === 'assistant' && turn.followupQuestions && turn.followupQuestions.length > 0) {\n <div class=\"wuic-rag-chatbot__followups\">\n <div class=\"wuic-rag-chatbot__followups-label\">{{ 'rag_chatbot.followups.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__followups-list\">\n @for (q of turn.followupQuestions; track q) {\n <p-chip\n [label]=\"q\"\n icon=\"pi pi-arrow-right\"\n styleClass=\"wuic-rag-chatbot__followup-chip\"\n (click)=\"applyFollowup(q)\">\n </p-chip>\n }\n </div>\n </div>\n }\n @if (turn.tokensIn || turn.tokensOut) {\n <div class=\"wuic-rag-chatbot__tokens\">\n tokens in/out: {{ turn.tokensIn ?? '?' }}/{{ turn.tokensOut ?? '?' }}\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Input area -->\n <div class=\"wuic-rag-chatbot__input-area\">\n <textarea #inputTextarea pTextarea class=\"wuic-rag-chatbot__input\" [(ngModel)]=\"inputValue\"\n (keydown)=\"onKeyDown($event)\" [placeholder]=\"placeholder\" [disabled]=\"loading || !!healthError\"\n [autoResize]=\"true\" rows=\"1\" [attr.aria-label]=\"'rag_chatbot.input.aria_label' | translate\">\n </textarea>\n <p-button icon=\"pi pi-send\" [disabled]=\"sendDisabled\" [loading]=\"loading\" (onClick)=\"onSend()\" severity=\"primary\"\n [ariaLabel]=\"'rag_chatbot.input.send_aria_label' | translate\">\n </p-button>\n </div>\n\n <!-- Dialog preview snippet sorgente.\n Mostra SOLO il contenuto del chunk (gia' embedded nella response RAG).\n NON espone il path del file ne' apre editor esterni: il codebase del\n framework vive sul repo dev e non deve essere linkato all'utente finale. -->\n @if (selectedSource) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw' }\" [header]=\"sourceDialogTitle(selectedSource)\"\n styleClass=\"wuic-rag-chatbot__source-dialog\" (onHide)=\"closeSourcePreview()\">\n <div class=\"wuic-rag-chatbot__source-meta\">\n @if (sourceFilePath(selectedSource); as fpath) {\n <span class=\"wuic-rag-chatbot__source-tag wuic-rag-chatbot__source-tag--file\">\n <i class=\"pi pi-file\" aria-hidden=\"true\"></i>\n {{ fpath }}\n </span>\n }\n @if (selectedSource.symbol_type) {\n <span class=\"wuic-rag-chatbot__source-tag\">{{ selectedSource.symbol_type }}</span>\n }\n @if (selectedSource.start_line && selectedSource.end_line) {\n <span class=\"wuic-rag-chatbot__source-tag\">linee {{ selectedSource.start_line }}-{{ selectedSource.end_line\n }}</span>\n }\n @if (selectedSource.score_vector !== null && selectedSource.score_vector !== undefined) {\n <span class=\"wuic-rag-chatbot__source-tag\">vec {{ selectedSource.score_vector | number:'1.2-2' }}</span>\n }\n </div>\n <pre\n class=\"wuic-rag-chatbot__source-snippet\"><code>{{ selectedSource.snippet || ('rag_chatbot.source.snippet_unavailable' | translate) }}</code></pre>\n </p-dialog>\n }\n\n <!-- Dialog \"Sessioni\": elenco ordinato desc per updatedAt. Per ogni riga:\n data + titolo (truncated 100 char, tooltip full) + bottone rinomina\n inline + click row = switch session. -->\n @if (sessionsDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw', maxHeight: '85vh' }\"\n [header]=\"'rag_chatbot.sessions_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__sessions-dialog\"\n (onHide)=\"closeSessionsDialog()\">\n @if (sessionsLoading) {\n <div class=\"wuic-rag-chatbot__sessions-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i> {{ 'rag_chatbot.sessions_dialog.loading' | translate }}\n </div>\n } @else if (sessionsList.length === 0) {\n <div class=\"wuic-rag-chatbot__sessions-empty\">{{ 'rag_chatbot.sessions_dialog.empty' | translate }}</div>\n } @else {\n <ul class=\"wuic-rag-chatbot__sessions-list\">\n @for (s of sessionsList; track s.id) {\n <li class=\"wuic-rag-chatbot__session-item\"\n [class.wuic-rag-chatbot__session-item--current]=\"s.id === currentSessionId\"\n (click)=\"selectSession(s)\">\n <div class=\"wuic-rag-chatbot__session-date\">{{ sessionDate(s) }}</div>\n @if (sessionRenameEditing[s.id]) {\n <div class=\"wuic-rag-chatbot__session-edit\" (click)=\"$event.stopPropagation()\">\n <input type=\"text\" pInputText\n [(ngModel)]=\"sessionRenameDraft[s.id]\"\n [autofocus]=\"true\"\n maxlength=\"200\"\n class=\"wuic-rag-chatbot__session-edit-input\"\n (keydown.enter)=\"confirmRenameSession(s, $event)\"\n (keydown.escape)=\"cancelRenameSession(s)\">\n <p-button icon=\"pi pi-check\" severity=\"success\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"confirmRenameSession(s, $event)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_confirm_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-times\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"cancelRenameSession(s)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_cancel_aria' | translate\"></p-button>\n </div>\n } @else {\n <div class=\"wuic-rag-chatbot__session-row\">\n <span class=\"wuic-rag-chatbot__session-title\" [pTooltip]=\"s.title || ('rag_chatbot.sessions_dialog.untitled' | translate)\" tooltipPosition=\"top\">\n {{ sessionPreview(s) }}\n </span>\n <p-button icon=\"pi pi-pencil\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"startRenameSession(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.rename' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-trash\" severity=\"danger\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"onDeleteSessionFromList(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.delete' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.delete_aria' | translate\"></p-button>\n </div>\n }\n </li>\n }\n </ul>\n }\n </p-dialog>\n }\n\n <!-- Dialog context-overflow: l'ultima call ha fallito perche' la history\n sfora il context window. 2 azioni: Compatta ora (riassume e prosegue\n stessa session) / Nuova sessione (clear + ricomincia). -->\n @if (contextOverflowDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"false\"\n [style]=\"{ width: '480px', maxWidth: '92vw' }\"\n [header]=\"'rag_chatbot.overflow_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__overflow-dialog\"\n (onHide)=\"contextOverflowDialogOpen = false\">\n <p>{{ 'rag_chatbot.overflow_dialog.intro' | translate }}</p>\n <ul>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_compact_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_compact_desc' | translate }}</li>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_new_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_new_desc' | translate }}</li>\n </ul>\n <div class=\"wuic-rag-chatbot__overflow-actions\">\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_compact_title' | translate\" icon=\"pi pi-compress\" severity=\"primary\"\n [loading]=\"compactingSession\"\n (onClick)=\"doCompactSession()\"></p-button>\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_new_title' | translate\" icon=\"pi pi-refresh\" severity=\"secondary\"\n (onClick)=\"startNewSession()\"></p-button>\n </div>\n </p-dialog>\n }\n\n</div>", styles: [".wuic-rag-chatbot{--wuic-rag-chatbot-radius: .5rem;--wuic-rag-chatbot-padding: .75rem;--wuic-rag-chatbot-gap: .5rem;display:flex;flex-direction:column;width:100%;height:100%;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:var(--wuic-rag-chatbot-radius);background-color:var(--p-content-background, transparent);color:inherit;overflow:hidden;font-family:inherit}.wuic-rag-chatbot__header{display:flex;align-items:center;justify-content:space-between;padding:var(--wuic-rag-chatbot-padding);border-bottom:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__title{display:flex;align-items:center;gap:.5rem;font-weight:600;color:inherit}.wuic-rag-chatbot__title-icon{color:var(--p-primary-color, #3b82f6)}.wuic-rag-chatbot__header-actions{display:flex;align-items:center;gap:.5rem}.wuic-rag-chatbot__context-cue{width:28px;height:28px;border-radius:50%;border:2px solid rgba(255,255,255,.85);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:.62rem;font-weight:700;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.35);box-shadow:0 0 0 1px #0000000f,0 1px 3px #0000001f;transition:transform .12s ease,box-shadow .12s ease,opacity .12s ease;padding:0}.wuic-rag-chatbot__context-cue:hover:not(:disabled){transform:scale(1.08);box-shadow:0 0 0 1px #0000001a,0 2px 6px #0003}.wuic-rag-chatbot__context-cue:disabled{cursor:default;opacity:.65}.wuic-rag-chatbot__context-cue>i{font-size:.85rem}.wuic-rag-chatbot__sessions-loading,.wuic-rag-chatbot__sessions-empty{padding:1.5rem;text-align:center;color:var(--p-text-muted-color, #6b7280);font-style:italic}.wuic-rag-chatbot__sessions-list{list-style:none;margin:0;padding:0;max-height:60vh;overflow-y:auto}.wuic-rag-chatbot__session-item{padding:.6rem .85rem;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;margin-bottom:.4rem;cursor:pointer;transition:background .12s ease,border-color .12s ease}.wuic-rag-chatbot__session-item:hover{background:var(--p-surface-50, #f3f4f6);border-color:var(--p-primary-300, rgba(59, 130, 246, .4))}.wuic-rag-chatbot__session-item--current{background:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-400, rgba(59, 130, 246, .55))}.wuic-rag-chatbot__session-date{font-size:.72rem;color:var(--p-text-muted-color, #6b7280);font-family:ui-monospace,monospace;margin-bottom:.2rem}.wuic-rag-chatbot__session-row{display:flex;align-items:center;gap:.4rem}.wuic-rag-chatbot__session-title{flex:1 1 auto;font-size:.9rem;color:var(--p-text-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wuic-rag-chatbot__session-edit{display:flex;align-items:center;gap:.3rem}.wuic-rag-chatbot__session-edit-input{flex:1 1 auto;font-size:.85rem}.wuic-rag-chatbot__overflow-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1rem}.wuic-rag-chatbot__error-banner{margin:var(--wuic-rag-chatbot-padding);margin-bottom:0}.wuic-rag-chatbot__history{flex:1 1 auto;min-height:0;overflow-y:auto;padding:var(--wuic-rag-chatbot-padding);display:flex;flex-direction:column;gap:var(--wuic-rag-chatbot-gap);background-color:transparent}.wuic-rag-chatbot__empty{margin:auto;text-align:center;color:var(--p-text-muted-color, #6b7280);max-width:80%;padding:1.5rem 0}.wuic-rag-chatbot__empty-icon{font-size:1.5rem;margin-bottom:.5rem;display:block}.wuic-rag-chatbot__suggestions{list-style:none;padding:0;margin:.75rem 0 0;display:flex;flex-direction:column;gap:.25rem;text-align:left}.wuic-rag-chatbot__suggestions li{padding:.35rem .75rem;border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:.375rem;font-size:.875rem}.wuic-rag-chatbot__turn{display:flex;flex-direction:column;gap:.35rem;padding:.65rem .85rem;border-radius:var(--wuic-rag-chatbot-radius);max-width:92%;color:inherit;background-color:transparent;border:1px solid var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__turn--user{align-self:flex-end;background-color:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-200, rgba(59, 130, 246, .25))}.wuic-rag-chatbot__turn--assistant{align-self:flex-start;background-color:var(--p-surface-50, rgba(0, 0, 0, .025))}.wuic-rag-chatbot__turn-header{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__turn-mode{margin-left:auto;font-family:ui-monospace,monospace;font-size:.7rem;opacity:.7}.wuic-rag-chatbot__turn-content{white-space:pre-wrap;word-break:break-word;line-height:1.45;font-size:.93rem;color:inherit}.wuic-rag-chatbot__turn-loading{display:flex;align-items:center;gap:.5rem;color:var(--p-text-muted-color, #6b7280);font-style:italic;font-size:.875rem}.wuic-rag-chatbot__spinner{width:1.25rem!important;height:1.25rem!important}.wuic-rag-chatbot__turn-error,.wuic-rag-chatbot__turn-warning{margin-top:.35rem}.wuic-rag-chatbot__sources{display:flex;flex-direction:column;gap:.35rem;margin-top:.5rem;padding-top:.5rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__sources-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.05em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__sources-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__source-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.wuic-rag-chatbot__source-chip:hover{transform:translateY(-1px);box-shadow:0 1px 4px #0000001f}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:var(--p-blue-50, #eff6ff);color:var(--p-blue-700, #1d4ed8);border:1px solid var(--p-blue-200, #bfdbfe)}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:var(--p-blue-600, #2563eb)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:var(--p-green-50, #f0fdf4);color:var(--p-green-700, #15803d);border:1px solid var(--p-green-200, #bbf7d0)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:var(--p-green-600, #16a34a)}.wuic-rag-chatbot__tokens{margin-top:.4rem;font-size:.7rem;color:var(--p-text-muted-color, #6b7280);text-align:right;font-family:ui-monospace,monospace}.wuic-rag-chatbot__followups{display:flex;flex-direction:column;gap:.3rem;margin-top:.55rem;padding-top:.55rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__followups-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__followups-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__followup-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease,background .12s ease}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip{background:var(--p-primary-50, rgba(59, 130, 246, .08));color:var(--p-primary-700, #1d4ed8);border:1px solid var(--p-primary-200, rgba(59, 130, 246, .25));font-size:.82rem;line-height:1.3;padding:.2rem .5rem}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip-icon{color:var(--p-primary-600, #2563eb);font-size:.75rem}.wuic-rag-chatbot__followup-chip:hover{transform:translateY(-1px);box-shadow:0 2px 6px #3b82f62e}.wuic-rag-chatbot__followup-chip:hover :host ::ng-deep .p-chip{background:var(--p-primary-100, rgba(59, 130, 246, .15))}.wuic-rag-chatbot__action-box{margin-top:.65rem;border:1px solid var(--p-primary-300, rgba(59, 130, 246, .45));border-radius:8px;background:var(--p-primary-50, rgba(59, 130, 246, .05));padding:.6rem .75rem;display:flex;flex-direction:column;gap:.45rem}.wuic-rag-chatbot__action-box--applied{border-color:var(--p-green-300, #86efac);background:var(--p-green-50, #f0fdf4)}.wuic-rag-chatbot__action-box--error{border-color:var(--p-red-300, #fca5a5);background:var(--p-red-50, #fef2f2)}.wuic-rag-chatbot__action-box-header{display:flex;align-items:center;gap:.45rem;font-size:.88rem;color:var(--p-primary-700, #1d4ed8);font-weight:600}.wuic-rag-chatbot__action-box-header>i{font-size:.95rem}.wuic-rag-chatbot__action-box-title{display:inline-flex;align-items:center;gap:.4rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-label{color:var(--p-text-color, #111827);font-weight:700}.wuic-rag-chatbot__action-box-route{color:var(--p-text-muted-color, #6b7280);font-weight:400;font-size:.8rem}.wuic-rag-chatbot__action-box-route>code{background:var(--p-surface-100, #f3f4f6);padding:1px 6px;border-radius:4px;font-family:ui-monospace,monospace;color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-rationale{font-size:.85rem;color:var(--p-text-color, #1f2937);line-height:1.4;font-style:italic}.wuic-rag-chatbot__action-box-side-effect{font-size:.8rem;color:var(--p-blue-700, #1d4ed8);background:var(--p-blue-50, #eff6ff);border-left:3px solid var(--p-blue-400, #60a5fa);padding:.4rem .6rem;border-radius:4px;display:flex;align-items:flex-start;gap:.4rem;line-height:1.35}.wuic-rag-chatbot__action-box-side-effect i{font-size:.9rem;margin-top:.1rem;flex-shrink:0}.wuic-rag-chatbot__action-box-editor-toolbar{display:flex;justify-content:flex-end;align-items:center}.wuic-rag-chatbot__action-box-extra-fields{display:flex;flex-direction:column;gap:6px;margin-bottom:8px}.wuic-rag-chatbot__action-box-extra-field{display:flex;align-items:center;gap:8px}.wuic-rag-chatbot__action-box-extra-field-label{flex:0 0 auto;min-width:100px;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__action-box-extra-field-value{flex:1 1 auto;min-width:0;font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;padding:4px 8px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:4px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-extra-field-value:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-textarea{width:100%;min-height:200px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;padding:8px 10px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827);font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;line-height:1.45;resize:vertical;white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:hidden;overflow-y:auto;tab-size:2}.wuic-rag-chatbot__action-box-textarea:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-editor{height:240px;min-height:240px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;overflow:hidden;background:#1e1e1e}.wuic-rag-chatbot__action-box-editor-loading{display:flex;align-items:center;justify-content:center;gap:.5rem;min-height:100px;color:var(--p-text-muted-color, #6b7280);font-size:.85rem;font-style:italic;background:var(--p-surface-50, #fafafa);border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:6px}.wuic-rag-chatbot__action-box-actions{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-applied{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-green-700, #15803d);font-weight:600;font-size:.85rem}.wuic-rag-chatbot__action-box-error{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-red-700, #b91c1c);font-size:.8rem;font-family:ui-monospace,monospace}.wuic-rag-chatbot__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:var(--wuic-rag-chatbot-padding);border-top:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__input{flex:1 1 auto;min-height:2.25rem;max-height:8rem;resize:none;color:inherit;background-color:transparent}.wuic-rag-chatbot__source-meta{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.75rem}.wuic-rag-chatbot__source-tag{display:inline-flex;align-items:center;gap:.25rem;padding:2px 8px;background:var(--p-surface-100, #f3f4f6);color:var(--p-text-color-secondary, #6b7280);border-radius:10px;font-size:.72rem;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.wuic-rag-chatbot__source-tag--file{background:var(--p-surface-0, #fff);color:var(--p-primary-color, #2563eb);border:1px solid var(--p-primary-200, #bfdbfe);font-weight:500;max-width:100%;word-break:break-all;white-space:normal;line-height:1.3}.wuic-rag-chatbot__source-tag--file i{font-size:.7rem;flex-shrink:0}.wuic-rag-chatbot__source-snippet{margin:0;padding:12px 14px;max-height:420px;overflow:auto;background:var(--p-surface-50, #fafafa);border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.78rem;line-height:1.4;white-space:pre-wrap;word-break:break-word;color:var(--p-text-color, #111827)}:host-context(.theme-dark) .wuic-rag-chatbot__turn--user{background-color:#60a5fa2e;border-color:#60a5fa73}:host-context(.theme-dark) .wuic-rag-chatbot__turn--assistant{background-color:#ffffff0f;border-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__turn-header,:host-context(.theme-dark) .wuic-rag-chatbot__turn-loading,:host-context(.theme-dark) .wuic-rag-chatbot__sources-label,:host-context(.theme-dark) .wuic-rag-chatbot__tokens{color:#ffffffa6}:host-context(.theme-dark) .wuic-rag-chatbot__sources{border-top-color:#ffffff2e}:host-context(.theme-dark) .wuic-rag-chatbot__input-area{border-top-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:#3b82f62e;color:#93c5fd;border:1px solid rgba(59,130,246,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:#bfdbfe}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:#22c55e2e;color:#86efac;border:1px solid rgba(34,197,94,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:#bbf7d0}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag{background:#ffffff14;color:#ffffffc7}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag--file{background:#3b82f624;color:#93c5fd;border-color:#3b82f673}:host-context(.theme-dark) .wuic-rag-chatbot__source-snippet{background:#00000052;border-color:#ffffff1f;color:#ffffffeb}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i7$1.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i13.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: BadgeModule }, { kind: "component", type: i10$1.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "ngmodule", type: ChipModule }, { kind: "component", type: i11$1.Chip, selector: "p-chip", inputs: ["label", "icon", "image", "alt", "styleClass", "disabled", "removable", "removeIcon", "chipProps"], outputs: ["onRemove", "onImageError"] }, { kind: "ngmodule", type: ScrollPanelModule }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i9$2.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i7.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant", "motionOptions"], outputs: ["onClose"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }], deferBlockDependencies: [() => [Promise.resolve().then(function () { return codeEditor_lazy_component; }).then(m => m.LazyCodeEditorComponent)]] });
|
|
61519
63508
|
}
|
|
61520
|
-
i0.ɵɵ
|
|
61521
|
-
|
|
61522
|
-
|
|
61523
|
-
|
|
61524
|
-
|
|
61525
|
-
|
|
61526
|
-
|
|
61527
|
-
|
|
61528
|
-
|
|
61529
|
-
|
|
61530
|
-
|
|
61531
|
-
|
|
61532
|
-
|
|
61533
|
-
|
|
61534
|
-
], template: "<div class=\"wuic-rag-chatbot\" [style.--chat-height]=\"chatHeight\">\n\n <!-- Header -->\n <div class=\"wuic-rag-chatbot__header\">\n <div class=\"wuic-rag-chatbot__title\">\n <i class=\"pi pi-comments wuic-rag-chatbot__title-icon\" aria-hidden=\"true\"></i>\n <span>{{ title }}</span>\n </div>\n <div class=\"wuic-rag-chatbot__header-actions\">\n <p-badge\n [value]=\"modeBadgeLabel\"\n [severity]=\"modeBadgeSeverity\"\n [pTooltip]=\"healthInfo\n ? ('chunks indicizzati: ' + healthInfo.docs_loaded + ' | modello: ' + healthInfo.default_model)\n : (healthError || 'connessione al server RAG...')\"\n tooltipPosition=\"bottom\">\n </p-badge>\n @if (showClearButton) {\n <p-button\n icon=\"pi pi-trash\"\n severity=\"secondary\"\n [text]=\"true\"\n [rounded]=\"true\"\n size=\"small\"\n [disabled]=\"history.length === 0 || loading\"\n (onClick)=\"onClearHistory()\"\n pTooltip=\"Svuota cronologia\"\n tooltipPosition=\"bottom\"\n ariaLabel=\"Svuota cronologia chat\">\n </p-button>\n }\n </div>\n </div>\n\n <!-- Health error banner -->\n @if (healthError) {\n <p-message severity=\"error\" [text]=\"'Server RAG non raggiungibile: ' + healthError\" styleClass=\"wuic-rag-chatbot__error-banner\"></p-message>\n }\n\n <!-- Chat history -->\n <div #scrollContainer class=\"wuic-rag-chatbot__history\" role=\"log\" aria-live=\"polite\">\n @if (history.length === 0) {\n <div class=\"wuic-rag-chatbot__empty\">\n <i class=\"pi pi-info-circle wuic-rag-chatbot__empty-icon\" aria-hidden=\"true\"></i>\n <p>Fai una domanda sul codebase WUIC, ad esempio:</p>\n <ul class=\"wuic-rag-chatbot__suggestions\">\n <li>Invalidare la cache metadata runtime sul server</li>\n <li>Spreadsheet per data entry massivo simile a excel</li>\n <li>Salvataggio di un report dal designer</li>\n </ul>\n </div>\n }\n\n @for (turn of history; track turn.messageId || $index) {\n <div class=\"wuic-rag-chatbot__turn\"\n [attr.data-message-id]=\"turn.messageId || null\"\n [class.wuic-rag-chatbot__turn--user]=\"turn.role === 'user'\"\n [class.wuic-rag-chatbot__turn--assistant]=\"turn.role === 'assistant'\">\n\n <div class=\"wuic-rag-chatbot__turn-header\">\n <i class=\"pi\"\n [class.pi-user]=\"turn.role === 'user'\"\n [class.pi-android]=\"turn.role === 'assistant'\"\n aria-hidden=\"true\"></i>\n <span>{{ turn.role === 'user' ? 'Tu' : 'Assistente' }}</span>\n @if (turn.mode) {\n <span class=\"wuic-rag-chatbot__turn-mode\">[{{ turn.mode === 'rag-llm' ? 'RAG+LLM' : 'retrieval' }}]</span>\n }\n </div>\n\n @if (turn.loading) {\n <div class=\"wuic-rag-chatbot__turn-loading\">\n <p-progressSpinner styleClass=\"wuic-rag-chatbot__spinner\" strokeWidth=\"3\" animationDuration=\".9s\"></p-progressSpinner>\n <span>Sto interrogando il codebase...</span>\n </div>\n } @else if (turn.errorMessage) {\n <p-message severity=\"error\" [text]=\"turn.errorMessage\" styleClass=\"wuic-rag-chatbot__turn-error\"></p-message>\n } @else {\n <div class=\"wuic-rag-chatbot__turn-content\">{{ turn.content }}</div>\n @if (turn.warning) {\n <p-message severity=\"warn\" [text]=\"turn.warning\" styleClass=\"wuic-rag-chatbot__turn-warning\"></p-message>\n }\n @if (showSources && turn.role === 'assistant' && turn.sources && turn.sources.length > 0) {\n @let dedupedSources = dedupSources(turn.sources);\n <div class=\"wuic-rag-chatbot__sources\">\n <div class=\"wuic-rag-chatbot__sources-label\">Fonti:</div>\n <div class=\"wuic-rag-chatbot__sources-list\">\n @for (source of dedupedSources; track source.chunk_id || source.rank) {\n <p-chip\n [label]=\"sourceLabel(source, dedupedSources)\"\n [icon]=\"isDocsSource(source) ? 'pi pi-book' : (isSkillSource(source) ? 'pi pi-wrench' : 'pi pi-file-edit')\"\n [removable]=\"false\"\n [pTooltip]=\"sourceTooltip(source)\"\n tooltipPosition=\"top\"\n [styleClass]=\"isDocsSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--docs'\n : (isSkillSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--skill'\n : 'wuic-rag-chatbot__source-chip')\"\n (click)=\"onSourceClick(source)\">\n </p-chip>\n }\n </div>\n </div>\n }\n @if (turn.tokensIn || turn.tokensOut) {\n <div class=\"wuic-rag-chatbot__tokens\">\n tokens in/out: {{ turn.tokensIn ?? '?' }}/{{ turn.tokensOut ?? '?' }}\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Input area -->\n <div class=\"wuic-rag-chatbot__input-area\">\n <textarea #inputTextarea\n pTextarea\n class=\"wuic-rag-chatbot__input\"\n [(ngModel)]=\"inputValue\"\n (keydown)=\"onKeyDown($event)\"\n [placeholder]=\"placeholder\"\n [disabled]=\"loading || !!healthError\"\n [autoResize]=\"true\"\n rows=\"1\"\n aria-label=\"Domanda per il chatbot codebase\">\n </textarea>\n <p-button\n icon=\"pi pi-send\"\n [disabled]=\"sendDisabled\"\n [loading]=\"loading\"\n (onClick)=\"onSend()\"\n severity=\"primary\"\n ariaLabel=\"Invia messaggio\">\n </p-button>\n </div>\n\n <!-- Dialog preview snippet sorgente.\n Mostra SOLO il contenuto del chunk (gia' embedded nella response RAG).\n NON espone il path del file ne' apre editor esterni: il codebase del\n framework vive sul repo dev e non deve essere linkato all'utente finale. -->\n @if (selectedSource) {\n <p-dialog\n [visible]=\"true\"\n [modal]=\"true\"\n [closable]=\"true\"\n [closeOnEscape]=\"true\"\n [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw' }\"\n [header]=\"sourceDialogTitle(selectedSource)\"\n styleClass=\"wuic-rag-chatbot__source-dialog\"\n (onHide)=\"closeSourcePreview()\">\n <div class=\"wuic-rag-chatbot__source-meta\">\n @if (sourceFilePath(selectedSource); as fpath) {\n <span class=\"wuic-rag-chatbot__source-tag wuic-rag-chatbot__source-tag--file\">\n <i class=\"pi pi-file\" aria-hidden=\"true\"></i>\n {{ fpath }}\n </span>\n }\n @if (selectedSource.symbol_type) {\n <span class=\"wuic-rag-chatbot__source-tag\">{{ selectedSource.symbol_type }}</span>\n }\n @if (selectedSource.start_line && selectedSource.end_line) {\n <span class=\"wuic-rag-chatbot__source-tag\">linee {{ selectedSource.start_line }}-{{ selectedSource.end_line }}</span>\n }\n @if (selectedSource.score_vector !== null && selectedSource.score_vector !== undefined) {\n <span class=\"wuic-rag-chatbot__source-tag\">vec {{ selectedSource.score_vector | number:'1.2-2' }}</span>\n }\n </div>\n <pre class=\"wuic-rag-chatbot__source-snippet\"><code>{{ selectedSource.snippet || '(snippet non disponibile)' }}</code></pre>\n </p-dialog>\n }\n\n</div>\n", styles: [".wuic-rag-chatbot{--wuic-rag-chatbot-radius: .5rem;--wuic-rag-chatbot-padding: .75rem;--wuic-rag-chatbot-gap: .5rem;--chat-height: 420px;display:flex;flex-direction:column;width:100%;height:auto;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:var(--wuic-rag-chatbot-radius);background-color:var(--p-content-background, transparent);color:inherit;overflow:hidden;font-family:inherit}.wuic-rag-chatbot__header{display:flex;align-items:center;justify-content:space-between;padding:var(--wuic-rag-chatbot-padding);border-bottom:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__title{display:flex;align-items:center;gap:.5rem;font-weight:600;color:inherit}.wuic-rag-chatbot__title-icon{color:var(--p-primary-color, #3b82f6)}.wuic-rag-chatbot__header-actions{display:flex;align-items:center;gap:.5rem}.wuic-rag-chatbot__error-banner{margin:var(--wuic-rag-chatbot-padding);margin-bottom:0}.wuic-rag-chatbot__history{flex:1 1 auto;height:var(--chat-height);overflow-y:auto;padding:var(--wuic-rag-chatbot-padding);display:flex;flex-direction:column;gap:var(--wuic-rag-chatbot-gap);background-color:transparent}.wuic-rag-chatbot__empty{margin:auto;text-align:center;color:var(--p-text-muted-color, #6b7280);max-width:80%;padding:1.5rem 0}.wuic-rag-chatbot__empty-icon{font-size:1.5rem;margin-bottom:.5rem;display:block}.wuic-rag-chatbot__suggestions{list-style:none;padding:0;margin:.75rem 0 0;display:flex;flex-direction:column;gap:.25rem;text-align:left}.wuic-rag-chatbot__suggestions li{padding:.35rem .75rem;border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:.375rem;font-size:.875rem}.wuic-rag-chatbot__turn{display:flex;flex-direction:column;gap:.35rem;padding:.65rem .85rem;border-radius:var(--wuic-rag-chatbot-radius);max-width:92%;color:inherit;background-color:transparent;border:1px solid var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__turn--user{align-self:flex-end;background-color:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-200, rgba(59, 130, 246, .25))}.wuic-rag-chatbot__turn--assistant{align-self:flex-start;background-color:var(--p-surface-50, rgba(0, 0, 0, .025))}.wuic-rag-chatbot__turn-header{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__turn-mode{margin-left:auto;font-family:ui-monospace,monospace;font-size:.7rem;opacity:.7}.wuic-rag-chatbot__turn-content{white-space:pre-wrap;word-break:break-word;line-height:1.45;font-size:.93rem;color:inherit}.wuic-rag-chatbot__turn-loading{display:flex;align-items:center;gap:.5rem;color:var(--p-text-muted-color, #6b7280);font-style:italic;font-size:.875rem}.wuic-rag-chatbot__spinner{width:1.25rem!important;height:1.25rem!important}.wuic-rag-chatbot__turn-error,.wuic-rag-chatbot__turn-warning{margin-top:.35rem}.wuic-rag-chatbot__sources{display:flex;flex-direction:column;gap:.35rem;margin-top:.5rem;padding-top:.5rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__sources-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.05em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__sources-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__source-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.wuic-rag-chatbot__source-chip:hover{transform:translateY(-1px);box-shadow:0 1px 4px #0000001f}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:var(--p-blue-50, #eff6ff);color:var(--p-blue-700, #1d4ed8);border:1px solid var(--p-blue-200, #bfdbfe)}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:var(--p-blue-600, #2563eb)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:var(--p-green-50, #f0fdf4);color:var(--p-green-700, #15803d);border:1px solid var(--p-green-200, #bbf7d0)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:var(--p-green-600, #16a34a)}.wuic-rag-chatbot__tokens{margin-top:.4rem;font-size:.7rem;color:var(--p-text-muted-color, #6b7280);text-align:right;font-family:ui-monospace,monospace}.wuic-rag-chatbot__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:var(--wuic-rag-chatbot-padding);border-top:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__input{flex:1 1 auto;min-height:2.25rem;max-height:8rem;resize:none;color:inherit;background-color:transparent}.wuic-rag-chatbot__source-meta{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.75rem}.wuic-rag-chatbot__source-tag{display:inline-flex;align-items:center;gap:.25rem;padding:2px 8px;background:var(--p-surface-100, #f3f4f6);color:var(--p-text-color-secondary, #6b7280);border-radius:10px;font-size:.72rem;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.wuic-rag-chatbot__source-tag--file{background:var(--p-surface-0, #fff);color:var(--p-primary-color, #2563eb);border:1px solid var(--p-primary-200, #bfdbfe);font-weight:500;max-width:100%;word-break:break-all;white-space:normal;line-height:1.3}.wuic-rag-chatbot__source-tag--file i{font-size:.7rem;flex-shrink:0}.wuic-rag-chatbot__source-snippet{margin:0;padding:12px 14px;max-height:420px;overflow:auto;background:var(--p-surface-50, #fafafa);border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.78rem;line-height:1.4;white-space:pre-wrap;word-break:break-word;color:var(--p-text-color, #111827)}\n"] }]
|
|
61535
|
-
|
|
61536
|
-
|
|
61537
|
-
}] }, { type: i1$2.Router, decorators: [{
|
|
61538
|
-
|
|
61539
|
-
|
|
61540
|
-
type:
|
|
61541
|
-
|
|
61542
|
-
|
|
61543
|
-
|
|
61544
|
-
|
|
61545
|
-
|
|
61546
|
-
|
|
61547
|
-
|
|
61548
|
-
|
|
61549
|
-
|
|
61550
|
-
|
|
61551
|
-
|
|
61552
|
-
|
|
61553
|
-
|
|
61554
|
-
|
|
61555
|
-
|
|
61556
|
-
|
|
61557
|
-
|
|
61558
|
-
|
|
61559
|
-
|
|
61560
|
-
|
|
61561
|
-
|
|
61562
|
-
|
|
61563
|
-
|
|
61564
|
-
|
|
61565
|
-
|
|
61566
|
-
|
|
61567
|
-
|
|
61568
|
-
|
|
61569
|
-
|
|
61570
|
-
|
|
61571
|
-
|
|
63509
|
+
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotComponent, resolveDeferredDeps: () => [Promise.resolve().then(function () { return codeEditor_lazy_component; }).then(m => m.LazyCodeEditorComponent)], resolveMetadata: LazyCodeEditorComponent => ({ decorators: [{
|
|
63510
|
+
type: Component,
|
|
63511
|
+
args: [{ selector: 'wuic-rag-chatbot', standalone: true, imports: [
|
|
63512
|
+
CommonModule,
|
|
63513
|
+
FormsModule,
|
|
63514
|
+
ButtonModule,
|
|
63515
|
+
TextareaModule,
|
|
63516
|
+
InputTextModule,
|
|
63517
|
+
BadgeModule,
|
|
63518
|
+
ChipModule,
|
|
63519
|
+
ScrollPanelModule,
|
|
63520
|
+
ProgressSpinnerModule,
|
|
63521
|
+
TooltipModule,
|
|
63522
|
+
MessageModule,
|
|
63523
|
+
DialogModule,
|
|
63524
|
+
TranslateModule,
|
|
63525
|
+
LazyCodeEditorComponent,
|
|
63526
|
+
], template: "<div class=\"wuic-rag-chatbot\" style=\"height: 100%;\">\n\n <!-- Header -->\n <div class=\"wuic-rag-chatbot__header\">\n <div class=\"wuic-rag-chatbot__title\">\n <i class=\"pi pi-comments wuic-rag-chatbot__title-icon\" aria-hidden=\"true\"></i>\n <span>{{ title }}</span>\n </div>\n <div class=\"wuic-rag-chatbot__header-actions\">\n @if (allowProfileToggle) {\n <p-button [label]=\"profileToggleLabel\" [icon]=\"profileOverride === 'release' ? 'pi pi-eye' : 'pi pi-code'\"\n [severity]=\"profileOverride === 'release' ? 'warn' : 'secondary'\" [outlined]=\"true\" [rounded]=\"true\"\n size=\"small\" (onClick)=\"toggleProfile()\" [pTooltip]=\"profileOverride === 'release'\n ? ('rag_chatbot.profile_toggle.tooltip_release' | translate)\n : ('rag_chatbot.profile_toggle.tooltip_dev' | translate)\" tooltipPosition=\"bottom\"\n [ariaLabel]=\"'rag_chatbot.profile_toggle.aria_label' | translate\">\n </p-button>\n }\n <p-badge [value]=\"modeBadgeLabel\" [severity]=\"modeBadgeSeverity\" [pTooltip]=\"healthInfo\n ? ('chunks indicizzati: ' + healthInfo.docs_loaded + ' | modello: ' + healthInfo.default_model)\n : (healthError || ('rag_chatbot.health_indicator.connecting' | translate))\" tooltipPosition=\"bottom\">\n </p-badge>\n <!-- Context cue: cerchio colorato che riflette % context window usato.\n Click + conferma -> /compact. Tooltip mostra k/k token + %. -->\n @if (contextWindowMax) {\n <button type=\"button\"\n class=\"wuic-rag-chatbot__context-cue\"\n [style.background]=\"contextCueColor\"\n [pTooltip]=\"contextCueTooltip\"\n tooltipPosition=\"bottom\"\n [disabled]=\"compactingSession || !currentSessionId\"\n (click)=\"onContextCueClick()\"\n attr.aria-label=\"Compatta sessione (context {{ contextUsagePercent }}%)\">\n @if (compactingSession) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else {\n <span>{{ contextUsagePercent }}%</span>\n }\n </button>\n }\n <!-- Bottone Sessioni: apre popup con lista sessioni ordinate desc per updatedAt. -->\n <p-button icon=\"pi pi-list\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"openSessionsDialog()\" [pTooltip]=\"'rag_chatbot.sessions_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.sessions_button.aria_label' | translate\">\n </p-button>\n @if (showClearButton) {\n <p-button icon=\"pi pi-trash\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n [disabled]=\"history.length === 0 || loading || clearingHistory\" [loading]=\"clearingHistory\"\n (onClick)=\"onClearHistory()\" [pTooltip]=\"'rag_chatbot.clear_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.clear_button.aria_label' | translate\">\n </p-button>\n }\n </div>\n </div>\n\n <!-- Health error banner -->\n @if (healthError) {\n <p-message severity=\"error\" [text]=\"('rag_chatbot.health_error.banner' | translate) + ': ' + healthError\"\n styleClass=\"wuic-rag-chatbot__error-banner\"></p-message>\n }\n\n <!-- Chat history -->\n <div #scrollContainer class=\"wuic-rag-chatbot__history\" role=\"log\" aria-live=\"polite\">\n @if (history.length === 0) {\n <div class=\"wuic-rag-chatbot__empty\">\n <i class=\"pi pi-info-circle wuic-rag-chatbot__empty-icon\" aria-hidden=\"true\"></i>\n <p>{{ 'rag_chatbot.empty.prompt' | translate }}</p>\n <ul class=\"wuic-rag-chatbot__suggestions\">\n <li>{{ 'rag_chatbot.suggestion.invalidate_cache' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.spreadsheet_massive' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.save_report' | translate }}</li>\n </ul>\n </div>\n }\n\n @for (turn of history; track turn.messageId || $index) {\n <div class=\"wuic-rag-chatbot__turn\" [attr.data-message-id]=\"turn.messageId || null\"\n [class.wuic-rag-chatbot__turn--user]=\"turn.role === 'user'\"\n [class.wuic-rag-chatbot__turn--assistant]=\"turn.role === 'assistant'\">\n\n <div class=\"wuic-rag-chatbot__turn-header\">\n <i class=\"pi\" [class.pi-user]=\"turn.role === 'user'\" [class.pi-android]=\"turn.role === 'assistant'\"\n aria-hidden=\"true\"></i>\n <span>{{ (turn.role === 'user' ? 'rag_chatbot.turn.role_user' : 'rag_chatbot.turn.role_assistant') | translate }}</span>\n @if (turn.mode) {\n <span class=\"wuic-rag-chatbot__turn-mode\">[{{ turn.mode === 'rag-llm' ? 'RAG+LLM' : 'retrieval' }}]</span>\n }\n </div>\n\n @if (turn.loading) {\n <div class=\"wuic-rag-chatbot__turn-loading\">\n <p-progressSpinner styleClass=\"wuic-rag-chatbot__spinner\" strokeWidth=\"3\"\n animationDuration=\".9s\"></p-progressSpinner>\n <span>{{ 'rag_chatbot.turn.loading' | translate }}</span>\n </div>\n } @else if (turn.errorMessage) {\n <p-message severity=\"error\" [text]=\"(turn.errorMessage | translate)\" styleClass=\"wuic-rag-chatbot__turn-error\"></p-message>\n } @else {\n <div class=\"wuic-rag-chatbot__turn-content\">{{ turn.content }}</div>\n @if (turn.warning) {\n <p-message severity=\"warn\" [text]=\"turn.warning\" styleClass=\"wuic-rag-chatbot__turn-warning\"></p-message>\n }\n <!-- Box \"Proposta azione\" emessa dall'LLM via tool-use. Renderizzato solo\n per i turn assistant che hanno proposedAction valorizzata. Il monaco\n editor riusa la stessa codeContext del metadata-editor sulla colonna\n equivalente (autocomplete identico, type-check identico). -->\n @if (turn.role === 'assistant' && turn.proposedAction) {\n <div class=\"wuic-rag-chatbot__action-box\"\n [class.wuic-rag-chatbot__action-box--applied]=\"turn.proposedActionAppliedAt\"\n [class.wuic-rag-chatbot__action-box--error]=\"turn.applyError\">\n <div class=\"wuic-rag-chatbot__action-box-header\">\n <i class=\"pi pi-bolt\" aria-hidden=\"true\"></i>\n <span class=\"wuic-rag-chatbot__action-box-title\">\n {{ proposedActionTitle(turn.proposedAction) }}\n @if (turn.proposedAction.label) {\n <span class=\"wuic-rag-chatbot__action-box-label\">\"{{ turn.proposedAction.label }}\"</span>\n }\n @if (turn.proposedAction.route) {\n <span class=\"wuic-rag-chatbot__action-box-route\">{{ 'rag_chatbot.action_box.label_on_route_prefix' | translate }} <code>{{ turn.proposedAction.route }}</code></span>\n }\n </span>\n </div>\n @if (turn.proposedAction.rationale) {\n <div class=\"wuic-rag-chatbot__action-box-rationale\">{{ turn.proposedAction.rationale }}</div>\n }\n <!-- Hint trasparenza UX: l'utente sa in anticipo che, oltre a creare\n la toolbar action, abiliteremo md_multiple_selection sulla route\n (necessario per le bulk operations che leggono getSelectedRows). -->\n @if (turn.proposedAction.requires_multi_selection) {\n <div class=\"wuic-rag-chatbot__action-box-side-effect\">\n <i class=\"pi pi-info-circle\"></i>\n {{ 'rag_chatbot.action_box.side_effect_multi_selection' | translate }}\n </div>\n }\n <!-- Campi NON-body della proposta (es. per table_style: classe/stile;\n per toolbar/row_action: label+icon; per sql_metadata_field: field\n target). Visibili sopra la textarea del body cosi' l'utente capisce\n a colpo d'occhio COSA viene applicato oltre al CODICE del callback.\n Sorgente: proposedActionExtraFields(kind) nel .ts. -->\n @if (proposedActionExtraFields(turn.proposedAction).length > 0) {\n <div class=\"wuic-rag-chatbot__action-box-extra-fields\">\n @for (extra of proposedActionExtraFields(turn.proposedAction); track extra.fieldName) {\n <div class=\"wuic-rag-chatbot__action-box-extra-field\">\n <label class=\"wuic-rag-chatbot__action-box-extra-field-label\">{{ extra.label }}</label>\n <input\n class=\"wuic-rag-chatbot__action-box-extra-field-value\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"extra.value\"\n (ngModelChange)=\"onProposedExtraFieldEdit(turn, extra, $event)\"\n [name]=\"'extra-' + turn.messageId + '-' + extra.fieldName\">\n </div>\n }\n </div>\n }\n @if (proposedActionJsBody(turn.proposedAction)) {\n <!-- Pattern <lib-code-area-editor>: textarea readonly (zero overhead,\n niente flash nero, contenuto immediatamente visibile) di default.\n Toggle \"Apri editor avanzato\" attiva `editorMode` -> @defer carica\n Monaco con codeContext+autocomplete. Doppio livello di lazy:\n @defer in HTML + LazyCodeEditorComponent fa import() dinamico.\n Risultato: Monaco scaricato SOLO se l'utente lo richiede.\n proposedActionJsBody() unifica callback_js / condition_js / formula_js\n in base al kind: stesso UX per tutti i kind che hanno una body JS. -->\n <div class=\"wuic-rag-chatbot__action-box-editor-toolbar\">\n <p-button\n [label]=\"(turn.editorMode ? 'rag_chatbot.action_box.editor_close' : 'rag_chatbot.action_box.editor_open') | translate\"\n [icon]=\"turn.editorMode ? 'pi pi-times' : 'pi pi-code'\"\n severity=\"secondary\"\n [text]=\"true\"\n size=\"small\"\n (onClick)=\"toggleEditorMode(turn)\">\n </p-button>\n </div>\n @if (!turn.editorMode) {\n <textarea\n class=\"wuic-rag-chatbot__action-box-textarea\"\n rows=\"10\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"proposedActionJsBody(turn.proposedAction)\"\n (ngModelChange)=\"onProposedBodyEdit(turn, $event)\"\n [name]=\"'body-' + turn.messageId\">\n </textarea>\n } @else {\n @defer (when turn.editorMode) {\n <div class=\"wuic-rag-chatbot__action-box-editor\">\n <wuic-code-editor-lazy\n [field]=\"getProposedActionField(turn)\"\n [record]=\"getProposedActionRecord(turn)\"\n style=\"display: block; width: 100%; height: 100%;\">\n </wuic-code-editor-lazy>\n </div>\n } @loading (minimum 200ms) {\n <div class=\"wuic-rag-chatbot__action-box-editor-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.editor_loading' | translate }}\n </div>\n }\n }\n }\n <div class=\"wuic-rag-chatbot__action-box-actions\">\n @if (turn.proposedActionAppliedAt) {\n <span class=\"wuic-rag-chatbot__action-box-applied\">\n <i class=\"pi pi-check-circle\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.applied' | translate }}\n </span>\n } @else {\n <p-button\n [label]=\"'rag_chatbot.action_box.apply' | translate\"\n icon=\"pi pi-check\"\n severity=\"primary\"\n size=\"small\"\n [loading]=\"!!turn.applyingAction\"\n (onClick)=\"onApplyAction(turn)\">\n </p-button>\n }\n @if (turn.applyError) {\n <span class=\"wuic-rag-chatbot__action-box-error\">\n <i class=\"pi pi-times-circle\" aria-hidden=\"true\"></i>\n {{ turn.applyError }}\n </span>\n }\n </div>\n </div>\n }\n @if (showSources && turn.role === 'assistant' && turn.sources && turn.sources.length > 0) {\n @let dedupedSources = dedupSources(turn.sources);\n <div class=\"wuic-rag-chatbot__sources\">\n <div class=\"wuic-rag-chatbot__sources-label\">{{ 'rag_chatbot.sources.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__sources-list\">\n @for (source of dedupedSources; track source.chunk_id || source.rank) {\n <p-chip [label]=\"sourceLabel(source, dedupedSources)\"\n [icon]=\"isDocsSource(source) ? 'pi pi-book' : (isSkillSource(source) ? 'pi pi-wrench' : 'pi pi-file-edit')\"\n [removable]=\"false\" [pTooltip]=\"sourceTooltip(source)\" tooltipPosition=\"top\" [styleClass]=\"isDocsSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--docs'\n : (isSkillSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--skill'\n : 'wuic-rag-chatbot__source-chip')\" (click)=\"onSourceClick(source)\">\n </p-chip>\n }\n </div>\n </div>\n }\n <!-- Follow-up questions emesse dall'LLM via tool `suggest_followups`.\n Renderizzate come chip cliccabili sotto la risposta: click popola\n l'input box (NON invia auto, l'utente puo' editare prima). -->\n @if (turn.role === 'assistant' && turn.followupQuestions && turn.followupQuestions.length > 0) {\n <div class=\"wuic-rag-chatbot__followups\">\n <div class=\"wuic-rag-chatbot__followups-label\">{{ 'rag_chatbot.followups.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__followups-list\">\n @for (q of turn.followupQuestions; track q) {\n <p-chip\n [label]=\"q\"\n icon=\"pi pi-arrow-right\"\n styleClass=\"wuic-rag-chatbot__followup-chip\"\n (click)=\"applyFollowup(q)\">\n </p-chip>\n }\n </div>\n </div>\n }\n @if (turn.tokensIn || turn.tokensOut) {\n <div class=\"wuic-rag-chatbot__tokens\">\n tokens in/out: {{ turn.tokensIn ?? '?' }}/{{ turn.tokensOut ?? '?' }}\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Input area -->\n <div class=\"wuic-rag-chatbot__input-area\">\n <textarea #inputTextarea pTextarea class=\"wuic-rag-chatbot__input\" [(ngModel)]=\"inputValue\"\n (keydown)=\"onKeyDown($event)\" [placeholder]=\"placeholder\" [disabled]=\"loading || !!healthError\"\n [autoResize]=\"true\" rows=\"1\" [attr.aria-label]=\"'rag_chatbot.input.aria_label' | translate\">\n </textarea>\n <p-button icon=\"pi pi-send\" [disabled]=\"sendDisabled\" [loading]=\"loading\" (onClick)=\"onSend()\" severity=\"primary\"\n [ariaLabel]=\"'rag_chatbot.input.send_aria_label' | translate\">\n </p-button>\n </div>\n\n <!-- Dialog preview snippet sorgente.\n Mostra SOLO il contenuto del chunk (gia' embedded nella response RAG).\n NON espone il path del file ne' apre editor esterni: il codebase del\n framework vive sul repo dev e non deve essere linkato all'utente finale. -->\n @if (selectedSource) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw' }\" [header]=\"sourceDialogTitle(selectedSource)\"\n styleClass=\"wuic-rag-chatbot__source-dialog\" (onHide)=\"closeSourcePreview()\">\n <div class=\"wuic-rag-chatbot__source-meta\">\n @if (sourceFilePath(selectedSource); as fpath) {\n <span class=\"wuic-rag-chatbot__source-tag wuic-rag-chatbot__source-tag--file\">\n <i class=\"pi pi-file\" aria-hidden=\"true\"></i>\n {{ fpath }}\n </span>\n }\n @if (selectedSource.symbol_type) {\n <span class=\"wuic-rag-chatbot__source-tag\">{{ selectedSource.symbol_type }}</span>\n }\n @if (selectedSource.start_line && selectedSource.end_line) {\n <span class=\"wuic-rag-chatbot__source-tag\">linee {{ selectedSource.start_line }}-{{ selectedSource.end_line\n }}</span>\n }\n @if (selectedSource.score_vector !== null && selectedSource.score_vector !== undefined) {\n <span class=\"wuic-rag-chatbot__source-tag\">vec {{ selectedSource.score_vector | number:'1.2-2' }}</span>\n }\n </div>\n <pre\n class=\"wuic-rag-chatbot__source-snippet\"><code>{{ selectedSource.snippet || ('rag_chatbot.source.snippet_unavailable' | translate) }}</code></pre>\n </p-dialog>\n }\n\n <!-- Dialog \"Sessioni\": elenco ordinato desc per updatedAt. Per ogni riga:\n data + titolo (truncated 100 char, tooltip full) + bottone rinomina\n inline + click row = switch session. -->\n @if (sessionsDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw', maxHeight: '85vh' }\"\n [header]=\"'rag_chatbot.sessions_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__sessions-dialog\"\n (onHide)=\"closeSessionsDialog()\">\n @if (sessionsLoading) {\n <div class=\"wuic-rag-chatbot__sessions-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i> {{ 'rag_chatbot.sessions_dialog.loading' | translate }}\n </div>\n } @else if (sessionsList.length === 0) {\n <div class=\"wuic-rag-chatbot__sessions-empty\">{{ 'rag_chatbot.sessions_dialog.empty' | translate }}</div>\n } @else {\n <ul class=\"wuic-rag-chatbot__sessions-list\">\n @for (s of sessionsList; track s.id) {\n <li class=\"wuic-rag-chatbot__session-item\"\n [class.wuic-rag-chatbot__session-item--current]=\"s.id === currentSessionId\"\n (click)=\"selectSession(s)\">\n <div class=\"wuic-rag-chatbot__session-date\">{{ sessionDate(s) }}</div>\n @if (sessionRenameEditing[s.id]) {\n <div class=\"wuic-rag-chatbot__session-edit\" (click)=\"$event.stopPropagation()\">\n <input type=\"text\" pInputText\n [(ngModel)]=\"sessionRenameDraft[s.id]\"\n [autofocus]=\"true\"\n maxlength=\"200\"\n class=\"wuic-rag-chatbot__session-edit-input\"\n (keydown.enter)=\"confirmRenameSession(s, $event)\"\n (keydown.escape)=\"cancelRenameSession(s)\">\n <p-button icon=\"pi pi-check\" severity=\"success\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"confirmRenameSession(s, $event)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_confirm_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-times\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"cancelRenameSession(s)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_cancel_aria' | translate\"></p-button>\n </div>\n } @else {\n <div class=\"wuic-rag-chatbot__session-row\">\n <span class=\"wuic-rag-chatbot__session-title\" [pTooltip]=\"s.title || ('rag_chatbot.sessions_dialog.untitled' | translate)\" tooltipPosition=\"top\">\n {{ sessionPreview(s) }}\n </span>\n <p-button icon=\"pi pi-pencil\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"startRenameSession(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.rename' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-trash\" severity=\"danger\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"onDeleteSessionFromList(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.delete' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.delete_aria' | translate\"></p-button>\n </div>\n }\n </li>\n }\n </ul>\n }\n </p-dialog>\n }\n\n <!-- Dialog context-overflow: l'ultima call ha fallito perche' la history\n sfora il context window. 2 azioni: Compatta ora (riassume e prosegue\n stessa session) / Nuova sessione (clear + ricomincia). -->\n @if (contextOverflowDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"false\"\n [style]=\"{ width: '480px', maxWidth: '92vw' }\"\n [header]=\"'rag_chatbot.overflow_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__overflow-dialog\"\n (onHide)=\"contextOverflowDialogOpen = false\">\n <p>{{ 'rag_chatbot.overflow_dialog.intro' | translate }}</p>\n <ul>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_compact_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_compact_desc' | translate }}</li>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_new_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_new_desc' | translate }}</li>\n </ul>\n <div class=\"wuic-rag-chatbot__overflow-actions\">\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_compact_title' | translate\" icon=\"pi pi-compress\" severity=\"primary\"\n [loading]=\"compactingSession\"\n (onClick)=\"doCompactSession()\"></p-button>\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_new_title' | translate\" icon=\"pi pi-refresh\" severity=\"secondary\"\n (onClick)=\"startNewSession()\"></p-button>\n </div>\n </p-dialog>\n }\n\n</div>", styles: [".wuic-rag-chatbot{--wuic-rag-chatbot-radius: .5rem;--wuic-rag-chatbot-padding: .75rem;--wuic-rag-chatbot-gap: .5rem;display:flex;flex-direction:column;width:100%;height:100%;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:var(--wuic-rag-chatbot-radius);background-color:var(--p-content-background, transparent);color:inherit;overflow:hidden;font-family:inherit}.wuic-rag-chatbot__header{display:flex;align-items:center;justify-content:space-between;padding:var(--wuic-rag-chatbot-padding);border-bottom:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__title{display:flex;align-items:center;gap:.5rem;font-weight:600;color:inherit}.wuic-rag-chatbot__title-icon{color:var(--p-primary-color, #3b82f6)}.wuic-rag-chatbot__header-actions{display:flex;align-items:center;gap:.5rem}.wuic-rag-chatbot__context-cue{width:28px;height:28px;border-radius:50%;border:2px solid rgba(255,255,255,.85);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:.62rem;font-weight:700;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.35);box-shadow:0 0 0 1px #0000000f,0 1px 3px #0000001f;transition:transform .12s ease,box-shadow .12s ease,opacity .12s ease;padding:0}.wuic-rag-chatbot__context-cue:hover:not(:disabled){transform:scale(1.08);box-shadow:0 0 0 1px #0000001a,0 2px 6px #0003}.wuic-rag-chatbot__context-cue:disabled{cursor:default;opacity:.65}.wuic-rag-chatbot__context-cue>i{font-size:.85rem}.wuic-rag-chatbot__sessions-loading,.wuic-rag-chatbot__sessions-empty{padding:1.5rem;text-align:center;color:var(--p-text-muted-color, #6b7280);font-style:italic}.wuic-rag-chatbot__sessions-list{list-style:none;margin:0;padding:0;max-height:60vh;overflow-y:auto}.wuic-rag-chatbot__session-item{padding:.6rem .85rem;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;margin-bottom:.4rem;cursor:pointer;transition:background .12s ease,border-color .12s ease}.wuic-rag-chatbot__session-item:hover{background:var(--p-surface-50, #f3f4f6);border-color:var(--p-primary-300, rgba(59, 130, 246, .4))}.wuic-rag-chatbot__session-item--current{background:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-400, rgba(59, 130, 246, .55))}.wuic-rag-chatbot__session-date{font-size:.72rem;color:var(--p-text-muted-color, #6b7280);font-family:ui-monospace,monospace;margin-bottom:.2rem}.wuic-rag-chatbot__session-row{display:flex;align-items:center;gap:.4rem}.wuic-rag-chatbot__session-title{flex:1 1 auto;font-size:.9rem;color:var(--p-text-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wuic-rag-chatbot__session-edit{display:flex;align-items:center;gap:.3rem}.wuic-rag-chatbot__session-edit-input{flex:1 1 auto;font-size:.85rem}.wuic-rag-chatbot__overflow-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1rem}.wuic-rag-chatbot__error-banner{margin:var(--wuic-rag-chatbot-padding);margin-bottom:0}.wuic-rag-chatbot__history{flex:1 1 auto;min-height:0;overflow-y:auto;padding:var(--wuic-rag-chatbot-padding);display:flex;flex-direction:column;gap:var(--wuic-rag-chatbot-gap);background-color:transparent}.wuic-rag-chatbot__empty{margin:auto;text-align:center;color:var(--p-text-muted-color, #6b7280);max-width:80%;padding:1.5rem 0}.wuic-rag-chatbot__empty-icon{font-size:1.5rem;margin-bottom:.5rem;display:block}.wuic-rag-chatbot__suggestions{list-style:none;padding:0;margin:.75rem 0 0;display:flex;flex-direction:column;gap:.25rem;text-align:left}.wuic-rag-chatbot__suggestions li{padding:.35rem .75rem;border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:.375rem;font-size:.875rem}.wuic-rag-chatbot__turn{display:flex;flex-direction:column;gap:.35rem;padding:.65rem .85rem;border-radius:var(--wuic-rag-chatbot-radius);max-width:92%;color:inherit;background-color:transparent;border:1px solid var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__turn--user{align-self:flex-end;background-color:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-200, rgba(59, 130, 246, .25))}.wuic-rag-chatbot__turn--assistant{align-self:flex-start;background-color:var(--p-surface-50, rgba(0, 0, 0, .025))}.wuic-rag-chatbot__turn-header{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__turn-mode{margin-left:auto;font-family:ui-monospace,monospace;font-size:.7rem;opacity:.7}.wuic-rag-chatbot__turn-content{white-space:pre-wrap;word-break:break-word;line-height:1.45;font-size:.93rem;color:inherit}.wuic-rag-chatbot__turn-loading{display:flex;align-items:center;gap:.5rem;color:var(--p-text-muted-color, #6b7280);font-style:italic;font-size:.875rem}.wuic-rag-chatbot__spinner{width:1.25rem!important;height:1.25rem!important}.wuic-rag-chatbot__turn-error,.wuic-rag-chatbot__turn-warning{margin-top:.35rem}.wuic-rag-chatbot__sources{display:flex;flex-direction:column;gap:.35rem;margin-top:.5rem;padding-top:.5rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__sources-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.05em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__sources-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__source-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.wuic-rag-chatbot__source-chip:hover{transform:translateY(-1px);box-shadow:0 1px 4px #0000001f}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:var(--p-blue-50, #eff6ff);color:var(--p-blue-700, #1d4ed8);border:1px solid var(--p-blue-200, #bfdbfe)}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:var(--p-blue-600, #2563eb)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:var(--p-green-50, #f0fdf4);color:var(--p-green-700, #15803d);border:1px solid var(--p-green-200, #bbf7d0)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:var(--p-green-600, #16a34a)}.wuic-rag-chatbot__tokens{margin-top:.4rem;font-size:.7rem;color:var(--p-text-muted-color, #6b7280);text-align:right;font-family:ui-monospace,monospace}.wuic-rag-chatbot__followups{display:flex;flex-direction:column;gap:.3rem;margin-top:.55rem;padding-top:.55rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__followups-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__followups-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__followup-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease,background .12s ease}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip{background:var(--p-primary-50, rgba(59, 130, 246, .08));color:var(--p-primary-700, #1d4ed8);border:1px solid var(--p-primary-200, rgba(59, 130, 246, .25));font-size:.82rem;line-height:1.3;padding:.2rem .5rem}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip-icon{color:var(--p-primary-600, #2563eb);font-size:.75rem}.wuic-rag-chatbot__followup-chip:hover{transform:translateY(-1px);box-shadow:0 2px 6px #3b82f62e}.wuic-rag-chatbot__followup-chip:hover :host ::ng-deep .p-chip{background:var(--p-primary-100, rgba(59, 130, 246, .15))}.wuic-rag-chatbot__action-box{margin-top:.65rem;border:1px solid var(--p-primary-300, rgba(59, 130, 246, .45));border-radius:8px;background:var(--p-primary-50, rgba(59, 130, 246, .05));padding:.6rem .75rem;display:flex;flex-direction:column;gap:.45rem}.wuic-rag-chatbot__action-box--applied{border-color:var(--p-green-300, #86efac);background:var(--p-green-50, #f0fdf4)}.wuic-rag-chatbot__action-box--error{border-color:var(--p-red-300, #fca5a5);background:var(--p-red-50, #fef2f2)}.wuic-rag-chatbot__action-box-header{display:flex;align-items:center;gap:.45rem;font-size:.88rem;color:var(--p-primary-700, #1d4ed8);font-weight:600}.wuic-rag-chatbot__action-box-header>i{font-size:.95rem}.wuic-rag-chatbot__action-box-title{display:inline-flex;align-items:center;gap:.4rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-label{color:var(--p-text-color, #111827);font-weight:700}.wuic-rag-chatbot__action-box-route{color:var(--p-text-muted-color, #6b7280);font-weight:400;font-size:.8rem}.wuic-rag-chatbot__action-box-route>code{background:var(--p-surface-100, #f3f4f6);padding:1px 6px;border-radius:4px;font-family:ui-monospace,monospace;color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-rationale{font-size:.85rem;color:var(--p-text-color, #1f2937);line-height:1.4;font-style:italic}.wuic-rag-chatbot__action-box-side-effect{font-size:.8rem;color:var(--p-blue-700, #1d4ed8);background:var(--p-blue-50, #eff6ff);border-left:3px solid var(--p-blue-400, #60a5fa);padding:.4rem .6rem;border-radius:4px;display:flex;align-items:flex-start;gap:.4rem;line-height:1.35}.wuic-rag-chatbot__action-box-side-effect i{font-size:.9rem;margin-top:.1rem;flex-shrink:0}.wuic-rag-chatbot__action-box-editor-toolbar{display:flex;justify-content:flex-end;align-items:center}.wuic-rag-chatbot__action-box-extra-fields{display:flex;flex-direction:column;gap:6px;margin-bottom:8px}.wuic-rag-chatbot__action-box-extra-field{display:flex;align-items:center;gap:8px}.wuic-rag-chatbot__action-box-extra-field-label{flex:0 0 auto;min-width:100px;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__action-box-extra-field-value{flex:1 1 auto;min-width:0;font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;padding:4px 8px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:4px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-extra-field-value:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-textarea{width:100%;min-height:200px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;padding:8px 10px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827);font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;line-height:1.45;resize:vertical;white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:hidden;overflow-y:auto;tab-size:2}.wuic-rag-chatbot__action-box-textarea:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-editor{height:240px;min-height:240px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;overflow:hidden;background:#1e1e1e}.wuic-rag-chatbot__action-box-editor-loading{display:flex;align-items:center;justify-content:center;gap:.5rem;min-height:100px;color:var(--p-text-muted-color, #6b7280);font-size:.85rem;font-style:italic;background:var(--p-surface-50, #fafafa);border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:6px}.wuic-rag-chatbot__action-box-actions{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-applied{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-green-700, #15803d);font-weight:600;font-size:.85rem}.wuic-rag-chatbot__action-box-error{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-red-700, #b91c1c);font-size:.8rem;font-family:ui-monospace,monospace}.wuic-rag-chatbot__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:var(--wuic-rag-chatbot-padding);border-top:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__input{flex:1 1 auto;min-height:2.25rem;max-height:8rem;resize:none;color:inherit;background-color:transparent}.wuic-rag-chatbot__source-meta{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.75rem}.wuic-rag-chatbot__source-tag{display:inline-flex;align-items:center;gap:.25rem;padding:2px 8px;background:var(--p-surface-100, #f3f4f6);color:var(--p-text-color-secondary, #6b7280);border-radius:10px;font-size:.72rem;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.wuic-rag-chatbot__source-tag--file{background:var(--p-surface-0, #fff);color:var(--p-primary-color, #2563eb);border:1px solid var(--p-primary-200, #bfdbfe);font-weight:500;max-width:100%;word-break:break-all;white-space:normal;line-height:1.3}.wuic-rag-chatbot__source-tag--file i{font-size:.7rem;flex-shrink:0}.wuic-rag-chatbot__source-snippet{margin:0;padding:12px 14px;max-height:420px;overflow:auto;background:var(--p-surface-50, #fafafa);border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.78rem;line-height:1.4;white-space:pre-wrap;word-break:break-word;color:var(--p-text-color, #111827)}:host-context(.theme-dark) .wuic-rag-chatbot__turn--user{background-color:#60a5fa2e;border-color:#60a5fa73}:host-context(.theme-dark) .wuic-rag-chatbot__turn--assistant{background-color:#ffffff0f;border-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__turn-header,:host-context(.theme-dark) .wuic-rag-chatbot__turn-loading,:host-context(.theme-dark) .wuic-rag-chatbot__sources-label,:host-context(.theme-dark) .wuic-rag-chatbot__tokens{color:#ffffffa6}:host-context(.theme-dark) .wuic-rag-chatbot__sources{border-top-color:#ffffff2e}:host-context(.theme-dark) .wuic-rag-chatbot__input-area{border-top-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:#3b82f62e;color:#93c5fd;border:1px solid rgba(59,130,246,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:#bfdbfe}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:#22c55e2e;color:#86efac;border:1px solid rgba(34,197,94,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:#bbf7d0}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag{background:#ffffff14;color:#ffffffc7}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag--file{background:#3b82f624;color:#93c5fd;border-color:#3b82f673}:host-context(.theme-dark) .wuic-rag-chatbot__source-snippet{background:#00000052;border-color:#ffffff1f;color:#ffffffeb}\n"] }]
|
|
63527
|
+
}], ctorParameters: () => [{ type: WuicRagService }, { type: i0.ChangeDetectorRef }, { type: ChatbotHostRegistryService }, { type: i1$2.ActivatedRoute, decorators: [{
|
|
63528
|
+
type: Optional
|
|
63529
|
+
}] }, { type: i1$2.Router, decorators: [{
|
|
63530
|
+
type: Optional
|
|
63531
|
+
}] }, { type: NotificationRealtimeService, decorators: [{
|
|
63532
|
+
type: Optional
|
|
63533
|
+
}] }, { type: MetadataProviderService, decorators: [{
|
|
63534
|
+
type: Optional
|
|
63535
|
+
}] }], propDecorators: { title: [{
|
|
63536
|
+
type: Input
|
|
63537
|
+
}], mode: [{
|
|
63538
|
+
type: Input
|
|
63539
|
+
}], topK: [{
|
|
63540
|
+
type: Input
|
|
63541
|
+
}], showSources: [{
|
|
63542
|
+
type: Input
|
|
63543
|
+
}], maxHistory: [{
|
|
63544
|
+
type: Input
|
|
63545
|
+
}], placeholder: [{
|
|
63546
|
+
type: Input
|
|
63547
|
+
}], model: [{
|
|
63548
|
+
type: Input
|
|
63549
|
+
}], chatHeight: [{
|
|
63550
|
+
type: Input
|
|
63551
|
+
}], showClearButton: [{
|
|
63552
|
+
type: Input
|
|
63553
|
+
}], persistSession: [{
|
|
63554
|
+
type: Input
|
|
63555
|
+
}], sendRouteContext: [{
|
|
63556
|
+
type: Input
|
|
63557
|
+
}], allowProfileToggle: [{
|
|
63558
|
+
type: Input
|
|
63559
|
+
}], resultSelected: [{
|
|
63560
|
+
type: Output
|
|
63561
|
+
}], errorOccurred: [{
|
|
63562
|
+
type: Output
|
|
63563
|
+
}], turnAdded: [{
|
|
63564
|
+
type: Output
|
|
63565
|
+
}], inputTextarea: [{
|
|
63566
|
+
type: ViewChild,
|
|
63567
|
+
args: ['inputTextarea']
|
|
63568
|
+
}], scrollContainer: [{
|
|
63569
|
+
type: ViewChild,
|
|
63570
|
+
args: ['scrollContainer']
|
|
63571
|
+
}] } }) });
|
|
61572
63572
|
|
|
61573
63573
|
var ragChatbot_component = /*#__PURE__*/Object.freeze({
|
|
61574
63574
|
__proto__: null,
|
|
61575
63575
|
WuicRagChatbotComponent: WuicRagChatbotComponent
|
|
61576
63576
|
});
|
|
61577
63577
|
|
|
63578
|
+
/**
|
|
63579
|
+
* Route principale che renderizza il chatbot full-page (vedi `app.routes.ts`:
|
|
63580
|
+
* path 'rag-chatbot' → WuicRagChatbotComponent). Quando l'utente e' gia' su
|
|
63581
|
+
* quella route, il FAB diventa ridondante (e visivamente sovrapposto al chatbot
|
|
63582
|
+
* stesso) → lo nascondiamo. Il match e' su prefisso per coprire eventuali
|
|
63583
|
+
* sub-route future tipo `/rag-chatbot/session/<id>`.
|
|
63584
|
+
*/
|
|
63585
|
+
const RAG_CHATBOT_ROUTE_PREFIX = '/rag-chatbot';
|
|
61578
63586
|
/**
|
|
61579
63587
|
* Floating Action Button (FAB) bottom-right che apre il chatbot RAG come
|
|
61580
63588
|
* dialog overlay non-modale. Riusa il componente esistente
|
|
@@ -61604,6 +63612,7 @@ class WuicRagChatbotFabComponent {
|
|
|
61604
63612
|
licenseService;
|
|
61605
63613
|
notificationRT;
|
|
61606
63614
|
cdr;
|
|
63615
|
+
router;
|
|
61607
63616
|
/** Tooltip dell'icona FAB. */
|
|
61608
63617
|
fabTooltip = 'Chiedi al codebase WUIC';
|
|
61609
63618
|
/** Titolo del dialog (passato anche al chatbot interno). */
|
|
@@ -61639,11 +63648,20 @@ class WuicRagChatbotFabComponent {
|
|
|
61639
63648
|
licenseAllowed = false;
|
|
61640
63649
|
/** Counter unread per il badge sul FAB. */
|
|
61641
63650
|
unreadRagCount = 0;
|
|
63651
|
+
/**
|
|
63652
|
+
* True quando la route corrente e' `/rag-chatbot` (o un suo sub-path).
|
|
63653
|
+
* Nascondiamo il FAB in quel caso perche' renderebbe un launcher duplicato
|
|
63654
|
+
* sopra il chatbot gia' aperto a tutta pagina. Aggiornato reattivamente
|
|
63655
|
+
* ai `NavigationEnd` del Router, e inizializzato dal `router.url` corrente
|
|
63656
|
+
* cosi' il primo render (es. F5 mentre sei gia' sulla route) e' coerente.
|
|
63657
|
+
*/
|
|
63658
|
+
onRagChatbotRoute = false;
|
|
61642
63659
|
subs = new Subscription();
|
|
61643
|
-
constructor(licenseService, notificationRT, cdr) {
|
|
63660
|
+
constructor(licenseService, notificationRT, cdr, router) {
|
|
61644
63661
|
this.licenseService = licenseService;
|
|
61645
63662
|
this.notificationRT = notificationRT;
|
|
61646
63663
|
this.cdr = cdr;
|
|
63664
|
+
this.router = router;
|
|
61647
63665
|
}
|
|
61648
63666
|
ngOnInit() {
|
|
61649
63667
|
// Subscribe al license status. La status$() emette null finche' refresh()
|
|
@@ -61664,11 +63682,34 @@ class WuicRagChatbotFabComponent {
|
|
|
61664
63682
|
this.cdr.markForCheck();
|
|
61665
63683
|
}
|
|
61666
63684
|
}));
|
|
63685
|
+
// Track route corrente: nascondi il FAB quando sei gia' sul chatbot
|
|
63686
|
+
// full-page. `router.url` e' valorizzato anche prima del primo NavigationEnd
|
|
63687
|
+
// (es. F5 con URL `#/rag-chatbot` -> router lo ha gia' parsato a costruzione
|
|
63688
|
+
// del component), quindi inizializziamo subito + restiamo in sync via events.
|
|
63689
|
+
this.updateOnRagChatbotRoute(this.router.url);
|
|
63690
|
+
this.subs.add(this.router.events
|
|
63691
|
+
.pipe(filter((e) => e instanceof NavigationEnd))
|
|
63692
|
+
.subscribe((e) => this.updateOnRagChatbotRoute(e.urlAfterRedirects || e.url)));
|
|
63693
|
+
}
|
|
63694
|
+
updateOnRagChatbotRoute(url) {
|
|
63695
|
+
const u = String(url || '').split('?')[0].split('#')[0];
|
|
63696
|
+
// match esatto o sub-path: `/rag-chatbot` oppure `/rag-chatbot/...`
|
|
63697
|
+
const next = u === RAG_CHATBOT_ROUTE_PREFIX || u.startsWith(RAG_CHATBOT_ROUTE_PREFIX + '/');
|
|
63698
|
+
if (next !== this.onRagChatbotRoute) {
|
|
63699
|
+
this.onRagChatbotRoute = next;
|
|
63700
|
+
this.cdr.markForCheck();
|
|
63701
|
+
}
|
|
61667
63702
|
}
|
|
61668
63703
|
ngOnDestroy() {
|
|
61669
63704
|
this.subs.unsubscribe();
|
|
61670
63705
|
}
|
|
61671
63706
|
get visible() {
|
|
63707
|
+
// Anche con licenza valida o `forceVisible=true`, il FAB resta nascosto
|
|
63708
|
+
// quando si e' gia' sulla route full-page del chatbot: aprirebbe un
|
|
63709
|
+
// launcher duplicato sopra il chatbot stesso.
|
|
63710
|
+
if (this.onRagChatbotRoute) {
|
|
63711
|
+
return false;
|
|
63712
|
+
}
|
|
61672
63713
|
return this.forceVisible || this.licenseAllowed;
|
|
61673
63714
|
}
|
|
61674
63715
|
/**
|
|
@@ -61719,8 +63760,8 @@ class WuicRagChatbotFabComponent {
|
|
|
61719
63760
|
this.dialogVisible = false;
|
|
61720
63761
|
this.cdr.markForCheck();
|
|
61721
63762
|
}
|
|
61722
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotFabComponent, deps: [{ token: LicenseFeatureService }, { token: NotificationRealtimeService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
61723
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: WuicRagChatbotFabComponent, isStandalone: true, selector: "wuic-rag-chatbot-fab", inputs: { fabTooltip: "fabTooltip", dialogTitle: "dialogTitle", dialogWidth: "dialogWidth", chatHeight: "chatHeight", forceVisible: "forceVisible" }, ngImport: i0, template: "@if (visible) {\n <!-- FAB bottom-right (position:fixed via .scss). z-index alto per stare sopra\n a navbar, dialog modali leggeri, ecc. ma sotto agli overlay critici (es.\n PrimeNG p-dialog modal stack). -->\n <button\n type=\"button\"\n class=\"wuic-rag-fab\"\n [class.wuic-rag-fab--dialog-open]=\"dialogVisible\"\n [attr.title]=\"fabTooltip\"\n aria-label=\"
|
|
63763
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotFabComponent, deps: [{ token: LicenseFeatureService }, { token: NotificationRealtimeService }, { token: i0.ChangeDetectorRef }, { token: i1$2.Router }], target: i0.ɵɵFactoryTarget.Component });
|
|
63764
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: WuicRagChatbotFabComponent, isStandalone: true, selector: "wuic-rag-chatbot-fab", inputs: { fabTooltip: "fabTooltip", dialogTitle: "dialogTitle", dialogWidth: "dialogWidth", chatHeight: "chatHeight", forceVisible: "forceVisible" }, ngImport: i0, template: "@if (visible) {\n <!-- FAB bottom-right (position:fixed via .scss). z-index alto per stare sopra\n a navbar, dialog modali leggeri, ecc. ma sotto agli overlay critici (es.\n PrimeNG p-dialog modal stack). -->\n <button\n type=\"button\"\n class=\"wuic-rag-fab\"\n [class.wuic-rag-fab--dialog-open]=\"dialogVisible\"\n [attr.title]=\"fabTooltip\"\n [attr.aria-label]=\"'rag_chatbot_fab.aria_label' | translate\"\n (click)=\"toggleDialog()\">\n <i class=\"pi pi-comments\" aria-hidden=\"true\"></i>\n @if (unreadRagCount > 0 && !dialogVisible) {\n <span class=\"wuic-rag-fab__badge\" [attr.data-count]=\"unreadRagCount\">\n {{ unreadRagCount > 9 ? '9+' : unreadRagCount }}\n </span>\n }\n </button>\n\n <!-- Dialog non-modal: l'utente puo' continuare a usare l'app sotto.\n contentStyle limita l'altezza interna al chatHeight + header.\n [draggable]=\"true\" + [resizable]=\"true\" per UX power-user.\n [appendTo]=\"'body'\" sposta il dialog nel body document: evita che\n layout/scroll interactions con componenti heavy sottostanti (Syncfusion\n Spreadsheet con N-mila record, list-grid virtualized, ecc.) freezino la\n pagina quando il dialog apre/chiude. Il parent container NON subisce\n re-layout causato dalla presenza del dialog nel proprio subtree. -->\n <p-dialog\n [(visible)]=\"dialogVisible\"\n [modal]=\"false\"\n [draggable]=\"true\"\n [resizable]=\"true\"\n [closable]=\"true\"\n [closeOnEscape]=\"true\"\n [dismissableMask]=\"false\"\n [appendTo]=\"'body'\"\n [style]=\"{ width: dialogWidth }\"\n [contentStyle]=\"{ height: chatHeight }\"\n [header]=\"dialogTitle\"\n styleClass=\"wuic-rag-fab__dialog\"\n (onHide)=\"onDialogHide()\">\n <!-- Lazy-mount MANUALE del chatbot: PrimeNG p-dialog v17+ NON lazy-monta\n il content projettato (lo istanzia eagerly al creation del parent).\n Senza il guard `@if (dialogOpened)` il chatbot sarebbe istanziato al\n refresh \u2192 ngOnInit \u2192 `ragService.health()` \u2192 `GET /api/Rag/Health`\n anche se l'utente non apre mai il FAB (errore rosso in console se\n il RAG server Python e' offline, 503 Service Unavailable).\n `dialogOpened` e' sticky (setato dal primo `toggleDialog()`, mai\n resetto): una volta aperto, il chatbot resta montato e le\n ri-aperture sono istantanee (PrimeNG controlla solo la visibilita'\n DOM, niente rimonto ngOnInit). -->\n @if (dialogOpened) {\n <wuic-rag-chatbot\n [title]=\"dialogTitle\"\n [chatHeight]=\"chatHeight\"\n [showClearButton]=\"true\">\n </wuic-rag-chatbot>\n }\n </p-dialog>\n}\n", styles: [".wuic-rag-fab{position:fixed;bottom:24px;right:24px;width:56px;height:56px;border-radius:50%;border:none;cursor:pointer;background:var(--p-primary-color, #2563eb);color:var(--p-primary-color-text, #fff);box-shadow:0 4px 12px #0000002e,0 2px 4px #0000001f;display:flex;align-items:center;justify-content:center;z-index:1100;transform:translate(calc(100% + 16px));opacity:.45;transition:transform .28s cubic-bezier(.16,1,.3,1) .25s,opacity .28s ease-out .25s,box-shadow .18s ease-out 0s,background-color .18s ease-out 0s}.wuic-rag-fab i{font-size:24px;line-height:1}.wuic-rag-fab:hover,.wuic-rag-fab:focus-visible{transform:translate(0) scale(1.05);opacity:1;box-shadow:0 6px 18px #00000038,0 3px 6px #00000024;transition:transform .28s cubic-bezier(.16,1,.3,1) 0s,opacity .28s ease-out 0s,box-shadow .18s ease-out 0s,background-color .18s ease-out 0s}.wuic-rag-fab:hover:after,.wuic-rag-fab:focus-visible:after{content:\"\";position:absolute;top:-8px;bottom:-8px;left:100%;width:96px;background:transparent;pointer-events:auto}.wuic-rag-fab:active{transform:translate(0) scale(.98);opacity:1}.wuic-rag-fab:focus-visible{outline:2px solid var(--p-primary-color, #2563eb);outline-offset:3px}.wuic-rag-fab.wuic-rag-fab--dialog-open{transform:translate(0);opacity:1}.wuic-rag-fab__badge{position:absolute;top:-4px;right:-4px;min-width:22px;height:22px;padding:0 6px;border-radius:11px;background:#e11d48;color:#fff;font-size:11px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;box-shadow:0 1px 3px #0000004d;pointer-events:none}.wuic-rag-fab__dialog{position:fixed!important;inset:auto 16px 96px auto!important;margin:0!important;max-height:calc(100vh - 112px)}.wuic-rag-fab__dialog .p-dialog-content{padding:0;overflow:hidden;display:flex;flex-direction:column;height:480px;max-height:calc(100vh - 160px)}.wuic-rag-fab__dialog wuic-rag-chatbot{display:flex;flex:1 1 auto;min-height:0;width:100%}.wuic-rag-fab__dialog .p-dialog-header{padding:12px 18px}.p-dialog-mask:has(>.wuic-rag-fab__dialog){pointer-events:none;background:transparent!important}.p-dialog-mask:has(>.wuic-rag-fab__dialog)>.wuic-rag-fab__dialog{pointer-events:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "ngmodule", type: BadgeModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: WuicRagChatbotComponent, selector: "wuic-rag-chatbot", inputs: ["title", "mode", "topK", "showSources", "maxHistory", "placeholder", "model", "chatHeight", "showClearButton", "persistSession", "sendRouteContext", "allowProfileToggle"], outputs: ["resultSelected", "errorOccurred", "turnAdded"] }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
61724
63765
|
}
|
|
61725
63766
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotFabComponent, decorators: [{
|
|
61726
63767
|
type: Component,
|
|
@@ -61730,9 +63771,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
61730
63771
|
DialogModule,
|
|
61731
63772
|
TooltipModule,
|
|
61732
63773
|
BadgeModule,
|
|
63774
|
+
TranslateModule,
|
|
61733
63775
|
WuicRagChatbotComponent,
|
|
61734
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if (visible) {\n <!-- FAB bottom-right (position:fixed via .scss). z-index alto per stare sopra\n a navbar, dialog modali leggeri, ecc. ma sotto agli overlay critici (es.\n PrimeNG p-dialog modal stack). -->\n <button\n type=\"button\"\n class=\"wuic-rag-fab\"\n [class.wuic-rag-fab--dialog-open]=\"dialogVisible\"\n [attr.title]=\"fabTooltip\"\n aria-label=\"
|
|
61735
|
-
}], ctorParameters: () => [{ type: LicenseFeatureService }, { type: NotificationRealtimeService }, { type: i0.ChangeDetectorRef }], propDecorators: { fabTooltip: [{
|
|
63776
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if (visible) {\n <!-- FAB bottom-right (position:fixed via .scss). z-index alto per stare sopra\n a navbar, dialog modali leggeri, ecc. ma sotto agli overlay critici (es.\n PrimeNG p-dialog modal stack). -->\n <button\n type=\"button\"\n class=\"wuic-rag-fab\"\n [class.wuic-rag-fab--dialog-open]=\"dialogVisible\"\n [attr.title]=\"fabTooltip\"\n [attr.aria-label]=\"'rag_chatbot_fab.aria_label' | translate\"\n (click)=\"toggleDialog()\">\n <i class=\"pi pi-comments\" aria-hidden=\"true\"></i>\n @if (unreadRagCount > 0 && !dialogVisible) {\n <span class=\"wuic-rag-fab__badge\" [attr.data-count]=\"unreadRagCount\">\n {{ unreadRagCount > 9 ? '9+' : unreadRagCount }}\n </span>\n }\n </button>\n\n <!-- Dialog non-modal: l'utente puo' continuare a usare l'app sotto.\n contentStyle limita l'altezza interna al chatHeight + header.\n [draggable]=\"true\" + [resizable]=\"true\" per UX power-user.\n [appendTo]=\"'body'\" sposta il dialog nel body document: evita che\n layout/scroll interactions con componenti heavy sottostanti (Syncfusion\n Spreadsheet con N-mila record, list-grid virtualized, ecc.) freezino la\n pagina quando il dialog apre/chiude. Il parent container NON subisce\n re-layout causato dalla presenza del dialog nel proprio subtree. -->\n <p-dialog\n [(visible)]=\"dialogVisible\"\n [modal]=\"false\"\n [draggable]=\"true\"\n [resizable]=\"true\"\n [closable]=\"true\"\n [closeOnEscape]=\"true\"\n [dismissableMask]=\"false\"\n [appendTo]=\"'body'\"\n [style]=\"{ width: dialogWidth }\"\n [contentStyle]=\"{ height: chatHeight }\"\n [header]=\"dialogTitle\"\n styleClass=\"wuic-rag-fab__dialog\"\n (onHide)=\"onDialogHide()\">\n <!-- Lazy-mount MANUALE del chatbot: PrimeNG p-dialog v17+ NON lazy-monta\n il content projettato (lo istanzia eagerly al creation del parent).\n Senza il guard `@if (dialogOpened)` il chatbot sarebbe istanziato al\n refresh \u2192 ngOnInit \u2192 `ragService.health()` \u2192 `GET /api/Rag/Health`\n anche se l'utente non apre mai il FAB (errore rosso in console se\n il RAG server Python e' offline, 503 Service Unavailable).\n `dialogOpened` e' sticky (setato dal primo `toggleDialog()`, mai\n resetto): una volta aperto, il chatbot resta montato e le\n ri-aperture sono istantanee (PrimeNG controlla solo la visibilita'\n DOM, niente rimonto ngOnInit). -->\n @if (dialogOpened) {\n <wuic-rag-chatbot\n [title]=\"dialogTitle\"\n [chatHeight]=\"chatHeight\"\n [showClearButton]=\"true\">\n </wuic-rag-chatbot>\n }\n </p-dialog>\n}\n", styles: [".wuic-rag-fab{position:fixed;bottom:24px;right:24px;width:56px;height:56px;border-radius:50%;border:none;cursor:pointer;background:var(--p-primary-color, #2563eb);color:var(--p-primary-color-text, #fff);box-shadow:0 4px 12px #0000002e,0 2px 4px #0000001f;display:flex;align-items:center;justify-content:center;z-index:1100;transform:translate(calc(100% + 16px));opacity:.45;transition:transform .28s cubic-bezier(.16,1,.3,1) .25s,opacity .28s ease-out .25s,box-shadow .18s ease-out 0s,background-color .18s ease-out 0s}.wuic-rag-fab i{font-size:24px;line-height:1}.wuic-rag-fab:hover,.wuic-rag-fab:focus-visible{transform:translate(0) scale(1.05);opacity:1;box-shadow:0 6px 18px #00000038,0 3px 6px #00000024;transition:transform .28s cubic-bezier(.16,1,.3,1) 0s,opacity .28s ease-out 0s,box-shadow .18s ease-out 0s,background-color .18s ease-out 0s}.wuic-rag-fab:hover:after,.wuic-rag-fab:focus-visible:after{content:\"\";position:absolute;top:-8px;bottom:-8px;left:100%;width:96px;background:transparent;pointer-events:auto}.wuic-rag-fab:active{transform:translate(0) scale(.98);opacity:1}.wuic-rag-fab:focus-visible{outline:2px solid var(--p-primary-color, #2563eb);outline-offset:3px}.wuic-rag-fab.wuic-rag-fab--dialog-open{transform:translate(0);opacity:1}.wuic-rag-fab__badge{position:absolute;top:-4px;right:-4px;min-width:22px;height:22px;padding:0 6px;border-radius:11px;background:#e11d48;color:#fff;font-size:11px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;box-shadow:0 1px 3px #0000004d;pointer-events:none}.wuic-rag-fab__dialog{position:fixed!important;inset:auto 16px 96px auto!important;margin:0!important;max-height:calc(100vh - 112px)}.wuic-rag-fab__dialog .p-dialog-content{padding:0;overflow:hidden;display:flex;flex-direction:column;height:480px;max-height:calc(100vh - 160px)}.wuic-rag-fab__dialog wuic-rag-chatbot{display:flex;flex:1 1 auto;min-height:0;width:100%}.wuic-rag-fab__dialog .p-dialog-header{padding:12px 18px}.p-dialog-mask:has(>.wuic-rag-fab__dialog){pointer-events:none;background:transparent!important}.p-dialog-mask:has(>.wuic-rag-fab__dialog)>.wuic-rag-fab__dialog{pointer-events:auto}\n"] }]
|
|
63777
|
+
}], ctorParameters: () => [{ type: LicenseFeatureService }, { type: NotificationRealtimeService }, { type: i0.ChangeDetectorRef }, { type: i1$2.Router }], propDecorators: { fabTooltip: [{
|
|
61736
63778
|
type: Input
|
|
61737
63779
|
}], dialogTitle: [{
|
|
61738
63780
|
type: Input
|
|
@@ -62020,7 +64062,7 @@ const designerPendingChangesGuard = async (component) => {
|
|
|
62020
64062
|
const routes = [
|
|
62021
64063
|
{
|
|
62022
64064
|
path: 'framework-docs',
|
|
62023
|
-
loadComponent: () => import('./wuic-framework-lib-framework-docs.component-
|
|
64065
|
+
loadComponent: () => import('./wuic-framework-lib-framework-docs.component-CGQbmSkv.mjs').then((m) => m.FrameworkDocsComponent),
|
|
62024
64066
|
canMatch: [menuRouteAccessCanMatchGuard],
|
|
62025
64067
|
canActivate: [menuRouteAccessCanActivateGuard],
|
|
62026
64068
|
data: {
|
|
@@ -62029,7 +64071,7 @@ const routes = [
|
|
|
62029
64071
|
},
|
|
62030
64072
|
{
|
|
62031
64073
|
path: 'framework-docs/:slug',
|
|
62032
|
-
loadComponent: () => import('./wuic-framework-lib-framework-docs.component-
|
|
64074
|
+
loadComponent: () => import('./wuic-framework-lib-framework-docs.component-CGQbmSkv.mjs').then((m) => m.FrameworkDocsComponent),
|
|
62033
64075
|
canMatch: [menuRouteAccessCanMatchGuard],
|
|
62034
64076
|
canActivate: [menuRouteAccessCanActivateGuard],
|
|
62035
64077
|
data: {
|
|
@@ -62047,7 +64089,7 @@ const routes = [
|
|
|
62047
64089
|
},
|
|
62048
64090
|
{
|
|
62049
64091
|
path: ':route/report-designer',
|
|
62050
|
-
loadComponent: () => import('./wuic-framework-lib-report-designer.component-
|
|
64092
|
+
loadComponent: () => import('./wuic-framework-lib-report-designer.component-DcpdzFjh.mjs').then((m) => m.ReportDesignerComponent),
|
|
62051
64093
|
canMatch: [menuRouteAccessCanMatchGuard, featureRouteCanMatchGuard],
|
|
62052
64094
|
canActivate: [menuRouteAccessCanActivateGuard, featureRouteCanActivateGuard],
|
|
62053
64095
|
data: {
|
|
@@ -62057,7 +64099,7 @@ const routes = [
|
|
|
62057
64099
|
},
|
|
62058
64100
|
{
|
|
62059
64101
|
path: ':route/report-viewer',
|
|
62060
|
-
loadComponent: () => import('./wuic-framework-lib-report-viewer.component-
|
|
64102
|
+
loadComponent: () => import('./wuic-framework-lib-report-viewer.component-BHpbGpp9.mjs').then((m) => m.ReportViewerComponent),
|
|
62061
64103
|
canMatch: [menuRouteAccessCanMatchGuard],
|
|
62062
64104
|
canActivate: [menuRouteAccessCanActivateGuard],
|
|
62063
64105
|
data: {
|
|
@@ -62077,7 +64119,7 @@ const routes = [
|
|
|
62077
64119
|
},
|
|
62078
64120
|
{
|
|
62079
64121
|
path: ':route/pivot-builder',
|
|
62080
|
-
loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-
|
|
64122
|
+
loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-0gJz9RWr.mjs').then((m) => m.PivotBuilderComponent),
|
|
62081
64123
|
canMatch: [menuRouteAccessCanMatchGuard, featureRouteCanMatchGuard],
|
|
62082
64124
|
canActivate: [menuRouteAccessCanActivateGuard, featureRouteCanActivateGuard],
|
|
62083
64125
|
data: {
|
|
@@ -62087,7 +64129,7 @@ const routes = [
|
|
|
62087
64129
|
},
|
|
62088
64130
|
{
|
|
62089
64131
|
path: 'pivot-builder',
|
|
62090
|
-
loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-
|
|
64132
|
+
loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-0gJz9RWr.mjs').then((m) => m.PivotBuilderComponent),
|
|
62091
64133
|
canMatch: [menuRouteAccessCanMatchGuard, featureRouteCanMatchGuard],
|
|
62092
64134
|
canActivate: [menuRouteAccessCanActivateGuard, featureRouteCanActivateGuard],
|
|
62093
64135
|
data: {
|
|
@@ -62345,5 +64387,5 @@ function getThemePrimaryHex(value) {
|
|
|
62345
64387
|
* Generated bundle index. Do not edit.
|
|
62346
64388
|
*/
|
|
62347
64389
|
|
|
62348
|
-
export {
|
|
62349
|
-
//# sourceMappingURL=wuic-framework-lib-wuic-framework-lib-
|
|
64390
|
+
export { IsSelectedRowPipe as $, ArchetypeConfiguratorComponent as A, BooleanEditorComponent as B, ChatbotHostRegistryService as C, DynamicGenericTemplateComponent as D, ColorEditorComponent as E, FilterBarComponent as F, GlobalHandler as G, CrashReporterService as H, ImportExportButtonComponent as I, CustomAttributesService as J, CustomException as K, LazyParametricDialogComponent as L, MetadataProviderService as M, DataActionButtonComponent as N, DataProviderOdataService as O, PagerComponent as P, DateEditorComponent as Q, DesignerRouteComponent as R, DictionaryEditorComponent as S, TranslationManagerService as T, UserInfoService as U, DynamicRowTemplateComponent as V, WtoolboxService as W, FormatGridViewValuePipe as X, GetSrcUploadPreviewPipe as Y, HtmlEditorComponent as Z, ImageWrapperComponent as _, DynamicCompilerService as a, loadHtmlEditorComponent as a$, KanbanListComponent as a0, KanbanOptions as a1, KanbanStatusColumnItem as a2, LazyChartListComponent as a3, LazyCodeEditorComponent as a4, LazyDataActionButtonComponent as a5, LazyDataSourceComponent as a6, LazyFieldEditorComponent as a7, LazyFirstRunWizardComponent as a8, LazyImageWrapperComponent as a9, ValueChangedPayload as aA, VisibleFieldListPipe as aB, WidgetDefinition as aC, WorkflowDesignerRouteComponent as aD, WorkflowRunnerComponent as aE, WuicAziendaSwitcherComponent as aF, WuicErrorDialogComponent as aG, WuicErrorInterceptor as aH, WuicFeatureDirective as aI, WuicFrozenColumnDirective as aJ, WuicRagChatbotComponent as aK, WuicRagChatbotFabComponent as aL, WuicRagService as aM, WuicRowTogglerDirective as aN, _PRIMENG_TABLE_DIRECTIVES_NAMES as aO, authExpiredInterceptor as aP, boundedRepeaterPendingChangesGuard as aQ, featureRouteCanActivateGuard as aR, featureRouteCanMatchGuard as aS, getThemePrimaryHex as aT, inferTemplateFieldFromBaseClass as aU, loadBooleanEditorComponent as aV, loadButtonEditorComponent as aW, loadCodeAreaEditorComponent as aX, loadColorEditorComponent as aY, loadDateEditorComponent as aZ, loadDictionaryEditorComponent as a_, LazyKanbanListComponent as aa, LazyMetaMenuComponent as ab, LazyPivotBuilderComponent as ac, LazyReportDesignerComponent as ad, LazyReportViewerComponent as ae, LazySchedulerListComponent as af, LazySpreadsheetListSfComponent as ag, ListGridComponent as ah, LookupEditorComponent as ai, MapListComponent as aj, MetaMenuComponent as ak, MetadatiTabella as al, NotificationBellComponent as am, NotificationRealtimeService as an, NumberEditorComponent as ao, PRIMARY_PALETTES as ap, PropertyArrayEditorComponent as aq, PropertyObjectEditorComponent as ar, THEME_OPTIONS as as, TextAreaEditorComponent as at, TextEditorComponent as au, TreeListComponent as av, TreeViewSelectorComponent as aw, UploadDialogComponent as ax, UploadEditorComponent as ay, ValidationRule as az, DataSourceComponent as b, loadLookupEditorComponent as b0, loadNumberEditorComponent as b1, loadPropertyArrayEditorComponent as b2, loadPropertyObjectEditorComponent as b3, loadTextAreaEditorComponent as b4, loadTextEditorComponent as b5, loadTreeViewSelectorComponent as b6, loadUploadEditorComponent as b7, menuRouteAccessCanActivateGuard as b8, menuRouteAccessCanMatchGuard as b9, routes as ba, wuicErrorInterceptor as bb, DataRepeaterComponent as c, ParametricDialogComponent as d, MetadatiColonna as e, DataProviderService as f, MetadataEditorService as g, WorkflowRuntimeMetadataService as h, FieldEditorComponent as i, MetadataEditorComponent as j, WuicClientException as k, WuicErrorCodes as l, MetaInfo as m, FilterInfo as n, LicenseFeatureService as o, FilterItem as p, getThemeOptions as q, AUTH_REDIRECT_REASON_KEY as r, AppSettingsEditorComponent as s, AuthSessionService as t, BoundedRepeaterComponent as u, ButtonEditorComponent as v, CallbackPipe as w, CallbackPipe2 as x, CarouselListComponent as y, CodeAreaEditorComponent as z };
|
|
64391
|
+
//# sourceMappingURL=wuic-framework-lib-wuic-framework-lib-Deudd4MY.mjs.map
|