wuic-framework-lib 1.2.1 → 1.3.2

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.
Files changed (16) hide show
  1. package/fesm2022/{wuic-framework-lib-chart-list.component-B1V8Q-Qq.mjs → wuic-framework-lib-chart-list.component-DAP-TPJR.mjs} +2 -2
  2. package/fesm2022/{wuic-framework-lib-code-editor.component-4N95qt3D.mjs → wuic-framework-lib-code-editor.component-CLMDvOWs.mjs} +59 -2
  3. package/fesm2022/{wuic-framework-lib-designer.component-pW1YK-oY.mjs → wuic-framework-lib-designer.component-DA7abiLW.mjs} +1063 -18
  4. package/fesm2022/{wuic-framework-lib-first-run-wizard.component-CaGePYLi.mjs → wuic-framework-lib-first-run-wizard.component-BLrUrfZ9.mjs} +83 -97
  5. package/fesm2022/{wuic-framework-lib-framework-docs.component-m0IlS0Sl.mjs → wuic-framework-lib-framework-docs.component-DphKhTef.mjs} +3733 -344
  6. package/fesm2022/{wuic-framework-lib-pivot-builder.component-VIs0CIH_.mjs → wuic-framework-lib-pivot-builder.component-BqUUwrJM.mjs} +5 -5
  7. package/fesm2022/{wuic-framework-lib-prompt-dialog-fallback-host.component-Dm9GTZ07.mjs → wuic-framework-lib-prompt-dialog-fallback-host.component-DZrhwMH5.mjs} +2 -2
  8. package/fesm2022/{wuic-framework-lib-report-designer.component-FiAwNkPM.mjs → wuic-framework-lib-report-designer.component-D_qbefud.mjs} +2 -2
  9. package/fesm2022/{wuic-framework-lib-report-viewer.component-DY0dGMrt.mjs → wuic-framework-lib-report-viewer.component-CIN2fY0P.mjs} +2 -2
  10. package/fesm2022/{wuic-framework-lib-scheduler-list.component-3E5m-9Qd.mjs → wuic-framework-lib-scheduler-list.component-CdcI1Uor.mjs} +2 -2
  11. package/fesm2022/{wuic-framework-lib-spreadsheet-list-sf.component-D9P9WbDl.mjs → wuic-framework-lib-spreadsheet-list-sf.component-j1HTRI_P.mjs} +2 -2
  12. package/fesm2022/{wuic-framework-lib-workflow-designer.component-4RC2EkGA.mjs → wuic-framework-lib-workflow-designer.component-SE_7ZogW.mjs} +2 -2
  13. package/fesm2022/{wuic-framework-lib-wuic-framework-lib-BSyGhpM1.mjs → wuic-framework-lib-wuic-framework-lib-BRujEwpR.mjs} +2252 -156
  14. package/fesm2022/wuic-framework-lib.mjs +1 -1
  15. package/package.json +1 -1
  16. package/types/wuic-framework-lib.d.ts +852 -13
@@ -9,7 +9,7 @@ import * as i1$6 from 'primeng/dynamicdialog';
9
9
  import { DialogService, DynamicDialogRef, DynamicDialogConfig } from 'primeng/dynamicdialog';
10
10
  import * as i1 from 'ngx-cookie-service';
11
11
  import * as i1$1 from '@angular/common/http';
12
- import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
12
+ import { HttpClient, HttpHeaders, HttpContextToken, HttpErrorResponse } from '@angular/common/http';
13
13
  import '@angular/compiler';
14
14
  import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
15
15
  import * as i8 from '@ngx-translate/core';
@@ -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 i7$2 from 'primeng/badge';
93
+ import * as i10$1 from 'primeng/badge';
94
94
  import { BadgeModule } from 'primeng/badge';
95
- import * as i8$2 from 'primeng/chip';
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-Dm9GTZ07.mjs');
2710
+ const { PromptDialogFallbackHostComponent } = await import('./wuic-framework-lib-prompt-dialog-fallback-host.component-DZrhwMH5.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
- localStorage.clear();
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 traduzioni gia caricate.
10227
- * Evita regressioni UX dove, dopo save metadata non-translation, le label tornano alle chiavi raw.
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
- const translationSnapshot = localStorage.getItem('translation');
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
- localStorage.clear();
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
- localStorage.clear();
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
- localStorage.clear();
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
- localStorage.clear();
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
- return String(userLang).trim();
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
- self.translationTable = [];
13545
- self.translationsLoaded$.next(false);
13546
- void self.ensureTranslationsLoaded();
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
- * `{ optional: true }` rende il field null fuori da un Angular DI context
13685
- * (es. unit test che istanziano la classe a mano) — il consumo in ngOnInit
13686
- * usa `?.nativeElement` per sopportare entrambi i path.
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 = inject(ElementRef, { optional: true });
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
- if (tableStyleClasses.length) {
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
- if (classes.length) {
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-3E5m-9Qd.mjs').then((m) => m.SchedulerListComponent);
21666
+ schedulerListComponentPromise ??= import('./wuic-framework-lib-scheduler-list.component-CdcI1Uor.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-D9P9WbDl.mjs').then((m) => m.SpreadsheetListSfComponent);
21874
+ spreadsheetListSfComponentPromise ??= import('./wuic-framework-lib-spreadsheet-list-sf.component-j1HTRI_P.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-B1V8Q-Qq.mjs').then((m) => m.ChartListComponent);
22152
+ chartListComponentPromise ??= import('./wuic-framework-lib-chart-list.component-DAP-TPJR.mjs').then((m) => m.ChartListComponent);
21686
22153
  this.loadedComponent = await chartListComponentPromise;
21687
22154
  this.cdr.markForCheck();
21688
22155
  }
@@ -30274,8 +30741,24 @@ class NotificationRealtimeService {
30274
30741
  if (this.reconnectTimer || this.manuallyClosed || !this.currentUserId) {
30275
30742
  return;
30276
30743
  }
30277
- this.reconnectTimer = setTimeout(() => {
30744
+ this.reconnectTimer = setTimeout(async () => {
30278
30745
  this.reconnectTimer = null;
30746
+ if (this.manuallyClosed || !this.currentUserId) {
30747
+ return;
30748
+ }
30749
+ // Fallback a polling HTTP: finche' il WS non si riconnette, ad ogni tick
30750
+ // rifacciamo lo snapshot via HTTP (GET Notifications/unread) cosi' le
30751
+ // notifiche NUOVE compaiono comunque (ritardo ~5s) anche dove il WS non e'
30752
+ // disponibile -> durante il first-run (endpoint /ws non ancora registrato
30753
+ // perche' i servizi notifiche sono gated su firstRun al boot), dietro
30754
+ // proxy/firewall che bloccano l'upgrade, o su `dotnet run` senza restart.
30755
+ // Quando openSocket riesce, `ws.onopen` rifa lo snapshot e il push live
30756
+ // riprende; il dedup di loadSnapshot (<1s) evita doppi fetch ravvicinati.
30757
+ try {
30758
+ await this.loadSnapshot(this.currentUserId);
30759
+ }
30760
+ catch {
30761
+ }
30279
30762
  if (!this.manuallyClosed && this.currentUserId) {
30280
30763
  this.openSocket(this.currentUserId);
30281
30764
  }
@@ -31397,6 +31880,22 @@ class ListGridComponent {
31397
31880
  releaseGridStateUrlPushTimer;
31398
31881
  navigationTriggeredByPopstate = false;
31399
31882
  suppressNextPageOnlyQuerySync = false;
31883
+ // PrimeNG p-table emette per ogni sort/filter/paging DUE (a volte 3)
31884
+ // eventi consecutivi (separati da ~30-60ms, NON nello stesso microtask).
31885
+ // Il fetchData a runtime fa 1 round-trip server completo (GetFlatData su
31886
+ // tabelle 200k+ righe -> facilmente >1s). I duplicati raddoppiano il
31887
+ // carico DB.
31888
+ //
31889
+ // Strategia in 2 layer:
31890
+ // 1. setTimeout(0) coalesce ravvicinato (rapid-fire stesso microtask)
31891
+ // 2. dedup-by-payload-hash (skip se sortInfo+pageInfo+filterInfo
31892
+ // identici al last-fetched). Cattura il pattern p-table dove gli
31893
+ // eventi sono separati da 30-60ms ma producono payload identico.
31894
+ // Verificato live 2026-06-04 via MCP Chrome (Order Lines / 231k righe,
31895
+ // sort su OrderID -> 2 POST identici a 39ms di distanza).
31896
+ pendingFilterChangeEvent = null;
31897
+ pendingFilterChangeTimer = null;
31898
+ lastFetchedPayloadHash = null;
31400
31899
  clientSideCrudToggleBusy = false;
31401
31900
  persistedColumnWidthsByRoute = {};
31402
31901
  persistedColumnLayoutByRoute = {};
@@ -32220,7 +32719,7 @@ class ListGridComponent {
32220
32719
  </div>
32221
32720
  <div class="wuic-mobile-card-body">
32222
32721
  <ng-container *ngFor="let col of columns | visibleFieldList">
32223
- <div class="wuic-mobile-card-row" [ngClass]="getCellClasses(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)">
32722
+ <div class="wuic-mobile-card-row" [ngClass]="getCellClasses(col.metaColumn, rowData)" [attr.style]="getCellInlineStyle(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)">
32224
32723
  <div class="wuic-mobile-card-label">{{ col.header }}</div>
32225
32724
  <div class="wuic-mobile-card-value">
32226
32725
  <ng-container [ngSwitch]="col.metaColumn.mc_nome_colonna">
@@ -32282,7 +32781,7 @@ class ListGridComponent {
32282
32781
  </div>
32283
32782
  <div class="wuic-mobile-card-body">
32284
32783
  <ng-container *ngFor="let col of columns | visibleFieldList">
32285
- <div class="wuic-mobile-card-row" [ngClass]="getCellClasses(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)">
32784
+ <div class="wuic-mobile-card-row" [ngClass]="getCellClasses(col.metaColumn, rowData)" [attr.style]="getCellInlineStyle(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)">
32286
32785
  <div class="wuic-mobile-card-label">{{ col.header }}</div>
32287
32786
  <div class="wuic-mobile-card-value">
32288
32787
  <ng-container [ngSwitch]="col.metaColumn.mc_nome_colonna">
@@ -32337,7 +32836,7 @@ class ListGridComponent {
32337
32836
  </ng-container>
32338
32837
  </td>
32339
32838
  <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)">
32839
+ <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
32840
  <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
32841
  <ng-container *ngIf="!rowData.__is_editing">
32343
32842
  <ng-container [ngSwitch]="col.metaColumn.mc_nome_colonna">
@@ -32358,7 +32857,7 @@ class ListGridComponent {
32358
32857
  </ng-container>
32359
32858
  </ng-container>
32360
32859
  </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)">
32860
+ <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
32861
  <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
32862
  <ng-container *ngIf="!rowData.__is_editing">
32364
32863
  <ng-container [ngSwitch]="col.metaColumn.mc_nome_colonna">
@@ -33281,6 +33780,32 @@ class ListGridComponent {
33281
33780
  async pageFilterChange(event) {
33282
33781
  // Applying a saved state updates sort/filter bindings programmatically.
33283
33782
  // Ignore transient table events to avoid duplicate fetches.
33783
+ if (this.applyingSavedState) {
33784
+ return;
33785
+ }
33786
+ // Coalesce rapid-fire events (sort emette 2-3 eventi in 0ms):
33787
+ // sovrascrivi il queued event e schedula un solo processing al next tick.
33788
+ // Eventi cross-tick legittimi (sort poi paging dopo click utente) sono
33789
+ // separati da almeno N ms del browser idle -> ognuno schedula proprio.
33790
+ this.pendingFilterChangeEvent = event;
33791
+ if (this.pendingFilterChangeTimer !== null) {
33792
+ return; // gia' schedulato, l'ultimo evento sara' processato
33793
+ }
33794
+ this.pendingFilterChangeTimer = setTimeout(() => {
33795
+ const ev = this.pendingFilterChangeEvent;
33796
+ this.pendingFilterChangeEvent = null;
33797
+ this.pendingFilterChangeTimer = null;
33798
+ if (ev) {
33799
+ void this._processPageFilterEvent(ev);
33800
+ }
33801
+ }, 0);
33802
+ }
33803
+ /**
33804
+ * Processore reale dell'evento PrimeNG (sort/filter/paging). Chiamato
33805
+ * tail-call da `pageFilterChange` via setTimeout(0) per coalesce.
33806
+ * NON chiamare direttamente: la coalesce e' la guard anti-duplicate-fetch.
33807
+ */
33808
+ async _processPageFilterEvent(event) {
33284
33809
  if (this.applyingSavedState) {
33285
33810
  return;
33286
33811
  }
@@ -33411,6 +33936,22 @@ class ListGridComponent {
33411
33936
  else {
33412
33937
  this.datasource.value.sortInfo = [];
33413
33938
  }
33939
+ // Dedup-by-payload-hash: PrimeNG emette spesso DUE eventi consecutivi
33940
+ // (sort + page-only follow-up) separati da ~30-60ms con payload
33941
+ // IDENTICO. Skip il fetch se sortInfo+pageInfo+filterInfo coincidono
33942
+ // col last-fetched. Verificato live 2026-06-04 via MCP Chrome.
33943
+ try {
33944
+ const payloadKey = JSON.stringify({
33945
+ s: this.datasource.value.sortInfo || [],
33946
+ p: { c: this.pageIndex, sz: this.pageSize },
33947
+ f: this.datasource.value.filterInfo || null,
33948
+ });
33949
+ if (payloadKey === this.lastFetchedPayloadHash) {
33950
+ return; // identico al fetch precedente: salta
33951
+ }
33952
+ this.lastFetchedPayloadHash = payloadKey;
33953
+ }
33954
+ catch { /* hash non bloccante: meglio re-fetch che bloccare */ }
33414
33955
  await this.datasource.value.fetchData();
33415
33956
  this.scheduleGridViewportHeightSync();
33416
33957
  // Emit @Output events DOPO fetchData: a questo punto TUTTO lo stato
@@ -37466,11 +38007,11 @@ class ListGridComponent {
37466
38007
  return Number.isFinite(width) ? width : NaN;
37467
38008
  }
37468
38009
  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" }] });
38010
+ 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
38011
  }
37471
38012
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ListGridComponent, decorators: [{
37472
38013
  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"] }]
38014
+ 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
38015
  }], 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
38016
  type: Optional
37476
38017
  }] }], propDecorators: { hardcodedRoute: [{
@@ -41916,6 +42457,9 @@ class AppSettingsEditorComponent {
41916
42457
  this.sections.set(resp.sections || []);
41917
42458
  this.filePath.set(resp.filePath || '');
41918
42459
  this.rebuildMaps(resp.sections || []);
42460
+ // Popola le dropdown a opzioni dinamiche (es. model list in base al provider).
42461
+ // Fire-and-forget: lo schema renderizza subito, le options arrivano async.
42462
+ this.refreshDynamicOptions();
41919
42463
  // Ripristina il tab attivo dal localStorage (per `name`, non index).
41920
42464
  // Senza questa logica ogni reload (incluso quello post-save+restart)
41921
42465
  // resetta a tab 0, perdendo il contesto dell'utente che stava editando
@@ -42000,10 +42544,54 @@ class AppSettingsEditorComponent {
42000
42544
  onFieldChange(key, newValue) {
42001
42545
  this.current.set(key, newValue);
42002
42546
  this.changeTick.update((t) => t + 1);
42547
+ // Se questo campo guida opzioni dinamiche di un altro (es. provider -> model list),
42548
+ // ri-fetcha le options di quei campi col nuovo valore.
42549
+ const shortKey = key.includes(':') ? key.substring(key.indexOf(':') + 1) : key;
42550
+ for (const sec of this.sections())
42551
+ for (const f of sec.fields)
42552
+ if (f.optionsEndpoint && f.optionsDependsOn === shortKey)
42553
+ this.fetchFieldOptions(f, String(newValue ?? ''));
42003
42554
  }
42004
42555
  fieldValue(key) {
42005
42556
  return this.current.get(key);
42006
42557
  }
42558
+ /** Per ogni field con `optionsEndpoint`, fetcha le options usando il valore corrente
42559
+ * del campo `optionsDependsOn`. Chiamato dopo ogni loadSchema. */
42560
+ refreshDynamicOptions() {
42561
+ for (const sec of this.sections()) {
42562
+ for (const f of sec.fields) {
42563
+ if (!f.optionsEndpoint || !f.optionsDependsOn)
42564
+ continue;
42565
+ this.fetchFieldOptions(f, this.dependsOnValue(f.optionsDependsOn));
42566
+ }
42567
+ }
42568
+ }
42569
+ /** Valore corrente del campo identificato da `dependsOnKey` (con/senza prefisso AppSettings:). */
42570
+ dependsOnValue(dependsOnKey) {
42571
+ for (const sec of this.sections())
42572
+ for (const f of sec.fields)
42573
+ if (f.key === dependsOnKey || f.key.endsWith(':' + dependsOnKey))
42574
+ return String(this.fieldValue(f.key) ?? '');
42575
+ return '';
42576
+ }
42577
+ /** GET `<endpoint>?<dependsOn>=<depVal>` -> popola `f.options`. Best-effort: su errore
42578
+ * lascia il campo come testo libero (options resta vuoto). */
42579
+ fetchFieldOptions(f, depVal) {
42580
+ if (!f.optionsEndpoint || !f.optionsDependsOn)
42581
+ return;
42582
+ const ep = f.optionsEndpoint.replace(/^\/?api\//, ''); // base gia' include /api/
42583
+ const url = `${this.buildApiBaseUrl()}${ep}?${encodeURIComponent(f.optionsDependsOn)}=${encodeURIComponent(depVal)}`;
42584
+ this.http.get(url, { withCredentials: true }).subscribe({
42585
+ next: (opts) => {
42586
+ if (Array.isArray(opts)) {
42587
+ f.options = opts;
42588
+ // bump del signal sections cosi' il <p-select> ri-legge f.options (CD signal-safe)
42589
+ this.sections.update((arr) => [...arr]);
42590
+ }
42591
+ },
42592
+ error: () => { },
42593
+ });
42594
+ }
42007
42595
  /**
42008
42596
  * Ritorna `true` se almeno uno dei changes coinvolge una chiave licenza
42009
42597
  * (`license-email` / `license-payload` / `license-signature`). Usato dal save
@@ -45350,7 +45938,7 @@ class LazyFirstRunWizardComponent {
45350
45938
  this.cdr = cdr;
45351
45939
  }
45352
45940
  async ngOnInit() {
45353
- firstRunWizardComponentPromise ??= import('./wuic-framework-lib-first-run-wizard.component-CaGePYLi.mjs').then((m) => m.WuicFirstRunWizardComponent);
45941
+ firstRunWizardComponentPromise ??= import('./wuic-framework-lib-first-run-wizard.component-BLrUrfZ9.mjs').then((m) => m.WuicFirstRunWizardComponent);
45354
45942
  const componentClass = await firstRunWizardComponentPromise;
45355
45943
  this.componentRef = this.vcr.createComponent(componentClass);
45356
45944
  // Forward output del componente reale al wrapper. Type assertion necessaria
@@ -56994,7 +57582,7 @@ class LazyCodeEditorComponent {
56994
57582
  this.cdr = cdr;
56995
57583
  }
56996
57584
  async ngOnInit() {
56997
- codeEditorComponentPromise ??= import('./wuic-framework-lib-code-editor.component-4N95qt3D.mjs').then((m) => m.CodeEditorComponent);
57585
+ codeEditorComponentPromise ??= import('./wuic-framework-lib-code-editor.component-CLMDvOWs.mjs').then((m) => m.CodeEditorComponent);
56998
57586
  this.loadedComponent = await codeEditorComponentPromise;
56999
57587
  this.cdr.markForCheck();
57000
57588
  }
@@ -58501,11 +59089,17 @@ class NotificationBellComponent {
58501
59089
  this.sub.add(this.realtime.enabled$.subscribe((enabled) => {
58502
59090
  this.isVisible = enabled !== false;
58503
59091
  }));
58504
- this.sub.add(this.realtime.unreadCount$.subscribe((count) => {
58505
- this.unreadCount = Number(count || 0);
58506
- }));
58507
59092
  this.sub.add(this.realtime.notifications$.subscribe((items) => {
58508
- this.notifications = Array.isArray(items) ? items : [];
59093
+ const arr = Array.isArray(items) ? items : [];
59094
+ // Escludi dal CAMPANELLO le notifiche "interne" del chatbot (rag.message.*):
59095
+ // servono SOLO al componente chatbot per refreshare il messaggio in chat, NON
59096
+ // sono voci utente. Senza questo filtro il bell si riempiva di "Errore RAG" /
59097
+ // "Risposta pronta" duplicate a ogni retry del prompt mentre l'engine non era
59098
+ // pronto. Il chatbot le riceve comunque (sottoscrizione separata a notifications$).
59099
+ this.notifications = arr.filter(n => !this.isChatbotInternalNotification(n));
59100
+ // Badge derivato dalla lista FILTRATA (lo snapshot include lette+non lette), cosi'
59101
+ // le rag.message.* non gonfiano il contatore.
59102
+ this.unreadCount = this.notifications.filter(n => !n.isRead).length;
58509
59103
  }));
58510
59104
  this.sub.add(this.realtime.progressEvents$.subscribe((evt) => {
58511
59105
  const guid = String(evt?.guid || '').trim();
@@ -58523,6 +59117,16 @@ class NotificationBellComponent {
58523
59117
  ngOnDestroy() {
58524
59118
  this.sub.unsubscribe();
58525
59119
  }
59120
+ /**
59121
+ * True per le notifiche "interne" del chatbot RAG (`rag.message.ready` /
59122
+ * `rag.message.error`): NON sono voci utente del campanello, ma segnali che il
59123
+ * componente chatbot usa per refreshare il messaggio in chat. Il bell le esclude
59124
+ * da lista e badge; le `rag.engine.bootstrap.*` (started/ready/error) restano
59125
+ * perche' sono feedback utente reale (download modelli in corso/pronto).
59126
+ */
59127
+ isChatbotInternalNotification(n) {
59128
+ return String(n?.type || '').startsWith('rag.message.');
59129
+ }
58526
59130
  async clearRead() {
58527
59131
  await this.realtime.clearRead(this.userId);
58528
59132
  }
@@ -58781,7 +59385,7 @@ class DesignerRouteComponent {
58781
59385
  * Inizializza il componente preparando stato, sottoscrizioni e primi caricamenti richiesti.
58782
59386
  */
58783
59387
  async ngOnInit() {
58784
- designerComponentPromise ??= import('./wuic-framework-lib-designer.component-pW1YK-oY.mjs').then((m) => m.DesignerComponent);
59388
+ designerComponentPromise ??= import('./wuic-framework-lib-designer.component-DA7abiLW.mjs').then((m) => m.DesignerComponent);
58785
59389
  this.loadedComponent = await designerComponentPromise;
58786
59390
  }
58787
59391
  /**
@@ -58858,7 +59462,7 @@ class LazyPivotBuilderComponent {
58858
59462
  this.cdr = cdr;
58859
59463
  }
58860
59464
  async ngOnInit() {
58861
- pivotBuilderComponentPromise ??= import('./wuic-framework-lib-pivot-builder.component-VIs0CIH_.mjs').then((m) => m.PivotBuilderComponent);
59465
+ pivotBuilderComponentPromise ??= import('./wuic-framework-lib-pivot-builder.component-BqUUwrJM.mjs').then((m) => m.PivotBuilderComponent);
58862
59466
  this.loadedComponent = await pivotBuilderComponentPromise;
58863
59467
  this.cdr.markForCheck();
58864
59468
  }
@@ -58897,7 +59501,7 @@ class WorkflowDesignerRouteComponent {
58897
59501
  * Inizializza il componente preparando stato, sottoscrizioni e primi caricamenti richiesti.
58898
59502
  */
58899
59503
  async ngOnInit() {
58900
- workflowDesignerComponentPromise ??= import('./wuic-framework-lib-workflow-designer.component-4RC2EkGA.mjs').then((m) => m.WorkflowDesignerComponent);
59504
+ workflowDesignerComponentPromise ??= import('./wuic-framework-lib-workflow-designer.component-SE_7ZogW.mjs').then((m) => m.WorkflowDesignerComponent);
58901
59505
  this.loadedComponent = await workflowDesignerComponentPromise;
58902
59506
  }
58903
59507
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WorkflowDesignerRouteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -58954,7 +59558,7 @@ class LazyReportDesignerComponent {
58954
59558
  this.cdr = cdr;
58955
59559
  }
58956
59560
  async ngOnInit() {
58957
- reportDesignerComponentPromise ??= import('./wuic-framework-lib-report-designer.component-FiAwNkPM.mjs').then((m) => m.ReportDesignerComponent);
59561
+ reportDesignerComponentPromise ??= import('./wuic-framework-lib-report-designer.component-D_qbefud.mjs').then((m) => m.ReportDesignerComponent);
58958
59562
  this.loadedComponent = await reportDesignerComponentPromise;
58959
59563
  this.cdr.markForCheck();
58960
59564
  }
@@ -59009,7 +59613,7 @@ class LazyReportViewerComponent {
59009
59613
  this.cdr = cdr;
59010
59614
  }
59011
59615
  async ngOnInit() {
59012
- reportViewerComponentPromise ??= import('./wuic-framework-lib-report-viewer.component-DY0dGMrt.mjs').then((m) => m.ReportViewerComponent);
59616
+ reportViewerComponentPromise ??= import('./wuic-framework-lib-report-viewer.component-CIN2fY0P.mjs').then((m) => m.ReportViewerComponent);
59013
59617
  this.loadedComponent = await reportViewerComponentPromise;
59014
59618
  this.cdr.markForCheck();
59015
59619
  }
@@ -60713,6 +61317,7 @@ class WuicRagService {
60713
61317
  query,
60714
61318
  top_k: options.topK ?? 8,
60715
61319
  use_lora: options.useLora ?? true,
61320
+ profile: options.profile,
60716
61321
  };
60717
61322
  return this.http.post(this.buildUrl('Query'), payload, { withCredentials: true });
60718
61323
  }
@@ -60734,7 +61339,10 @@ class WuicRagService {
60734
61339
  query,
60735
61340
  history,
60736
61341
  top_k: options.topK ?? 5,
60737
- model: options.model ?? 'claude-haiku-4-5-20251001',
61342
+ // Vuoto = il backend usa il model configurato (`rag-llm-default-chat-model`),
61343
+ // provider-appropriate. Non forzare un model Anthropic hardcoded.
61344
+ model: options.model ?? '',
61345
+ profile: options.profile,
60738
61346
  };
60739
61347
  return this.http.post(this.buildUrl('Chat'), payload, { withCredentials: true });
60740
61348
  }
@@ -60818,6 +61426,55 @@ class WuicRagService {
60818
61426
  askAsync(req) {
60819
61427
  return firstValueFrom(this.ask(req));
60820
61428
  }
61429
+ /**
61430
+ * Applica la proposta d'azione precedentemente emessa dall'LLM sul messaggio
61431
+ * indicato. Server-side: re-legge la `proposed_action_json` dal DB (non dal
61432
+ * client: prevenzione tampering del `callback_js`), valida, fa INSERT sul
61433
+ * metadata corrispondente + `InvalidateMetadataCachesAndSetVersion`.
61434
+ *
61435
+ * Gating: solo `isSuperAdmin` (server torna `403 admin-required` in caso).
61436
+ * Esiti tipici (`error`): `not-authenticated` | `admin-required` |
61437
+ * `message-not-found` | `no-proposed-action-on-message` | `already-applied` |
61438
+ * `unsupported-kind: <x>` | `apply-failed`.
61439
+ */
61440
+ applyProposedAction(req) {
61441
+ return this.http.post(this.buildUrl('ApplyAction'), req, { withCredentials: true });
61442
+ }
61443
+ applyProposedActionAsync(req) {
61444
+ return firstValueFrom(this.applyProposedAction(req));
61445
+ }
61446
+ /**
61447
+ * Sovrascrive il `proposed_action_json` di un messaggio assistant con il
61448
+ * payload editato dall'utente in UI (campo "Classe/Stile" + textarea callback).
61449
+ * Va chiamato PRIMA di `applyProposedActionAsync` quando l'utente modifica
61450
+ * qualcosa, cosi' che il backend ApplyAction rilegga dal DB il payload giusto.
61451
+ * Filtri di sicurezza nel backend: ownership user_id + non gia' applicata.
61452
+ */
61453
+ updateProposedAction(req) {
61454
+ return this.http.post(this.buildUrl('UpdateProposedAction'), req, { withCredentials: true });
61455
+ }
61456
+ updateProposedActionAsync(req) {
61457
+ return firstValueFrom(this.updateProposedAction(req));
61458
+ }
61459
+ /**
61460
+ * Compatta la sessione: chiama il backend che a sua volta chiama il rag_server
61461
+ * per produrre un summary della history. Persiste su `_rag_chat_sessions`
61462
+ * + avanza `summary_up_to_message_id`. Le future call /Ask passeranno il
61463
+ * summary nel system prompt al posto dei turn coperti.
61464
+ */
61465
+ compactSession(req) {
61466
+ return this.http.post(this.buildUrl('Compact'), req, { withCredentials: true });
61467
+ }
61468
+ compactSessionAsync(req) {
61469
+ return firstValueFrom(this.compactSession(req));
61470
+ }
61471
+ /** Rinomina il titolo di una session (mostrato nella sessions list). */
61472
+ renameSession(req) {
61473
+ return this.http.post(this.buildUrl('RenameSession'), req, { withCredentials: true });
61474
+ }
61475
+ renameSessionAsync(req) {
61476
+ return firstValueFrom(this.renameSession(req));
61477
+ }
60821
61478
  // ----- private helpers -----
60822
61479
  /**
60823
61480
  * Costruisce l'URL `<api_base>Rag/<action>` rispettando l'eventuale
@@ -60846,6 +61503,134 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
60846
61503
  args: [{ providedIn: 'root' }]
60847
61504
  }], ctorParameters: () => [{ type: i1$1.HttpClient }] });
60848
61505
 
61506
+ class ChatbotHostRegistryService {
61507
+ handlers = new Map();
61508
+ stateProviders = new Map();
61509
+ /**
61510
+ * Registra (o sostituisce) il handler per un dato `kind`. L'ultima
61511
+ * registrazione vince - utile per scenari edge in cui due viste
61512
+ * concorrenti gestiscono lo stesso kind (raro, ma documentato).
61513
+ */
61514
+ register(kind, handler) {
61515
+ if (!kind || typeof handler !== 'function')
61516
+ return;
61517
+ this.handlers.set(kind, handler);
61518
+ }
61519
+ /**
61520
+ * Rimuove il handler per un dato `kind`. No-op se non registrato.
61521
+ * Da chiamare in ngOnDestroy dell'host per evitare retain.
61522
+ */
61523
+ unregister(kind) {
61524
+ if (!kind)
61525
+ return;
61526
+ this.handlers.delete(kind);
61527
+ }
61528
+ /**
61529
+ * Restituisce il handler registrato per `kind`, o `null` se nessuno
61530
+ * lo gestisce client-side. Il chatbot usa questo per decidere se
61531
+ * bypassare il backend o no.
61532
+ */
61533
+ get(kind) {
61534
+ if (!kind)
61535
+ return null;
61536
+ return this.handlers.get(kind) || null;
61537
+ }
61538
+ /**
61539
+ * Helper diagnostico: lista tutti i kind attualmente registrati.
61540
+ * Usato dai test e dal debug panel.
61541
+ */
61542
+ registeredKinds() {
61543
+ return Array.from(this.handlers.keys());
61544
+ }
61545
+ // ===========================================================================
61546
+ // State Providers - per iniettare snapshot state nel route_context del LLM
61547
+ // ===========================================================================
61548
+ /**
61549
+ * Registra (o sostituisce) un state provider per un dato `kind`.
61550
+ * Il provider verra' chiamato dal chatbot in `buildRouteContext()` per
61551
+ * arricchire il system prompt del LLM con lo state corrente dell'host
61552
+ * (es. designer.dashboardElements serializzati come tree compatto).
61553
+ *
61554
+ * Esempio (designer.component):
61555
+ * this.chatbotHostRegistry.registerStateProvider(
61556
+ * 'designer_inject',
61557
+ * () => this.buildCompactStateDump()
61558
+ * );
61559
+ */
61560
+ registerStateProvider(kind, provider) {
61561
+ if (!kind || typeof provider !== 'function')
61562
+ return;
61563
+ this.stateProviders.set(kind, provider);
61564
+ }
61565
+ /**
61566
+ * Rimuove il state provider. Da chiamare in ngOnDestroy.
61567
+ */
61568
+ unregisterStateProvider(kind) {
61569
+ if (!kind)
61570
+ return;
61571
+ this.stateProviders.delete(kind);
61572
+ }
61573
+ /**
61574
+ * Invoca il state provider registrato per `kind` e ritorna il dump.
61575
+ * Se non registrato, ritorna `null`. Errori nel provider sono
61576
+ * swallowed e logged (best-effort: il chatbot deve sempre funzionare
61577
+ * anche se l'host crasha durante il dump).
61578
+ */
61579
+ getStateDump(kind) {
61580
+ if (!kind)
61581
+ return null;
61582
+ const provider = this.stateProviders.get(kind);
61583
+ if (!provider)
61584
+ return null;
61585
+ try {
61586
+ const dump = provider();
61587
+ return (typeof dump === 'string' && dump.length > 0) ? dump : null;
61588
+ }
61589
+ catch (err) {
61590
+ try {
61591
+ console.warn('[ChatbotHostRegistry] stateProvider failed for', kind, err);
61592
+ }
61593
+ catch { /* ignore */ }
61594
+ return null;
61595
+ }
61596
+ }
61597
+ /**
61598
+ * Helper diagnostico: lista tutti i kind con state provider attivo.
61599
+ */
61600
+ registeredStateKinds() {
61601
+ return Array.from(this.stateProviders.keys());
61602
+ }
61603
+ /**
61604
+ * Invoca SYNC tutti gli state provider registrati ignorandone il risultato.
61605
+ *
61606
+ * Scopo: dare ai provider la possibilita' di triggerare i loro fetch lazy
61607
+ * (es. il designer prefetcha `getAvailableRoutes` solo quando lo state
61608
+ * provider viene invocato — altrimenti spreca chiamate al mount). Chiamato
61609
+ * dal chatbot in ngOnInit: la finestra "apertura chatbot" → "primo prompt
61610
+ * inviato" e' di almeno 1-2 secondi (l'utente deve digitare), tempo
61611
+ * sufficiente perche' la cache sia pronta al primo Ask. Senza warm-up,
61612
+ * il primo prompt vedrebbe state dump 'caricamento' e il LLM
61613
+ * potrebbe hallucinare nomi alternativi inventati.
61614
+ *
61615
+ * Errori dei provider vengono swallowed (lo stesso pattern di getStateDump).
61616
+ */
61617
+ warmUpAllStateProviders() {
61618
+ const kinds = this.registeredStateKinds();
61619
+ for (const k of kinds) {
61620
+ try {
61621
+ this.getStateDump(k);
61622
+ }
61623
+ catch { /* ignore */ }
61624
+ }
61625
+ }
61626
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChatbotHostRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
61627
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChatbotHostRegistryService, providedIn: 'root' });
61628
+ }
61629
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChatbotHostRegistryService, decorators: [{
61630
+ type: Injectable,
61631
+ args: [{ providedIn: 'root' }]
61632
+ }] });
61633
+
60849
61634
  /**
60850
61635
  * Componente standalone che espone una chat sopra il RAG del codebase WUIC.
60851
61636
  *
@@ -60869,9 +61654,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
60869
61654
  class WuicRagChatbotComponent {
60870
61655
  ragService;
60871
61656
  cdr;
61657
+ chatbotHostRegistry;
60872
61658
  route;
60873
61659
  router;
60874
61660
  notificationRT;
61661
+ metaSrv;
60875
61662
  /** Titolo mostrato nell'header del chatbot. */
60876
61663
  title = 'Assistente codebase WUIC';
60877
61664
  /** Modalita' operativa: vedi nota nella classDoc. */
@@ -60884,12 +61671,52 @@ class WuicRagChatbotComponent {
60884
61671
  maxHistory = 50;
60885
61672
  /** Placeholder dell'input. */
60886
61673
  placeholder = 'Chiedi qualcosa sul codebase WUIC (italiano o inglese)...';
60887
- /** Modello Claude da usare in modalita' chat (default haiku 4.5). */
60888
- model = 'claude-haiku-4-5-20251001';
61674
+ /** Modello LLM in modalita' chat. Vuoto (default) = usa il model configurato lato
61675
+ * backend (`rag-llm-default-chat-model`), provider-appropriate. Non hardcodare un
61676
+ * model Anthropic: su provider openrouter/openai manderebbe un ID invalido (HTTP 400
61677
+ * -> retrieval-only). Valorizzare solo per forzare un model specifico. */
61678
+ model = '';
60889
61679
  /** Altezza fissa dell'area chat in CSS units (default `420px`). */
60890
61680
  chatHeight = '420px';
60891
61681
  /** Mostra il bottone "Svuota cronologia" nell'header. */
60892
61682
  showClearButton = true;
61683
+ /**
61684
+ * Persiste l'id della sessione corrente in `localStorage` e lo ripristina al
61685
+ * boot del componente (es. refresh della pagina) quando NON arriva un
61686
+ * `?sessionId` dalla route. La cronologia completa viene poi ricaricata dal DB
61687
+ * via `/api/Rag/History` (che ritorna TUTTI i messaggi della sessione, non un
61688
+ * sottoinsieme) -> il refresh non svuota piu' la chat. Default true.
61689
+ */
61690
+ persistSession = true;
61691
+ /**
61692
+ * Se `true` (default), prima di ogni `Ask` il componente costruisce un
61693
+ * **route context** sintetico (route corrente + tabella metadata + colonne)
61694
+ * dal `MetadataProviderService` e lo invia al rag_server come `routeContext`
61695
+ * nel payload. Il server lo inietta nel system prompt dell'LLM cosi' che le
61696
+ * risposte usino i NOMI REALI delle colonne della pagina corrente invece di
61697
+ * placeholder generici.
61698
+ *
61699
+ * Disabilitare a `false` per la route full-page `/rag-chatbot` (il chatbot
61700
+ * stesso non e' una "business route" — niente context utile da iniettare) o
61701
+ * per UI di privacy avanzata.
61702
+ */
61703
+ sendRouteContext = true;
61704
+ /** Chiave `localStorage` dell'ultima sessione (scoped per browser). */
61705
+ static LAST_SESSION_KEY = 'wuic_rag_last_session_id';
61706
+ /**
61707
+ * Mostra il toggle "Vista Dev / Vista Cliente" nell'header (default OFF).
61708
+ * Da abilitare SOLO nelle pagine dev/demo: la chat spedita al cliente NON
61709
+ * deve esporlo. E' comunque leak-safe (il server applica strictest-of: il
61710
+ * toggle puo' solo aumentare la redazione, mai diminuirla).
61711
+ */
61712
+ allowProfileToggle = false;
61713
+ /**
61714
+ * Profilo di redazione corrente scelto dal toggle UI.
61715
+ * - 'internal' = Vista Dev (full source);
61716
+ * - 'release' = Vista Cliente (firme + niente skill, come on-prem).
61717
+ * Inviato al server solo se {@link allowProfileToggle} e' true.
61718
+ */
61719
+ profileOverride = 'internal';
60893
61720
  // ----- Outputs -----
60894
61721
  /** Emette quando l'utente clicca su un source chip. */
60895
61722
  resultSelected = new EventEmitter();
@@ -60920,6 +61747,35 @@ class WuicRagChatbotComponent {
60920
61747
  processedNotificationIds = new Set();
60921
61748
  /** Sub a notificationRT.notifications$ + ActivatedRoute.queryParams. */
60922
61749
  subs = new Subscription();
61750
+ /**
61751
+ * Context window usage del modello corrente (ultima stima dal backend, 0-100%).
61752
+ * Aggiornato a ogni completion. Driver del cerchio colorato in header:
61753
+ * verde <60% / giallo 60-80% / arancione 80-90% / rosso >90%.
61754
+ */
61755
+ contextUsagePercent = 0;
61756
+ /** Token usati (dall'ultima call) / context window max. Mostrati in tooltip. */
61757
+ contextUsedTokens = null;
61758
+ contextWindowMax = null;
61759
+ /** True quando il dialog "context-overflow" e' aperto: l'ultima call ha
61760
+ * fallito perche' la history sfora il context. UX: 2 bottoni (Compatta / Nuova). */
61761
+ contextOverflowDialogOpen = false;
61762
+ /** True mentre il /compact e' in-flight (loading nel bottone). */
61763
+ compactingSession = false;
61764
+ /**
61765
+ * True per tutto il flow `onClearHistory`: apertura prompt di conferma
61766
+ * (PrimeNG Dialog lazy-load, ~300-800ms al primo click) + DELETE backend +
61767
+ * reset stato locale. Driver dello spinner sul bottone cestino in header.
61768
+ * Re-entry guard: blocca click multipli mentre il flow e' in corso.
61769
+ */
61770
+ clearingHistory = false;
61771
+ /** True quando il dialog "Sessioni" e' aperto. Lazy-load lista al toggle. */
61772
+ sessionsDialogOpen = false;
61773
+ sessionsList = [];
61774
+ sessionsLoading = false;
61775
+ /** Map sessionId -> bool: true se quella riga e' in modalita' rinomina. */
61776
+ sessionRenameEditing = {};
61777
+ /** Map sessionId -> string: valore temporaneo del titolo durante l'edit inline. */
61778
+ sessionRenameDraft = {};
60923
61779
  /**
60924
61780
  * Source selezionato per il dialog preview. Null = dialog chiuso.
60925
61781
  * Il dialog mostra SOLO lo snippet embedded in RagSource (max 500 char dal
@@ -60929,12 +61785,14 @@ class WuicRagChatbotComponent {
60929
61785
  selectedSource = null;
60930
61786
  inputTextarea;
60931
61787
  scrollContainer;
60932
- constructor(ragService, cdr, route, router, notificationRT) {
61788
+ constructor(ragService, cdr, chatbotHostRegistry, route, router, notificationRT, metaSrv) {
60933
61789
  this.ragService = ragService;
60934
61790
  this.cdr = cdr;
61791
+ this.chatbotHostRegistry = chatbotHostRegistry;
60935
61792
  this.route = route;
60936
61793
  this.router = router;
60937
61794
  this.notificationRT = notificationRT;
61795
+ this.metaSrv = metaSrv;
60938
61796
  }
60939
61797
  ngOnInit() {
60940
61798
  // 1. Probe health del backend (esistente)
@@ -60944,17 +61802,47 @@ class WuicRagChatbotComponent {
60944
61802
  next: (info) => { this.healthInfo = info; this.cdr.markForCheck(); },
60945
61803
  error: (err) => { this.healthError = this.extractErrorMessage(err); this.cdr.markForCheck(); },
60946
61804
  });
61805
+ // Warm-up degli state provider degli host (es. designer): da' loro la
61806
+ // possibilita' di triggerare i loro fetch lazy (es. lista route metadata)
61807
+ // PRIMA del primo prompt dell'utente. Senza warm-up, il primo prompt
61808
+ // vedrebbe state dump 'caricamento' e il LLM potrebbe hallucinare nomi
61809
+ // alternativi inventati (visto in produzione: utente chiede "grid bindata
61810
+ // a provincie", LLM elenca "provinces/states/regions" che NON sono nella
61811
+ // whitelist reale -> utente sceglie "provinces", al secondo prompt il LLM
61812
+ // vede la whitelist vera che contiene "stateprovinces" e fa fuzzy match
61813
+ // silenzioso provinces->stateprovinces, confondendo l'utente).
61814
+ try {
61815
+ this.chatbotHostRegistry.warmUpAllStateProviders();
61816
+ }
61817
+ catch { /* ignore */ }
60947
61818
  // 2. Deep-link da notifica: ?sessionId=X(&messageId=Y) -> carica history.
60948
- // Se manca, lazy: la sessione viene creata al primo Ask.
61819
+ // Refresh pagina senza query param -> ripristina l'ultima sessione da
61820
+ // localStorage (se persistSession). Se non c'e' nulla, lazy: la sessione
61821
+ // viene creata al primo Ask.
61822
+ let restoreAttempted = false;
60949
61823
  if (this.route) {
60950
61824
  this.subs.add(this.route.queryParamMap.subscribe((qp) => {
60951
61825
  const sid = Number(qp.get('sessionId') || 0);
60952
61826
  const mid = Number(qp.get('messageId') || 0);
60953
61827
  if (sid > 0 && sid !== this.currentSessionId) {
61828
+ restoreAttempted = true;
60954
61829
  this.loadSession(sid, mid > 0 ? mid : null);
60955
61830
  }
61831
+ else if (sid <= 0 && !restoreAttempted && this.persistSession && this.currentSessionId == null) {
61832
+ // Nessun sessionId in URL (refresh diretto): recupera l'ultima sessione.
61833
+ restoreAttempted = true;
61834
+ const saved = this.readLastSession();
61835
+ if (saved)
61836
+ this.loadSession(saved, null);
61837
+ }
60956
61838
  }));
60957
61839
  }
61840
+ else if (this.persistSession && this.currentSessionId == null) {
61841
+ // Componente usato fuori da contesto routing: restore best-effort.
61842
+ const saved = this.readLastSession();
61843
+ if (saved)
61844
+ this.loadSession(saved, null);
61845
+ }
60958
61846
  // 3. Subscribe alle notifiche real-time. Filtriamo type rag.message.* +
60959
61847
  // payload.sessionId match + dedup via processedNotificationIds.
60960
61848
  // handleNotification -> refreshSingleMessage -> applyMessageToTurn
@@ -60972,6 +61860,13 @@ class WuicRagChatbotComponent {
60972
61860
  }
60973
61861
  ngOnDestroy() {
60974
61862
  this.subs.unsubscribe();
61863
+ // Cleanup delle subscription al BehaviorSubject del Monaco editor per
61864
+ // ogni proposta (vedi getProposedActionField). Senza, ogni messageId
61865
+ // tenuto in cache lascerebbe un listener orfano sul record subject.
61866
+ for (const cached of this.proposedActionEditorCache.values()) {
61867
+ cached.bodySubscription?.unsubscribe();
61868
+ }
61869
+ this.proposedActionEditorCache.clear();
60975
61870
  }
60976
61871
  // ----- Computed getters per template -----
60977
61872
  get effectiveMode() {
@@ -60999,6 +61894,19 @@ class WuicRagChatbotComponent {
60999
61894
  get sendDisabled() {
61000
61895
  return this.loading || !this.inputValue.trim() || !!this.healthError;
61001
61896
  }
61897
+ /** Label del toggle profilo. */
61898
+ get profileToggleLabel() {
61899
+ return this.profileOverride === 'release' ? 'Vista Cliente' : 'Vista Dev';
61900
+ }
61901
+ /**
61902
+ * Flippa il profilo di redazione (Dev <-> Cliente). Le risposte gia' in
61903
+ * cronologia restano com'erano (il profilo e' per-richiesta); i prossimi
61904
+ * invii usano il nuovo profilo.
61905
+ */
61906
+ toggleProfile() {
61907
+ this.profileOverride = this.profileOverride === 'release' ? 'internal' : 'release';
61908
+ this.cdr.markForCheck();
61909
+ }
61002
61910
  // ----- Azioni utente -----
61003
61911
  /**
61004
61912
  * Invia il prompt corrente via flow async:
@@ -61012,6 +61920,14 @@ class WuicRagChatbotComponent {
61012
61920
  const text = this.inputValue.trim();
61013
61921
  if (!text || this.loading || this.healthError)
61014
61922
  return;
61923
+ // Slash command `/compact`: intercetta PRIMA del send. Comportamento identico
61924
+ // al click sul context cue. Niente call Ask -> niente turn in history.
61925
+ if (text === '/compact' || text.toLowerCase() === '/compatta') {
61926
+ this.inputValue = '';
61927
+ this.cdr.markForCheck();
61928
+ await this.onContextCueClick();
61929
+ return;
61930
+ }
61015
61931
  const userTurn = { role: 'user', content: text };
61016
61932
  const assistantTurn = {
61017
61933
  role: 'assistant',
@@ -61026,6 +61942,16 @@ class WuicRagChatbotComponent {
61026
61942
  this.loading = true;
61027
61943
  this.cdr.markForCheck();
61028
61944
  this.scheduleScrollToBottom();
61945
+ // Costruisce il route context PRIMA del send (best-effort: errore => null).
61946
+ // Il server lo inietta nel system prompt dell'LLM cosi' che le risposte
61947
+ // possano usare i nomi REALI delle colonne della pagina corrente.
61948
+ let routeContext = null;
61949
+ if (this.sendRouteContext) {
61950
+ try {
61951
+ routeContext = await this.buildRouteContext();
61952
+ }
61953
+ catch { /* best-effort: niente context, ma il send va lo stesso */ }
61954
+ }
61029
61955
  try {
61030
61956
  const ack = await this.ragService.askAsync({
61031
61957
  prompt: text,
@@ -61033,9 +61959,12 @@ class WuicRagChatbotComponent {
61033
61959
  mode: this.mode,
61034
61960
  topK: this.topK,
61035
61961
  model: this.model,
61962
+ profile: this.allowProfileToggle ? this.profileOverride : undefined,
61963
+ routeContext: routeContext,
61036
61964
  });
61037
61965
  // Salva la sessione corrente (puo' essere stata appena creata)
61038
61966
  this.currentSessionId = ack.sessionId;
61967
+ this.saveLastSession(ack.sessionId);
61039
61968
  userTurn.messageId = ack.userMessageId;
61040
61969
  assistantTurn.messageId = ack.assistantMessageId;
61041
61970
  assistantTurn.correlationId = ack.correlationId;
@@ -61060,37 +61989,57 @@ class WuicRagChatbotComponent {
61060
61989
  /**
61061
61990
  * Svuota la cronologia chat (soft-delete della sessione corrente sul DB) +
61062
61991
  * reset stato locale.
61992
+ *
61993
+ * Il flag `clearingHistory` resta `true` per tutto il flow (dall'apertura
61994
+ * del dialog di conferma fino al completamento DELETE+reset). Driver dello
61995
+ * spinner sul bottone cestino in header — necessario perche' il PRIMO click
61996
+ * subisce ~300-800ms di lazy-load del bundle PrimeNG Dialog
61997
+ * (`promptDialog` apre un `p-dialog` dinamico, non gia' istanziato). Senza
61998
+ * feedback l'utente cliccava 2-3 volte pensando che non rispondesse.
61063
61999
  */
61064
62000
  async onClearHistory() {
61065
62001
  if (this.history.length === 0 && !this.currentSessionId)
61066
62002
  return;
61067
- let confirmed = true;
62003
+ if (this.clearingHistory)
62004
+ return; // re-entry guard
62005
+ this.clearingHistory = true;
62006
+ this.cdr.markForCheck();
61068
62007
  try {
61069
- const tools = WtoolboxService;
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) {
62008
+ let confirmed = true;
61086
62009
  try {
61087
- await this.ragService.deleteSessionAsync(this.currentSessionId);
62010
+ const tools = WtoolboxService;
62011
+ if (typeof tools.promptDialog === 'function') {
62012
+ const ts = WtoolboxService.translationService;
62013
+ const tr = (k, fallback) => (ts && typeof ts.instant === 'function' ? ts.instant(k) : fallback);
62014
+ const res = await tools.promptDialog({
62015
+ title: tr('rag_chatbot.confirm.delete_session.title', 'Svuota cronologia'),
62016
+ message: tr('rag_chatbot.confirm.delete_session', 'Eliminare la cronologia di questa chat?'),
62017
+ confirmLabel: tr('rag_chatbot.confirm.delete_session.confirm', 'Elimina'),
62018
+ cancelLabel: tr('rag_chatbot.confirm.cancel', 'Annulla'),
62019
+ });
62020
+ confirmed = !!res && res.confirmed !== false;
62021
+ }
61088
62022
  }
61089
- catch { /* best effort */ }
62023
+ catch {
62024
+ confirmed = true;
62025
+ }
62026
+ if (!confirmed)
62027
+ return;
62028
+ if (this.currentSessionId) {
62029
+ try {
62030
+ await this.ragService.deleteSessionAsync(this.currentSessionId);
62031
+ }
62032
+ catch { /* best effort */ }
62033
+ }
62034
+ this.history = [];
62035
+ this.pendingMap.clear();
62036
+ this.currentSessionId = null;
62037
+ this.saveLastSession(null);
62038
+ }
62039
+ finally {
62040
+ this.clearingHistory = false;
62041
+ this.cdr.markForCheck();
61090
62042
  }
61091
- this.history = [];
61092
- this.pendingMap.clear();
61093
- this.currentSessionId = null;
61094
62043
  }
61095
62044
  /**
61096
62045
  * Click dispatcher:
@@ -61388,9 +62337,14 @@ class WuicRagChatbotComponent {
61388
62337
  async loadSession(sessionId, highlightMessageId) {
61389
62338
  try {
61390
62339
  const snap = await this.ragService.historyAsync(sessionId);
61391
- if (!snap || !snap.session)
62340
+ if (!snap || !snap.session) {
62341
+ // Sessione salvata non piu' valida (cancellata/altro utente): pulisci il
62342
+ // riferimento persistito cosi' al prossimo boot non si ritenta.
62343
+ this.saveLastSession(null);
61392
62344
  return;
62345
+ }
61393
62346
  this.currentSessionId = snap.session.id;
62347
+ this.saveLastSession(snap.session.id);
61394
62348
  this.history = (snap.messages || []).map((m) => this.dtoToTurn(m));
61395
62349
  this.pendingMap.clear();
61396
62350
  // Re-popola pendingMap per i messaggi assistant ancora in attesa
@@ -61399,6 +62353,19 @@ class WuicRagChatbotComponent {
61399
62353
  this.pendingMap.set(turn.messageId, turn);
61400
62354
  }
61401
62355
  }
62356
+ // Aggiorna il context cue dal piu' recente assistant turn completato:
62357
+ // riflette lo stato corrente del context window a sessione caricata.
62358
+ const lastCtx = [...(snap.messages || [])].reverse()
62359
+ .find(m => m.role === 'assistant' && m.contextUsedTokens != null && m.contextWindowMax != null);
62360
+ if (lastCtx) {
62361
+ this.updateContextCue(lastCtx.contextUsedTokens, lastCtx.contextWindowMax);
62362
+ }
62363
+ else {
62364
+ // Reset: nuova session o session senza turn assistant
62365
+ this.contextUsagePercent = 0;
62366
+ this.contextUsedTokens = null;
62367
+ this.contextWindowMax = null;
62368
+ }
61402
62369
  this.cdr.markForCheck();
61403
62370
  this.scheduleScrollToBottom();
61404
62371
  if (highlightMessageId) {
@@ -61407,6 +62374,27 @@ class WuicRagChatbotComponent {
61407
62374
  }
61408
62375
  }
61409
62376
  catch (err) {
62377
+ // Caso comune: la session salvata in localStorage punta a un record
62378
+ // che non esiste piu' sul backend (es. cambio DBMS, DB pulito, riga
62379
+ // cancellata o appartenente a un altro tenant). Backend risponde 404
62380
+ // con `{error:"session-not-found"}`. Degradazione gracefully:
62381
+ // - rimuovi la lastSession persistita (evita re-tentativo al prossimo boot)
62382
+ // - resetta lo state interno a "nuova session vuota"
62383
+ // - NIENTE alert/healthError invadente.
62384
+ // Per altri tipi di errore (rete, 500, auth) mostriamo healthError come prima.
62385
+ const isSessionNotFound = (err && typeof err === 'object' && 'status' in err && err.status === 404)
62386
+ || /session-not-found|404/i.test(this.extractErrorMessage(err) || '');
62387
+ if (isSessionNotFound) {
62388
+ this.saveLastSession(null);
62389
+ this.currentSessionId = null;
62390
+ this.history = [];
62391
+ this.pendingMap.clear();
62392
+ this.contextUsagePercent = 0;
62393
+ this.contextUsedTokens = null;
62394
+ this.contextWindowMax = null;
62395
+ this.cdr.markForCheck();
62396
+ return;
62397
+ }
61410
62398
  this.healthError = this.extractErrorMessage(err);
61411
62399
  this.cdr.markForCheck();
61412
62400
  }
@@ -61426,10 +62414,13 @@ class WuicRagChatbotComponent {
61426
62414
  loading: m.status === 'pending',
61427
62415
  mode: m.mode || null,
61428
62416
  sources,
61429
- errorMessage: m.status === 'error' ? (m.errorMessage || 'Errore sconosciuto') : null,
62417
+ errorMessage: m.status === 'error' ? (m.errorMessage || 'rag_chatbot.error.unknown') : null,
61430
62418
  tokensIn: m.tokensIn,
61431
62419
  tokensOut: m.tokensOut,
61432
62420
  correlationId: m.correlationId,
62421
+ proposedAction: this.parseProposedAction(m.proposedActionJson),
62422
+ proposedActionAppliedAt: m.proposedActionAppliedAt || null,
62423
+ followupQuestions: this.parseFollowupQuestions(m.followupQuestionsJson),
61433
62424
  };
61434
62425
  if (turn.role === 'assistant' && turn.loading && (!turn.content || turn.content === '')) {
61435
62426
  turn.content = '';
@@ -61456,13 +62447,48 @@ class WuicRagChatbotComponent {
61456
62447
  target.errorMessage = null;
61457
62448
  }
61458
62449
  else if (m.status === 'error') {
61459
- target.errorMessage = m.errorMessage || 'Errore sconosciuto';
62450
+ target.errorMessage = m.errorMessage || 'rag_chatbot.error.unknown';
62451
+ // Errore strutturato `context-overflow`: il backend l'ha settato perche'
62452
+ // la history sfora il context window del modello. Apri il dialog dedicato.
62453
+ if (m.errorMessage === 'context-overflow') {
62454
+ this.contextOverflowDialogOpen = true;
62455
+ }
61460
62456
  }
61461
62457
  target.tokensIn = m.tokensIn;
61462
62458
  target.tokensOut = m.tokensOut;
62459
+ // Aggiorna la proposta (puo' arrivare solo al complete: prima e' null su pending)
62460
+ target.proposedAction = this.parseProposedAction(m.proposedActionJson);
62461
+ target.proposedActionAppliedAt = m.proposedActionAppliedAt || null;
62462
+ target.followupQuestions = this.parseFollowupQuestions(m.followupQuestionsJson);
62463
+ // Aggiorna il context cue (cerchio colorato in header) usando i valori
62464
+ // dell'assistant turn appena ricevuto: e' lo stato corrente del context window.
62465
+ if (m.role === 'assistant' && m.contextUsedTokens != null && m.contextWindowMax != null) {
62466
+ this.updateContextCue(m.contextUsedTokens, m.contextWindowMax);
62467
+ }
61463
62468
  if (!target.loading) {
61464
62469
  this.pendingMap.delete(m.id);
61465
62470
  }
62471
+ // Auto-sync del badge mode (chip 'retrieval' / 'RAG + LLM' in header):
62472
+ // `healthInfo` viene fetchato SOLO in ngOnInit -> dopo restart del
62473
+ // rag_server (rebuild RAG, fresh deploy, crash recovery) il backend
62474
+ // re-pusha la API key e il rag_server passa a `llm_enabled:true`, MA
62475
+ // il componente continua a mostrare 'retrieval' fino al refresh pagina.
62476
+ //
62477
+ // Detection: se l'assistant turn completato ha `target.mode='rag-llm'`
62478
+ // (= il backend ha effettivamente girato LLM) ma `healthInfo.llm_enabled`
62479
+ // e' ancora false, c'e' un mismatch -> rifetcho /health silente.
62480
+ // Simmetricamente per il caso opposto (key rimossa: llm passa a false).
62481
+ // Cost: ~1 call /health per turn quando c'e' divergenza, zero quando OK.
62482
+ if (m.role === 'assistant' && m.status === 'completed' && target.mode) {
62483
+ const turnIsLlm = target.mode === 'rag-llm';
62484
+ const healthIsLlm = !!this.healthInfo?.llm_enabled;
62485
+ if (turnIsLlm !== healthIsLlm) {
62486
+ this.ragService.health().subscribe({
62487
+ next: (info) => { this.healthInfo = info; this.cdr.markForCheck(); },
62488
+ error: () => { }
62489
+ });
62490
+ }
62491
+ }
61466
62492
  // markForCheck: la mutation avviene su un oggetto esistente dentro
61467
62493
  // `this.history` (nested property), invisibile ad Angular con
61468
62494
  // ChangeDetectionStrategy.OnPush dei componenti ancestor (es. quando
@@ -61481,11 +62507,1006 @@ class WuicRagChatbotComponent {
61481
62507
  el.scrollIntoView({ behavior: 'smooth', block: 'center' });
61482
62508
  }
61483
62509
  // ----- Helpers (esistenti, riutilizzati) -----
62510
+ /**
62511
+ * Persiste (o cancella se id null) l'ultima sessione in localStorage.
62512
+ * Best-effort: guard SSR + try/catch (private-mode / quota / storage disabled).
62513
+ */
62514
+ saveLastSession(id) {
62515
+ if (!this.persistSession || typeof window === 'undefined' || !window.localStorage)
62516
+ return;
62517
+ try {
62518
+ const key = WuicRagChatbotComponent.LAST_SESSION_KEY;
62519
+ if (id && id > 0)
62520
+ window.localStorage.setItem(key, String(id));
62521
+ else
62522
+ window.localStorage.removeItem(key);
62523
+ }
62524
+ catch { /* storage non disponibile: la persistenza e' best-effort */ }
62525
+ }
62526
+ /** Legge l'ultima sessione salvata (>0) o null. */
62527
+ readLastSession() {
62528
+ if (typeof window === 'undefined' || !window.localStorage)
62529
+ return null;
62530
+ try {
62531
+ const raw = window.localStorage.getItem(WuicRagChatbotComponent.LAST_SESSION_KEY);
62532
+ const id = Number(raw || 0);
62533
+ return id > 0 ? id : null;
62534
+ }
62535
+ catch {
62536
+ return null;
62537
+ }
62538
+ }
62539
+ // ---------------------------------------------------------------------
62540
+ // Proposed actions (LLM tool-use)
62541
+ // ---------------------------------------------------------------------
62542
+ /** Parse JSON del field `proposed_action_json` -> oggetto tipato (o null). */
62543
+ parseProposedAction(raw) {
62544
+ if (!raw)
62545
+ return null;
62546
+ try {
62547
+ const parsed = JSON.parse(raw);
62548
+ return (parsed && typeof parsed === 'object' && typeof parsed.kind === 'string')
62549
+ ? parsed
62550
+ : null;
62551
+ }
62552
+ catch {
62553
+ return null;
62554
+ }
62555
+ }
62556
+ /** Parse JSON del field `followup_questions_json` -> array di stringhe (o null). */
62557
+ parseFollowupQuestions(raw) {
62558
+ if (!raw)
62559
+ return null;
62560
+ try {
62561
+ const parsed = JSON.parse(raw);
62562
+ if (!Array.isArray(parsed))
62563
+ return null;
62564
+ const cleaned = parsed.filter(q => typeof q === 'string' && q.trim().length > 0).slice(0, 3);
62565
+ return cleaned.length > 0 ? cleaned : null;
62566
+ }
62567
+ catch {
62568
+ return null;
62569
+ }
62570
+ }
62571
+ /**
62572
+ * Click su un chip follow-up: popola l'input box con la domanda (NON invia
62573
+ * automaticamente; l'utente puo' editare prima di premere invio). Coerente
62574
+ * con la scelta UX di non lanciare query "per sbaglio".
62575
+ */
62576
+ applyFollowup(question) {
62577
+ if (!question)
62578
+ return;
62579
+ this.inputValue = question;
62580
+ this.cdr.markForCheck();
62581
+ // focus input cosi' l'utente puo' editare subito o premere invio
62582
+ this.focusInput();
62583
+ }
62584
+ // -------------------------------------------------------------------
62585
+ // Context cue (cerchio colorato in header) + /compact + overflow dialog
62586
+ // -------------------------------------------------------------------
62587
+ /** Aggiorna lo stato del cerchio cue dato (usedTokens, windowMax). */
62588
+ updateContextCue(usedTokens, windowMax) {
62589
+ if (usedTokens == null || windowMax == null || windowMax <= 0)
62590
+ return;
62591
+ this.contextUsedTokens = usedTokens;
62592
+ this.contextWindowMax = windowMax;
62593
+ this.contextUsagePercent = Math.min(100, Math.round((usedTokens / windowMax) * 100));
62594
+ this.cdr.markForCheck();
62595
+ }
62596
+ /** Colore del cerchio basato sul `contextUsagePercent`. */
62597
+ get contextCueColor() {
62598
+ const p = this.contextUsagePercent;
62599
+ if (p < 60)
62600
+ return 'var(--p-green-500, #22c55e)';
62601
+ if (p < 80)
62602
+ return 'var(--p-yellow-500, #eab308)';
62603
+ if (p < 90)
62604
+ return 'var(--p-orange-500, #f97316)';
62605
+ return 'var(--p-red-500, #ef4444)';
62606
+ }
62607
+ get contextCueTooltip() {
62608
+ // Guard: il getter e' chiamato AD OGNI Change Detection (binding di
62609
+ // [pTooltip]). `WtoolboxService.translationService` e' uno static
62610
+ // assegnato in bootstrap; durante una route transition (es. click
62611
+ // su un source chip → router navigation) un CD cycle puo' partire
62612
+ // prima che lo static sia popolato → `undefined.instant(...)` boom
62613
+ // (TypeError "Cannot read properties of undefined (reading 'instant')"
62614
+ // visto in stack trace su WuicRagChatbotComponent_Conditional_10_Template).
62615
+ // Senza guard, l'errore si propaga al global error handler e mostra il
62616
+ // dialog "Si e' verificato un errore inatteso" che blocca l'utente.
62617
+ const ts = WtoolboxService.translationService;
62618
+ if (!ts || typeof ts.instant !== 'function')
62619
+ return '';
62620
+ if (this.contextWindowMax == null || this.contextUsedTokens == null) {
62621
+ return ts.instant('rag_chatbot.context_cue.tooltip_waiting');
62622
+ }
62623
+ const usedK = Math.round(this.contextUsedTokens / 1000);
62624
+ const maxK = Math.round(this.contextWindowMax / 1000);
62625
+ // WtoolboxService.translationService.instant(key) accetta SOLO 1 argomento.
62626
+ // Niente interpolation built-in: replace manuale dei placeholder -> valore.
62627
+ // Regex accetta SIA `{var}` (single brace, format usato nello script
62628
+ // upsert-wuic-translations.ps1) SIA `{{var}}` (double brace, format
62629
+ // tipico Angular template) per robustezza. Storicamente le traduzioni
62630
+ // nel DB hanno usato single brace -> match falliva su double-only.
62631
+ const tpl = ts.instant('rag_chatbot.context_cue.tooltip');
62632
+ return String(tpl)
62633
+ .replace(/\{\{?\s*percent\s*\}?\}/g, String(this.contextUsagePercent))
62634
+ .replace(/\{\{?\s*used\s*\}?\}/g, String(usedK))
62635
+ .replace(/\{\{?\s*max\s*\}?\}/g, String(maxK));
62636
+ }
62637
+ /**
62638
+ * Click sul cerchio context cue: conferma + lancia /compact. Disponibile sempre
62639
+ * (anche <60%): l'utente puo' compattare on-demand.
62640
+ */
62641
+ async onContextCueClick() {
62642
+ if (!this.currentSessionId || this.compactingSession)
62643
+ return;
62644
+ // Conferma via WtoolboxService.confirm (framework). Fallback su no-op confirm
62645
+ // se non disponibile (es. tests).
62646
+ let confirmed = true;
62647
+ try {
62648
+ const tools = WtoolboxService;
62649
+ if (typeof tools.confirm === 'function') {
62650
+ confirmed = await tools.confirm({
62651
+ header: WtoolboxService.translationService.instant('rag_chatbot.confirm.compact_session.header'),
62652
+ message: WtoolboxService.translationService.instant('rag_chatbot.confirm.compact_session.message'),
62653
+ icon: 'pi pi-exclamation-triangle',
62654
+ });
62655
+ }
62656
+ }
62657
+ catch {
62658
+ confirmed = true;
62659
+ }
62660
+ if (!confirmed)
62661
+ return;
62662
+ await this.doCompactSession();
62663
+ }
62664
+ /**
62665
+ * Chiama l'endpoint /api/Rag/Compact. Aggiorna lo stato dopo successo.
62666
+ * Riusato da: contextCue click, slash-command /compact, dialog overflow "Compatta ora".
62667
+ */
62668
+ async doCompactSession() {
62669
+ if (!this.currentSessionId || this.compactingSession)
62670
+ return;
62671
+ this.compactingSession = true;
62672
+ this.cdr.markForCheck();
62673
+ try {
62674
+ const resp = await this.ragService.compactSessionAsync({ sessionId: this.currentSessionId });
62675
+ if (resp?.ok && !resp.noop) {
62676
+ WtoolboxService.messageNotificationService?.add?.({
62677
+ severity: 'success',
62678
+ summary: WtoolboxService.translationService.instant('rag_chatbot.toast.compact_success.summary'),
62679
+ detail: `Summary di ${resp.summaryLength} caratteri, fino al messaggio #${resp.summaryUpToMessageId}.`,
62680
+ });
62681
+ // Ricarica history per riflettere il nuovo summary (alcuni client potrebbero
62682
+ // voler mostrare un divider "--- compattato ---" prima dei msg coperti).
62683
+ if (this.currentSessionId)
62684
+ await this.loadSession(this.currentSessionId, null);
62685
+ // Reset overflow dialog se era aperto
62686
+ this.contextOverflowDialogOpen = false;
62687
+ }
62688
+ else if (resp?.noop) {
62689
+ WtoolboxService.messageNotificationService?.add?.({
62690
+ severity: 'info',
62691
+ summary: WtoolboxService.translationService.instant('rag_chatbot.toast.compact_noop.summary'),
62692
+ detail: resp.reason || 'La sessione non ha turn completati da riassumere.',
62693
+ });
62694
+ }
62695
+ else {
62696
+ WtoolboxService.messageNotificationService?.add?.({
62697
+ severity: 'error',
62698
+ summary: WtoolboxService.translationService.instant('rag_chatbot.toast.compact_failed.summary'),
62699
+ detail: resp?.error || WtoolboxService.translationService.instant('rag_chatbot.toast.generic_error'),
62700
+ });
62701
+ }
62702
+ }
62703
+ catch (err) {
62704
+ const msg = this.extractErrorMessage(err);
62705
+ WtoolboxService.messageNotificationService?.add?.({
62706
+ severity: 'error',
62707
+ summary: WtoolboxService.translationService.instant('rag_chatbot.toast.compact_failed.summary'),
62708
+ detail: msg,
62709
+ });
62710
+ }
62711
+ finally {
62712
+ this.compactingSession = false;
62713
+ this.cdr.markForCheck();
62714
+ }
62715
+ }
62716
+ /**
62717
+ * Nuova sessione: chiude la corrente (clear localStorage), resetta history, ricomincia.
62718
+ * Chiamato dal bottone "Nuova sessione" del dialog overflow.
62719
+ */
62720
+ startNewSession() {
62721
+ this.history = [];
62722
+ this.pendingMap.clear();
62723
+ this.currentSessionId = null;
62724
+ this.saveLastSession(null);
62725
+ this.contextUsagePercent = 0;
62726
+ this.contextUsedTokens = null;
62727
+ this.contextOverflowDialogOpen = false;
62728
+ this.cdr.markForCheck();
62729
+ }
62730
+ // -------------------------------------------------------------------
62731
+ // Sessions popup (lista + rinomina + switch)
62732
+ // -------------------------------------------------------------------
62733
+ async openSessionsDialog() {
62734
+ this.sessionsDialogOpen = true;
62735
+ this.sessionsLoading = true;
62736
+ this.cdr.markForCheck();
62737
+ try {
62738
+ const resp = await this.ragService.sessionsAsync();
62739
+ this.sessionsList = (resp?.sessions || []).slice().sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''));
62740
+ }
62741
+ catch (err) {
62742
+ this.sessionsList = [];
62743
+ WtoolboxService.messageNotificationService?.add?.({
62744
+ severity: 'error',
62745
+ summary: WtoolboxService.translationService.instant('rag_chatbot.toast.sessions_load_failed.summary'),
62746
+ detail: this.extractErrorMessage(err),
62747
+ });
62748
+ }
62749
+ finally {
62750
+ this.sessionsLoading = false;
62751
+ this.cdr.markForCheck();
62752
+ }
62753
+ }
62754
+ closeSessionsDialog() {
62755
+ this.sessionsDialogOpen = false;
62756
+ this.sessionRenameEditing = {};
62757
+ this.sessionRenameDraft = {};
62758
+ }
62759
+ /** Switch alla session selezionata (carica history). */
62760
+ async selectSession(s) {
62761
+ if (!s || !s.id || s.id === this.currentSessionId) {
62762
+ this.closeSessionsDialog();
62763
+ return;
62764
+ }
62765
+ this.closeSessionsDialog();
62766
+ await this.loadSession(s.id, null);
62767
+ }
62768
+ /** Apre l'edit inline del titolo. */
62769
+ startRenameSession(s, event) {
62770
+ if (event) {
62771
+ event.stopPropagation();
62772
+ event.preventDefault();
62773
+ }
62774
+ if (!s?.id)
62775
+ return;
62776
+ this.sessionRenameEditing[s.id] = true;
62777
+ this.sessionRenameDraft[s.id] = s.title || '';
62778
+ this.cdr.markForCheck();
62779
+ }
62780
+ async confirmRenameSession(s, event) {
62781
+ if (event) {
62782
+ event.stopPropagation();
62783
+ event.preventDefault();
62784
+ }
62785
+ if (!s?.id)
62786
+ return;
62787
+ const newTitle = (this.sessionRenameDraft[s.id] || '').trim();
62788
+ if (!newTitle || newTitle === s.title) {
62789
+ this.cancelRenameSession(s);
62790
+ return;
62791
+ }
62792
+ try {
62793
+ const resp = await this.ragService.renameSessionAsync({ sessionId: s.id, title: newTitle });
62794
+ if (resp?.ok) {
62795
+ s.title = resp.title || newTitle;
62796
+ }
62797
+ else {
62798
+ WtoolboxService.messageNotificationService?.add?.({
62799
+ severity: 'error',
62800
+ summary: WtoolboxService.translationService.instant('rag_chatbot.toast.rename_failed.summary'),
62801
+ detail: resp?.error || WtoolboxService.translationService.instant('rag_chatbot.toast.generic_error'),
62802
+ });
62803
+ }
62804
+ }
62805
+ catch (err) {
62806
+ WtoolboxService.messageNotificationService?.add?.({
62807
+ severity: 'error',
62808
+ summary: WtoolboxService.translationService.instant('rag_chatbot.toast.rename_failed.summary'),
62809
+ detail: this.extractErrorMessage(err),
62810
+ });
62811
+ }
62812
+ finally {
62813
+ delete this.sessionRenameEditing[s.id];
62814
+ delete this.sessionRenameDraft[s.id];
62815
+ this.cdr.markForCheck();
62816
+ }
62817
+ }
62818
+ cancelRenameSession(s) {
62819
+ if (!s?.id)
62820
+ return;
62821
+ delete this.sessionRenameEditing[s.id];
62822
+ delete this.sessionRenameDraft[s.id];
62823
+ this.cdr.markForCheck();
62824
+ }
62825
+ /**
62826
+ * Cancella una sessione dalla cronologia (chiamato dall'icona cestino
62827
+ * sulla riga della lista sessioni). Confirm via WtoolboxService.promptDialog
62828
+ * (regola 12 AGENTS: mai window.confirm). Backend soft-delete via
62829
+ * POST /api/Rag/DeleteSession (gia' esistente).
62830
+ *
62831
+ * Se la sessione cancellata e' quella CORRENTE: clear della view (history
62832
+ * + currentSessionId reset) e remove da localStorage. Se non corrente:
62833
+ * solo splice dalla lista locale senza toccare la chat aperta.
62834
+ *
62835
+ * Best-effort: errori backend swallowed (la lista si refresha al prossimo
62836
+ * open del dialog comunque, e l'utente puo' riprovare).
62837
+ */
62838
+ async onDeleteSessionFromList(s, event) {
62839
+ if (event)
62840
+ event.stopPropagation();
62841
+ if (!s?.id)
62842
+ return;
62843
+ let confirmed = true;
62844
+ try {
62845
+ const tools = WtoolboxService;
62846
+ if (typeof tools.promptDialog === 'function') {
62847
+ const titlePreview = this.sessionPreview(s) || WtoolboxService.translationService.instant('rag_chatbot.sessions_dialog.untitled');
62848
+ const tmpl = WtoolboxService.translationService.instant('rag_chatbot.sessions_dialog.delete_confirm_message');
62849
+ // Accetta sia {title} sia {{title}} per coerenza col resto del codebase.
62850
+ const message = String(tmpl).replace(/\{\{?\s*title\s*\}?\}/g, titlePreview);
62851
+ const res = await tools.promptDialog({
62852
+ title: WtoolboxService.translationService.instant('rag_chatbot.sessions_dialog.delete_confirm_title'),
62853
+ message,
62854
+ confirmLabel: WtoolboxService.translationService.instant('rag_chatbot.sessions_dialog.delete_confirm_yes'),
62855
+ cancelLabel: WtoolboxService.translationService.instant('rag_chatbot.confirm.cancel'),
62856
+ });
62857
+ confirmed = !!res && res.confirmed !== false;
62858
+ }
62859
+ }
62860
+ catch {
62861
+ confirmed = true;
62862
+ }
62863
+ if (!confirmed)
62864
+ return;
62865
+ const sessionId = s.id;
62866
+ try {
62867
+ await this.ragService.deleteSessionAsync(sessionId);
62868
+ }
62869
+ catch { /* best-effort, prosegui col refresh UI */ }
62870
+ // Rimuove dalla lista locale (immediate UI feedback senza re-fetch).
62871
+ const idx = this.sessionsList.findIndex(x => x.id === sessionId);
62872
+ if (idx >= 0) {
62873
+ this.sessionsList = [...this.sessionsList.slice(0, idx), ...this.sessionsList.slice(idx + 1)];
62874
+ }
62875
+ // Se era la sessione corrente: clear view + localStorage.
62876
+ if (this.currentSessionId === sessionId) {
62877
+ this.history = [];
62878
+ this.pendingMap.clear();
62879
+ this.currentSessionId = null;
62880
+ this.saveLastSession(null);
62881
+ }
62882
+ this.cdr.markForCheck();
62883
+ }
62884
+ /** Truncate del primo prompt a 100 char per anteprima list (full su tooltip). */
62885
+ sessionPreview(s) {
62886
+ const t = s?.title || '';
62887
+ if (!t)
62888
+ return '(senza titolo)';
62889
+ return t.length > 100 ? t.substring(0, 100) + '…' : t;
62890
+ }
62891
+ /** Formato data sessione (locale). */
62892
+ sessionDate(s) {
62893
+ const raw = s?.updatedAt || s?.createdAt;
62894
+ if (!raw)
62895
+ return '';
62896
+ try {
62897
+ const d = new Date(raw);
62898
+ return d.toLocaleString();
62899
+ }
62900
+ catch {
62901
+ return raw;
62902
+ }
62903
+ }
62904
+ /** Label umana per il bottone/header in base al `kind`. */
62905
+ proposedActionTitle(a) {
62906
+ if (!a)
62907
+ return '';
62908
+ // Stessa guard di `contextCueTooltip`: chiamato come binding del template,
62909
+ // soggetto al CD durante route transition con WtoolboxService.translationService
62910
+ // potenzialmente undefined. Fallback al kind raw cosi' l'utente vede comunque
62911
+ // qualcosa di sensato anche prima del bootstrap i18n.
62912
+ const ts = WtoolboxService.translationService;
62913
+ if (!ts || typeof ts.instant !== 'function')
62914
+ return String(a.kind ?? '');
62915
+ // Chiave i18n per ognuno dei 10 kind documentati in callback-cookbook.md.
62916
+ // Per kind sconosciuti (futuri o malformati) fallback a una chiave
62917
+ // generica con interpolation {kind}.
62918
+ const knownKinds = new Set([
62919
+ 'toolbar_action', 'row_action', 'table_style', 'column_style',
62920
+ 'display_formula', 'form_title_formula', 'default_value_callback',
62921
+ 'custom_validation', 'selection_changed', 'lifecycle_callback',
62922
+ 'simple_metadata_update', 'designer_inject',
62923
+ 'sql_metadata_field', 'metadata_column_create',
62924
+ ]);
62925
+ if (knownKinds.has(a.kind)) {
62926
+ return ts.instant(`rag_chatbot.action_box.title.${a.kind}`);
62927
+ }
62928
+ // instant(key) accetta SOLO 1 argomento: interpolation manuale.
62929
+ // Regex accetta sia {kind} (single brace, format script upsert)
62930
+ // sia {{kind}} (double brace, format Angular template) per robustezza.
62931
+ const tpl = ts.instant('rag_chatbot.action_box.title.unknown');
62932
+ return String(tpl).replace(/\{\{?\s*kind\s*\}?\}/g, String(a.kind ?? ''));
62933
+ }
62934
+ /**
62935
+ * Estrae il body del code/template della proposta indipendentemente dal kind:
62936
+ * - toolbar_action / row_action / default_value / custom_validation /
62937
+ * selection_changed / lifecycle / form_title → `callback_js` / `formula_js`
62938
+ * - table_style / column_style → `condition_js`
62939
+ * - display_formula → `template_html` (Angular markup)
62940
+ * o `formula_js` (legacy backward-compat)
62941
+ * - sql_metadata_field → `sql_snippet` (SQL fragment)
62942
+ * Ritorna stringa vuota se nessun campo body e' presente.
62943
+ */
62944
+ proposedActionJsBody(a) {
62945
+ if (!a)
62946
+ return '';
62947
+ return a.callback_js
62948
+ || a.condition_js
62949
+ || a.template_html
62950
+ || a.formula_js
62951
+ || a.sql_snippet
62952
+ || '';
62953
+ }
62954
+ /**
62955
+ * Campi NON-body della proposta che e' utile mostrare all'utente accanto
62956
+ * al codice (es. per `table_style` la classe CSS o l'inline-style che verra'
62957
+ * applicato alla `<tr>` quando il callback ritorna true; per `toolbar_action`
62958
+ * la label del bottone; per `sql_metadata_field` il field target). Per ogni
62959
+ * kind ritorna 0..N coppie `{label, value}`. Il template li renderizza in
62960
+ * lettura sopra la textarea del body. Mantenere allineato alla definizione
62961
+ * tool in `rag_tools.json`: quando si aggiunge un campo importante a un kind
62962
+ * (es. `inline_style` su `propose_table_style`), aggiungerlo anche qui per
62963
+ * non far perdere visibilita' all'utente sulla decisione del LLM.
62964
+ */
62965
+ proposedActionExtraFields(a) {
62966
+ if (!a)
62967
+ return [];
62968
+ const out = [];
62969
+ const get = (k) => a[k];
62970
+ switch (a.kind) {
62971
+ case 'table_style':
62972
+ case 'column_style': {
62973
+ const cssClass = get('css_class');
62974
+ const isInline = !!cssClass && cssClass.startsWith('style:');
62975
+ out.push({
62976
+ label: isInline ? 'Stile inline' : 'Classe CSS',
62977
+ fieldName: 'css_class',
62978
+ value: isInline ? cssClass.substring('style:'.length).trim() : (cssClass || ''),
62979
+ isInlineStyle: isInline,
62980
+ });
62981
+ break;
62982
+ }
62983
+ case 'toolbar_action':
62984
+ case 'row_action': {
62985
+ out.push({ label: 'Etichetta', fieldName: 'label', value: get('label') || '' });
62986
+ out.push({ label: 'Icona', fieldName: 'icon', value: get('icon') || '' });
62987
+ break;
62988
+ }
62989
+ case 'sql_metadata_field': {
62990
+ out.push({ label: 'Campo metadata', fieldName: 'field_name', value: get('field_name') || '' });
62991
+ out.push({ label: 'Tabella', fieldName: 'target_table', value: get('target_table') || '' });
62992
+ break;
62993
+ }
62994
+ case 'metadata_column_create': {
62995
+ out.push({ label: 'Colonna', fieldName: 'column_name', value: get('column_name') || '' });
62996
+ out.push({ label: 'Display', fieldName: 'display_string', value: get('display_string') || '' });
62997
+ out.push({ label: 'Tipo UI', fieldName: 'ui_column_type', value: get('ui_column_type') || '' });
62998
+ break;
62999
+ }
63000
+ }
63001
+ return out;
63002
+ }
63003
+ /**
63004
+ * Nome del campo nella `RagProposedAction` che porta il body editabile della
63005
+ * textarea (callback / template / sql snippet). Speculare a
63006
+ * `proposedActionJsBody` ma ritorna l'identificatore del field, cosi' il
63007
+ * `(ngModelChange)` puo' scrivere su `a[name]`. Default `callback_js` per i
63008
+ * kind che non hanno un body editabile (no-op all'edit).
63009
+ */
63010
+ proposedActionJsBodyFieldName(a) {
63011
+ if (!a)
63012
+ return 'callback_js';
63013
+ switch (a.kind) {
63014
+ case 'table_style':
63015
+ case 'column_style':
63016
+ return 'condition_js';
63017
+ case 'display_formula':
63018
+ // Backward-compat: template_html nuovo, formula_js legacy.
63019
+ return a.template_html !== undefined ? 'template_html' : 'formula_js';
63020
+ case 'form_title_formula':
63021
+ return 'formula_js';
63022
+ case 'sql_metadata_field':
63023
+ return 'sql_snippet';
63024
+ default:
63025
+ return 'callback_js';
63026
+ }
63027
+ }
63028
+ /**
63029
+ * Setter chiamato da `(ngModelChange)` su un campo extra (es. classe/stile).
63030
+ * Per `css_class` con `isInlineStyle=true` ri-prepende il prefisso `style:`
63031
+ * cosi' la convenzione persistita resta omogenea (vedi
63032
+ * `dynamic-template.component.ts:getTableInlineStyle`). Segna il turno come
63033
+ * `proposedActionDirty=true` cosi' `onApplyAction` chiama il PATCH backend
63034
+ * prima dell'Apply.
63035
+ */
63036
+ onProposedExtraFieldEdit(turn, extra, newValue) {
63037
+ if (!turn?.proposedAction)
63038
+ return;
63039
+ let stored = newValue;
63040
+ if (extra.fieldName === 'css_class' && extra.isInlineStyle) {
63041
+ stored = 'style:' + (newValue || '').trim();
63042
+ }
63043
+ turn.proposedAction[extra.fieldName] = stored;
63044
+ turn.proposedActionDirty = true;
63045
+ }
63046
+ /**
63047
+ * Setter per la textarea del body (callback_js / condition_js / template_html
63048
+ * / formula_js / sql_snippet). Aggiorna il field corretto in base al kind.
63049
+ */
63050
+ onProposedBodyEdit(turn, newValue) {
63051
+ if (!turn?.proposedAction)
63052
+ return;
63053
+ const fieldName = this.proposedActionJsBodyFieldName(turn.proposedAction);
63054
+ turn.proposedAction[fieldName] = newValue;
63055
+ turn.proposedActionDirty = true;
63056
+ }
63057
+ /**
63058
+ * Stessa `codeContext` che il metadata-editor usa per editare il callback
63059
+ * sullo stesso campo metadata. Permette al Monaco TS provider di applicare
63060
+ * autocomplete + type-check come se l'utente stesse editando in metadata-editor.
63061
+ * Centralizzato qui per kind: future aggiunte = entry nella map.
63062
+ */
63063
+ proposedActionCodeContext(a) {
63064
+ if (!a)
63065
+ return '';
63066
+ // Le firme corrispondono a quelle documentate in docs/pages/callback-cookbook.md
63067
+ // (tabella "Contratto dei callback").
63068
+ switch (a.kind) {
63069
+ case 'toolbar_action':
63070
+ // (datasource, metaInfo, record, event, wtoolbox)
63071
+ return 'var datasource: DataSourceComponent; var metaInfo: MetaInfo; var record: {[key: string]: BehaviorSubject<any>}; var event: any; var wtoolbox: typeof WtoolboxService';
63072
+ case 'row_action':
63073
+ // (datasource, record, event, field, wtoolbox)
63074
+ return 'var datasource: DataSourceComponent; var record: {[key: string]: BehaviorSubject<any>}; var event: any; var field: MetadatiColonna; var wtoolbox: typeof WtoolboxService';
63075
+ case 'display_formula':
63076
+ // mc_ui_grid_column_data_template scope: Angular template markup, scope=rowData (oggetto riga raw, NO BehaviorSubject).
63077
+ // Monaco lo renderizza come HTML/Angular template - il "type context" qui e' solo informativo per autocomplete fields.
63078
+ return 'var rowData: {[key: string]: any}';
63079
+ case 'form_title_formula':
63080
+ // mddisplayformula scope: (metaInfo, record, datasource, wtoolbox)
63081
+ return 'var metaInfo: MetaInfo; var record: {[key: string]: BehaviorSubject<any>}; var datasource: DataSourceComponent; var wtoolbox: typeof WtoolboxService';
63082
+ case 'default_value_callback':
63083
+ // mcdefaultvaluecallback scope: (metaInfo, record, datasource, wtoolbox)
63084
+ return 'var metaInfo: MetaInfo; var record: {[key: string]: BehaviorSubject<any>}; var datasource: DataSourceComponent; var wtoolbox: typeof WtoolboxService';
63085
+ case 'custom_validation':
63086
+ // mc_validation_custom_callback scope: (valore, record, validateResult, ...)
63087
+ return 'var valore: any; var record: {[key: string]: BehaviorSubject<any>}; var validateResult: (ok: boolean, message?: string) => void';
63088
+ case 'selection_changed':
63089
+ // mcslctionchangedcustomfunction scope: (record, value, datasource, wtoolbox)
63090
+ return 'var record: {[key: string]: BehaviorSubject<any>}; var value: any; var datasource: DataSourceComponent; var wtoolbox: typeof WtoolboxService';
63091
+ case 'lifecycle_callback':
63092
+ // mdbeforesave/mdaftersave/mdafterload scope: (record, datasource, wtoolbox)
63093
+ return 'var record: {[key: string]: BehaviorSubject<any>}; var datasource: DataSourceComponent; var wtoolbox: typeof WtoolboxService';
63094
+ case 'table_style':
63095
+ case 'column_style':
63096
+ // mustattributevalue / musc_attribute_value scope: solo record (dataItem)
63097
+ return 'var record: any';
63098
+ default:
63099
+ return '';
63100
+ }
63101
+ }
63102
+ /**
63103
+ * Cache field+record sintetici per `<wuic-code-editor>` indicizzata per messageId.
63104
+ * MOTIVATION: il code-editor accetta `field: MetadatiColonna` e
63105
+ * `record: { [k]: BehaviorSubject<any> }`; ricostruirli ad ogni ciclo di CD
63106
+ * causerebbe re-init pesanti del Monaco (riload types, ecc). Cache stabile per
63107
+ * messageId -> stesse referenze -> niente re-init.
63108
+ */
63109
+ proposedActionEditorCache = new Map();
63110
+ /**
63111
+ * Costruisce (o riusa cached) il `MetadatiColonna` sintetico da passare al
63112
+ * `<wuic-code-editor>` per il box proposta. Inietta il `codeContext` corretto
63113
+ * per il `kind` cosi' il TSProvider Monaco applica autocomplete come nel
63114
+ * metadata-editor stesso. L'editor e' EDITABILE (readOnly:false): le modifiche
63115
+ * vengono propagate a `turn.proposedAction[bodyFieldName]` tramite subscription
63116
+ * al BehaviorSubject del record, e segnano il turno come dirty cosi'
63117
+ * `onApplyAction` chiama il PATCH backend prima dell'Apply.
63118
+ */
63119
+ getProposedActionField(turn) {
63120
+ if (!turn?.messageId || !turn.proposedAction)
63121
+ return new MetadatiColonna('');
63122
+ const cached = this.proposedActionEditorCache.get(turn.messageId);
63123
+ if (cached)
63124
+ return cached.field;
63125
+ const field = new MetadatiColonna('');
63126
+ field.mc_nome_colonna = 'callback_js';
63127
+ field.mc_ui_column_type = 'code_editor';
63128
+ field.extras = {
63129
+ customEditorConfig: {
63130
+ language: 'typescript',
63131
+ codeContext: this.proposedActionCodeContext(turn.proposedAction),
63132
+ readOnly: false,
63133
+ }
63134
+ };
63135
+ // Body JS unificato: per i kind che usano `condition_js` (table_style)
63136
+ // o `formula_js` (display_formula) il Monaco editor riceve comunque il
63137
+ // contenuto giusto, e il `field.mc_nome_colonna='callback_js'` resta
63138
+ // arbitrario (e' solo l'etichetta della cella nel record).
63139
+ const jsBody = this.proposedActionJsBody(turn.proposedAction);
63140
+ const recordSubj = new BehaviorSubject(jsBody);
63141
+ const record = { callback_js: recordSubj };
63142
+ // Subscription: ogni edit Monaco emette sul BehaviorSubject. Propaghiamo a
63143
+ // `turn.proposedAction[bodyFieldName]` cosi' textarea e Monaco condividono
63144
+ // la stessa fonte di verita'. Skip della prima emit (BehaviorSubject emette
63145
+ // subito il valore iniziale, che e' gia' nel proposedAction).
63146
+ let firstEmit = true;
63147
+ const bodySubscription = recordSubj.subscribe((val) => {
63148
+ if (firstEmit) {
63149
+ firstEmit = false;
63150
+ return;
63151
+ }
63152
+ if (!turn.proposedAction)
63153
+ return;
63154
+ const fieldName = this.proposedActionJsBodyFieldName(turn.proposedAction);
63155
+ const current = turn.proposedAction[fieldName];
63156
+ if (current === val)
63157
+ return; // no-op se non e' cambiato (evita dirty spurio)
63158
+ turn.proposedAction[fieldName] = val;
63159
+ turn.proposedActionDirty = true;
63160
+ this.cdr.markForCheck();
63161
+ });
63162
+ this.proposedActionEditorCache.set(turn.messageId, { field, record, bodySubscription });
63163
+ return field;
63164
+ }
63165
+ /** Compagno di `getProposedActionField`: ritorna lo stesso `record` cached. */
63166
+ getProposedActionRecord(turn) {
63167
+ if (!turn?.messageId || !turn.proposedAction)
63168
+ return {};
63169
+ const cached = this.proposedActionEditorCache.get(turn.messageId);
63170
+ if (cached)
63171
+ return cached.record;
63172
+ // forza inizializzazione tramite getProposedActionField + ri-prende cached
63173
+ this.getProposedActionField(turn);
63174
+ return this.proposedActionEditorCache.get(turn.messageId)?.record || {};
63175
+ }
63176
+ /**
63177
+ * Toggle textarea (default) <-> Monaco editor avanzato per il callback proposto.
63178
+ * Pattern `<lib-code-area-editor>`: textarea istantanea, Monaco on-demand via
63179
+ * `@defer (when turn.editorMode)`. Entrando in Monaco ri-sincronizza il
63180
+ * BehaviorSubject del record cached col valore corrente di `proposedAction`
63181
+ * (potrebbe essere stato editato dalla textarea nel frattempo, vedi
63182
+ * `onProposedBodyEdit`).
63183
+ */
63184
+ toggleEditorMode(turn) {
63185
+ turn.editorMode = !turn.editorMode;
63186
+ if (turn.editorMode && turn.messageId) {
63187
+ const cached = this.proposedActionEditorCache.get(turn.messageId);
63188
+ if (cached) {
63189
+ const current = this.proposedActionJsBody(turn.proposedAction);
63190
+ // bracket notation (TS4111: index-signature property).
63191
+ const subj = cached.record['callback_js'];
63192
+ if (subj && subj.value !== current) {
63193
+ subj.next(current);
63194
+ }
63195
+ }
63196
+ }
63197
+ this.cdr.markForCheck();
63198
+ }
63199
+ /** True se il turn ha una proposta non ancora applicata (mostra "Applica"). */
63200
+ canApplyAction(turn) {
63201
+ return !!turn?.proposedAction && !turn.proposedActionAppliedAt && !turn.applyingAction;
63202
+ }
63203
+ /**
63204
+ * Bottone "Applica": chiama POST /api/Rag/ApplyAction con conferma. Il server
63205
+ * re-legge la proposta dal DB (anti-tampering del callback_js), checka admin,
63206
+ * INSERT su metadata, InvalidateMetadataCachesAndSetVersion. La proposta viene
63207
+ * marcata come applicata in DB.
63208
+ */
63209
+ async onApplyAction(turn) {
63210
+ if (!this.canApplyAction(turn) || !turn.messageId)
63211
+ return;
63212
+ const action = turn.proposedAction;
63213
+ // ---- Client-only kinds (host registry) ---------------------------------
63214
+ // Alcuni kind (es. `designer_inject`) NON vengono applicati dal backend
63215
+ // ma da un handler registrato da un componente host (es. designer.component)
63216
+ // tramite `ChatbotHostRegistryService.register(kind, handler)`. In tal
63217
+ // caso bypassiamo `/api/Rag/ApplyAction` e deleghiamo l'esecuzione al
63218
+ // handler client. Il `proposedActionAppliedAt` viene marcato qui localmente
63219
+ // perche' il server non sa che l'azione e' stata applicata.
63220
+ const hostHandler = this.chatbotHostRegistry.get(action.kind);
63221
+ // Conferma (best-effort: se promptDialog framework non disponibile, fallback a confirm).
63222
+ // Per host-handler: messaggio piu' chiaro che la modifica e' visibile SOLO al designer
63223
+ // corrente fino al save esplicito (vs. apply backend che e' globale).
63224
+ let confirmed = true;
63225
+ try {
63226
+ const tools = WtoolboxService;
63227
+ if (typeof tools.promptDialog === 'function') {
63228
+ const confirmMsg = hostHandler
63229
+ ? `Applicare '${action.label || action.kind}' al designer corrente? La modifica sara' visibile solo a te fino al save esplicito della dashboard.`
63230
+ : `Applicare '${action.label || action.kind}' sulla route '${action.route || '?'}'? L'azione sara' visibile a tutti gli utenti dopo refresh.`;
63231
+ const r = await tools.promptDialog({
63232
+ title: this.proposedActionTitle(action),
63233
+ message: confirmMsg,
63234
+ confirmLabel: 'Applica',
63235
+ cancelLabel: 'Annulla',
63236
+ });
63237
+ confirmed = !!r && r.confirmed !== false;
63238
+ }
63239
+ }
63240
+ catch {
63241
+ confirmed = true;
63242
+ }
63243
+ if (!confirmed)
63244
+ return;
63245
+ turn.applyingAction = true;
63246
+ turn.applyError = null;
63247
+ this.cdr.markForCheck();
63248
+ try {
63249
+ if (hostHandler) {
63250
+ // ---- Branch CLIENT-ONLY -------------------------------------------
63251
+ const result = await hostHandler(action);
63252
+ if (result?.ok) {
63253
+ turn.proposedActionAppliedAt = new Date().toISOString();
63254
+ try {
63255
+ WtoolboxService.messageNotificationService?.add?.({
63256
+ severity: 'success',
63257
+ summary: 'Azione applicata',
63258
+ detail: result.message || `${action.label || action.kind} applicata al designer.`,
63259
+ });
63260
+ }
63261
+ catch { /* notif opzionale */ }
63262
+ }
63263
+ else {
63264
+ turn.applyError = result?.message || 'host-handler-failed';
63265
+ }
63266
+ }
63267
+ else {
63268
+ // ---- Branch BACKEND (default) -------------------------------------
63269
+ // Pre-flight: se l'utente ha editato classe/stile/callback in UI, sync
63270
+ // il `proposed_action_json` del messaggio sul DB. Il backend ApplyAction
63271
+ // rilegge dal DB (security per evitare tampering, vedi commento in
63272
+ // RagController.ApplyAction). Senza questo PATCH le modifiche utente
63273
+ // andrebbero perse all'apply.
63274
+ if (turn.proposedActionDirty && turn.proposedAction) {
63275
+ try {
63276
+ const upd = await this.ragService.updateProposedActionAsync({
63277
+ messageId: turn.messageId,
63278
+ proposedAction: turn.proposedAction,
63279
+ });
63280
+ if (upd?.ok) {
63281
+ turn.proposedActionDirty = false;
63282
+ }
63283
+ else {
63284
+ turn.applyError = upd?.error || 'proposed-action-update-failed';
63285
+ return;
63286
+ }
63287
+ }
63288
+ catch (patchErr) {
63289
+ turn.applyError = this.extractErrorMessage(patchErr);
63290
+ return;
63291
+ }
63292
+ }
63293
+ const resp = await this.ragService.applyProposedActionAsync({ messageId: turn.messageId });
63294
+ if (resp?.ok) {
63295
+ // Mark applied localmente (il server ha gia' persistito audit)
63296
+ turn.proposedActionAppliedAt = new Date().toISOString();
63297
+ // ─── Invalidate metadata caches (server + client, route-scoped) ─────
63298
+ // Backend RagController.ApplyAction GIA' chiama
63299
+ // RawHelpers.InvalidateMetadataCachesAndSetVersion(true), che bump-a la
63300
+ // projectMetadataVersion server-side. Ma il frontend ha la Dexie
63301
+ // MetadataProviderService.MetaDB persistente che mostra i metadati
63302
+ // vecchi finche' qualcuno non la flusha esplicitamente (bug UX riportato
63303
+ // 2026-06-09: l'utente fa Apply, riapre il dropdown "Metadati correlati"
63304
+ // -> "Stili colonna" mostra "Nessun record trovato" anche se la riga
63305
+ // e' stata salvata server-side).
63306
+ //
63307
+ // SCOPE LIMITATO (richiesta utente):
63308
+ // 1. Eseguiamo il flush SOLO se l'utente sta lavorando in un contesto
63309
+ // route business compatibile con l'action (cioe' buildRouteContext
63310
+ // ha ritornato non-null E action.route corrisponde alla route
63311
+ // corrente). In designer / chatbot fullscreen / route senza
63312
+ // contesto, niente flush: alla prossima navigazione il provider
63313
+ // ricaricherà i metadati comunque via projectMetadataVersion check.
63314
+ // 2. Cancelliamo SOLO le righe Dexie della specifica route impattata
63315
+ // (vedi MetadataProviderService.deleteRouteFromCache: 8 tabelle
63316
+ // filtrate per md_route_name + md_id + mc_id + CG_Id), evitando
63317
+ // il flush totale che e' costoso (~MB di re-fetch su F5).
63318
+ const actionRoute = (action.route || '').trim().toLowerCase();
63319
+ const currentRoute = (() => {
63320
+ try {
63321
+ const url = String(this.router?.url || '').split('?')[0].split('#')[0];
63322
+ return url.replace(/^\/+/, '').split('/')[0].trim().toLowerCase();
63323
+ }
63324
+ catch {
63325
+ return '';
63326
+ }
63327
+ })();
63328
+ const sameRoute = !!actionRoute && actionRoute === currentRoute;
63329
+ if (sameRoute) {
63330
+ // POST invalidateMetadataRuntime: idempotente (server-side ha gia'
63331
+ // bumpato la versione, ritorna OK al volo). Pattern canonico
63332
+ // pivot-builder.component.ts:227.
63333
+ try {
63334
+ await WtoolboxService.http.post(WtoolboxService.appSettings.global_root_url + 'MetaService.invalidateMetadataRuntime', {}, { withCredentials: true }).toPromise();
63335
+ }
63336
+ catch { /* non-fatal: backend ApplyAction l'ha gia' fatto server-side */ }
63337
+ // Flush mirato della Dexie per la sola route impattata.
63338
+ try {
63339
+ await MetadataProviderService.deleteRouteFromCache(actionRoute);
63340
+ }
63341
+ catch { }
63342
+ // Reset cache in-memory custom-attributes (pattern documentato in
63343
+ // custom-attributes.service.ts:199 — "Reset cache chiamato dopo
63344
+ // invalidateMetadataRuntime").
63345
+ try {
63346
+ WtoolboxService.customAttributesService?.clearCache?.();
63347
+ }
63348
+ catch { }
63349
+ }
63350
+ try {
63351
+ WtoolboxService.messageNotificationService?.add?.({
63352
+ severity: 'success',
63353
+ summary: 'Azione applicata',
63354
+ detail: sameRoute
63355
+ ? `${action.label || action.kind} creata su '${action.route}'. Ricarica la pagina (F5) per vederla.`
63356
+ : `${action.label || action.kind} creata su '${action.route}'. Naviga alla route per vederla.`,
63357
+ });
63358
+ }
63359
+ catch { /* notif opzionale */ }
63360
+ }
63361
+ else {
63362
+ turn.applyError = resp?.error || 'apply-failed';
63363
+ }
63364
+ }
63365
+ }
63366
+ catch (err) {
63367
+ turn.applyError = this.extractErrorMessage(err);
63368
+ this.errorOccurred.emit({ message: turn.applyError, details: err });
63369
+ }
63370
+ finally {
63371
+ turn.applyingAction = false;
63372
+ this.cdr.markForCheck();
63373
+ }
63374
+ }
61484
63375
  trimHistory() {
61485
63376
  if (this.history.length > this.maxHistory) {
61486
63377
  this.history = this.history.slice(this.history.length - this.maxHistory);
61487
63378
  }
61488
63379
  }
63380
+ /**
63381
+ * Costruisce un riepilogo testuale della pagina/route corrente che viene
63382
+ * iniettato nel system prompt dell'LLM. Permette di generare snippet che
63383
+ * usano i nomi REALI delle colonne (es. `record.CityName.value`) invece di
63384
+ * placeholder generici.
63385
+ *
63386
+ * Formato (compatto, leggibile dall'LLM):
63387
+ * ```
63388
+ * Route corrente: cities
63389
+ * Tabella: Cities (md_id=42, mdtablename=Cities)
63390
+ * Colonne:
63391
+ * - CityName: type=text, required=yes (real column: CityName)
63392
+ * - StateProvince: type=lookupByID -> StateProvinces
63393
+ * - Location: type=text
63394
+ * - LatestRecordedPopulation: type=number
63395
+ * - btn_send: type=button (row action)
63396
+ * ```
63397
+ *
63398
+ * Ritorna `null` se: router assente, route non resolvibile, route NON e'
63399
+ * una business route (framework-docs/rag-chatbot/auth), o metadata
63400
+ * non disponibile in cache (l'utente probabilmente non e' su una list/edit
63401
+ * della route).
63402
+ */
63403
+ async buildRouteContext() {
63404
+ if (!this.router)
63405
+ return null;
63406
+ const url = String(this.router.url || '').split('?')[0].split('#')[0];
63407
+ // Path completo della pagina SENZA slash iniziale: /cities/list -> 'cities/list',
63408
+ // /designer -> 'designer'. Il route_name (per metadata) e' il primo segment.
63409
+ const pagePath = url.replace(/^\/+/, '').trim();
63410
+ if (!pagePath)
63411
+ return null;
63412
+ // ---- Sezioni indipendenti, concatenate alla fine -----------------------
63413
+ // 1. PAGE SECTION (MINIMALE): SOLO la route/pagina corrente. Le colonne reali,
63414
+ // l'identita' SQL e i dettagli dei lookup NON sono inlineati (gonfiavano OGNI
63415
+ // richiesta a prescindere dal prompt): il LLM li recupera ON-DEMAND via il tool
63416
+ // `request_metadata_detail` (risolto dal backend, RagController.ResolveMetadataDetail).
63417
+ // Cosi' il contesto resta lean e scala a N dimensioni di metadata senza bloat.
63418
+ // 2. HOST STATE SECTION: dump dello state dei componenti host che hanno registrato
63419
+ // uno state provider in ChatbotHostRegistryService (es. designer.component dumpa il
63420
+ // tree dashboardElements). E' state CLIENT-only -> resta inlineato qui (il backend
63421
+ // non puo' risolverlo via retrieval). Permette i riferimenti deittici dell'utente.
63422
+ const sections = [];
63423
+ const pageSection = this.buildMinimalPageContext(pagePath);
63424
+ if (pageSection)
63425
+ sections.push(pageSection);
63426
+ const hostStateSection = this.buildHostStateContext();
63427
+ if (hostStateSection)
63428
+ sections.push(hostStateSection);
63429
+ return sections.length > 0 ? sections.join('\n\n') : null;
63430
+ }
63431
+ /**
63432
+ * Contesto MINIMALE della pagina: SOLO la route/pagina corrente (es. `cities/list`,
63433
+ * `cities/edit`, `designer`). I dettagli di metadata (elenco colonne reali, identita'
63434
+ * SQL schema/tabella, alias di join dei lookup) NON sono inlineati: il LLM li recupera
63435
+ * ON-DEMAND via il tool `request_metadata_detail` (detail='columns'|'lookup_columns'),
63436
+ * risolto lato backend da `RagController.ResolveMetadataDetail`.
63437
+ *
63438
+ * RAZIONALE: inlinare colonne + SQL identity gonfiava OGNI richiesta a prescindere dal
63439
+ * prompt (context window saturato, token sprecati). Col retrieval agentico il model
63440
+ * recupera SOLO cio' che il prompt richiede, e il meccanismo scala a N dimensioni di
63441
+ * metadata (physical_columns, related_routes, enum_values, ...) senza toccare questo
63442
+ * builder. Vale per qualunque pagina, incluse quelle framework (designer, ...): il
63443
+ * model puo' comunque targetizzare una route business via retrieval.
63444
+ */
63445
+ buildMinimalPageContext(pagePath) {
63446
+ if (!pagePath)
63447
+ return null;
63448
+ // route_name = primo segment (la route metadata); l'azione (list/edit/dashboard) e' suffisso.
63449
+ const routeName = decodeURIComponent((pagePath.split('/')[0] || '')).trim();
63450
+ const lines = [];
63451
+ lines.push(`Route/pagina corrente: ${pagePath}`);
63452
+ if (routeName && routeName !== pagePath)
63453
+ lines.push(`route_name (per metadata/tool): ${routeName}`);
63454
+ lines.push(`Contesto minimale: l'elenco colonne, l'identita' SQL e i dettagli dei lookup NON sono qui. ` +
63455
+ `Per ottenerli usa il tool request_metadata_detail: {detail:'columns', route:'<route_name>'} per le ` +
63456
+ `colonne reali e l'identita' SQL (schema/tabella + full qualifier); ` +
63457
+ `{detail:'lookup_columns', route:'<route_name>', column:'<col>'} per l'alias di join e le colonne ` +
63458
+ `correlate di una colonna lookupByID.`);
63459
+ return lines.join('\n');
63460
+ }
63461
+ /**
63462
+ * Concatena lo state dump fornito dai componenti host che hanno registrato
63463
+ * un state provider per un proposed_action kind. Tipicamente designer.component
63464
+ * registra `designer_inject` con un tree dei dashboardElements (uniqueName +
63465
+ * inputs significativi), cosi' il LLM puo' identificare componenti via
63466
+ * uniqueName quando l'utente chiede modifiche tipo "cambia background
63467
+ * del riquadro in alto a destra".
63468
+ *
63469
+ * Ritorna `null` se nessun host ha registrato dump per il turn corrente.
63470
+ */
63471
+ buildHostStateContext() {
63472
+ const kinds = this.chatbotHostRegistry.registeredStateKinds();
63473
+ if (!kinds || kinds.length === 0)
63474
+ return null;
63475
+ const sections = [];
63476
+ for (const kind of kinds) {
63477
+ const dump = this.chatbotHostRegistry.getStateDump(kind);
63478
+ if (dump && dump.trim().length > 0) {
63479
+ // Disambiguation hint: quando il dump contiene componenti del designer
63480
+ // (TABLE_/SPLITTER_/TABVIEW_/ACCORDION_/KPI_/...) prepende una nota
63481
+ // esplicita per il LLM. Senza questa nota, prompt come "aggiungi una
63482
+ // colonna alla tabella" vengono interpretati come "aggiungi una colonna
63483
+ // metadata SQL alla route business corrente" invece che "incrementa
63484
+ // cols sul componente TABLE del canvas designer".
63485
+ const dumpTrim = dump.trim();
63486
+ let header = `HOST STATE (${kind})`;
63487
+ if (kind === 'designer_inject') {
63488
+ header += ':\nNOTA AMBIGUITA\': i componenti elencati sotto sono nel CANVAS DESIGNER corrente. '
63489
+ + 'Quando l\'utente parla di "tabella", "riga", "colonna", "splitter", "tab", "accordion", '
63490
+ + '"kpi", "label", "div", ecc. si riferisce SEMPRE a questi uniqueName del canvas, NON a '
63491
+ + 'tabelle/colonne metadata SQL business. Esempi:\n'
63492
+ + ' - "aggiungi una colonna alla tabella" => set_property cols=cols+1 sul TABLE__N del canvas (NON propose_simple_metadata_update)\n'
63493
+ + ' - "aggiungi una riga alla tabella" => set_property rows=rows+1\n'
63494
+ + ' - "rimuovi una colonna/riga" => set_property cols=cols-1 / rows=rows-1\n'
63495
+ + ' - "aggiungi un\'area allo splitter" => set_property areas=areas+1\n'
63496
+ + ' - "aggiungi un tab al tabview" => inject TABPANEL con parent_unique_name=TABVIEW__N\n'
63497
+ + ' - "aggiungi un item all\'accordion" => set_property items=items+1\n'
63498
+ + ' - "cambia il colore della tabella" => set_property backgroundColor sul TABLE__N\n'
63499
+ + 'Se invece l\'utente menziona ESPLICITAMENTE "metadata", "route", "datasource cities", '
63500
+ + '"colonna SQL", "campo del database" => usa propose_simple_metadata_update.';
63501
+ }
63502
+ else {
63503
+ header += ':';
63504
+ }
63505
+ sections.push(`${header}\n${dumpTrim}`);
63506
+ }
63507
+ }
63508
+ return sections.length > 0 ? sections.join('\n\n') : null;
63509
+ }
61489
63510
  scheduleScrollToBottom() {
61490
63511
  if (typeof window === 'undefined')
61491
63512
  return;
@@ -61514,67 +63535,86 @@ class WuicRagChatbotComponent {
61514
63535
  return `${anyErr.status} ${anyErr.statusText}`;
61515
63536
  return JSON.stringify(err);
61516
63537
  }
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" }] });
63538
+ 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 });
63539
+ 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.rel_path + ':' + source.start_line + ':' + source.end_line + ':' + source.symbol_name)) {\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
63540
  }
61520
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotComponent, decorators: [{
61521
- type: Component,
61522
- args: [{ selector: 'wuic-rag-chatbot', standalone: true, imports: [
61523
- CommonModule,
61524
- FormsModule,
61525
- ButtonModule,
61526
- TextareaModule,
61527
- BadgeModule,
61528
- ChipModule,
61529
- ScrollPanelModule,
61530
- ProgressSpinnerModule,
61531
- TooltipModule,
61532
- MessageModule,
61533
- DialogModule,
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
- }], ctorParameters: () => [{ type: WuicRagService }, { type: i0.ChangeDetectorRef }, { type: i1$2.ActivatedRoute, decorators: [{
61536
- type: Optional
61537
- }] }, { type: i1$2.Router, decorators: [{
61538
- type: Optional
61539
- }] }, { type: NotificationRealtimeService, decorators: [{
61540
- type: Optional
61541
- }] }], propDecorators: { title: [{
61542
- type: Input
61543
- }], mode: [{
61544
- type: Input
61545
- }], topK: [{
61546
- type: Input
61547
- }], showSources: [{
61548
- type: Input
61549
- }], maxHistory: [{
61550
- type: Input
61551
- }], placeholder: [{
61552
- type: Input
61553
- }], model: [{
61554
- type: Input
61555
- }], chatHeight: [{
61556
- type: Input
61557
- }], showClearButton: [{
61558
- type: Input
61559
- }], resultSelected: [{
61560
- type: Output
61561
- }], errorOccurred: [{
61562
- type: Output
61563
- }], turnAdded: [{
61564
- type: Output
61565
- }], inputTextarea: [{
61566
- type: ViewChild,
61567
- args: ['inputTextarea']
61568
- }], scrollContainer: [{
61569
- type: ViewChild,
61570
- args: ['scrollContainer']
61571
- }] } });
63541
+ 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: [{
63542
+ type: Component,
63543
+ args: [{ selector: 'wuic-rag-chatbot', standalone: true, imports: [
63544
+ CommonModule,
63545
+ FormsModule,
63546
+ ButtonModule,
63547
+ TextareaModule,
63548
+ InputTextModule,
63549
+ BadgeModule,
63550
+ ChipModule,
63551
+ ScrollPanelModule,
63552
+ ProgressSpinnerModule,
63553
+ TooltipModule,
63554
+ MessageModule,
63555
+ DialogModule,
63556
+ TranslateModule,
63557
+ LazyCodeEditorComponent,
63558
+ ], 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.rel_path + ':' + source.start_line + ':' + source.end_line + ':' + source.symbol_name)) {\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"] }]
63559
+ }], ctorParameters: () => [{ type: WuicRagService }, { type: i0.ChangeDetectorRef }, { type: ChatbotHostRegistryService }, { type: i1$2.ActivatedRoute, decorators: [{
63560
+ type: Optional
63561
+ }] }, { type: i1$2.Router, decorators: [{
63562
+ type: Optional
63563
+ }] }, { type: NotificationRealtimeService, decorators: [{
63564
+ type: Optional
63565
+ }] }, { type: MetadataProviderService, decorators: [{
63566
+ type: Optional
63567
+ }] }], propDecorators: { title: [{
63568
+ type: Input
63569
+ }], mode: [{
63570
+ type: Input
63571
+ }], topK: [{
63572
+ type: Input
63573
+ }], showSources: [{
63574
+ type: Input
63575
+ }], maxHistory: [{
63576
+ type: Input
63577
+ }], placeholder: [{
63578
+ type: Input
63579
+ }], model: [{
63580
+ type: Input
63581
+ }], chatHeight: [{
63582
+ type: Input
63583
+ }], showClearButton: [{
63584
+ type: Input
63585
+ }], persistSession: [{
63586
+ type: Input
63587
+ }], sendRouteContext: [{
63588
+ type: Input
63589
+ }], allowProfileToggle: [{
63590
+ type: Input
63591
+ }], resultSelected: [{
63592
+ type: Output
63593
+ }], errorOccurred: [{
63594
+ type: Output
63595
+ }], turnAdded: [{
63596
+ type: Output
63597
+ }], inputTextarea: [{
63598
+ type: ViewChild,
63599
+ args: ['inputTextarea']
63600
+ }], scrollContainer: [{
63601
+ type: ViewChild,
63602
+ args: ['scrollContainer']
63603
+ }] } }) });
61572
63604
 
61573
63605
  var ragChatbot_component = /*#__PURE__*/Object.freeze({
61574
63606
  __proto__: null,
61575
63607
  WuicRagChatbotComponent: WuicRagChatbotComponent
61576
63608
  });
61577
63609
 
63610
+ /**
63611
+ * Route principale che renderizza il chatbot full-page (vedi `app.routes.ts`:
63612
+ * path 'rag-chatbot' → WuicRagChatbotComponent). Quando l'utente e' gia' su
63613
+ * quella route, il FAB diventa ridondante (e visivamente sovrapposto al chatbot
63614
+ * stesso) → lo nascondiamo. Il match e' su prefisso per coprire eventuali
63615
+ * sub-route future tipo `/rag-chatbot/session/<id>`.
63616
+ */
63617
+ const RAG_CHATBOT_ROUTE_PREFIX = '/rag-chatbot';
61578
63618
  /**
61579
63619
  * Floating Action Button (FAB) bottom-right che apre il chatbot RAG come
61580
63620
  * dialog overlay non-modale. Riusa il componente esistente
@@ -61604,6 +63644,7 @@ class WuicRagChatbotFabComponent {
61604
63644
  licenseService;
61605
63645
  notificationRT;
61606
63646
  cdr;
63647
+ router;
61607
63648
  /** Tooltip dell'icona FAB. */
61608
63649
  fabTooltip = 'Chiedi al codebase WUIC';
61609
63650
  /** Titolo del dialog (passato anche al chatbot interno). */
@@ -61639,11 +63680,20 @@ class WuicRagChatbotFabComponent {
61639
63680
  licenseAllowed = false;
61640
63681
  /** Counter unread per il badge sul FAB. */
61641
63682
  unreadRagCount = 0;
63683
+ /**
63684
+ * True quando la route corrente e' `/rag-chatbot` (o un suo sub-path).
63685
+ * Nascondiamo il FAB in quel caso perche' renderebbe un launcher duplicato
63686
+ * sopra il chatbot gia' aperto a tutta pagina. Aggiornato reattivamente
63687
+ * ai `NavigationEnd` del Router, e inizializzato dal `router.url` corrente
63688
+ * cosi' il primo render (es. F5 mentre sei gia' sulla route) e' coerente.
63689
+ */
63690
+ onRagChatbotRoute = false;
61642
63691
  subs = new Subscription();
61643
- constructor(licenseService, notificationRT, cdr) {
63692
+ constructor(licenseService, notificationRT, cdr, router) {
61644
63693
  this.licenseService = licenseService;
61645
63694
  this.notificationRT = notificationRT;
61646
63695
  this.cdr = cdr;
63696
+ this.router = router;
61647
63697
  }
61648
63698
  ngOnInit() {
61649
63699
  // Subscribe al license status. La status$() emette null finche' refresh()
@@ -61664,11 +63714,34 @@ class WuicRagChatbotFabComponent {
61664
63714
  this.cdr.markForCheck();
61665
63715
  }
61666
63716
  }));
63717
+ // Track route corrente: nascondi il FAB quando sei gia' sul chatbot
63718
+ // full-page. `router.url` e' valorizzato anche prima del primo NavigationEnd
63719
+ // (es. F5 con URL `#/rag-chatbot` -> router lo ha gia' parsato a costruzione
63720
+ // del component), quindi inizializziamo subito + restiamo in sync via events.
63721
+ this.updateOnRagChatbotRoute(this.router.url);
63722
+ this.subs.add(this.router.events
63723
+ .pipe(filter((e) => e instanceof NavigationEnd))
63724
+ .subscribe((e) => this.updateOnRagChatbotRoute(e.urlAfterRedirects || e.url)));
63725
+ }
63726
+ updateOnRagChatbotRoute(url) {
63727
+ const u = String(url || '').split('?')[0].split('#')[0];
63728
+ // match esatto o sub-path: `/rag-chatbot` oppure `/rag-chatbot/...`
63729
+ const next = u === RAG_CHATBOT_ROUTE_PREFIX || u.startsWith(RAG_CHATBOT_ROUTE_PREFIX + '/');
63730
+ if (next !== this.onRagChatbotRoute) {
63731
+ this.onRagChatbotRoute = next;
63732
+ this.cdr.markForCheck();
63733
+ }
61667
63734
  }
61668
63735
  ngOnDestroy() {
61669
63736
  this.subs.unsubscribe();
61670
63737
  }
61671
63738
  get visible() {
63739
+ // Anche con licenza valida o `forceVisible=true`, il FAB resta nascosto
63740
+ // quando si e' gia' sulla route full-page del chatbot: aprirebbe un
63741
+ // launcher duplicato sopra il chatbot stesso.
63742
+ if (this.onRagChatbotRoute) {
63743
+ return false;
63744
+ }
61672
63745
  return this.forceVisible || this.licenseAllowed;
61673
63746
  }
61674
63747
  /**
@@ -61719,8 +63792,8 @@ class WuicRagChatbotFabComponent {
61719
63792
  this.dialogVisible = false;
61720
63793
  this.cdr.markForCheck();
61721
63794
  }
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=\"Apri chatbot codebase WUIC\"\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 [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}.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: "component", type: WuicRagChatbotComponent, selector: "wuic-rag-chatbot", inputs: ["title", "mode", "topK", "showSources", "maxHistory", "placeholder", "model", "chatHeight", "showClearButton"], outputs: ["resultSelected", "errorOccurred", "turnAdded"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
63795
+ 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 });
63796
+ 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
63797
  }
61725
63798
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotFabComponent, decorators: [{
61726
63799
  type: Component,
@@ -61730,9 +63803,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
61730
63803
  DialogModule,
61731
63804
  TooltipModule,
61732
63805
  BadgeModule,
63806
+ TranslateModule,
61733
63807
  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=\"Apri chatbot codebase WUIC\"\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 [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}.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"] }]
61735
- }], ctorParameters: () => [{ type: LicenseFeatureService }, { type: NotificationRealtimeService }, { type: i0.ChangeDetectorRef }], propDecorators: { fabTooltip: [{
63808
+ ], 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"] }]
63809
+ }], ctorParameters: () => [{ type: LicenseFeatureService }, { type: NotificationRealtimeService }, { type: i0.ChangeDetectorRef }, { type: i1$2.Router }], propDecorators: { fabTooltip: [{
61736
63810
  type: Input
61737
63811
  }], dialogTitle: [{
61738
63812
  type: Input
@@ -61929,6 +64003,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
61929
64003
  ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<p-dialog [header]=\"currentException?.title || ('error' | translate)\"\n [attr.title]=\"currentException?.title || ('error' | translate)\"\n [modal]=\"true\" [(visible)]=\"visible\" [style]=\"{ width: '75%' }\"\n [contentStyle]=\"{ 'max-height': '70vh', 'overflow': 'auto' }\"\n (onHide)=\"onClose()\">\n\n <!-- Body principale.\n For SQL passthrough: raw engine text (NOT translated) + meta line.\n Per envelope tipizzato: title + parserMessage/line/route da args. -->\n <div class=\"wuic-err-main\" data-testid=\"wuic-error-dialog-body\">\n <ng-container *ngIf=\"currentException?.kind === 'sql-passthrough'; else legacyBody\">\n <p class=\"wuic-err-msg\" style=\"font-size: 1rem; margin: 0 0 0.5rem 0; white-space: pre-wrap;\">\n {{ currentException?.body }}\n </p>\n <p class=\"wuic-err-meta\" *ngIf=\"currentException?.sqlDetails\"\n style=\"color: var(--text-color-secondary); font-size: 0.85rem; margin: 0 0 1rem 0;\">\n {{ formatSqlDetails(currentException?.sqlDetails) }}\n </p>\n </ng-container>\n <ng-template #legacyBody>\n <p style=\"margin: 0 0 0.5rem 0; white-space: pre-wrap;\">\n {{ currentException?.title }}\n </p>\n <p *ngIf=\"currentException?.args?.['parserMessage']\"\n style=\"color: var(--text-color-secondary); font-size: 0.85rem; margin: 0 0 0.5rem 0; white-space: pre-wrap;\">\n {{ currentException?.args?.['parserMessage'] }}\n </p>\n <p *ngIf=\"currentException?.args?.['lineNumber'] !== undefined && currentException?.args?.['lineNumber'] !== null\"\n style=\"color: var(--text-color-secondary); font-size: 0.85rem; margin: 0 0 1rem 0;\">\n line {{ currentException?.args?.['lineNumber'] }} \u00B7 pos {{ currentException?.args?.['linePosition'] }}\n <ng-container *ngIf=\"currentException?.args?.['route']\"> \u00B7 route <code>{{ currentException?.args?.['route'] }}</code></ng-container>\n </p>\n </ng-template>\n </div>\n\n <!-- Executed query (SQL passthrough only) -->\n <p-fieldset *ngIf=\"currentException?.query\"\n [legend]=\"currentException?.labels?.query || 'Executed query'\"\n [toggleable]=\"true\" [collapsed]=\"true\"\n styleClass=\"wuic-err-section wuic-err-section-spacer\">\n <pre style=\"margin: 0; white-space: pre-wrap; word-break: break-word; font-size: 0.78rem; max-height: 28vh; overflow: auto;\">{{ currentException?.query }}</pre>\n </p-fieldset>\n\n <!-- Parameters (SQL passthrough only) -->\n <p-fieldset *ngIf=\"currentException?.parameters && (currentException?.parameters | json) !== '{}'\"\n [legend]=\"currentException?.labels?.params || 'Parameters'\"\n [toggleable]=\"true\" [collapsed]=\"true\"\n styleClass=\"wuic-err-section wuic-err-section-spacer\">\n <pre style=\"margin: 0; white-space: pre-wrap; font-size: 0.78rem; max-height: 22vh; overflow: auto;\">{{ formatParameters(currentException?.parameters) }}</pre>\n </p-fieldset>\n\n <!-- Stack trace (collapsed by default) -->\n <p-fieldset *ngIf=\"currentException?.stackTrace\"\n [legend]=\"currentException?.labels?.stack || 'Stack trace'\"\n [toggleable]=\"true\" [collapsed]=\"true\"\n styleClass=\"wuic-err-section wuic-err-section-spacer\">\n <pre style=\"margin: 0; white-space: pre-wrap; font-size: 0.78rem; max-height: 28vh; overflow: auto;\">{{ currentException?.stackTrace }}</pre>\n </p-fieldset>\n\n <!-- traceId footer line -->\n <p *ngIf=\"currentException?.traceId\"\n style=\"color: var(--text-color-secondary); font-size: 0.78rem; margin: 0 0 0.5rem 0;\">\n traceId: <code>{{ currentException?.traceId }}</code>\n </p>\n\n <ng-template pTemplate=\"footer\">\n <div class=\"flex justify-content-end gap-2\">\n <p-button [label]=\"currentException?.labels?.copy || 'Copia'\"\n severity=\"secondary\" icon=\"pi pi-copy\"\n (onClick)=\"copyErrorDetails()\" />\n <p-button [label]=\"'ok' | translate\" (onClick)=\"onClose()\" />\n </div>\n </ng-template>\n</p-dialog>\n", styles: [":host ::ng-deep .wuic-err-section+.wuic-err-section,:host ::ng-deep .wuic-err-section-spacer{margin-top:.5rem}\n"] }]
61930
64004
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }] });
61931
64005
 
64006
+ /**
64007
+ * Quando una richiesta HTTP imposta questo token nel suo `HttpContext`,
64008
+ * l'interceptor NON inoltra l'errore al `GlobalHandler` (niente dialog/toast
64009
+ * globale): l'errore viene comunque normalizzato e rilanciato, ma la sua
64010
+ * presentazione e' lasciata interamente al chiamante (es. il first-run wizard
64011
+ * che mostra l'errore SOLO inline nel form). Default: false (comportamento
64012
+ * standard col dialog globale).
64013
+ *
64014
+ * Uso:
64015
+ * import { HttpContext } from '@angular/common/http';
64016
+ * this.http.post(url, body, { context: new HttpContext().set(WUIC_SUPPRESS_GLOBAL_ERROR, true) });
64017
+ */
64018
+ const WUIC_SUPPRESS_GLOBAL_ERROR = new HttpContextToken(() => false);
61932
64019
  /**
61933
64020
  * Normalizes HTTP errors so the GlobalHandler always sees an
61934
64021
  * `errorCode + args + fallbackMessage` envelope.
@@ -61948,14 +64035,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
61948
64035
  * still fire (they may close busy spinners, etc).
61949
64036
  */
61950
64037
  function processHttpError(req, err) {
64038
+ // Se la richiesta ha richiesto la soppressione del dialog/toast globale
64039
+ // (es. first-run wizard, che mostra gli errori SOLO inline nel form), NON
64040
+ // inoltriamo al GlobalHandler. L'errore viene comunque normalizzato e
64041
+ // rilanciato: il catch del chiamante lo presenta come preferisce.
64042
+ const suppressGlobal = req.context?.get(WUIC_SUPPRESS_GLOBAL_ERROR) === true;
61951
64043
  // Already typed by the server → notify GlobalHandler directly so the
61952
64044
  // dialog renders even if the subscribing component handles the error
61953
64045
  // in its own catchError.
61954
64046
  if (err.error && typeof err.error === 'object' && 'errorCode' in err.error) {
61955
- try {
61956
- GlobalHandler.handleErrorStatic({ error: err.error });
64047
+ if (!suppressGlobal) {
64048
+ try {
64049
+ GlobalHandler.handleErrorStatic({ error: err.error });
64050
+ }
64051
+ catch { /* noop */ }
61957
64052
  }
61958
- catch { /* noop */ }
61959
64053
  return err;
61960
64054
  }
61961
64055
  // Generic HTTP failure — wrap with a stable code so the
@@ -61972,10 +64066,12 @@ function processHttpError(req, err) {
61972
64066
  fallbackMessage: typeof err.error === 'string' ? err.error : err.message,
61973
64067
  traceId: err.headers?.get('traceparent') || undefined,
61974
64068
  };
61975
- try {
61976
- GlobalHandler.handleErrorStatic({ error: wrappedError });
64069
+ if (!suppressGlobal) {
64070
+ try {
64071
+ GlobalHandler.handleErrorStatic({ error: wrappedError });
64072
+ }
64073
+ catch { /* noop */ }
61977
64074
  }
61978
- catch { /* noop */ }
61979
64075
  return new HttpErrorResponse({
61980
64076
  error: wrappedError,
61981
64077
  headers: err.headers,
@@ -62020,7 +64116,7 @@ const designerPendingChangesGuard = async (component) => {
62020
64116
  const routes = [
62021
64117
  {
62022
64118
  path: 'framework-docs',
62023
- loadComponent: () => import('./wuic-framework-lib-framework-docs.component-m0IlS0Sl.mjs').then((m) => m.FrameworkDocsComponent),
64119
+ loadComponent: () => import('./wuic-framework-lib-framework-docs.component-DphKhTef.mjs').then((m) => m.FrameworkDocsComponent),
62024
64120
  canMatch: [menuRouteAccessCanMatchGuard],
62025
64121
  canActivate: [menuRouteAccessCanActivateGuard],
62026
64122
  data: {
@@ -62029,7 +64125,7 @@ const routes = [
62029
64125
  },
62030
64126
  {
62031
64127
  path: 'framework-docs/:slug',
62032
- loadComponent: () => import('./wuic-framework-lib-framework-docs.component-m0IlS0Sl.mjs').then((m) => m.FrameworkDocsComponent),
64128
+ loadComponent: () => import('./wuic-framework-lib-framework-docs.component-DphKhTef.mjs').then((m) => m.FrameworkDocsComponent),
62033
64129
  canMatch: [menuRouteAccessCanMatchGuard],
62034
64130
  canActivate: [menuRouteAccessCanActivateGuard],
62035
64131
  data: {
@@ -62047,7 +64143,7 @@ const routes = [
62047
64143
  },
62048
64144
  {
62049
64145
  path: ':route/report-designer',
62050
- loadComponent: () => import('./wuic-framework-lib-report-designer.component-FiAwNkPM.mjs').then((m) => m.ReportDesignerComponent),
64146
+ loadComponent: () => import('./wuic-framework-lib-report-designer.component-D_qbefud.mjs').then((m) => m.ReportDesignerComponent),
62051
64147
  canMatch: [menuRouteAccessCanMatchGuard, featureRouteCanMatchGuard],
62052
64148
  canActivate: [menuRouteAccessCanActivateGuard, featureRouteCanActivateGuard],
62053
64149
  data: {
@@ -62057,7 +64153,7 @@ const routes = [
62057
64153
  },
62058
64154
  {
62059
64155
  path: ':route/report-viewer',
62060
- loadComponent: () => import('./wuic-framework-lib-report-viewer.component-DY0dGMrt.mjs').then((m) => m.ReportViewerComponent),
64156
+ loadComponent: () => import('./wuic-framework-lib-report-viewer.component-CIN2fY0P.mjs').then((m) => m.ReportViewerComponent),
62061
64157
  canMatch: [menuRouteAccessCanMatchGuard],
62062
64158
  canActivate: [menuRouteAccessCanActivateGuard],
62063
64159
  data: {
@@ -62077,7 +64173,7 @@ const routes = [
62077
64173
  },
62078
64174
  {
62079
64175
  path: ':route/pivot-builder',
62080
- loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-VIs0CIH_.mjs').then((m) => m.PivotBuilderComponent),
64176
+ loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-BqUUwrJM.mjs').then((m) => m.PivotBuilderComponent),
62081
64177
  canMatch: [menuRouteAccessCanMatchGuard, featureRouteCanMatchGuard],
62082
64178
  canActivate: [menuRouteAccessCanActivateGuard, featureRouteCanActivateGuard],
62083
64179
  data: {
@@ -62087,7 +64183,7 @@ const routes = [
62087
64183
  },
62088
64184
  {
62089
64185
  path: 'pivot-builder',
62090
- loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-VIs0CIH_.mjs').then((m) => m.PivotBuilderComponent),
64186
+ loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-BqUUwrJM.mjs').then((m) => m.PivotBuilderComponent),
62091
64187
  canMatch: [menuRouteAccessCanMatchGuard, featureRouteCanMatchGuard],
62092
64188
  canActivate: [menuRouteAccessCanActivateGuard, featureRouteCanActivateGuard],
62093
64189
  data: {
@@ -62345,5 +64441,5 @@ function getThemePrimaryHex(value) {
62345
64441
  * Generated bundle index. Do not edit.
62346
64442
  */
62347
64443
 
62348
- export { KanbanListComponent as $, ArchetypeConfiguratorComponent as A, BooleanEditorComponent as B, CallbackPipe as C, DynamicGenericTemplateComponent as D, CrashReporterService as E, FilterBarComponent as F, GlobalHandler as G, CustomAttributesService as H, ImportExportButtonComponent as I, CustomException as J, DataActionButtonComponent as K, LazyParametricDialogComponent as L, MetadataProviderService as M, DataProviderOdataService as N, DateEditorComponent as O, PagerComponent as P, DesignerRouteComponent as Q, DictionaryEditorComponent as R, DynamicRowTemplateComponent as S, TranslationManagerService as T, UserInfoService as U, FormatGridViewValuePipe as V, WtoolboxService as W, GetSrcUploadPreviewPipe as X, HtmlEditorComponent as Y, ImageWrapperComponent as Z, IsSelectedRowPipe as _, DynamicCompilerService as a, loadLookupEditorComponent as a$, KanbanOptions as a0, KanbanStatusColumnItem as a1, LazyChartListComponent as a2, LazyCodeEditorComponent as a3, LazyDataActionButtonComponent as a4, LazyDataSourceComponent as a5, LazyFieldEditorComponent as a6, LazyFirstRunWizardComponent as a7, LazyImageWrapperComponent as a8, LazyKanbanListComponent as a9, VisibleFieldListPipe as aA, WidgetDefinition as aB, WorkflowDesignerRouteComponent as aC, WorkflowRunnerComponent as aD, WuicAziendaSwitcherComponent as aE, WuicErrorDialogComponent as aF, WuicErrorInterceptor as aG, WuicFeatureDirective as aH, WuicFrozenColumnDirective as aI, WuicRagChatbotComponent as aJ, WuicRagChatbotFabComponent as aK, WuicRagService as aL, WuicRowTogglerDirective as aM, _PRIMENG_TABLE_DIRECTIVES_NAMES as aN, authExpiredInterceptor as aO, boundedRepeaterPendingChangesGuard as aP, featureRouteCanActivateGuard as aQ, featureRouteCanMatchGuard as aR, getThemePrimaryHex as aS, inferTemplateFieldFromBaseClass as aT, loadBooleanEditorComponent as aU, loadButtonEditorComponent as aV, loadCodeAreaEditorComponent as aW, loadColorEditorComponent as aX, loadDateEditorComponent as aY, loadDictionaryEditorComponent as aZ, loadHtmlEditorComponent as a_, LazyMetaMenuComponent as aa, LazyPivotBuilderComponent as ab, LazyReportDesignerComponent as ac, LazyReportViewerComponent as ad, LazySchedulerListComponent as ae, LazySpreadsheetListSfComponent as af, ListGridComponent as ag, LookupEditorComponent as ah, MapListComponent as ai, MetaMenuComponent as aj, MetadatiTabella as ak, NotificationBellComponent as al, NotificationRealtimeService as am, NumberEditorComponent as an, PRIMARY_PALETTES as ao, PropertyArrayEditorComponent as ap, PropertyObjectEditorComponent as aq, THEME_OPTIONS as ar, TextAreaEditorComponent as as, TextEditorComponent as at, TreeListComponent as au, TreeViewSelectorComponent as av, UploadDialogComponent as aw, UploadEditorComponent as ax, ValidationRule as ay, ValueChangedPayload as az, DataSourceComponent as b, loadNumberEditorComponent as b0, loadPropertyArrayEditorComponent as b1, loadPropertyObjectEditorComponent as b2, loadTextAreaEditorComponent as b3, loadTextEditorComponent as b4, loadTreeViewSelectorComponent as b5, loadUploadEditorComponent as b6, menuRouteAccessCanActivateGuard as b7, menuRouteAccessCanMatchGuard as b8, routes as b9, wuicErrorInterceptor as ba, 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, AuthSessionService as n, FilterInfo as o, LicenseFeatureService as p, FilterItem as q, getThemeOptions as r, AUTH_REDIRECT_REASON_KEY as s, AppSettingsEditorComponent as t, BoundedRepeaterComponent as u, ButtonEditorComponent as v, CallbackPipe2 as w, CarouselListComponent as x, CodeAreaEditorComponent as y, ColorEditorComponent as z };
62349
- //# sourceMappingURL=wuic-framework-lib-wuic-framework-lib-BSyGhpM1.mjs.map
64444
+ export { ImageWrapperComponent as $, ArchetypeConfiguratorComponent as A, BooleanEditorComponent as B, ChatbotHostRegistryService as C, DynamicGenericTemplateComponent as D, CodeAreaEditorComponent as E, FilterBarComponent as F, GlobalHandler as G, ColorEditorComponent as H, ImportExportButtonComponent as I, CrashReporterService as J, CustomAttributesService as K, LazyParametricDialogComponent as L, MetadataProviderService as M, CustomException as N, DataActionButtonComponent as O, PagerComponent as P, DataProviderOdataService as Q, DateEditorComponent as R, DesignerRouteComponent as S, TranslationManagerService as T, UserInfoService as U, DictionaryEditorComponent as V, WtoolboxService as W, DynamicRowTemplateComponent as X, FormatGridViewValuePipe as Y, GetSrcUploadPreviewPipe as Z, HtmlEditorComponent as _, DynamicCompilerService as a, loadDictionaryEditorComponent as a$, IsSelectedRowPipe as a0, KanbanListComponent as a1, KanbanOptions as a2, KanbanStatusColumnItem as a3, LazyChartListComponent as a4, LazyCodeEditorComponent as a5, LazyDataActionButtonComponent as a6, LazyDataSourceComponent as a7, LazyFieldEditorComponent as a8, LazyFirstRunWizardComponent as a9, ValidationRule as aA, ValueChangedPayload as aB, VisibleFieldListPipe as aC, WidgetDefinition as aD, WorkflowDesignerRouteComponent as aE, WorkflowRunnerComponent as aF, WuicAziendaSwitcherComponent as aG, WuicErrorDialogComponent as aH, WuicErrorInterceptor as aI, WuicFeatureDirective as aJ, WuicFrozenColumnDirective as aK, WuicRagChatbotComponent as aL, WuicRagChatbotFabComponent as aM, WuicRagService as aN, WuicRowTogglerDirective as aO, _PRIMENG_TABLE_DIRECTIVES_NAMES as aP, authExpiredInterceptor as aQ, boundedRepeaterPendingChangesGuard as aR, featureRouteCanActivateGuard as aS, featureRouteCanMatchGuard as aT, getThemePrimaryHex as aU, inferTemplateFieldFromBaseClass as aV, loadBooleanEditorComponent as aW, loadButtonEditorComponent as aX, loadCodeAreaEditorComponent as aY, loadColorEditorComponent as aZ, loadDateEditorComponent as a_, LazyImageWrapperComponent as aa, LazyKanbanListComponent as ab, LazyMetaMenuComponent as ac, LazyPivotBuilderComponent as ad, LazyReportDesignerComponent as ae, LazyReportViewerComponent as af, LazySchedulerListComponent as ag, LazySpreadsheetListSfComponent as ah, ListGridComponent as ai, LookupEditorComponent as aj, MapListComponent as ak, MetaMenuComponent as al, MetadatiTabella as am, NotificationBellComponent as an, NotificationRealtimeService as ao, NumberEditorComponent as ap, PRIMARY_PALETTES as aq, PropertyArrayEditorComponent as ar, PropertyObjectEditorComponent as as, THEME_OPTIONS as at, TextAreaEditorComponent as au, TextEditorComponent as av, TreeListComponent as aw, TreeViewSelectorComponent as ax, UploadDialogComponent as ay, UploadEditorComponent as az, DataSourceComponent as b, loadHtmlEditorComponent as b0, loadLookupEditorComponent as b1, loadNumberEditorComponent as b2, loadPropertyArrayEditorComponent as b3, loadPropertyObjectEditorComponent as b4, loadTextAreaEditorComponent as b5, loadTextEditorComponent as b6, loadTreeViewSelectorComponent as b7, loadUploadEditorComponent as b8, menuRouteAccessCanActivateGuard as b9, menuRouteAccessCanMatchGuard as ba, routes as bb, wuicErrorInterceptor as bc, 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, WUIC_SUPPRESS_GLOBAL_ERROR as n, FilterInfo as o, LicenseFeatureService as p, FilterItem as q, getThemeOptions as r, AUTH_REDIRECT_REASON_KEY as s, AppSettingsEditorComponent as t, AuthSessionService as u, BoundedRepeaterComponent as v, ButtonEditorComponent as w, CallbackPipe as x, CallbackPipe2 as y, CarouselListComponent as z };
64445
+ //# sourceMappingURL=wuic-framework-lib-wuic-framework-lib-BRujEwpR.mjs.map