wuic-framework-lib 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (16) hide show
  1. package/fesm2022/{wuic-framework-lib-chart-list.component-B1V8Q-Qq.mjs → wuic-framework-lib-chart-list.component-C02jeFGy.mjs} +2 -2
  2. package/fesm2022/{wuic-framework-lib-code-editor.component-4N95qt3D.mjs → wuic-framework-lib-code-editor.component-BZNLr0Jj.mjs} +59 -2
  3. package/fesm2022/{wuic-framework-lib-designer.component-pW1YK-oY.mjs → wuic-framework-lib-designer.component-N_CfbGnr.mjs} +986 -17
  4. package/fesm2022/{wuic-framework-lib-first-run-wizard.component-CaGePYLi.mjs → wuic-framework-lib-first-run-wizard.component-lW7-xMm3.mjs} +7 -61
  5. package/fesm2022/{wuic-framework-lib-framework-docs.component-m0IlS0Sl.mjs → wuic-framework-lib-framework-docs.component-CGQbmSkv.mjs} +3716 -347
  6. package/fesm2022/{wuic-framework-lib-pivot-builder.component-VIs0CIH_.mjs → wuic-framework-lib-pivot-builder.component-0gJz9RWr.mjs} +5 -5
  7. package/fesm2022/{wuic-framework-lib-prompt-dialog-fallback-host.component-Dm9GTZ07.mjs → wuic-framework-lib-prompt-dialog-fallback-host.component-C9LvmFsL.mjs} +2 -2
  8. package/fesm2022/{wuic-framework-lib-report-designer.component-FiAwNkPM.mjs → wuic-framework-lib-report-designer.component-DcpdzFjh.mjs} +2 -2
  9. package/fesm2022/{wuic-framework-lib-report-viewer.component-DY0dGMrt.mjs → wuic-framework-lib-report-viewer.component-BHpbGpp9.mjs} +2 -2
  10. package/fesm2022/{wuic-framework-lib-scheduler-list.component-3E5m-9Qd.mjs → wuic-framework-lib-scheduler-list.component-DHoukjf-.mjs} +2 -2
  11. package/fesm2022/{wuic-framework-lib-spreadsheet-list-sf.component-D9P9WbDl.mjs → wuic-framework-lib-spreadsheet-list-sf.component-VsaXepUC.mjs} +2 -2
  12. package/fesm2022/{wuic-framework-lib-workflow-designer.component-4RC2EkGA.mjs → wuic-framework-lib-workflow-designer.component-epj3oKq3.mjs} +2 -2
  13. package/fesm2022/{wuic-framework-lib-wuic-framework-lib-BSyGhpM1.mjs → wuic-framework-lib-wuic-framework-lib-Deudd4MY.mjs} +2186 -144
  14. package/fesm2022/wuic-framework-lib.mjs +1 -1
  15. package/package.json +1 -1
  16. package/types/wuic-framework-lib.d.ts +829 -11
@@ -50,7 +50,7 @@ import { Title } from '@angular/platform-browser';
50
50
  import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'primeng/tabs';
51
51
  import * as i5$1 from 'primeng/menubar';
52
52
  import { MenubarModule } from 'primeng/menubar';
53
- import { distinctUntilChanged, catchError as catchError$1 } from 'rxjs/operators';
53
+ import { distinctUntilChanged, filter, catchError as catchError$1 } from 'rxjs/operators';
54
54
  import * as i11 from 'primeng/progressbar';
55
55
  import { ProgressBarModule } from 'primeng/progressbar';
56
56
  import { MarkerClusterer } from '@googlemaps/markerclusterer';
@@ -90,9 +90,9 @@ import * as i4$3 from 'primeng/popover';
90
90
  import { PopoverModule } from 'primeng/popover';
91
91
  import * as i5$4 from '@angular/cdk/drag-drop';
92
92
  import { moveItemInArray, transferArrayItem, DragDropModule } from '@angular/cdk/drag-drop';
93
- import * as 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-C9LvmFsL.mjs');
2711
2711
  const hostRef = createComponent(PromptDialogFallbackHostComponent, { environmentInjector });
2712
2712
  hostRef.instance.datasource = dialogConfig?.data?.datasource;
2713
2713
  hostRef.instance.header = String(dialogConfig?.header || '');
@@ -7283,6 +7283,83 @@ class MetadataProviderService {
7283
7283
  clearMenuAdminMethodsCache(userId) {
7284
7284
  localStorage.removeItem(this.getMenuAdminMethodsCacheKey(userId));
7285
7285
  }
7286
+ /**
7287
+ * Cancella dalla Dexie locale SOLO i record collegati alla route data, lasciando
7288
+ * intatto il resto del database multi-tenant. Necessario dopo che un'operazione
7289
+ * server-side (es. RAG chatbot Apply, metadata-editor save) modifica i metadati
7290
+ * di una singola route senza invalidare l'intera cache: chiamare
7291
+ * <c>Dexie.delete()</c> per tutto il DB causa una riapertura completa al primo
7292
+ * F5 (costosa: ~MB di re-fetch dal backend per le route che non hanno cambiato);
7293
+ * con questo metodo si esegue un flush mirato sulle 7 tabelle Dexie correlate.
7294
+ *
7295
+ * Tabelle gestite (vedi <c>generateLocalDB</c>):
7296
+ * - MetadatiTabella (filter: md_route_name)
7297
+ * - MetadatiColonna (filter: md_id derivato)
7298
+ * - MetadatiCustomActionTabella (filter: md_id)
7299
+ * - MetadatiUiStiliTabella (filter: md_id)
7300
+ * - MetadatiUiStiliColonna (filter: mc_id ∈ colonne della route)
7301
+ * - MetadatiUtentiAutorizzazioniColonna (filter: mc_id ∈ colonne della route)
7302
+ * - MetadatiConditionGroup (filter: md_id)
7303
+ *
7304
+ * No-op se: route vuota, Dexie non inizializzata, o la route non e' presente in
7305
+ * cache (gia' "lazy" - verra' fetched dal backend al primo accesso).
7306
+ * Best-effort: errori dei singoli delete non bloccano gli altri.
7307
+ *
7308
+ * @param route Nome route metadata (es. "cities", "stockitems") — corrisponde
7309
+ * a <c>MetadatiTabella.md_route_name</c>.
7310
+ */
7311
+ static async deleteRouteFromCache(route) {
7312
+ if (!route || typeof route !== 'string')
7313
+ return;
7314
+ const db = MetadataProviderService.MetaDB;
7315
+ if (!db || typeof db.table !== 'function')
7316
+ return;
7317
+ const safe = async (fn) => { try {
7318
+ await fn();
7319
+ }
7320
+ catch { /* best-effort */ } };
7321
+ // Step 1: trova md_id della route via MetadatiTabella (l'indice
7322
+ // [md_id+__user_id] e' composito; cerchiamo direttamente per md_route_name).
7323
+ let mdIds = [];
7324
+ try {
7325
+ const rows = await db.table('MetadatiTabella').where('md_route_name').equals(route).toArray();
7326
+ mdIds = (rows || []).map((r) => r?.md_id).filter((x) => x !== undefined && x !== null);
7327
+ }
7328
+ catch {
7329
+ return; /* tabella non presente o store mancante */
7330
+ }
7331
+ if (mdIds.length === 0)
7332
+ return; // route non in cache locale
7333
+ // Step 2: trova i mc_id (colonne) appartenenti a quegli md_id PRIMA di
7334
+ // cancellarli, cosi' possiamo flushare in cascata stili/autorizzazioni
7335
+ // colonna che hanno solo mc_id come FK.
7336
+ let mcIds = [];
7337
+ try {
7338
+ const cols = await db.table('MetadatiColonna').where('md_id').anyOf(mdIds).toArray();
7339
+ mcIds = (cols || []).map((c) => c?.mc_id).filter((x) => x !== undefined && x !== null);
7340
+ }
7341
+ catch { }
7342
+ // Step 3: trova i CG_Id (condition groups) per cancellare in cascata anche
7343
+ // MetadatiConditionGroupAction (che ha FK_CG_Id come FK).
7344
+ let cgIds = [];
7345
+ try {
7346
+ const cgs = await db.table('MetadatiConditionGroup').where('md_id').anyOf(mdIds).toArray();
7347
+ cgIds = (cgs || []).map((g) => g?.CG_Id).filter((x) => x !== undefined && x !== null);
7348
+ }
7349
+ catch { }
7350
+ // Step 4: cancella tutte e 8 le tabelle in parallelo (le primary key sono
7351
+ // disgiunte tra le tabelle, niente race condition).
7352
+ await Promise.all([
7353
+ safe(() => db.table('MetadatiTabella').where('md_route_name').equals(route).delete()),
7354
+ safe(() => db.table('MetadatiColonna').where('md_id').anyOf(mdIds).delete()),
7355
+ safe(() => db.table('MetadatiCustomActionTabella').where('md_id').anyOf(mdIds).delete()),
7356
+ safe(() => db.table('MetadatiUiStiliTabella').where('md_id').anyOf(mdIds).delete()),
7357
+ safe(() => mcIds.length ? db.table('MetadatiUiStiliColonna').where('mc_id').anyOf(mcIds).delete() : Promise.resolve()),
7358
+ safe(() => mcIds.length ? db.table('MetadatiUtentiAutorizzazioniColonna').where('mc_id').anyOf(mcIds).delete() : Promise.resolve()),
7359
+ safe(() => db.table('MetadatiConditionGroup').where('md_id').anyOf(mdIds).delete()),
7360
+ safe(() => cgIds.length ? db.table('MetadatiConditionGroupAction').where('FK_CG_Id').anyOf(cgIds).delete() : Promise.resolve()),
7361
+ ]);
7362
+ }
7286
7363
  /**
7287
7364
  * Invalida la cache menu dell'utente corrente (o dell'utente passato) rimuovendo la voce `menu_{userId}` da localStorage.
7288
7365
  * Se richiesto elimina anche la cache dei metodi amministrativi `menu_admin_methods_{userId}`.
@@ -7377,6 +7454,93 @@ class MetadataProviderService {
7377
7454
  async getMetadatiById(md_id) {
7378
7455
  return await this.getMetadati('', md_id);
7379
7456
  }
7457
+ // Cache process-wide della lista route metadata non-system disponibili
7458
+ // all'utente corrente. Server-side e' gia' cached in
7459
+ // `HttpContext.Current.Application["availableRoutes"]` (vedi
7460
+ // _Metadati_suggestions_base.cs:1641), qui evitiamo round-trip HTTP
7461
+ // ripetuti durante la sessione (la lista cambia raramente: solo dopo
7462
+ // crea/elimina route in scaffolding, scenari controllati).
7463
+ //
7464
+ // La cache e' shared per istanza di MetadataProviderService (Angular
7465
+ // providedIn:'root' singleton). Per forzare refresh dopo scaffolding,
7466
+ // chiamare `clearAvailableRoutesCache()`.
7467
+ static _availableRoutesCache = null;
7468
+ static _availableRoutesInflight = null;
7469
+ /**
7470
+ * Lista delle route metadata utente (md_route_name + md_display_string).
7471
+ * Backend: `MetaService.getAvailableRoutes` (admin-only, cached server-side).
7472
+ *
7473
+ * Idempotente + de-duplicato in-flight: chiamate concorrenti condividono
7474
+ * la stessa Promise per evitare race.
7475
+ *
7476
+ * Usata dal chatbot RAG (designer state provider) per:
7477
+ * 1) fornire al LLM la whitelist di route valide,
7478
+ * 2) permettere fuzzy-match semantico ("provincie" -> "provinces"),
7479
+ * 3) fare follow-up question quando l'utente menziona una route ambigua.
7480
+ */
7481
+ async getAvailableRoutesAsync() {
7482
+ if (MetadataProviderService._availableRoutesCache) {
7483
+ return MetadataProviderService._availableRoutesCache;
7484
+ }
7485
+ if (MetadataProviderService._availableRoutesInflight) {
7486
+ return MetadataProviderService._availableRoutesInflight;
7487
+ }
7488
+ // URL canonico hardcoded (no concat con global_root_url che varia tra
7489
+ // dev/prod e non sempre include il prefisso AsmxProxy). Stesso pattern
7490
+ // di spreadsheet-list-sf.component.ts:3684.
7491
+ const url = '/api/Meta/AsmxProxy/MetaService.getAvailableRoutes';
7492
+ // user_id e' parametro legacy ignorato dal backend (usa sempre
7493
+ // l'utente autenticato via cookie k-user). Lo passiamo per
7494
+ // compatibilita' di schema ma puo' anche essere stringa vuota.
7495
+ const userId = String(this.userInfo.getuserInfo()?.user_id ?? '');
7496
+ MetadataProviderService._availableRoutesInflight = new Promise((resolve) => {
7497
+ this.http.post(url, { user_id: userId }).subscribe({
7498
+ next: (resp) => {
7499
+ // Il backend ritorna `{ results: [...], TotalRecords: N }` (rawPagedResult).
7500
+ // serialize via RawHelpers.serialize ritorna JSON come stringa, ma il
7501
+ // controller AsmxProxy fa il parse — quindi qui arriva oggetto.
7502
+ let raw = resp;
7503
+ if (typeof raw === 'string') {
7504
+ try {
7505
+ raw = JSON.parse(raw);
7506
+ }
7507
+ catch {
7508
+ raw = null;
7509
+ }
7510
+ }
7511
+ const list = Array.isArray(raw?.results) ? raw.results : (Array.isArray(raw) ? raw : []);
7512
+ const normalized = list
7513
+ .map((r) => ({
7514
+ md_id: Number(r?.md_id ?? 0),
7515
+ md_route_name: String(r?.md_route_name ?? '').trim(),
7516
+ md_display_string: String(r?.md_display_string ?? '').trim(),
7517
+ }))
7518
+ .filter((r) => r.md_route_name.length > 0);
7519
+ MetadataProviderService._availableRoutesCache = normalized;
7520
+ MetadataProviderService._availableRoutesInflight = null;
7521
+ resolve(normalized);
7522
+ },
7523
+ error: () => {
7524
+ // Best-effort: in caso di errore (utente non admin, backend giu',
7525
+ // ecc.) ritorna lista vuota. Il chiamante (state provider chatbot)
7526
+ // gestira' graceful con messaggio "lista non disponibile".
7527
+ MetadataProviderService._availableRoutesInflight = null;
7528
+ resolve([]);
7529
+ }
7530
+ });
7531
+ });
7532
+ return MetadataProviderService._availableRoutesInflight;
7533
+ }
7534
+ /**
7535
+ * Invalida la cache process-wide delle route disponibili. Chiamare dopo
7536
+ * scaffolding (crea/elimina route) per forzare un re-fetch al prossimo
7537
+ * accesso. La cache server-side va invalidata separatamente (boot di
7538
+ * un nuovo worker o gesture esplicita).
7539
+ */
7540
+ clearAvailableRoutesCache() {
7541
+ MetadataProviderService._availableRoutesCache = null;
7542
+ MetadataProviderService._availableRoutesInflight = null;
7543
+ }
7380
7544
  /**
7381
7545
  * Restituisce i metadata di una route (o `md_id`) da cache Dexie quando validi; in caso contrario ricarica da backend.
7382
7546
  * Gestisce invalidazione per versioning metadata progetto e bypass cache in modalita client-side CRUD.
@@ -7949,11 +8113,14 @@ class MetadataProviderService {
7949
8113
  const resolve = (doSync) => beforeSync(doSync);
7950
8114
  const prevent = () => beforeSync(false);
7951
8115
  const record = savingData;
8116
+ const metaInfo = dataSource.metaInfo;
7952
8117
  ${table_metadata.md_before_save}
7953
8118
  `) : async () => true;
7954
8119
  table_metadata.md_after_save_fn = table_metadata.md_after_save ? new AsyncFunction('dataSource, savingData, syncedData, isInsert, isClone, isDelete, event, wtoolbox', `
7955
8120
  const record = savingData;
7956
8121
  const result = syncedData;
8122
+ const metaInfo = dataSource.metaInfo;
8123
+
7957
8124
  ${table_metadata.md_after_save}
7958
8125
  `) : async () => { };
7959
8126
  table_metadata.md_after_load_fn = table_metadata.md_after_load ? new AsyncFunction('dataSource, originalEvent, result, loadedRecords, isInsert, wtoolbox', `
@@ -9564,6 +9731,77 @@ class UpdateInfo {
9564
9731
  }
9565
9732
  }
9566
9733
 
9734
+ /**
9735
+ * Helper centralizzato per ripulire `localStorage` post-save metadata
9736
+ * preservando le chiavi runtime user-state che NON dipendono dai metadati.
9737
+ *
9738
+ * Background: il framework, dopo un save metadata editor, fa `localStorage.clear()`
9739
+ * per invalidare le cache (schema, lookup, ecc.). Senza whitelist, ogni save
9740
+ * azzera anche stato che dovrebbe sopravvivere fino al logout, degradando UX:
9741
+ *
9742
+ * - `wuic_rag_last_session_id` (chatbot RAG): id della sessione corrente. Senza
9743
+ * preservarlo, ogni modifica metadata fa perdere la cronologia chat (anche
9744
+ * se i messaggi sono salvati in DB).
9745
+ * - `wuic-selected-language` / `wuic-selected-theme` / `wuic-theme-mode`:
9746
+ * preferenze UI utente — il cambio metadati NON dovrebbe resettarle.
9747
+ * - `translation` / `translation_<locale>`: cache traduzioni; senza, le label
9748
+ * tornano alle chiavi raw per ~500ms-2s.
9749
+ * - `wuic_auth_redirect_reason`: marker auth flow.
9750
+ *
9751
+ * Per aggiungere altre chiavi protette: estendere `LOCALSTORAGE_PRESERVE_KEYS`
9752
+ * (match esatto) o `LOCALSTORAGE_PRESERVE_PREFIXES` (match per prefisso).
9753
+ *
9754
+ * **Logout**: `auth-session.service.ts:clearBrowserStorage` fa `localStorage.clear()`
9755
+ * DIRETTO (senza whitelist) — by design. Il logout deve rimuovere tutto,
9756
+ * incluse le chiavi protette qui.
9757
+ */
9758
+ /** Chiavi esatte da preservare (match `===`). */
9759
+ const LOCALSTORAGE_PRESERVE_KEYS = [
9760
+ 'translation',
9761
+ 'wuic_rag_last_session_id',
9762
+ 'wuic-selected-language',
9763
+ 'wuic-selected-theme',
9764
+ 'wuic-theme-mode',
9765
+ 'wuic_auth_redirect_reason',
9766
+ ];
9767
+ /** Prefissi da preservare (match `startsWith`). */
9768
+ const LOCALSTORAGE_PRESERVE_PREFIXES = [
9769
+ 'translation_', // cache traduzioni per locale (it-IT, en-US, ...)
9770
+ ];
9771
+ /**
9772
+ * Equivalente di `localStorage.clear()` ma rispetta la whitelist sopra.
9773
+ * Idempotente, no-op in private mode / storage non disponibile.
9774
+ */
9775
+ function clearLocalStoragePreservingUserState() {
9776
+ try {
9777
+ if (typeof localStorage === 'undefined')
9778
+ return;
9779
+ // Snapshot delle chiavi protette PRIMA del clear (l'iterazione su
9780
+ // localStorage.key(i) viene invalidata dal clear).
9781
+ const preserved = {};
9782
+ for (let i = 0; i < localStorage.length; i++) {
9783
+ const key = localStorage.key(i);
9784
+ if (!key)
9785
+ continue;
9786
+ const exactMatch = LOCALSTORAGE_PRESERVE_KEYS.includes(key);
9787
+ const prefixMatch = !exactMatch
9788
+ && LOCALSTORAGE_PRESERVE_PREFIXES.some((p) => key.startsWith(p));
9789
+ if (exactMatch || prefixMatch) {
9790
+ const val = localStorage.getItem(key);
9791
+ if (val !== null)
9792
+ preserved[key] = val;
9793
+ }
9794
+ }
9795
+ localStorage.clear();
9796
+ for (const [k, v] of Object.entries(preserved)) {
9797
+ localStorage.setItem(k, v);
9798
+ }
9799
+ }
9800
+ catch {
9801
+ /* private mode / quota / disabled storage: best-effort, no-throw */
9802
+ }
9803
+ }
9804
+
9567
9805
  // import { config, EntityManager, EntityQuery } from 'breeze-client';
9568
9806
  // import { OData4DataService, OData4UriBuilder } from 'breeze-odata4';
9569
9807
  class DataProviderOdataService {
@@ -9943,7 +10181,7 @@ class DataProviderOdataService {
9943
10181
  }).toPromise();
9944
10182
  debugger;
9945
10183
  if (MetadataProviderService.isMetaRoute(route)) {
9946
- 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-DHoukjf-.mjs').then((m) => m.SchedulerListComponent);
21200
21667
  this.loadedComponent = await schedulerListComponentPromise;
21201
21668
  this.cdr.markForCheck();
21202
21669
  }
@@ -21404,7 +21871,7 @@ class LazySpreadsheetListSfComponent {
21404
21871
  this.activatedRoute = activatedRoute;
21405
21872
  }
21406
21873
  async ngOnInit() {
21407
- spreadsheetListSfComponentPromise ??= import('./wuic-framework-lib-spreadsheet-list-sf.component-D9P9WbDl.mjs').then((m) => m.SpreadsheetListSfComponent);
21874
+ spreadsheetListSfComponentPromise ??= import('./wuic-framework-lib-spreadsheet-list-sf.component-VsaXepUC.mjs').then((m) => m.SpreadsheetListSfComponent);
21408
21875
  this.loadedComponent = await spreadsheetListSfComponentPromise;
21409
21876
  // Quando la promise e' gia' risolta dal cache module-scope (secondo mount
21410
21877
  // post-refresh), il microtask che risolve `await` non scatena sempre CD
@@ -21682,7 +22149,7 @@ class LazyChartListComponent {
21682
22149
  * Inizializza il componente preparando stato, sottoscrizioni e primi caricamenti richiesti.
21683
22150
  */
21684
22151
  async ngOnInit() {
21685
- chartListComponentPromise ??= import('./wuic-framework-lib-chart-list.component-B1V8Q-Qq.mjs').then((m) => m.ChartListComponent);
22152
+ chartListComponentPromise ??= import('./wuic-framework-lib-chart-list.component-C02jeFGy.mjs').then((m) => m.ChartListComponent);
21686
22153
  this.loadedComponent = await chartListComponentPromise;
21687
22154
  this.cdr.markForCheck();
21688
22155
  }
@@ -31397,6 +31864,22 @@ class ListGridComponent {
31397
31864
  releaseGridStateUrlPushTimer;
31398
31865
  navigationTriggeredByPopstate = false;
31399
31866
  suppressNextPageOnlyQuerySync = false;
31867
+ // PrimeNG p-table emette per ogni sort/filter/paging DUE (a volte 3)
31868
+ // eventi consecutivi (separati da ~30-60ms, NON nello stesso microtask).
31869
+ // Il fetchData a runtime fa 1 round-trip server completo (GetFlatData su
31870
+ // tabelle 200k+ righe -> facilmente >1s). I duplicati raddoppiano il
31871
+ // carico DB.
31872
+ //
31873
+ // Strategia in 2 layer:
31874
+ // 1. setTimeout(0) coalesce ravvicinato (rapid-fire stesso microtask)
31875
+ // 2. dedup-by-payload-hash (skip se sortInfo+pageInfo+filterInfo
31876
+ // identici al last-fetched). Cattura il pattern p-table dove gli
31877
+ // eventi sono separati da 30-60ms ma producono payload identico.
31878
+ // Verificato live 2026-06-04 via MCP Chrome (Order Lines / 231k righe,
31879
+ // sort su OrderID -> 2 POST identici a 39ms di distanza).
31880
+ pendingFilterChangeEvent = null;
31881
+ pendingFilterChangeTimer = null;
31882
+ lastFetchedPayloadHash = null;
31400
31883
  clientSideCrudToggleBusy = false;
31401
31884
  persistedColumnWidthsByRoute = {};
31402
31885
  persistedColumnLayoutByRoute = {};
@@ -32220,7 +32703,7 @@ class ListGridComponent {
32220
32703
  </div>
32221
32704
  <div class="wuic-mobile-card-body">
32222
32705
  <ng-container *ngFor="let col of columns | visibleFieldList">
32223
- <div class="wuic-mobile-card-row" [ngClass]="getCellClasses(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)">
32706
+ <div class="wuic-mobile-card-row" [ngClass]="getCellClasses(col.metaColumn, rowData)" [attr.style]="getCellInlineStyle(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)">
32224
32707
  <div class="wuic-mobile-card-label">{{ col.header }}</div>
32225
32708
  <div class="wuic-mobile-card-value">
32226
32709
  <ng-container [ngSwitch]="col.metaColumn.mc_nome_colonna">
@@ -32282,7 +32765,7 @@ class ListGridComponent {
32282
32765
  </div>
32283
32766
  <div class="wuic-mobile-card-body">
32284
32767
  <ng-container *ngFor="let col of columns | visibleFieldList">
32285
- <div class="wuic-mobile-card-row" [ngClass]="getCellClasses(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)">
32768
+ <div class="wuic-mobile-card-row" [ngClass]="getCellClasses(col.metaColumn, rowData)" [attr.style]="getCellInlineStyle(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)">
32286
32769
  <div class="wuic-mobile-card-label">{{ col.header }}</div>
32287
32770
  <div class="wuic-mobile-card-value">
32288
32771
  <ng-container [ngSwitch]="col.metaColumn.mc_nome_colonna">
@@ -32337,7 +32820,7 @@ class ListGridComponent {
32337
32820
  </ng-container>
32338
32821
  </td>
32339
32822
  <ng-container *ngFor="let col of columns | visibleFieldList">
32340
- <td *ngIf="!col?.frozen" [ngClass]="getCellClasses(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)" (focusout)="onCellFocusOut($event, rowData, col.metaColumn)">
32823
+ <td *ngIf="!col?.frozen" [ngClass]="getCellClasses(col.metaColumn, rowData)" [attr.style]="getCellInlineStyle(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)" (focusout)="onCellFocusOut($event, rowData, col.metaColumn)">
32341
32824
  <wuic-field-editor-lazy *ngIf="rowData.__is_editing && !col.metaColumn.mc_hide_in_edit" [record]="rowData.__observable" [field]="getRuntimeGridFieldMeta(col.metaColumn, rowData)" [metaInfo]="metaInfo" [datasource]="datasource" [hideLabel]="metaInfo.tableMetadata.md_inline_edit" [onInlineCellValueChange]="onInlineCellEditorValueChange"></wuic-field-editor-lazy>
32342
32825
  <ng-container *ngIf="!rowData.__is_editing">
32343
32826
  <ng-container [ngSwitch]="col.metaColumn.mc_nome_colonna">
@@ -32358,7 +32841,7 @@ class ListGridComponent {
32358
32841
  </ng-container>
32359
32842
  </ng-container>
32360
32843
  </td>
32361
- <td *ngIf="col?.frozen" [wuicFrozenColumn]="true" [alignFrozen]="col?.alignFrozen || 'left'" [ngClass]="getCellClasses(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)" (focusout)="onCellFocusOut($event, rowData, col.metaColumn)">
32844
+ <td *ngIf="col?.frozen" [wuicFrozenColumn]="true" [alignFrozen]="col?.alignFrozen || 'left'" [ngClass]="getCellClasses(col.metaColumn, rowData)" [attr.style]="getCellInlineStyle(col.metaColumn, rowData)" (click)="onRowSelect($event, rowData)" (focusout)="onCellFocusOut($event, rowData, col.metaColumn)">
32362
32845
  <wuic-field-editor-lazy *ngIf="rowData.__is_editing && !col.metaColumn.mc_hide_in_edit" [record]="rowData.__observable" [field]="getRuntimeGridFieldMeta(col.metaColumn, rowData)" [metaInfo]="metaInfo" [datasource]="datasource" [hideLabel]="metaInfo.tableMetadata.md_inline_edit" [onInlineCellValueChange]="onInlineCellEditorValueChange"></wuic-field-editor-lazy>
32363
32846
  <ng-container *ngIf="!rowData.__is_editing">
32364
32847
  <ng-container [ngSwitch]="col.metaColumn.mc_nome_colonna">
@@ -33281,6 +33764,32 @@ class ListGridComponent {
33281
33764
  async pageFilterChange(event) {
33282
33765
  // Applying a saved state updates sort/filter bindings programmatically.
33283
33766
  // Ignore transient table events to avoid duplicate fetches.
33767
+ if (this.applyingSavedState) {
33768
+ return;
33769
+ }
33770
+ // Coalesce rapid-fire events (sort emette 2-3 eventi in 0ms):
33771
+ // sovrascrivi il queued event e schedula un solo processing al next tick.
33772
+ // Eventi cross-tick legittimi (sort poi paging dopo click utente) sono
33773
+ // separati da almeno N ms del browser idle -> ognuno schedula proprio.
33774
+ this.pendingFilterChangeEvent = event;
33775
+ if (this.pendingFilterChangeTimer !== null) {
33776
+ return; // gia' schedulato, l'ultimo evento sara' processato
33777
+ }
33778
+ this.pendingFilterChangeTimer = setTimeout(() => {
33779
+ const ev = this.pendingFilterChangeEvent;
33780
+ this.pendingFilterChangeEvent = null;
33781
+ this.pendingFilterChangeTimer = null;
33782
+ if (ev) {
33783
+ void this._processPageFilterEvent(ev);
33784
+ }
33785
+ }, 0);
33786
+ }
33787
+ /**
33788
+ * Processore reale dell'evento PrimeNG (sort/filter/paging). Chiamato
33789
+ * tail-call da `pageFilterChange` via setTimeout(0) per coalesce.
33790
+ * NON chiamare direttamente: la coalesce e' la guard anti-duplicate-fetch.
33791
+ */
33792
+ async _processPageFilterEvent(event) {
33284
33793
  if (this.applyingSavedState) {
33285
33794
  return;
33286
33795
  }
@@ -33411,6 +33920,22 @@ class ListGridComponent {
33411
33920
  else {
33412
33921
  this.datasource.value.sortInfo = [];
33413
33922
  }
33923
+ // Dedup-by-payload-hash: PrimeNG emette spesso DUE eventi consecutivi
33924
+ // (sort + page-only follow-up) separati da ~30-60ms con payload
33925
+ // IDENTICO. Skip il fetch se sortInfo+pageInfo+filterInfo coincidono
33926
+ // col last-fetched. Verificato live 2026-06-04 via MCP Chrome.
33927
+ try {
33928
+ const payloadKey = JSON.stringify({
33929
+ s: this.datasource.value.sortInfo || [],
33930
+ p: { c: this.pageIndex, sz: this.pageSize },
33931
+ f: this.datasource.value.filterInfo || null,
33932
+ });
33933
+ if (payloadKey === this.lastFetchedPayloadHash) {
33934
+ return; // identico al fetch precedente: salta
33935
+ }
33936
+ this.lastFetchedPayloadHash = payloadKey;
33937
+ }
33938
+ catch { /* hash non bloccante: meglio re-fetch che bloccare */ }
33414
33939
  await this.datasource.value.fetchData();
33415
33940
  this.scheduleGridViewportHeightSync();
33416
33941
  // Emit @Output events DOPO fetchData: a questo punto TUTTO lo stato
@@ -37466,11 +37991,11 @@ class ListGridComponent {
37466
37991
  return Number.isFinite(width) ? width : NaN;
37467
37992
  }
37468
37993
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ListGridComponent, deps: [{ token: MetadataProviderService }, { token: i1$5.DomSanitizer }, { token: i1$2.ActivatedRoute }, { token: i1$2.Router }, { token: i1$3.Location }, { token: i1$5.Title }, { token: TranslationManagerService }, { token: i0.ChangeDetectorRef }, { token: UserInfoService }, { token: i0.ElementRef }, { token: DeviceAwarenessService }, { token: CustomAttributesService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
37469
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ListGridComponent, isStandalone: true, selector: "wuic-list-grid", inputs: { hardcodedRoute: "hardcodedRoute", parentRecord: "parentRecord", parentMetaInfo: "parentMetaInfo", datasource: "datasource", hardcodedDatasource: "hardcodedDatasource", rowCustomSelect: "rowCustomSelect", hideToolbar: "hideToolbar" }, outputs: { onBeforeRowRender: "onBeforeRowRender", onAfterRowRender: "onAfterRowRender", onAfterRender: "onAfterRender", onPaging: "onPaging", onSorting: "onSorting", onFiltering: "onFiltering", onRefresh: "onRefresh", onPTableSelectionChange: "onPTableSelectionChange", onPTableRowExpand: "onPTableRowExpand", onPTableRowCollapse: "onPTableRowCollapse", onPTableColumnResize: "onPTableColumnResize", onPTableColumnReorder: "onPTableColumnReorder" }, host: { listeners: { "window:resize": "onWindowResizeForGridLayout()" } }, providers: [Title], viewQueries: [{ propertyName: "table", first: true, predicate: ["dt1"], descendants: true }, { propertyName: "columnCtxMenu", first: true, predicate: ["columnCtxMenu"], descendants: true }, { propertyName: "metadataColumnsDatasource", first: true, predicate: ["metadataColumnsDatasource"], descendants: true }, { propertyName: "gridContainerRef", first: true, predicate: ["gridContainer"], descendants: true }, { propertyName: "tableActionsMenu", first: true, predicate: ["tableActionsMenu"], descendants: true }], ngImport: i0, template: "<wuic-data-source #metadataColumnsDatasource [hardcodedRoute]=\"' metadati colonne'\"\r\n style=\"display: none;\"></wuic-data-source>\r\n<p-contextMenu #columnCtxMenu [model]=\"columnContextMenuItems\" appendTo=\"body\"></p-contextMenu>\r\n\r\n<div class=\"list-grid-container\" #gridContainer\r\n [class.wuic-list-grid-mobile]=\"gridLayoutIsMobile\">\r\n @if (rowTemplate) {\r\n <p-table #dt1 [columns]=\"cols\" [value]=\"records\" [scrollable]=\"true\"\r\n [scrollHeight]=\"metaInfo.tableMetadata.md_grid_scroll_height ? metaInfo.tableMetadata.md_grid_scroll_height : 'flex'\"\r\n [virtualScroll]=\"isListVirtualizationEnabled()\" [virtualScrollItemSize]=\"getListVirtualizationItemSize()\"\r\n [resizableColumns]=\"true\" [reorderableColumns]=\"true\" columnResizeMode=\"expand\" [rows]=\"pageSize\"\r\n [first]=\"rowNumber\" [lazy]=\"metaInfo.tableMetadata.md_server_side_operations\" [sortField]=\"orderColumn || null\"\r\n [sortOrder]=\"orderColumn ? (orderDir === 'desc' ? -1 : 1) : 0\" [(selection)]=\"selectedItems\"\r\n [dataKey]=\"metaInfo.pKey?.mc_nome_colonna\" [paginator]=\"metaInfo.tableMetadata.md_pageable\"\r\n [rowsPerPageOptions]=\"metaInfo.rowsPerPageOptions\" [totalRecords]=\"totalRecords\"\r\n [showPageLinks]=\"!datasource?.value?.cursorMode\" [showFirstLastIcon]=\"!datasource?.value?.cursorMode\"\r\n [showCurrentPageReport]=\"metaInfo.tableMetadata.md_show_record_count && !datasource?.value?.cursorMode\"\r\n [loading]=\"datasource?.value?.loading | async\"\r\n [currentPageReportTemplate]=\"'Showing {first} to {last} of {totalRecords} entries' | translate\"\r\n (onPage)=\"pageFilterChange($event)\" (onFilter)=\"pageFilterChange($event)\" (onSort)=\"pageFilterChange($event)\"\r\n (selectionChange)=\"onSelectionChange($event)\" (onColResize)=\"onColumnResize($event)\"\r\n (onColReorder)=\"onColumnReorder($event)\" [expandedRowKeys]=\"expandedRows\" (onRowExpand)=\"onRowExpand($event)\"\r\n (onRowCollapse)=\"onRowCollapse($event)\" [selectionMode]=\"rowCustomSelect ? 'single' : undefined\"\r\n [tableStyleClass]=\"getTableStyleClass()\">\r\n <ng-template pTemplate=\"colgroup\" let-columns>\r\n <colgroup>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <col class=\"nested-grid-column\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <col class=\"multiple-selection-grid-column\" />\r\n }\r\n @if (showActionColumn()) {\r\n <col class=\"command-grid-column\" />\r\n }\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n <col [style.width]=\"getColumnWidthCss(col)\" [style.min-width]=\"columnMinWidthCss\" />\r\n }\r\n </colgroup>\r\n </ng-template>\r\n <ng-template pTemplate=\"caption\">\r\n @if (!hideToolbar) {\r\n <div class=\"grid-caption-bar\">\r\n <div class=\"grid-caption-left\">\r\n @if (!metaInfo.tableMetadata.md_hide_refresh) {\r\n <p-button icon=\"pi pi-refresh\" label=\"{{'refresh_grid' | translate}}\" (onClick)=\"refresh()\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_insertable) {\r\n <p-button icon=\"pi pi-plus\" label=\"{{'add_grid' | translate}}\" (onClick)=\"addRecord()\" />\r\n }\r\n @if (tableActionMenuItems?.length) {\r\n <p-button icon=\"pi pi-bars\" [label]=\"'actions' | translate\" (onClick)=\"tableActionsMenu.toggle($event)\" />\r\n <p-menu #tableActionsMenu [popup]=\"true\" [model]=\"tableActionMenuItems\" appendTo=\"body\"\r\n (onShow)=\"reEvaluateActionEnabledStates()\"></p-menu>\r\n }\r\n <wuic-import-export-button [routeName]=\"routeName\"\r\n [tableMetadata]=\"metaInfo?.tableMetadata\"\r\n [filterInfoProvider]=\"getCurrentFilterInfoForExport\"></wuic-import-export-button>\r\n @if (reportMenuItems?.length) {\r\n <p-splitButton icon=\"pi pi-chart-bar\" [label]=\"'list_grid.reports' | translate\" [model]=\"reportMenuItems\"\r\n [menuButtonProps]=\"{ ariaLabel: ('list_grid.reports' | translate) }\"\r\n (onClick)=\"reportMenuItems[0]?.command({})\" />\r\n }\r\n @if (canToggleClientSideCrud()) {\r\n @if (!isClientSideCrudActive()) {\r\n <p-button icon=\"pi pi-database\" [label]=\"'client_side_crud_enable' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"toggleClientSideCrud()\" />\r\n } @else {\r\n <p-button icon=\"pi pi-cloud-upload\" [label]=\"'client_side_crud_sync' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"syncAndDisableClientSideCrud()\" />\r\n <p-button icon=\"pi pi-ban\" [label]=\"'client_side_crud_disable_without_sync' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"clientSideCrudToggleBusy\"\r\n (onClick)=\"discardLocalAndDisableClientSideCrud()\" />\r\n }\r\n }\r\n @if (isInlineBatchSaveEnabled() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <p-button icon=\"pi pi-save\" [label]=\"'list_grid.save_changes' | translate\"\r\n [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\" (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button icon=\"pi pi-times\" [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n }\r\n @if (hasDatasourceChanges() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <button type=\"button\" class=\"grid-changes-indicator\"\r\n [title]=\"'Changes in memory: ' + getDatasourceChangesCount()\" (click)=\"openChangesDialog()\">\r\n <i class=\"pi pi-pencil\"></i>\r\n <span class=\"grid-changes-count\">{{ getDatasourceChangesCount() }}</span>\r\n </button>\r\n }\r\n </div>\r\n <div class=\"grid-caption-right\">\r\n @if (!metaInfo?.tableMetadata?.extraProps?.toolbar?.hideManageState) {\r\n @if (currentRouteSavedStates?.length) {\r\n <select class=\"p-inputtext p-component wuic-grid-state-select\" [(ngModel)]=\"selectedSavedStateId\"\r\n (change)=\"onSavedStateDropdownChange($event)\">\r\n <option value=\"\"></option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n }\r\n <p-button [label]=\"'list_grid.manage_state' | translate\" icon=\"pi pi-bookmark\" iconPos=\"left\"\r\n (onClick)=\"onSaveStateMenuToggle($event, saveStateMenu)\" />\r\n <p-menu #saveStateMenu [popup]=\"true\" [model]=\"saveStateMenuItems\" appendTo=\"body\"></p-menu>\r\n }\r\n @if (!isAdvancedFilterModeEnabled()) {\r\n <p-button [label]=\"'clear_filters' | translate\" icon=\"pi pi-filter-slash\" (onClick)=\"clearFilter(dt1)\" />\r\n }\r\n <!-- <i class=\"pi pi-search\"></i>\r\n <input type=\"text\" pInputText [(ngModel)]=\"searchValue\"\r\n class=\"p-inputtext p-component p-element ng-pristine ng-valid ng-touched\"\r\n (input)=\"dt1.filterGlobal(getTgtVal($event.target), 'contains')\"\r\n [placeholder]=\"'search_keyword' | translate\" /> -->\r\n </div>\r\n </div>\r\n }\r\n </ng-template>\r\n <ng-template pTemplate=\"header\" let-columns>\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <th class=\"nested-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <th class=\"multiple-selection-grid-column\" role=\"presentation\" pFrozenColumn [frozen]=\"true\" alignFrozen=\"left\"\r\n class=\"wuic-selection-th\">\r\n <p-tableHeaderCheckbox [ariaLabel]=\"'Seleziona tutte le righe'\" />\r\n </th>\r\n }\r\n @if (showActionColumn()) {\r\n <th class=\"command-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n <!-- @for (col of columns | visibleFieldList; track col.metaColumn.mc_id) { NON FUNZIONA si spaccano i filter template !!! -->\r\n <!-- *ngFor NON FUNGE SE USI IL trackBy -> si spaccano i filter template -->\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n @if (!!col?.frozen) {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n pFrozenColumn [frozen]=\"true\" [alignFrozen]=\"col?.alignFrozen || 'left'\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n }\r\n </div>\r\n </th>\r\n } @else {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <!-- <ng-container [ngSwitch]=\"col.metaColumn.mc_ui_column_type\"> -->\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n <!-- <p-columnFilter *ngSwitchDefault type=\"text\" [field]=\"col.field\" display=\"menu\" /> -->\r\n <!-- </ng-container> -->\r\n }\r\n </div>\r\n </th>\r\n }\r\n }\r\n <!-- } -->\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"body\" let-rowData let-columns=\"columns\" let-expanded=\"expanded\" let-rowIndex=\"rowIndex\">\r\n <ng-container pTemplate=\"body\"\r\n *ngComponentOutlet=\"rowTemplate; inputs: { columns: columns, rowData: rowData, expanded: expanded, rowIndex: rowIndex, actionButtonRowIsVisible: isActionButtonRowVisible, isListVirtualizationEnabled: isListVirtualizationEnabled.bind(this), inlineCellEditingEnabled: isInlineCellEditingEnabled(), onInlineCellEditorBlur: onInlineCellEditorBlur.bind(this), onInlineCellEditorValueChange: onInlineCellEditorValueChange.bind(this), getRuntimeGridFieldMeta: getRuntimeGridFieldMetaFn, metaInfo: metaInfo, datasource: datasource, MetadatiColonna: MetadatiColonna, dt: dt1, toggleRow: toggleRow, rowSelect: rowSelect, rowCustomSelect: rowCustomSelect }\"></ng-container>\r\n </ng-template>\r\n <ng-template pTemplate=\"expandedrow\" let-rowData let-columns=\"columns\">\r\n <tr>\r\n <td\r\n [attr.colspan]=\"(columns | visibleFieldList).length + (metaInfo.tableMetadata.md_nested_grid_routes ? 1 : 0) + (metaInfo.tableMetadata.md_multiple_selection ? 1 : 0) + (showActionColumn() ? 1 : 0)\">\r\n @if (metaInfo.nestedRoutes?.length == 1) {\r\n <div [ngClass]=\"metaInfo.nestedRoutes[0].nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"metaInfo.nestedRoutes[0].route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <div class=\"list-grid-long-description-container\"\r\n [innerHTML]=\"getNestedLongDescriptionSafeHtml(nestedSource)\">\r\n </div>\r\n\r\n <wuic-data-repeater\r\n [hardcodedAction]=\"metaInfo.nestedRoutes[0].action == '' ? 'list' : metaInfo.nestedRoutes[0].action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n }\r\n @if (metaInfo.nestedRoutes?.length > 1) {\r\n <div>\r\n <p-tabs [value]=\"0\">\r\n <p-tablist>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tab [value]=\"i\">{{ nestedRoute.nestedTabCaption }}</p-tab>\r\n }\r\n </p-tablist>\r\n <p-tabpanels>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tabpanel [value]=\"i\">\r\n <div [ngClass]=\"nestedRoute.nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"nestedRoute.route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <wuic-data-repeater [hardcodedAction]=\"nestedRoute.action == '' ? 'list' : nestedRoute.action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n </p-tabpanel>\r\n }\r\n </p-tabpanels>\r\n </p-tabs>\r\n </div>\r\n }\r\n </td>\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"footer\">\r\n @if (metaInfo.hasFooter) {\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n @if (showActionColumn()) {\r\n <td></td>\r\n }\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <td></td>\r\n }\r\n <td></td>\r\n @for (col of metaInfo.columnMetadata | visibleFieldList; track col.mc_id) {\r\n <td>\r\n @if (col.mc_aggregation) {\r\n <table>\r\n @for (agg of getAggregations(col); track agg.field + '#' + agg.aggregation) {\r\n <tr>\r\n <td>\r\n {{agg.aggregation | translate}}:\r\n </td>\r\n <td>\r\n {{agg.aggregateValue}}\r\n </td>\r\n </tr>\r\n }\r\n </table>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </ng-template>\r\n </p-table>\r\n }\r\n\r\n <!--\r\n Mobile: la list-grid usa la STESSA <p-table> del desktop, sopra.\r\n Il row template (compilato via `DynamicRowTemplateComponent.\r\n getComponentFromTemplate`) cambia contenuto in base a\r\n `deviceAwareness.isMobile` \u2014 vedi `getEffectiveGridRowTemplate` in\r\n list-grid.component.ts: quando mobile, emette una singola <td colspan>\r\n con layout card-style invece di <td> per ogni colonna. CSS\r\n `@media (max-width: 768px)` nel file .scss nasconde <thead>, rende\r\n `display: block` le <tr>/<td> per liberarsi della semantica table.\r\n Vantaggio: un solo code path (riusa il JIT compile del row template\r\n gia' testato) eliminando il bug `*ngComponentOutlet [inputs]` che\r\n affliggeva il percorso mobile-card separato.\r\n -->\r\n</div>\r\n\r\n<p-dialog [header]=\"'list_grid.save_state_dialog_header' | translate\" [(visible)]=\"saveGridStateDialogVisible\"\r\n [modal]=\"true\" [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-save-state-select\">{{ 'list_grid.state' | translate }}</label>\r\n <select id=\"wuic-save-state-select\" class=\"p-inputtext p-component\" [(ngModel)]=\"saveGridStateDialogSelectedId\"\r\n (change)=\"onSaveGridStateDialogSelectionChange()\">\r\n <option [value]=\"NEW_GRID_STATE_OPTION_ID\">{{ 'new' | translate }}</option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n\r\n @if (saveGridStateDialogSelectedId === NEW_GRID_STATE_OPTION_ID) {\r\n <label for=\"wuic-save-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-save-state-name\" type=\"text\" pInputText [(ngModel)]=\"saveGridStateDialogNewName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n }\r\n\r\n <label for=\"wuic-save-state-default\" class=\"wuic-column-layout-check\">\r\n <input id=\"wuic-save-state-default\" type=\"checkbox\" [(ngModel)]=\"saveGridStateDialogSetAsDefault\" />\r\n <span>{{ 'list_grid.set_as_default' | translate }}</span>\r\n </label>\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"saveGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveCurrentRouteGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.rename_state' | translate\" [(visible)]=\"renameGridStateDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '30rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-rename-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-rename-state-name\" type=\"text\" pInputText [(ngModel)]=\"renameGridStateDialogName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"renameGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"renameSelectedGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.configure_columns' | translate\" [(visible)]=\"columnLayoutDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '36rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-column-layout-dialog\">\r\n @for (item of columnLayoutDraft; track item.field + '#' + $index; let i = $index) {\r\n <div class=\"wuic-column-layout-row\">\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.visible\" />\r\n <span>{{ item.header }}</span>\r\n </label>\r\n <div class=\"wuic-column-layout-actions\">\r\n <p-button icon=\"pi pi-arrow-up\" [outlined]=\"true\" [disabled]=\"i === 0\"\r\n (onClick)=\"moveColumnLayoutDraft(i, -1)\" />\r\n <p-button icon=\"pi pi-arrow-down\" [outlined]=\"true\" [disabled]=\"i === (columnLayoutDraft.length - 1)\"\r\n (onClick)=\"moveColumnLayoutDraft(i, 1)\" />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"columnLayoutDialogVisible = false\" />\r\n <p-button [label]=\"'list_grid.reset_metadata' | translate\" [outlined]=\"true\" icon=\"pi pi-refresh\"\r\n (onClick)=\"resetColumnLayoutDraftAndWidths()\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveColumnLayoutFromDialog()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.report_parameters' | translate\" [(visible)]=\"reportVariableDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\" (onHide)=\"cancelReportVariableDialog()\">\r\n <div class=\"wuic-save-state-dialog\">\r\n @for (variable of selectedReportVariables; track variable.name; let i = $index) {\r\n <label [for]=\"'wuic-report-var-' + i\">{{ variable.alias || variable.name }}</label>\r\n <input [id]=\"'wuic-report-var-' + i\" [name]=\"'wuic-report-var-name-' + i\" type=\"text\" pInputText\r\n [(ngModel)]=\"variable.value\" />\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"cancelReportVariableDialog()\" />\r\n <p-button [label]=\"'list_grid.open_report' | translate\" (onClick)=\"applyReportVariablesAndOpenViewer()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.pending_changes' | translate\" [(visible)]=\"changesDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '44rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-changes-dialog\">\r\n @if (!pendingChangeItems.length) {\r\n <div>{{ 'list_grid.no_pending_changes' | translate }}</div>\r\n } @else {\r\n @for (item of pendingChangeItems; track item.id) {\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.selected\" />\r\n <span><strong>{{ item.label }}</strong> - {{ item.details }}</span>\r\n </label>\r\n }\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'list_grid.save_all' | translate\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.save_selected' | translate\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !getSelectedPendingChangesCount() || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveSelectedPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n </ng-template>\r\n</p-dialog>", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;max-height:100%;min-height:0;overflow:hidden;--wuic-grid-row-odd: #ffffff;--wuic-grid-row-even: #f6f8fc;--wuic-grid-row-hover: #edf2ff;--wuic-grid-row-selected-bg: #dbe8ff;--wuic-grid-row-selected-text: #0f2348;--wuic-grid-row-selected-ring: rgba(59, 130, 246, .55);--wuic-grid-accent: var(--p-primary-color, #3b82f6);--wuic-grid-base: var(--p-surface-0, #ffffff)}:host-context(.theme-dark){--wuic-grid-row-odd: #1f2430;--wuic-grid-row-even: #252b38;--wuic-grid-row-hover: #31394b;--wuic-grid-row-selected-bg: #284774;--wuic-grid-row-selected-text: #eaf2ff;--wuic-grid-row-selected-ring: rgba(120, 170, 255, .65);--wuic-grid-base: var(--p-surface-900, #1f2430)}@supports (color: color-mix(in srgb,white,black)){:host{--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 96%, var(--wuic-grid-accent) 4%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 90%, var(--wuic-grid-accent) 10%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 82%, var(--wuic-grid-accent) 18%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 56%, var(--wuic-grid-accent) 44%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 72%, transparent)}:host-context(.theme-dark){--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 92%, var(--wuic-grid-accent) 8%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 84%, var(--wuic-grid-accent) 16%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 72%, var(--wuic-grid-accent) 28%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 36%, var(--wuic-grid-accent) 64%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 82%, transparent)}}.list-grid-container{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-view{flex:1 1 auto;min-height:0}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;height:auto}:host ::ng-deep .p-datatable-table-container:has(>table.wuic-nested-grid),:host ::ng-deep .p-datatable:has(>.p-datatable-table-container>table.wuic-nested-grid),:host ::ng-deep .list-grid-container:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid-lazy:has(table.wuic-nested-grid),:host ::ng-deep .data-repeater-body:has(table.wuic-nested-grid),:host ::ng-deep td:has(wuic-data-repeater table.wuic-nested-grid){overflow:visible!important;max-height:none!important}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead{position:sticky!important;top:var(--wuic-outer-thead-h, 0px);z-index:3}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead>tr>th{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(odd)>td{background:var(--wuic-grid-row-odd)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(2n)>td{background:var(--wuic-grid-row-even)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):hover>td{background:var(--wuic-grid-row-hover)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td{background:var(--wuic-grid-row-selected-bg)!important;color:var(--wuic-grid-row-selected-text);box-shadow:inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td:first-child{box-shadow:inset 4px 0 0 var(--wuic-grid-accent),inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected:hover>td{background:var(--wuic-grid-row-selected-bg)!important}:host ::ng-deep td.p-datatable-frozen-column,:host ::ng-deep th.p-datatable-frozen-column{border-right:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep td.wuic-action-cell,:host ::ng-deep th.command-grid-column{width:64px!important;min-width:64px!important;max-width:64px!important;box-sizing:border-box;text-align:center;vertical-align:middle}:host ::ng-deep td.wuic-action-cell wuic-data-action-button-lazy,:host ::ng-deep td.wuic-action-cell wuic-data-action-button{display:inline-flex;align-items:center;justify-content:center}:host ::ng-deep .p-datatable-tbody>tr>td:first-child,:host ::ng-deep .p-datatable-thead>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-box,:host ::ng-deep .p-datatable-tbody>tr>td input.p-checkbox-box{width:18px;height:18px;border:1px solid #9ca3af;border-radius:4px;background:#fff;box-sizing:border-box;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th.wuic-selection-th{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td>input.p-checkbox-box{margin-left:auto!important;margin-right:auto!important;display:block}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-icon{font-size:.72rem}:host ::ng-deep .p-datatable-table.width_defined{display:table}:host ::ng-deep .p-datatable-table{display:table}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table{width:100%!important;min-width:100%}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>td,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>th{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>td:first-child,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td{min-width:0;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable>.p-paginator{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator{border-top:2px solid currentColor;background:var(--p-content-background, #fff);font-weight:600;padding:.5rem .75rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page{min-width:2.25rem;min-height:2.25rem;border:2px solid currentColor!important;border-radius:4px;margin:0 2px;font-weight:700;background:transparent;color:currentColor}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled){background:currentColor;color:var(--p-content-background, #fff)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible{outline:3px solid currentColor;outline-offset:2px}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled{opacity:.45;cursor:not-allowed}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:var(--p-primary-color, #2563eb)!important;color:var(--p-primary-contrast-color, #fff)!important;border-color:var(--p-primary-color, #2563eb)!important;border-width:3px!important;font-weight:800}@media(forced-colors:active){:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:Highlight!important;color:HighlightText!important;border-color:Highlight!important;forced-color-adjust:none}}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current{font-weight:700;padding:0 .5rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select{border:2px solid currentColor!important;font-weight:700;min-height:2.25rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within{outline:3px solid currentColor;outline-offset:2px}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td .list-grid-cell-text-content,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td>a{display:block;width:0;min-width:100%;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap!important;overflow-wrap:anywhere}.wuic-save-state-dialog{display:flex;flex-direction:column;gap:.6rem}.wuic-save-state-dialog select,.wuic-save-state-dialog input{width:100%}.wuic-column-layout-dialog{display:flex;flex-direction:column;gap:.45rem;max-height:55vh;overflow:auto}.wuic-column-layout-row{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.35rem 0}.wuic-column-layout-check{display:inline-flex;align-items:center;gap:.5rem}.wuic-column-layout-actions{display:inline-flex;gap:.35rem}.wuic-changes-dialog{display:flex;flex-direction:column;gap:.5rem;max-height:55vh;overflow:auto}.wuic-export-progress-dialog{display:flex;flex-direction:column;gap:.65rem}::ng-deep .wuic-progress-dialog .p-progressbar{--p-progressbar-value-transition: none}::ng-deep .wuic-progress-dialog .p-progressbar .p-progressbar-value,::ng-deep .wuic-progress-dialog .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar-value{transition:none!important;animation:none!important}.wuic-export-progress-detail{font-size:.9rem;font-weight:600;text-align:right}.wuic-import-dialog{display:flex;flex-direction:column;gap:.75rem}.wuic-import-grid{display:grid;grid-template-columns:1fr;gap:.35rem}.wuic-import-file-row{display:flex;align-items:center;gap:.65rem}.grid-caption-bar{display:flex;align-items:stretch;justify-content:space-between;gap:.75rem;flex-wrap:nowrap}.grid-caption-left,.grid-caption-right{display:flex;align-items:stretch;gap:.5rem;flex-wrap:nowrap;min-height:2.25rem;height:2.25rem}.grid-caption-right{margin-left:auto}.grid-caption-left>*,.grid-caption-right>*{margin:0;align-self:stretch}.grid-changes-indicator{display:inline-flex;align-items:center;gap:.3rem;padding:0 .55rem;border:1px solid #f59e0b;border-radius:999px;background:#fffbeb;color:#b45309;font-size:.82rem;font-weight:700;cursor:pointer;appearance:none}.grid-changes-count{min-width:1.2rem;text-align:center}.wuic-grid-state-select{min-width:220px}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button,:host ::ng-deep .grid-caption-bar .p-inputtext{min-height:2.25rem;height:2.25rem;padding-top:.45rem;padding-bottom:.45rem;margin:0;box-sizing:border-box;line-height:1.1rem;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button,:host ::ng-deep .grid-caption-bar p-splitbutton{display:inline-flex;align-items:stretch;margin:0;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button>.p-button{min-height:2.25rem;height:2.25rem;display:inline-flex;align-items:center}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{padding-left:.75rem;padding-right:.75rem}:host ::ng-deep .grid-caption-bar .p-splitbutton{display:inline-flex;align-items:stretch;margin:0}:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{height:100%}:host ::ng-deep .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .grid-caption-bar .p-button .p-button-icon{line-height:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td{padding-top:.35rem;padding-bottom:.35rem;vertical-align:middle;position:relative}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within{overflow:visible!important;z-index:50}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper>div{overflow:visible!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{position:relative;z-index:100;min-width:360px!important;max-width:80vw!important;background:var(--p-content-background, var(--p-surface-0, #fff));box-shadow:0 4px 12px #0000002e;border:1px solid var(--p-primary-color, #3b82f6);border-radius:4px}:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{background:var(--p-surface-900, #1f2430)}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{display:block;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor{display:flex!important;align-items:center!important;min-height:2.2rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper{margin:0!important;padding:0!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .caption-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{width:100%!important;max-width:100%!important;min-width:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper{display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{min-height:2.2rem;height:2.2rem;line-height:1.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-autocomplete .p-inputtext{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .disabled-textfield{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete>svg[data-p-icon]{display:none!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete .p-autocomplete-input{padding-right:.5rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within p-autocomplete>svg[data-p-icon]{z-index:101!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker{display:flex;align-items:stretch}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-input,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-dropdown{min-height:2.2rem;height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-disabled .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext:disabled{opacity:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor{display:flex;align-items:center;min-height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper{margin:0!important;padding:0!important;width:100%!important;max-width:100%!important;min-width:0!important;display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important;width:100%!important;max-width:100%!important;min-width:0!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}.command-grid-column{width:65px}.multiple-selection-grid-column{width:50px}.nested-grid-column{width:30px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-thead{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tfoot,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-footer,:host ::ng-deep .wuic-list-grid-mobile tfoot{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-resizable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table{display:block}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody{display:block;width:100%;padding-top:12px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr{display:block;width:100%;box-sizing:border-box;margin-bottom:12px;border-radius:12px;overflow:hidden;background:var(--p-content-background, var(--wuic-grid-row-odd));border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 80%, var(--wuic-grid-accent) 20%))}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr:last-child{margin-bottom:0}:host ::ng-deep .wuic-list-grid-mobile td.wuic-mobile-card-cell{display:block;width:100%;padding:14px;border-bottom:1px solid var(--surface-border, #e5e7eb);box-sizing:border-box}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table{width:100%!important;min-width:unset!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-splitbutton .p-button .p-button-label{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button{padding-left:.5rem!important;padding-right:.5rem!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-icon{margin-right:0!important;margin-left:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator{display:flex!important;flex-wrap:nowrap;justify-content:center;align-items:center;gap:4px;padding:8px 4px}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-current{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-pages{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-first,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-prev,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-next,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-last{min-width:36px!important;height:36px!important;margin:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-rpp-options,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-select{min-width:56px!important;height:36px!important;margin-left:4px!important}.wuic-mobile-card-list{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:18px;padding:14px;overflow:auto}.wuic-mobile-card-slot{padding-bottom:18px;box-sizing:border-box}.wuic-mobile-card-toolbar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:4px 0;border-bottom:1px solid color-mix(in srgb,var(--wuic-grid-base) 80%,var(--wuic-grid-accent) 20%);margin-bottom:4px}.wuic-mobile-card{display:flex;flex-direction:column;gap:6px;padding:14px 16px;border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 75%, var(--wuic-grid-accent) 25%));border-radius:12px;background:var(--p-content-background, var(--wuic-grid-row-odd));color:var(--p-text-color, inherit);box-shadow:0 4px 14px #0f172a14}:host-context(.theme-dark) .wuic-mobile-card{box-shadow:0 2px 8px #00000059}.wuic-mobile-card.wuic-row-selected{background:var(--wuic-grid-row-selected-bg);color:var(--wuic-grid-row-selected-text);outline:2px solid var(--wuic-grid-row-selected-ring)}:host ::ng-deep .wuic-mobile-card-actions{display:flex;justify-content:flex-end;align-items:center;margin:0 -4px 4px;padding-bottom:6px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-body{display:flex;flex-direction:column;gap:0}:host ::ng-deep .wuic-mobile-card-row{display:grid;grid-template-columns:minmax(110px,38%) 1fr;column-gap:12px;align-items:baseline;padding:8px 2px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-row:last-child{border-bottom:none}:host ::ng-deep .wuic-mobile-card-label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:currentColor;opacity:.75;line-height:1.3;word-break:break-word}:host ::ng-deep .wuic-mobile-card-value{font-size:.95rem;font-weight:500;text-align:right;word-break:break-word;min-width:0;color:currentColor}:host ::ng-deep .wuic-mobile-card-value a{color:var(--p-primary-color, #3b82f6);text-decoration:underline}.wuic-mobile-card-empty{padding:24px 12px;text-align:center;color:#6b7280;font-style:italic}p-menu:not(.p-menu-overlay-visible){display:none}@media(max-width:420px){:host ::ng-deep .wuic-mobile-card-row{grid-template-columns:1fr;row-gap:2px}:host ::ng-deep .wuic-mobile-card-value{text-align:left}}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => DataSourceComponent), selector: "wuic-data-source", inputs: ["route", "routeFromRouting", "hardcodedRoute", "autoload", "loading", "changeTracking", "parentRecord", "parentMetaInfo", "parentDatasource", "useComboEndpoint", "comboFormulaLookup", "comboMcId", "comboField", "comboExtraFields", "comboCurrentRecord", "componentRef"] }, { kind: "component", type: i0.forwardRef(() => DataRepeaterComponent), selector: "wuic-data-repeater", inputs: ["datasource", "hardcodedDatasource", "action", "hardcodedAction", "field", "record", "rowCustomSelect", "hideToolbar", "isEditForm", "fillHeight"], outputs: ["templateReady", "archetypeInstanceReady", "archetypeEvent", "archetypeDataBound"] }, { kind: "ngmodule", type: i0.forwardRef(() => TableModule) }, { kind: "component", type: i0.forwardRef(() => i9$1.Table), selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i0.forwardRef(() => i3.PrimeTemplate), selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.SortableColumn), selector: "[pSortableColumn]", inputs: ["pSortableColumn", "pSortableColumnDisabled"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.FrozenColumn), selector: "[pFrozenColumn]", inputs: ["frozen", "alignFrozen"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.ResizableColumn), selector: "[pResizableColumn]", inputs: ["pResizableColumnDisabled"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.ReorderableColumn), selector: "[pReorderableColumn]", inputs: ["pReorderableColumnDisabled"] }, { kind: "component", type: i0.forwardRef(() => i9$1.SortIcon), selector: "p-sortIcon", inputs: ["field"] }, { kind: "component", type: i0.forwardRef(() => i9$1.TableHeaderCheckbox), selector: "p-tableHeaderCheckbox", inputs: ["disabled", "inputId", "name", "ariaLabel"] }, { kind: "component", type: i0.forwardRef(() => i9$1.ColumnFilter), selector: "p-columnFilter, p-column-filter, p-columnfilter", inputs: ["field", "type", "display", "showMenu", "matchMode", "operator", "showOperator", "showClearButton", "showApplyButton", "showMatchModes", "showAddButton", "hideOnClear", "placeholder", "matchModeOptions", "maxConstraints", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "locale", "localeMatcher", "currency", "currencyDisplay", "filterOn", "useGrouping", "showButtons", "ariaLabel", "filterButtonProps", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: i0.forwardRef(() => ButtonModule) }, { kind: "component", type: i0.forwardRef(() => i4.Button), selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: i0.forwardRef(() => SplitButtonModule) }, { kind: "component", type: i0.forwardRef(() => i12.SplitButton), selector: "p-splitbutton, p-splitButton, p-split-button", inputs: ["model", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "icon", "iconPos", "label", "tooltip", "tooltipOptions", "styleClass", "menuStyle", "menuStyleClass", "dropdownIcon", "appendTo", "dir", "expandAriaLabel", "showTransitionOptions", "hideTransitionOptions", "motionOptions", "buttonProps", "menuButtonProps", "autofocus", "disabled", "tabindex", "menuButtonDisabled", "buttonDisabled"], outputs: ["onClick", "onMenuHide", "onMenuShow", "onDropdownClick"] }, { kind: "ngmodule", type: i0.forwardRef(() => DialogModule) }, { kind: "component", type: i0.forwardRef(() => i2.Dialog), selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: i0.forwardRef(() => ContextMenuModule) }, { kind: "component", type: i0.forwardRef(() => i14.ContextMenu), selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: i0.forwardRef(() => MenuModule) }, { kind: "component", type: i0.forwardRef(() => i9.Menu), selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex", "appendTo", "motionOptions"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }, { kind: "ngmodule", type: i0.forwardRef(() => PaginatorModule) }, { kind: "ngmodule", type: i0.forwardRef(() => ScrollerModule) }, { kind: "component", type: i0.forwardRef(() => Tabs), selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i0.forwardRef(() => TabList), selector: "p-tablist" }, { kind: "component", type: i0.forwardRef(() => Tab), selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: i0.forwardRef(() => TabPanels), selector: "p-tabpanels" }, { kind: "component", type: i0.forwardRef(() => TabPanel), selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "directive", type: i0.forwardRef(() => NgClass), selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: i0.forwardRef(() => FormsModule) }, { kind: "directive", type: i0.forwardRef(() => i1$4.NgSelectOption), selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.ɵNgSelectMultipleOption), selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.DefaultValueAccessor), selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i0.forwardRef(() => i1$4.CheckboxControlValueAccessor), selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i0.forwardRef(() => i1$4.SelectControlValueAccessor), selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.NgControlStatus), selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i0.forwardRef(() => i1$4.NgModel), selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i0.forwardRef(() => InputTextModule) }, { kind: "directive", type: i0.forwardRef(() => i13.InputText), selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i0.forwardRef(() => FieldFilterComponent), selector: "wuic-field-filter", inputs: ["record", "field", "metaInfo", "readOnly", "hideLabel", "operator", "datasource"] }, { kind: "directive", type: i0.forwardRef(() => NgComponentOutlet), selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: i0.forwardRef(() => TranslateModule) }, { kind: "component", type: i0.forwardRef(() => MetadataEditorComponent), selector: "wuic-metadata-editor", inputs: ["datasource", "hardcodedDatasource", "saveCallback", "hideReports", "hideRelatedTableActions", "hideRelatedColumnActions"] }, { kind: "component", type: i0.forwardRef(() => ImportExportButtonComponent), selector: "wuic-import-export-button", inputs: ["routeName", "tableMetadata", "filterInfoProvider"] }, { kind: "pipe", type: i0.forwardRef(() => AsyncPipe), name: "async" }, { kind: "pipe", type: i0.forwardRef(() => i8.TranslatePipe), name: "translate" }, { kind: "pipe", type: i0.forwardRef(() => VisibleFieldListPipe), name: "visibleFieldList" }] });
37994
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ListGridComponent, isStandalone: true, selector: "wuic-list-grid", inputs: { hardcodedRoute: "hardcodedRoute", parentRecord: "parentRecord", parentMetaInfo: "parentMetaInfo", datasource: "datasource", hardcodedDatasource: "hardcodedDatasource", rowCustomSelect: "rowCustomSelect", hideToolbar: "hideToolbar" }, outputs: { onBeforeRowRender: "onBeforeRowRender", onAfterRowRender: "onAfterRowRender", onAfterRender: "onAfterRender", onPaging: "onPaging", onSorting: "onSorting", onFiltering: "onFiltering", onRefresh: "onRefresh", onPTableSelectionChange: "onPTableSelectionChange", onPTableRowExpand: "onPTableRowExpand", onPTableRowCollapse: "onPTableRowCollapse", onPTableColumnResize: "onPTableColumnResize", onPTableColumnReorder: "onPTableColumnReorder" }, host: { listeners: { "window:resize": "onWindowResizeForGridLayout()" } }, providers: [Title], viewQueries: [{ propertyName: "table", first: true, predicate: ["dt1"], descendants: true }, { propertyName: "columnCtxMenu", first: true, predicate: ["columnCtxMenu"], descendants: true }, { propertyName: "metadataColumnsDatasource", first: true, predicate: ["metadataColumnsDatasource"], descendants: true }, { propertyName: "gridContainerRef", first: true, predicate: ["gridContainer"], descendants: true }, { propertyName: "tableActionsMenu", first: true, predicate: ["tableActionsMenu"], descendants: true }], ngImport: i0, template: "<wuic-data-source #metadataColumnsDatasource [hardcodedRoute]=\"' metadati colonne'\"\r\n style=\"display: none;\"></wuic-data-source>\r\n<p-contextMenu #columnCtxMenu [model]=\"columnContextMenuItems\" appendTo=\"body\"></p-contextMenu>\r\n\r\n<div class=\"list-grid-container\" #gridContainer\r\n [class.wuic-list-grid-mobile]=\"gridLayoutIsMobile\">\r\n @if (rowTemplate) {\r\n <p-table #dt1 [columns]=\"cols\" [value]=\"records\" [scrollable]=\"true\"\r\n [scrollHeight]=\"metaInfo.tableMetadata.md_grid_scroll_height ? metaInfo.tableMetadata.md_grid_scroll_height : 'flex'\"\r\n [virtualScroll]=\"isListVirtualizationEnabled()\" [virtualScrollItemSize]=\"getListVirtualizationItemSize()\"\r\n [resizableColumns]=\"true\" [reorderableColumns]=\"true\" columnResizeMode=\"expand\" [rows]=\"pageSize\"\r\n [first]=\"rowNumber\" [lazy]=\"metaInfo.tableMetadata.md_server_side_operations\" [sortField]=\"orderColumn || null\"\r\n [sortOrder]=\"orderColumn ? (orderDir === 'desc' ? -1 : 1) : 0\" [(selection)]=\"selectedItems\"\r\n [dataKey]=\"metaInfo.pKey?.mc_nome_colonna\" [paginator]=\"metaInfo.tableMetadata.md_pageable\"\r\n [rowsPerPageOptions]=\"metaInfo.rowsPerPageOptions\" [totalRecords]=\"totalRecords\"\r\n [showPageLinks]=\"!datasource?.value?.cursorMode\" [showFirstLastIcon]=\"!datasource?.value?.cursorMode\"\r\n [showCurrentPageReport]=\"metaInfo.tableMetadata.md_show_record_count && !datasource?.value?.cursorMode\"\r\n [loading]=\"datasource?.value?.loading | async\"\r\n [currentPageReportTemplate]=\"'Showing {first} to {last} of {totalRecords} entries' | translate\"\r\n (onPage)=\"pageFilterChange($event)\" (onFilter)=\"pageFilterChange($event)\" (onSort)=\"pageFilterChange($event)\"\r\n (selectionChange)=\"onSelectionChange($event)\" (onColResize)=\"onColumnResize($event)\"\r\n (onColReorder)=\"onColumnReorder($event)\" [expandedRowKeys]=\"expandedRows\" (onRowExpand)=\"onRowExpand($event)\"\r\n (onRowCollapse)=\"onRowCollapse($event)\" [selectionMode]=\"rowCustomSelect ? 'single' : undefined\"\r\n [tableStyleClass]=\"getTableStyleClass()\">\r\n <ng-template pTemplate=\"colgroup\" let-columns>\r\n <colgroup>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <col class=\"nested-grid-column\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <col class=\"multiple-selection-grid-column\" />\r\n }\r\n @if (showActionColumn()) {\r\n <col class=\"command-grid-column\" />\r\n }\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n <col [style.width]=\"getColumnWidthCss(col)\" [style.min-width]=\"columnMinWidthCss\" />\r\n }\r\n </colgroup>\r\n </ng-template>\r\n <ng-template pTemplate=\"caption\">\r\n @if (!hideToolbar) {\r\n <div class=\"grid-caption-bar\">\r\n <div class=\"grid-caption-left\">\r\n @if (!metaInfo.tableMetadata.md_hide_refresh) {\r\n <p-button icon=\"pi pi-refresh\" label=\"{{'refresh_grid' | translate}}\" (onClick)=\"refresh()\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_insertable) {\r\n <p-button icon=\"pi pi-plus\" label=\"{{'add_grid' | translate}}\" (onClick)=\"addRecord()\" />\r\n }\r\n @if (tableActionMenuItems?.length) {\r\n <p-button icon=\"pi pi-bars\" [label]=\"'actions' | translate\" (onClick)=\"tableActionsMenu.toggle($event)\" />\r\n <p-menu #tableActionsMenu [popup]=\"true\" [model]=\"tableActionMenuItems\" appendTo=\"body\"\r\n (onShow)=\"reEvaluateActionEnabledStates()\"></p-menu>\r\n }\r\n <wuic-import-export-button [routeName]=\"routeName\"\r\n [tableMetadata]=\"metaInfo?.tableMetadata\"\r\n [filterInfoProvider]=\"getCurrentFilterInfoForExport\"></wuic-import-export-button>\r\n @if (reportMenuItems?.length) {\r\n <p-splitButton icon=\"pi pi-chart-bar\" [label]=\"'list_grid.reports' | translate\" [model]=\"reportMenuItems\"\r\n [menuButtonProps]=\"{ ariaLabel: ('list_grid.reports' | translate) }\"\r\n (onClick)=\"reportMenuItems[0]?.command({})\" />\r\n }\r\n @if (canToggleClientSideCrud()) {\r\n @if (!isClientSideCrudActive()) {\r\n <p-button icon=\"pi pi-database\" [label]=\"'client_side_crud_enable' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"toggleClientSideCrud()\" />\r\n } @else {\r\n <p-button icon=\"pi pi-cloud-upload\" [label]=\"'client_side_crud_sync' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"syncAndDisableClientSideCrud()\" />\r\n <p-button icon=\"pi pi-ban\" [label]=\"'client_side_crud_disable_without_sync' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"clientSideCrudToggleBusy\"\r\n (onClick)=\"discardLocalAndDisableClientSideCrud()\" />\r\n }\r\n }\r\n @if (isInlineBatchSaveEnabled() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <p-button icon=\"pi pi-save\" [label]=\"'list_grid.save_changes' | translate\"\r\n [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\" (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button icon=\"pi pi-times\" [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n }\r\n @if (hasDatasourceChanges() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <button type=\"button\" class=\"grid-changes-indicator\"\r\n [title]=\"'Changes in memory: ' + getDatasourceChangesCount()\" (click)=\"openChangesDialog()\">\r\n <i class=\"pi pi-pencil\"></i>\r\n <span class=\"grid-changes-count\">{{ getDatasourceChangesCount() }}</span>\r\n </button>\r\n }\r\n </div>\r\n <div class=\"grid-caption-right\">\r\n @if (!metaInfo?.tableMetadata?.extraProps?.toolbar?.hideManageState) {\r\n @if (currentRouteSavedStates?.length) {\r\n <select class=\"p-inputtext p-component wuic-grid-state-select\" [(ngModel)]=\"selectedSavedStateId\"\r\n (change)=\"onSavedStateDropdownChange($event)\">\r\n <option value=\"\"></option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n }\r\n <p-button [label]=\"'list_grid.manage_state' | translate\" icon=\"pi pi-bookmark\" iconPos=\"left\"\r\n (onClick)=\"onSaveStateMenuToggle($event, saveStateMenu)\" />\r\n <p-menu #saveStateMenu [popup]=\"true\" [model]=\"saveStateMenuItems\" appendTo=\"body\"></p-menu>\r\n }\r\n @if (!isAdvancedFilterModeEnabled()) {\r\n <p-button [label]=\"'clear_filters' | translate\" icon=\"pi pi-filter-slash\" (onClick)=\"clearFilter(dt1)\" />\r\n }\r\n <!-- <i class=\"pi pi-search\"></i>\r\n <input type=\"text\" pInputText [(ngModel)]=\"searchValue\"\r\n class=\"p-inputtext p-component p-element ng-pristine ng-valid ng-touched\"\r\n (input)=\"dt1.filterGlobal(getTgtVal($event.target), 'contains')\"\r\n [placeholder]=\"'search_keyword' | translate\" /> -->\r\n </div>\r\n </div>\r\n }\r\n </ng-template>\r\n <ng-template pTemplate=\"header\" let-columns>\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <th class=\"nested-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <th class=\"multiple-selection-grid-column\" role=\"presentation\" pFrozenColumn [frozen]=\"true\" alignFrozen=\"left\"\r\n class=\"wuic-selection-th\">\r\n <p-tableHeaderCheckbox [ariaLabel]=\"'Seleziona tutte le righe'\" />\r\n </th>\r\n }\r\n @if (showActionColumn()) {\r\n <th class=\"command-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n <!-- @for (col of columns | visibleFieldList; track col.metaColumn.mc_id) { NON FUNZIONA si spaccano i filter template !!! -->\r\n <!-- *ngFor NON FUNGE SE USI IL trackBy -> si spaccano i filter template -->\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n @if (!!col?.frozen) {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n pFrozenColumn [frozen]=\"true\" [alignFrozen]=\"col?.alignFrozen || 'left'\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n }\r\n </div>\r\n </th>\r\n } @else {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <!-- <ng-container [ngSwitch]=\"col.metaColumn.mc_ui_column_type\"> -->\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n <!-- <p-columnFilter *ngSwitchDefault type=\"text\" [field]=\"col.field\" display=\"menu\" /> -->\r\n <!-- </ng-container> -->\r\n }\r\n </div>\r\n </th>\r\n }\r\n }\r\n <!-- } -->\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"body\" let-rowData let-columns=\"columns\" let-expanded=\"expanded\" let-rowIndex=\"rowIndex\">\r\n <ng-container pTemplate=\"body\"\r\n *ngComponentOutlet=\"rowTemplate; inputs: { columns: columns, rowData: rowData, expanded: expanded, rowIndex: rowIndex, actionButtonRowIsVisible: isActionButtonRowVisible, isListVirtualizationEnabled: isListVirtualizationEnabled.bind(this), inlineCellEditingEnabled: isInlineCellEditingEnabled(), onInlineCellEditorBlur: onInlineCellEditorBlur.bind(this), onInlineCellEditorValueChange: onInlineCellEditorValueChange.bind(this), getRuntimeGridFieldMeta: getRuntimeGridFieldMetaFn, metaInfo: metaInfo, datasource: datasource, MetadatiColonna: MetadatiColonna, dt: dt1, toggleRow: toggleRow, rowSelect: rowSelect, rowCustomSelect: rowCustomSelect }\"></ng-container>\r\n </ng-template>\r\n <ng-template pTemplate=\"expandedrow\" let-rowData let-columns=\"columns\">\r\n <tr>\r\n <td\r\n [attr.colspan]=\"(columns | visibleFieldList).length + (metaInfo.tableMetadata.md_nested_grid_routes ? 1 : 0) + (metaInfo.tableMetadata.md_multiple_selection ? 1 : 0) + (showActionColumn() ? 1 : 0)\">\r\n @if (metaInfo.nestedRoutes?.length == 1) {\r\n <div [ngClass]=\"metaInfo.nestedRoutes[0].nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"metaInfo.nestedRoutes[0].route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <div class=\"list-grid-long-description-container\"\r\n [innerHTML]=\"getNestedLongDescriptionSafeHtml(nestedSource)\">\r\n </div>\r\n\r\n <wuic-data-repeater\r\n [hardcodedAction]=\"metaInfo.nestedRoutes[0].action == '' ? 'list' : metaInfo.nestedRoutes[0].action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n }\r\n @if (metaInfo.nestedRoutes?.length > 1) {\r\n <div>\r\n <p-tabs [value]=\"0\">\r\n <p-tablist>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tab [value]=\"i\">{{ nestedRoute.nestedTabCaption }}</p-tab>\r\n }\r\n </p-tablist>\r\n <p-tabpanels>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tabpanel [value]=\"i\">\r\n <div [ngClass]=\"nestedRoute.nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"nestedRoute.route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <wuic-data-repeater [hardcodedAction]=\"nestedRoute.action == '' ? 'list' : nestedRoute.action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n </p-tabpanel>\r\n }\r\n </p-tabpanels>\r\n </p-tabs>\r\n </div>\r\n }\r\n </td>\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"footer\">\r\n @if (metaInfo.hasFooter) {\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n @if (showActionColumn()) {\r\n <td></td>\r\n }\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <td></td>\r\n }\r\n <td></td>\r\n @for (col of metaInfo.columnMetadata | visibleFieldList; track col.mc_id) {\r\n <td>\r\n @if (col.mc_aggregation) {\r\n <table>\r\n @for (agg of getAggregations(col); track agg.field + '#' + agg.aggregation) {\r\n <tr>\r\n <td>\r\n {{agg.aggregation | translate}}:\r\n </td>\r\n <td>\r\n {{agg.aggregateValue}}\r\n </td>\r\n </tr>\r\n }\r\n </table>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </ng-template>\r\n </p-table>\r\n }\r\n\r\n <!--\r\n Mobile: la list-grid usa la STESSA <p-table> del desktop, sopra.\r\n Il row template (compilato via `DynamicRowTemplateComponent.\r\n getComponentFromTemplate`) cambia contenuto in base a\r\n `deviceAwareness.isMobile` \u2014 vedi `getEffectiveGridRowTemplate` in\r\n list-grid.component.ts: quando mobile, emette una singola <td colspan>\r\n con layout card-style invece di <td> per ogni colonna. CSS\r\n `@media (max-width: 768px)` nel file .scss nasconde <thead>, rende\r\n `display: block` le <tr>/<td> per liberarsi della semantica table.\r\n Vantaggio: un solo code path (riusa il JIT compile del row template\r\n gia' testato) eliminando il bug `*ngComponentOutlet [inputs]` che\r\n affliggeva il percorso mobile-card separato.\r\n -->\r\n</div>\r\n\r\n<p-dialog [header]=\"'list_grid.save_state_dialog_header' | translate\" [(visible)]=\"saveGridStateDialogVisible\"\r\n [modal]=\"true\" [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-save-state-select\">{{ 'list_grid.state' | translate }}</label>\r\n <select id=\"wuic-save-state-select\" class=\"p-inputtext p-component\" [(ngModel)]=\"saveGridStateDialogSelectedId\"\r\n (change)=\"onSaveGridStateDialogSelectionChange()\">\r\n <option [value]=\"NEW_GRID_STATE_OPTION_ID\">{{ 'new' | translate }}</option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n\r\n @if (saveGridStateDialogSelectedId === NEW_GRID_STATE_OPTION_ID) {\r\n <label for=\"wuic-save-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-save-state-name\" type=\"text\" pInputText [(ngModel)]=\"saveGridStateDialogNewName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n }\r\n\r\n <label for=\"wuic-save-state-default\" class=\"wuic-column-layout-check\">\r\n <input id=\"wuic-save-state-default\" type=\"checkbox\" [(ngModel)]=\"saveGridStateDialogSetAsDefault\" />\r\n <span>{{ 'list_grid.set_as_default' | translate }}</span>\r\n </label>\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"saveGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveCurrentRouteGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.rename_state' | translate\" [(visible)]=\"renameGridStateDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '30rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-rename-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-rename-state-name\" type=\"text\" pInputText [(ngModel)]=\"renameGridStateDialogName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"renameGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"renameSelectedGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.configure_columns' | translate\" [(visible)]=\"columnLayoutDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '36rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-column-layout-dialog\">\r\n @for (item of columnLayoutDraft; track item.field + '#' + $index; let i = $index) {\r\n <div class=\"wuic-column-layout-row\">\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.visible\" />\r\n <span>{{ item.header }}</span>\r\n </label>\r\n <div class=\"wuic-column-layout-actions\">\r\n <p-button icon=\"pi pi-arrow-up\" [outlined]=\"true\" [disabled]=\"i === 0\"\r\n (onClick)=\"moveColumnLayoutDraft(i, -1)\" />\r\n <p-button icon=\"pi pi-arrow-down\" [outlined]=\"true\" [disabled]=\"i === (columnLayoutDraft.length - 1)\"\r\n (onClick)=\"moveColumnLayoutDraft(i, 1)\" />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"columnLayoutDialogVisible = false\" />\r\n <p-button [label]=\"'list_grid.reset_metadata' | translate\" [outlined]=\"true\" icon=\"pi pi-refresh\"\r\n (onClick)=\"resetColumnLayoutDraftAndWidths()\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveColumnLayoutFromDialog()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.report_parameters' | translate\" [(visible)]=\"reportVariableDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\" (onHide)=\"cancelReportVariableDialog()\">\r\n <div class=\"wuic-save-state-dialog\">\r\n @for (variable of selectedReportVariables; track variable.name; let i = $index) {\r\n <label [for]=\"'wuic-report-var-' + i\">{{ variable.alias || variable.name }}</label>\r\n <input [id]=\"'wuic-report-var-' + i\" [name]=\"'wuic-report-var-name-' + i\" type=\"text\" pInputText\r\n [(ngModel)]=\"variable.value\" />\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"cancelReportVariableDialog()\" />\r\n <p-button [label]=\"'list_grid.open_report' | translate\" (onClick)=\"applyReportVariablesAndOpenViewer()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.pending_changes' | translate\" [(visible)]=\"changesDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '44rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-changes-dialog\">\r\n @if (!pendingChangeItems.length) {\r\n <div>{{ 'list_grid.no_pending_changes' | translate }}</div>\r\n } @else {\r\n @for (item of pendingChangeItems; track item.id) {\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.selected\" />\r\n <span><strong>{{ item.label }}</strong> - {{ item.details }}</span>\r\n </label>\r\n }\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'list_grid.save_all' | translate\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.save_selected' | translate\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !getSelectedPendingChangesCount() || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveSelectedPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n </ng-template>\r\n</p-dialog>", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;max-height:100%;min-height:0;overflow:hidden;--wuic-grid-row-odd: #ffffff;--wuic-grid-row-even: #f6f8fc;--wuic-grid-row-hover: #edf2ff;--wuic-grid-row-selected-bg: #dbe8ff;--wuic-grid-row-selected-text: #0f2348;--wuic-grid-row-selected-ring: rgba(59, 130, 246, .55);--wuic-grid-accent: var(--p-primary-color, #3b82f6);--wuic-grid-base: var(--p-surface-0, #ffffff)}:host-context(.theme-dark){--wuic-grid-row-odd: #1f2430;--wuic-grid-row-even: #252b38;--wuic-grid-row-hover: #31394b;--wuic-grid-row-selected-bg: #284774;--wuic-grid-row-selected-text: #eaf2ff;--wuic-grid-row-selected-ring: rgba(120, 170, 255, .65);--wuic-grid-base: var(--p-surface-900, #1f2430)}@supports (color: color-mix(in srgb,white,black)){:host{--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 96%, var(--wuic-grid-accent) 4%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 90%, var(--wuic-grid-accent) 10%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 82%, var(--wuic-grid-accent) 18%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 56%, var(--wuic-grid-accent) 44%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 72%, transparent)}:host-context(.theme-dark){--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 92%, var(--wuic-grid-accent) 8%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 84%, var(--wuic-grid-accent) 16%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 72%, var(--wuic-grid-accent) 28%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 36%, var(--wuic-grid-accent) 64%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 82%, transparent)}}.list-grid-container{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-view{flex:1 1 auto;min-height:0}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;height:auto}:host ::ng-deep .p-datatable-table-container:has(>table.wuic-nested-grid),:host ::ng-deep .p-datatable:has(>.p-datatable-table-container>table.wuic-nested-grid),:host ::ng-deep .list-grid-container:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid-lazy:has(table.wuic-nested-grid),:host ::ng-deep .data-repeater-body:has(table.wuic-nested-grid),:host ::ng-deep td:has(wuic-data-repeater table.wuic-nested-grid){overflow:visible!important;max-height:none!important}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead{position:sticky!important;top:var(--wuic-outer-thead-h, 0px);z-index:3}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead>tr>th{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(odd)>td{background:var(--wuic-grid-row-odd)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(2n)>td{background:var(--wuic-grid-row-even)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):hover>td{background:var(--wuic-grid-row-hover)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-style-applied:not(.p-highlight):not(.wuic-row-selected)>td{background:transparent!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):not(.wuic-row-selected)>td.wuic-cell-style-applied{background:transparent}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td{background:var(--wuic-grid-row-selected-bg)!important;color:var(--wuic-grid-row-selected-text);box-shadow:inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td:first-child{box-shadow:inset 4px 0 0 var(--wuic-grid-accent),inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected:hover>td{background:var(--wuic-grid-row-selected-bg)!important}:host ::ng-deep td.p-datatable-frozen-column,:host ::ng-deep th.p-datatable-frozen-column{border-right:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep td.wuic-action-cell,:host ::ng-deep th.command-grid-column{width:64px!important;min-width:64px!important;max-width:64px!important;box-sizing:border-box;text-align:center;vertical-align:middle}:host ::ng-deep td.wuic-action-cell wuic-data-action-button-lazy,:host ::ng-deep td.wuic-action-cell wuic-data-action-button{display:inline-flex;align-items:center;justify-content:center}:host ::ng-deep .p-datatable-tbody>tr>td:first-child,:host ::ng-deep .p-datatable-thead>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-box,:host ::ng-deep .p-datatable-tbody>tr>td input.p-checkbox-box{width:18px;height:18px;border:1px solid #9ca3af;border-radius:4px;background:#fff;box-sizing:border-box;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th.wuic-selection-th{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td>input.p-checkbox-box{margin-left:auto!important;margin-right:auto!important;display:block}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-icon{font-size:.72rem}:host ::ng-deep .p-datatable-table.width_defined{display:table}:host ::ng-deep .p-datatable-table{display:table}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table{width:100%!important;min-width:100%}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>td,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>th{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>td:first-child,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td{min-width:0;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable>.p-paginator{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator{border-top:2px solid currentColor;background:var(--p-content-background, #fff);font-weight:600;padding:.5rem .75rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page{min-width:2.25rem;min-height:2.25rem;border:2px solid currentColor!important;border-radius:4px;margin:0 2px;font-weight:700;background:transparent;color:currentColor}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled){background:currentColor;color:var(--p-content-background, #fff)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible{outline:3px solid currentColor;outline-offset:2px}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled{opacity:.45;cursor:not-allowed}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:var(--p-primary-color, #2563eb)!important;color:var(--p-primary-contrast-color, #fff)!important;border-color:var(--p-primary-color, #2563eb)!important;border-width:3px!important;font-weight:800}@media(forced-colors:active){:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:Highlight!important;color:HighlightText!important;border-color:Highlight!important;forced-color-adjust:none}}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current{font-weight:700;padding:0 .5rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select{border:2px solid currentColor!important;font-weight:700;min-height:2.25rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within{outline:3px solid currentColor;outline-offset:2px}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td .list-grid-cell-text-content,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td>a{display:block;width:0;min-width:100%;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap!important;overflow-wrap:anywhere}.wuic-save-state-dialog{display:flex;flex-direction:column;gap:.6rem}.wuic-save-state-dialog select,.wuic-save-state-dialog input{width:100%}.wuic-column-layout-dialog{display:flex;flex-direction:column;gap:.45rem;max-height:55vh;overflow:auto}.wuic-column-layout-row{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.35rem 0}.wuic-column-layout-check{display:inline-flex;align-items:center;gap:.5rem}.wuic-column-layout-actions{display:inline-flex;gap:.35rem}.wuic-changes-dialog{display:flex;flex-direction:column;gap:.5rem;max-height:55vh;overflow:auto}.wuic-export-progress-dialog{display:flex;flex-direction:column;gap:.65rem}::ng-deep .wuic-progress-dialog .p-progressbar{--p-progressbar-value-transition: none}::ng-deep .wuic-progress-dialog .p-progressbar .p-progressbar-value,::ng-deep .wuic-progress-dialog .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar-value{transition:none!important;animation:none!important}.wuic-export-progress-detail{font-size:.9rem;font-weight:600;text-align:right}.wuic-import-dialog{display:flex;flex-direction:column;gap:.75rem}.wuic-import-grid{display:grid;grid-template-columns:1fr;gap:.35rem}.wuic-import-file-row{display:flex;align-items:center;gap:.65rem}.grid-caption-bar{display:flex;align-items:stretch;justify-content:space-between;gap:.75rem;flex-wrap:nowrap}.grid-caption-left,.grid-caption-right{display:flex;align-items:stretch;gap:.5rem;flex-wrap:nowrap;min-height:2.25rem;height:2.25rem}.grid-caption-right{margin-left:auto}.grid-caption-left>*,.grid-caption-right>*{margin:0;align-self:stretch}.grid-changes-indicator{display:inline-flex;align-items:center;gap:.3rem;padding:0 .55rem;border:1px solid #f59e0b;border-radius:999px;background:#fffbeb;color:#b45309;font-size:.82rem;font-weight:700;cursor:pointer;appearance:none}.grid-changes-count{min-width:1.2rem;text-align:center}.wuic-grid-state-select{min-width:220px}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button,:host ::ng-deep .grid-caption-bar .p-inputtext{min-height:2.25rem;height:2.25rem;padding-top:.45rem;padding-bottom:.45rem;margin:0;box-sizing:border-box;line-height:1.1rem;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button,:host ::ng-deep .grid-caption-bar p-splitbutton{display:inline-flex;align-items:stretch;margin:0;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button>.p-button{min-height:2.25rem;height:2.25rem;display:inline-flex;align-items:center}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{padding-left:.75rem;padding-right:.75rem}:host ::ng-deep .grid-caption-bar .p-splitbutton{display:inline-flex;align-items:stretch;margin:0}:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{height:100%}:host ::ng-deep .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .grid-caption-bar .p-button .p-button-icon{line-height:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td{padding-top:.35rem;padding-bottom:.35rem;vertical-align:middle;position:relative}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within{overflow:visible!important;z-index:50}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper>div{overflow:visible!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{position:relative;z-index:100;min-width:360px!important;max-width:80vw!important;background:var(--p-content-background, var(--p-surface-0, #fff));box-shadow:0 4px 12px #0000002e;border:1px solid var(--p-primary-color, #3b82f6);border-radius:4px}:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{background:var(--p-surface-900, #1f2430)}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{display:block;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor{display:flex!important;align-items:center!important;min-height:2.2rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper{margin:0!important;padding:0!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .caption-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{width:100%!important;max-width:100%!important;min-width:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper{display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{min-height:2.2rem;height:2.2rem;line-height:1.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-autocomplete .p-inputtext{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .disabled-textfield{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete>svg[data-p-icon]{display:none!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete .p-autocomplete-input{padding-right:.5rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within p-autocomplete>svg[data-p-icon]{z-index:101!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker{display:flex;align-items:stretch}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-input,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-dropdown{min-height:2.2rem;height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-disabled .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext:disabled{opacity:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor{display:flex;align-items:center;min-height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper{margin:0!important;padding:0!important;width:100%!important;max-width:100%!important;min-width:0!important;display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important;width:100%!important;max-width:100%!important;min-width:0!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}.command-grid-column{width:65px}.multiple-selection-grid-column{width:50px}.nested-grid-column{width:30px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-thead{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tfoot,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-footer,:host ::ng-deep .wuic-list-grid-mobile tfoot{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-resizable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table{display:block}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody{display:block;width:100%;padding-top:12px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr{display:block;width:100%;box-sizing:border-box;margin-bottom:12px;border-radius:12px;overflow:hidden;background:var(--p-content-background, var(--wuic-grid-row-odd));border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 80%, var(--wuic-grid-accent) 20%))}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr:last-child{margin-bottom:0}:host ::ng-deep .wuic-list-grid-mobile td.wuic-mobile-card-cell{display:block;width:100%;padding:14px;border-bottom:1px solid var(--surface-border, #e5e7eb);box-sizing:border-box}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table{width:100%!important;min-width:unset!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-splitbutton .p-button .p-button-label{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button{padding-left:.5rem!important;padding-right:.5rem!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-icon{margin-right:0!important;margin-left:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator{display:flex!important;flex-wrap:nowrap;justify-content:center;align-items:center;gap:4px;padding:8px 4px}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-current{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-pages{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-first,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-prev,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-next,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-last{min-width:36px!important;height:36px!important;margin:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-rpp-options,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-select{min-width:56px!important;height:36px!important;margin-left:4px!important}.wuic-mobile-card-list{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:18px;padding:14px;overflow:auto}.wuic-mobile-card-slot{padding-bottom:18px;box-sizing:border-box}.wuic-mobile-card-toolbar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:4px 0;border-bottom:1px solid color-mix(in srgb,var(--wuic-grid-base) 80%,var(--wuic-grid-accent) 20%);margin-bottom:4px}.wuic-mobile-card{display:flex;flex-direction:column;gap:6px;padding:14px 16px;border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 75%, var(--wuic-grid-accent) 25%));border-radius:12px;background:var(--p-content-background, var(--wuic-grid-row-odd));color:var(--p-text-color, inherit);box-shadow:0 4px 14px #0f172a14}:host-context(.theme-dark) .wuic-mobile-card{box-shadow:0 2px 8px #00000059}.wuic-mobile-card.wuic-row-selected{background:var(--wuic-grid-row-selected-bg);color:var(--wuic-grid-row-selected-text);outline:2px solid var(--wuic-grid-row-selected-ring)}:host ::ng-deep .wuic-mobile-card-actions{display:flex;justify-content:flex-end;align-items:center;margin:0 -4px 4px;padding-bottom:6px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-body{display:flex;flex-direction:column;gap:0}:host ::ng-deep .wuic-mobile-card-row{display:grid;grid-template-columns:minmax(110px,38%) 1fr;column-gap:12px;align-items:baseline;padding:8px 2px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-row:last-child{border-bottom:none}:host ::ng-deep .wuic-mobile-card-label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:currentColor;opacity:.75;line-height:1.3;word-break:break-word}:host ::ng-deep .wuic-mobile-card-value{font-size:.95rem;font-weight:500;text-align:right;word-break:break-word;min-width:0;color:currentColor}:host ::ng-deep .wuic-mobile-card-value a{color:var(--p-primary-color, #3b82f6);text-decoration:underline}.wuic-mobile-card-empty{padding:24px 12px;text-align:center;color:#6b7280;font-style:italic}p-menu:not(.p-menu-overlay-visible){display:none}@media(max-width:420px){:host ::ng-deep .wuic-mobile-card-row{grid-template-columns:1fr;row-gap:2px}:host ::ng-deep .wuic-mobile-card-value{text-align:left}}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => DataSourceComponent), selector: "wuic-data-source", inputs: ["route", "routeFromRouting", "hardcodedRoute", "autoload", "loading", "changeTracking", "parentRecord", "parentMetaInfo", "parentDatasource", "useComboEndpoint", "comboFormulaLookup", "comboMcId", "comboField", "comboExtraFields", "comboCurrentRecord", "componentRef"] }, { kind: "component", type: i0.forwardRef(() => DataRepeaterComponent), selector: "wuic-data-repeater", inputs: ["datasource", "hardcodedDatasource", "action", "hardcodedAction", "field", "record", "rowCustomSelect", "hideToolbar", "isEditForm", "fillHeight"], outputs: ["templateReady", "archetypeInstanceReady", "archetypeEvent", "archetypeDataBound"] }, { kind: "ngmodule", type: i0.forwardRef(() => TableModule) }, { kind: "component", type: i0.forwardRef(() => i9$1.Table), selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i0.forwardRef(() => i3.PrimeTemplate), selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.SortableColumn), selector: "[pSortableColumn]", inputs: ["pSortableColumn", "pSortableColumnDisabled"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.FrozenColumn), selector: "[pFrozenColumn]", inputs: ["frozen", "alignFrozen"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.ResizableColumn), selector: "[pResizableColumn]", inputs: ["pResizableColumnDisabled"] }, { kind: "directive", type: i0.forwardRef(() => i9$1.ReorderableColumn), selector: "[pReorderableColumn]", inputs: ["pReorderableColumnDisabled"] }, { kind: "component", type: i0.forwardRef(() => i9$1.SortIcon), selector: "p-sortIcon", inputs: ["field"] }, { kind: "component", type: i0.forwardRef(() => i9$1.TableHeaderCheckbox), selector: "p-tableHeaderCheckbox", inputs: ["disabled", "inputId", "name", "ariaLabel"] }, { kind: "component", type: i0.forwardRef(() => i9$1.ColumnFilter), selector: "p-columnFilter, p-column-filter, p-columnfilter", inputs: ["field", "type", "display", "showMenu", "matchMode", "operator", "showOperator", "showClearButton", "showApplyButton", "showMatchModes", "showAddButton", "hideOnClear", "placeholder", "matchModeOptions", "maxConstraints", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "locale", "localeMatcher", "currency", "currencyDisplay", "filterOn", "useGrouping", "showButtons", "ariaLabel", "filterButtonProps", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: i0.forwardRef(() => ButtonModule) }, { kind: "component", type: i0.forwardRef(() => i4.Button), selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: i0.forwardRef(() => SplitButtonModule) }, { kind: "component", type: i0.forwardRef(() => i12.SplitButton), selector: "p-splitbutton, p-splitButton, p-split-button", inputs: ["model", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "icon", "iconPos", "label", "tooltip", "tooltipOptions", "styleClass", "menuStyle", "menuStyleClass", "dropdownIcon", "appendTo", "dir", "expandAriaLabel", "showTransitionOptions", "hideTransitionOptions", "motionOptions", "buttonProps", "menuButtonProps", "autofocus", "disabled", "tabindex", "menuButtonDisabled", "buttonDisabled"], outputs: ["onClick", "onMenuHide", "onMenuShow", "onDropdownClick"] }, { kind: "ngmodule", type: i0.forwardRef(() => DialogModule) }, { kind: "component", type: i0.forwardRef(() => i2.Dialog), selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: i0.forwardRef(() => ContextMenuModule) }, { kind: "component", type: i0.forwardRef(() => i14.ContextMenu), selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: i0.forwardRef(() => MenuModule) }, { kind: "component", type: i0.forwardRef(() => i9.Menu), selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex", "appendTo", "motionOptions"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }, { kind: "ngmodule", type: i0.forwardRef(() => PaginatorModule) }, { kind: "ngmodule", type: i0.forwardRef(() => ScrollerModule) }, { kind: "component", type: i0.forwardRef(() => Tabs), selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i0.forwardRef(() => TabList), selector: "p-tablist" }, { kind: "component", type: i0.forwardRef(() => Tab), selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: i0.forwardRef(() => TabPanels), selector: "p-tabpanels" }, { kind: "component", type: i0.forwardRef(() => TabPanel), selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "directive", type: i0.forwardRef(() => NgClass), selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: i0.forwardRef(() => FormsModule) }, { kind: "directive", type: i0.forwardRef(() => i1$4.NgSelectOption), selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.ɵNgSelectMultipleOption), selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.DefaultValueAccessor), selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i0.forwardRef(() => i1$4.CheckboxControlValueAccessor), selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i0.forwardRef(() => i1$4.SelectControlValueAccessor), selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i0.forwardRef(() => i1$4.NgControlStatus), selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i0.forwardRef(() => i1$4.NgModel), selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i0.forwardRef(() => InputTextModule) }, { kind: "directive", type: i0.forwardRef(() => i13.InputText), selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i0.forwardRef(() => FieldFilterComponent), selector: "wuic-field-filter", inputs: ["record", "field", "metaInfo", "readOnly", "hideLabel", "operator", "datasource"] }, { kind: "directive", type: i0.forwardRef(() => NgComponentOutlet), selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: i0.forwardRef(() => TranslateModule) }, { kind: "component", type: i0.forwardRef(() => MetadataEditorComponent), selector: "wuic-metadata-editor", inputs: ["datasource", "hardcodedDatasource", "saveCallback", "hideReports", "hideRelatedTableActions", "hideRelatedColumnActions"] }, { kind: "component", type: i0.forwardRef(() => ImportExportButtonComponent), selector: "wuic-import-export-button", inputs: ["routeName", "tableMetadata", "filterInfoProvider"] }, { kind: "pipe", type: i0.forwardRef(() => AsyncPipe), name: "async" }, { kind: "pipe", type: i0.forwardRef(() => i8.TranslatePipe), name: "translate" }, { kind: "pipe", type: i0.forwardRef(() => VisibleFieldListPipe), name: "visibleFieldList" }] });
37470
37995
  }
37471
37996
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ListGridComponent, decorators: [{
37472
37997
  type: Component,
37473
- args: [{ selector: 'wuic-list-grid', imports: [DataSourceComponent, forwardRef(() => DataRepeaterComponent), TableModule, ButtonModule, SplitButtonModule, DialogModule, ContextMenuModule, MenuModule, PaginatorModule, ScrollerModule, Tabs, TabList, Tab, TabPanels, TabPanel, NgClass, AsyncPipe, FormsModule, InputTextModule, FieldFilterComponent, NgComponentOutlet, TranslateModule, VisibleFieldListPipe, MetadataEditorComponent, ImportExportButtonComponent], providers: [Title], template: "<wuic-data-source #metadataColumnsDatasource [hardcodedRoute]=\"' metadati colonne'\"\r\n style=\"display: none;\"></wuic-data-source>\r\n<p-contextMenu #columnCtxMenu [model]=\"columnContextMenuItems\" appendTo=\"body\"></p-contextMenu>\r\n\r\n<div class=\"list-grid-container\" #gridContainer\r\n [class.wuic-list-grid-mobile]=\"gridLayoutIsMobile\">\r\n @if (rowTemplate) {\r\n <p-table #dt1 [columns]=\"cols\" [value]=\"records\" [scrollable]=\"true\"\r\n [scrollHeight]=\"metaInfo.tableMetadata.md_grid_scroll_height ? metaInfo.tableMetadata.md_grid_scroll_height : 'flex'\"\r\n [virtualScroll]=\"isListVirtualizationEnabled()\" [virtualScrollItemSize]=\"getListVirtualizationItemSize()\"\r\n [resizableColumns]=\"true\" [reorderableColumns]=\"true\" columnResizeMode=\"expand\" [rows]=\"pageSize\"\r\n [first]=\"rowNumber\" [lazy]=\"metaInfo.tableMetadata.md_server_side_operations\" [sortField]=\"orderColumn || null\"\r\n [sortOrder]=\"orderColumn ? (orderDir === 'desc' ? -1 : 1) : 0\" [(selection)]=\"selectedItems\"\r\n [dataKey]=\"metaInfo.pKey?.mc_nome_colonna\" [paginator]=\"metaInfo.tableMetadata.md_pageable\"\r\n [rowsPerPageOptions]=\"metaInfo.rowsPerPageOptions\" [totalRecords]=\"totalRecords\"\r\n [showPageLinks]=\"!datasource?.value?.cursorMode\" [showFirstLastIcon]=\"!datasource?.value?.cursorMode\"\r\n [showCurrentPageReport]=\"metaInfo.tableMetadata.md_show_record_count && !datasource?.value?.cursorMode\"\r\n [loading]=\"datasource?.value?.loading | async\"\r\n [currentPageReportTemplate]=\"'Showing {first} to {last} of {totalRecords} entries' | translate\"\r\n (onPage)=\"pageFilterChange($event)\" (onFilter)=\"pageFilterChange($event)\" (onSort)=\"pageFilterChange($event)\"\r\n (selectionChange)=\"onSelectionChange($event)\" (onColResize)=\"onColumnResize($event)\"\r\n (onColReorder)=\"onColumnReorder($event)\" [expandedRowKeys]=\"expandedRows\" (onRowExpand)=\"onRowExpand($event)\"\r\n (onRowCollapse)=\"onRowCollapse($event)\" [selectionMode]=\"rowCustomSelect ? 'single' : undefined\"\r\n [tableStyleClass]=\"getTableStyleClass()\">\r\n <ng-template pTemplate=\"colgroup\" let-columns>\r\n <colgroup>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <col class=\"nested-grid-column\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <col class=\"multiple-selection-grid-column\" />\r\n }\r\n @if (showActionColumn()) {\r\n <col class=\"command-grid-column\" />\r\n }\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n <col [style.width]=\"getColumnWidthCss(col)\" [style.min-width]=\"columnMinWidthCss\" />\r\n }\r\n </colgroup>\r\n </ng-template>\r\n <ng-template pTemplate=\"caption\">\r\n @if (!hideToolbar) {\r\n <div class=\"grid-caption-bar\">\r\n <div class=\"grid-caption-left\">\r\n @if (!metaInfo.tableMetadata.md_hide_refresh) {\r\n <p-button icon=\"pi pi-refresh\" label=\"{{'refresh_grid' | translate}}\" (onClick)=\"refresh()\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_insertable) {\r\n <p-button icon=\"pi pi-plus\" label=\"{{'add_grid' | translate}}\" (onClick)=\"addRecord()\" />\r\n }\r\n @if (tableActionMenuItems?.length) {\r\n <p-button icon=\"pi pi-bars\" [label]=\"'actions' | translate\" (onClick)=\"tableActionsMenu.toggle($event)\" />\r\n <p-menu #tableActionsMenu [popup]=\"true\" [model]=\"tableActionMenuItems\" appendTo=\"body\"\r\n (onShow)=\"reEvaluateActionEnabledStates()\"></p-menu>\r\n }\r\n <wuic-import-export-button [routeName]=\"routeName\"\r\n [tableMetadata]=\"metaInfo?.tableMetadata\"\r\n [filterInfoProvider]=\"getCurrentFilterInfoForExport\"></wuic-import-export-button>\r\n @if (reportMenuItems?.length) {\r\n <p-splitButton icon=\"pi pi-chart-bar\" [label]=\"'list_grid.reports' | translate\" [model]=\"reportMenuItems\"\r\n [menuButtonProps]=\"{ ariaLabel: ('list_grid.reports' | translate) }\"\r\n (onClick)=\"reportMenuItems[0]?.command({})\" />\r\n }\r\n @if (canToggleClientSideCrud()) {\r\n @if (!isClientSideCrudActive()) {\r\n <p-button icon=\"pi pi-database\" [label]=\"'client_side_crud_enable' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"toggleClientSideCrud()\" />\r\n } @else {\r\n <p-button icon=\"pi pi-cloud-upload\" [label]=\"'client_side_crud_sync' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"syncAndDisableClientSideCrud()\" />\r\n <p-button icon=\"pi pi-ban\" [label]=\"'client_side_crud_disable_without_sync' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"clientSideCrudToggleBusy\"\r\n (onClick)=\"discardLocalAndDisableClientSideCrud()\" />\r\n }\r\n }\r\n @if (isInlineBatchSaveEnabled() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <p-button icon=\"pi pi-save\" [label]=\"'list_grid.save_changes' | translate\"\r\n [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\" (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button icon=\"pi pi-times\" [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n }\r\n @if (hasDatasourceChanges() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <button type=\"button\" class=\"grid-changes-indicator\"\r\n [title]=\"'Changes in memory: ' + getDatasourceChangesCount()\" (click)=\"openChangesDialog()\">\r\n <i class=\"pi pi-pencil\"></i>\r\n <span class=\"grid-changes-count\">{{ getDatasourceChangesCount() }}</span>\r\n </button>\r\n }\r\n </div>\r\n <div class=\"grid-caption-right\">\r\n @if (!metaInfo?.tableMetadata?.extraProps?.toolbar?.hideManageState) {\r\n @if (currentRouteSavedStates?.length) {\r\n <select class=\"p-inputtext p-component wuic-grid-state-select\" [(ngModel)]=\"selectedSavedStateId\"\r\n (change)=\"onSavedStateDropdownChange($event)\">\r\n <option value=\"\"></option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n }\r\n <p-button [label]=\"'list_grid.manage_state' | translate\" icon=\"pi pi-bookmark\" iconPos=\"left\"\r\n (onClick)=\"onSaveStateMenuToggle($event, saveStateMenu)\" />\r\n <p-menu #saveStateMenu [popup]=\"true\" [model]=\"saveStateMenuItems\" appendTo=\"body\"></p-menu>\r\n }\r\n @if (!isAdvancedFilterModeEnabled()) {\r\n <p-button [label]=\"'clear_filters' | translate\" icon=\"pi pi-filter-slash\" (onClick)=\"clearFilter(dt1)\" />\r\n }\r\n <!-- <i class=\"pi pi-search\"></i>\r\n <input type=\"text\" pInputText [(ngModel)]=\"searchValue\"\r\n class=\"p-inputtext p-component p-element ng-pristine ng-valid ng-touched\"\r\n (input)=\"dt1.filterGlobal(getTgtVal($event.target), 'contains')\"\r\n [placeholder]=\"'search_keyword' | translate\" /> -->\r\n </div>\r\n </div>\r\n }\r\n </ng-template>\r\n <ng-template pTemplate=\"header\" let-columns>\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <th class=\"nested-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <th class=\"multiple-selection-grid-column\" role=\"presentation\" pFrozenColumn [frozen]=\"true\" alignFrozen=\"left\"\r\n class=\"wuic-selection-th\">\r\n <p-tableHeaderCheckbox [ariaLabel]=\"'Seleziona tutte le righe'\" />\r\n </th>\r\n }\r\n @if (showActionColumn()) {\r\n <th class=\"command-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n <!-- @for (col of columns | visibleFieldList; track col.metaColumn.mc_id) { NON FUNZIONA si spaccano i filter template !!! -->\r\n <!-- *ngFor NON FUNGE SE USI IL trackBy -> si spaccano i filter template -->\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n @if (!!col?.frozen) {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n pFrozenColumn [frozen]=\"true\" [alignFrozen]=\"col?.alignFrozen || 'left'\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n }\r\n </div>\r\n </th>\r\n } @else {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <!-- <ng-container [ngSwitch]=\"col.metaColumn.mc_ui_column_type\"> -->\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n <!-- <p-columnFilter *ngSwitchDefault type=\"text\" [field]=\"col.field\" display=\"menu\" /> -->\r\n <!-- </ng-container> -->\r\n }\r\n </div>\r\n </th>\r\n }\r\n }\r\n <!-- } -->\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"body\" let-rowData let-columns=\"columns\" let-expanded=\"expanded\" let-rowIndex=\"rowIndex\">\r\n <ng-container pTemplate=\"body\"\r\n *ngComponentOutlet=\"rowTemplate; inputs: { columns: columns, rowData: rowData, expanded: expanded, rowIndex: rowIndex, actionButtonRowIsVisible: isActionButtonRowVisible, isListVirtualizationEnabled: isListVirtualizationEnabled.bind(this), inlineCellEditingEnabled: isInlineCellEditingEnabled(), onInlineCellEditorBlur: onInlineCellEditorBlur.bind(this), onInlineCellEditorValueChange: onInlineCellEditorValueChange.bind(this), getRuntimeGridFieldMeta: getRuntimeGridFieldMetaFn, metaInfo: metaInfo, datasource: datasource, MetadatiColonna: MetadatiColonna, dt: dt1, toggleRow: toggleRow, rowSelect: rowSelect, rowCustomSelect: rowCustomSelect }\"></ng-container>\r\n </ng-template>\r\n <ng-template pTemplate=\"expandedrow\" let-rowData let-columns=\"columns\">\r\n <tr>\r\n <td\r\n [attr.colspan]=\"(columns | visibleFieldList).length + (metaInfo.tableMetadata.md_nested_grid_routes ? 1 : 0) + (metaInfo.tableMetadata.md_multiple_selection ? 1 : 0) + (showActionColumn() ? 1 : 0)\">\r\n @if (metaInfo.nestedRoutes?.length == 1) {\r\n <div [ngClass]=\"metaInfo.nestedRoutes[0].nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"metaInfo.nestedRoutes[0].route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <div class=\"list-grid-long-description-container\"\r\n [innerHTML]=\"getNestedLongDescriptionSafeHtml(nestedSource)\">\r\n </div>\r\n\r\n <wuic-data-repeater\r\n [hardcodedAction]=\"metaInfo.nestedRoutes[0].action == '' ? 'list' : metaInfo.nestedRoutes[0].action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n }\r\n @if (metaInfo.nestedRoutes?.length > 1) {\r\n <div>\r\n <p-tabs [value]=\"0\">\r\n <p-tablist>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tab [value]=\"i\">{{ nestedRoute.nestedTabCaption }}</p-tab>\r\n }\r\n </p-tablist>\r\n <p-tabpanels>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tabpanel [value]=\"i\">\r\n <div [ngClass]=\"nestedRoute.nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"nestedRoute.route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <wuic-data-repeater [hardcodedAction]=\"nestedRoute.action == '' ? 'list' : nestedRoute.action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n </p-tabpanel>\r\n }\r\n </p-tabpanels>\r\n </p-tabs>\r\n </div>\r\n }\r\n </td>\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"footer\">\r\n @if (metaInfo.hasFooter) {\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n @if (showActionColumn()) {\r\n <td></td>\r\n }\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <td></td>\r\n }\r\n <td></td>\r\n @for (col of metaInfo.columnMetadata | visibleFieldList; track col.mc_id) {\r\n <td>\r\n @if (col.mc_aggregation) {\r\n <table>\r\n @for (agg of getAggregations(col); track agg.field + '#' + agg.aggregation) {\r\n <tr>\r\n <td>\r\n {{agg.aggregation | translate}}:\r\n </td>\r\n <td>\r\n {{agg.aggregateValue}}\r\n </td>\r\n </tr>\r\n }\r\n </table>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </ng-template>\r\n </p-table>\r\n }\r\n\r\n <!--\r\n Mobile: la list-grid usa la STESSA <p-table> del desktop, sopra.\r\n Il row template (compilato via `DynamicRowTemplateComponent.\r\n getComponentFromTemplate`) cambia contenuto in base a\r\n `deviceAwareness.isMobile` \u2014 vedi `getEffectiveGridRowTemplate` in\r\n list-grid.component.ts: quando mobile, emette una singola <td colspan>\r\n con layout card-style invece di <td> per ogni colonna. CSS\r\n `@media (max-width: 768px)` nel file .scss nasconde <thead>, rende\r\n `display: block` le <tr>/<td> per liberarsi della semantica table.\r\n Vantaggio: un solo code path (riusa il JIT compile del row template\r\n gia' testato) eliminando il bug `*ngComponentOutlet [inputs]` che\r\n affliggeva il percorso mobile-card separato.\r\n -->\r\n</div>\r\n\r\n<p-dialog [header]=\"'list_grid.save_state_dialog_header' | translate\" [(visible)]=\"saveGridStateDialogVisible\"\r\n [modal]=\"true\" [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-save-state-select\">{{ 'list_grid.state' | translate }}</label>\r\n <select id=\"wuic-save-state-select\" class=\"p-inputtext p-component\" [(ngModel)]=\"saveGridStateDialogSelectedId\"\r\n (change)=\"onSaveGridStateDialogSelectionChange()\">\r\n <option [value]=\"NEW_GRID_STATE_OPTION_ID\">{{ 'new' | translate }}</option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n\r\n @if (saveGridStateDialogSelectedId === NEW_GRID_STATE_OPTION_ID) {\r\n <label for=\"wuic-save-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-save-state-name\" type=\"text\" pInputText [(ngModel)]=\"saveGridStateDialogNewName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n }\r\n\r\n <label for=\"wuic-save-state-default\" class=\"wuic-column-layout-check\">\r\n <input id=\"wuic-save-state-default\" type=\"checkbox\" [(ngModel)]=\"saveGridStateDialogSetAsDefault\" />\r\n <span>{{ 'list_grid.set_as_default' | translate }}</span>\r\n </label>\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"saveGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveCurrentRouteGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.rename_state' | translate\" [(visible)]=\"renameGridStateDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '30rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-rename-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-rename-state-name\" type=\"text\" pInputText [(ngModel)]=\"renameGridStateDialogName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"renameGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"renameSelectedGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.configure_columns' | translate\" [(visible)]=\"columnLayoutDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '36rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-column-layout-dialog\">\r\n @for (item of columnLayoutDraft; track item.field + '#' + $index; let i = $index) {\r\n <div class=\"wuic-column-layout-row\">\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.visible\" />\r\n <span>{{ item.header }}</span>\r\n </label>\r\n <div class=\"wuic-column-layout-actions\">\r\n <p-button icon=\"pi pi-arrow-up\" [outlined]=\"true\" [disabled]=\"i === 0\"\r\n (onClick)=\"moveColumnLayoutDraft(i, -1)\" />\r\n <p-button icon=\"pi pi-arrow-down\" [outlined]=\"true\" [disabled]=\"i === (columnLayoutDraft.length - 1)\"\r\n (onClick)=\"moveColumnLayoutDraft(i, 1)\" />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"columnLayoutDialogVisible = false\" />\r\n <p-button [label]=\"'list_grid.reset_metadata' | translate\" [outlined]=\"true\" icon=\"pi pi-refresh\"\r\n (onClick)=\"resetColumnLayoutDraftAndWidths()\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveColumnLayoutFromDialog()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.report_parameters' | translate\" [(visible)]=\"reportVariableDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\" (onHide)=\"cancelReportVariableDialog()\">\r\n <div class=\"wuic-save-state-dialog\">\r\n @for (variable of selectedReportVariables; track variable.name; let i = $index) {\r\n <label [for]=\"'wuic-report-var-' + i\">{{ variable.alias || variable.name }}</label>\r\n <input [id]=\"'wuic-report-var-' + i\" [name]=\"'wuic-report-var-name-' + i\" type=\"text\" pInputText\r\n [(ngModel)]=\"variable.value\" />\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"cancelReportVariableDialog()\" />\r\n <p-button [label]=\"'list_grid.open_report' | translate\" (onClick)=\"applyReportVariablesAndOpenViewer()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.pending_changes' | translate\" [(visible)]=\"changesDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '44rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-changes-dialog\">\r\n @if (!pendingChangeItems.length) {\r\n <div>{{ 'list_grid.no_pending_changes' | translate }}</div>\r\n } @else {\r\n @for (item of pendingChangeItems; track item.id) {\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.selected\" />\r\n <span><strong>{{ item.label }}</strong> - {{ item.details }}</span>\r\n </label>\r\n }\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'list_grid.save_all' | translate\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.save_selected' | translate\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !getSelectedPendingChangesCount() || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveSelectedPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n </ng-template>\r\n</p-dialog>", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;max-height:100%;min-height:0;overflow:hidden;--wuic-grid-row-odd: #ffffff;--wuic-grid-row-even: #f6f8fc;--wuic-grid-row-hover: #edf2ff;--wuic-grid-row-selected-bg: #dbe8ff;--wuic-grid-row-selected-text: #0f2348;--wuic-grid-row-selected-ring: rgba(59, 130, 246, .55);--wuic-grid-accent: var(--p-primary-color, #3b82f6);--wuic-grid-base: var(--p-surface-0, #ffffff)}:host-context(.theme-dark){--wuic-grid-row-odd: #1f2430;--wuic-grid-row-even: #252b38;--wuic-grid-row-hover: #31394b;--wuic-grid-row-selected-bg: #284774;--wuic-grid-row-selected-text: #eaf2ff;--wuic-grid-row-selected-ring: rgba(120, 170, 255, .65);--wuic-grid-base: var(--p-surface-900, #1f2430)}@supports (color: color-mix(in srgb,white,black)){:host{--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 96%, var(--wuic-grid-accent) 4%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 90%, var(--wuic-grid-accent) 10%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 82%, var(--wuic-grid-accent) 18%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 56%, var(--wuic-grid-accent) 44%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 72%, transparent)}:host-context(.theme-dark){--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 92%, var(--wuic-grid-accent) 8%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 84%, var(--wuic-grid-accent) 16%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 72%, var(--wuic-grid-accent) 28%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 36%, var(--wuic-grid-accent) 64%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 82%, transparent)}}.list-grid-container{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-view{flex:1 1 auto;min-height:0}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;height:auto}:host ::ng-deep .p-datatable-table-container:has(>table.wuic-nested-grid),:host ::ng-deep .p-datatable:has(>.p-datatable-table-container>table.wuic-nested-grid),:host ::ng-deep .list-grid-container:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid-lazy:has(table.wuic-nested-grid),:host ::ng-deep .data-repeater-body:has(table.wuic-nested-grid),:host ::ng-deep td:has(wuic-data-repeater table.wuic-nested-grid){overflow:visible!important;max-height:none!important}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead{position:sticky!important;top:var(--wuic-outer-thead-h, 0px);z-index:3}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead>tr>th{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(odd)>td{background:var(--wuic-grid-row-odd)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(2n)>td{background:var(--wuic-grid-row-even)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):hover>td{background:var(--wuic-grid-row-hover)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td{background:var(--wuic-grid-row-selected-bg)!important;color:var(--wuic-grid-row-selected-text);box-shadow:inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td:first-child{box-shadow:inset 4px 0 0 var(--wuic-grid-accent),inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected:hover>td{background:var(--wuic-grid-row-selected-bg)!important}:host ::ng-deep td.p-datatable-frozen-column,:host ::ng-deep th.p-datatable-frozen-column{border-right:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep td.wuic-action-cell,:host ::ng-deep th.command-grid-column{width:64px!important;min-width:64px!important;max-width:64px!important;box-sizing:border-box;text-align:center;vertical-align:middle}:host ::ng-deep td.wuic-action-cell wuic-data-action-button-lazy,:host ::ng-deep td.wuic-action-cell wuic-data-action-button{display:inline-flex;align-items:center;justify-content:center}:host ::ng-deep .p-datatable-tbody>tr>td:first-child,:host ::ng-deep .p-datatable-thead>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-box,:host ::ng-deep .p-datatable-tbody>tr>td input.p-checkbox-box{width:18px;height:18px;border:1px solid #9ca3af;border-radius:4px;background:#fff;box-sizing:border-box;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th.wuic-selection-th{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td>input.p-checkbox-box{margin-left:auto!important;margin-right:auto!important;display:block}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-icon{font-size:.72rem}:host ::ng-deep .p-datatable-table.width_defined{display:table}:host ::ng-deep .p-datatable-table{display:table}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table{width:100%!important;min-width:100%}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>td,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>th{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>td:first-child,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td{min-width:0;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable>.p-paginator{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator{border-top:2px solid currentColor;background:var(--p-content-background, #fff);font-weight:600;padding:.5rem .75rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page{min-width:2.25rem;min-height:2.25rem;border:2px solid currentColor!important;border-radius:4px;margin:0 2px;font-weight:700;background:transparent;color:currentColor}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled){background:currentColor;color:var(--p-content-background, #fff)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible{outline:3px solid currentColor;outline-offset:2px}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled{opacity:.45;cursor:not-allowed}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:var(--p-primary-color, #2563eb)!important;color:var(--p-primary-contrast-color, #fff)!important;border-color:var(--p-primary-color, #2563eb)!important;border-width:3px!important;font-weight:800}@media(forced-colors:active){:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:Highlight!important;color:HighlightText!important;border-color:Highlight!important;forced-color-adjust:none}}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current{font-weight:700;padding:0 .5rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select{border:2px solid currentColor!important;font-weight:700;min-height:2.25rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within{outline:3px solid currentColor;outline-offset:2px}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td .list-grid-cell-text-content,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td>a{display:block;width:0;min-width:100%;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap!important;overflow-wrap:anywhere}.wuic-save-state-dialog{display:flex;flex-direction:column;gap:.6rem}.wuic-save-state-dialog select,.wuic-save-state-dialog input{width:100%}.wuic-column-layout-dialog{display:flex;flex-direction:column;gap:.45rem;max-height:55vh;overflow:auto}.wuic-column-layout-row{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.35rem 0}.wuic-column-layout-check{display:inline-flex;align-items:center;gap:.5rem}.wuic-column-layout-actions{display:inline-flex;gap:.35rem}.wuic-changes-dialog{display:flex;flex-direction:column;gap:.5rem;max-height:55vh;overflow:auto}.wuic-export-progress-dialog{display:flex;flex-direction:column;gap:.65rem}::ng-deep .wuic-progress-dialog .p-progressbar{--p-progressbar-value-transition: none}::ng-deep .wuic-progress-dialog .p-progressbar .p-progressbar-value,::ng-deep .wuic-progress-dialog .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar-value{transition:none!important;animation:none!important}.wuic-export-progress-detail{font-size:.9rem;font-weight:600;text-align:right}.wuic-import-dialog{display:flex;flex-direction:column;gap:.75rem}.wuic-import-grid{display:grid;grid-template-columns:1fr;gap:.35rem}.wuic-import-file-row{display:flex;align-items:center;gap:.65rem}.grid-caption-bar{display:flex;align-items:stretch;justify-content:space-between;gap:.75rem;flex-wrap:nowrap}.grid-caption-left,.grid-caption-right{display:flex;align-items:stretch;gap:.5rem;flex-wrap:nowrap;min-height:2.25rem;height:2.25rem}.grid-caption-right{margin-left:auto}.grid-caption-left>*,.grid-caption-right>*{margin:0;align-self:stretch}.grid-changes-indicator{display:inline-flex;align-items:center;gap:.3rem;padding:0 .55rem;border:1px solid #f59e0b;border-radius:999px;background:#fffbeb;color:#b45309;font-size:.82rem;font-weight:700;cursor:pointer;appearance:none}.grid-changes-count{min-width:1.2rem;text-align:center}.wuic-grid-state-select{min-width:220px}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button,:host ::ng-deep .grid-caption-bar .p-inputtext{min-height:2.25rem;height:2.25rem;padding-top:.45rem;padding-bottom:.45rem;margin:0;box-sizing:border-box;line-height:1.1rem;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button,:host ::ng-deep .grid-caption-bar p-splitbutton{display:inline-flex;align-items:stretch;margin:0;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button>.p-button{min-height:2.25rem;height:2.25rem;display:inline-flex;align-items:center}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{padding-left:.75rem;padding-right:.75rem}:host ::ng-deep .grid-caption-bar .p-splitbutton{display:inline-flex;align-items:stretch;margin:0}:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{height:100%}:host ::ng-deep .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .grid-caption-bar .p-button .p-button-icon{line-height:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td{padding-top:.35rem;padding-bottom:.35rem;vertical-align:middle;position:relative}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within{overflow:visible!important;z-index:50}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper>div{overflow:visible!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{position:relative;z-index:100;min-width:360px!important;max-width:80vw!important;background:var(--p-content-background, var(--p-surface-0, #fff));box-shadow:0 4px 12px #0000002e;border:1px solid var(--p-primary-color, #3b82f6);border-radius:4px}:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{background:var(--p-surface-900, #1f2430)}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{display:block;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor{display:flex!important;align-items:center!important;min-height:2.2rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper{margin:0!important;padding:0!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .caption-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{width:100%!important;max-width:100%!important;min-width:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper{display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{min-height:2.2rem;height:2.2rem;line-height:1.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-autocomplete .p-inputtext{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .disabled-textfield{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete>svg[data-p-icon]{display:none!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete .p-autocomplete-input{padding-right:.5rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within p-autocomplete>svg[data-p-icon]{z-index:101!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker{display:flex;align-items:stretch}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-input,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-dropdown{min-height:2.2rem;height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-disabled .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext:disabled{opacity:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor{display:flex;align-items:center;min-height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper{margin:0!important;padding:0!important;width:100%!important;max-width:100%!important;min-width:0!important;display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important;width:100%!important;max-width:100%!important;min-width:0!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}.command-grid-column{width:65px}.multiple-selection-grid-column{width:50px}.nested-grid-column{width:30px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-thead{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tfoot,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-footer,:host ::ng-deep .wuic-list-grid-mobile tfoot{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-resizable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table{display:block}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody{display:block;width:100%;padding-top:12px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr{display:block;width:100%;box-sizing:border-box;margin-bottom:12px;border-radius:12px;overflow:hidden;background:var(--p-content-background, var(--wuic-grid-row-odd));border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 80%, var(--wuic-grid-accent) 20%))}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr:last-child{margin-bottom:0}:host ::ng-deep .wuic-list-grid-mobile td.wuic-mobile-card-cell{display:block;width:100%;padding:14px;border-bottom:1px solid var(--surface-border, #e5e7eb);box-sizing:border-box}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table{width:100%!important;min-width:unset!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-splitbutton .p-button .p-button-label{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button{padding-left:.5rem!important;padding-right:.5rem!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-icon{margin-right:0!important;margin-left:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator{display:flex!important;flex-wrap:nowrap;justify-content:center;align-items:center;gap:4px;padding:8px 4px}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-current{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-pages{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-first,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-prev,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-next,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-last{min-width:36px!important;height:36px!important;margin:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-rpp-options,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-select{min-width:56px!important;height:36px!important;margin-left:4px!important}.wuic-mobile-card-list{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:18px;padding:14px;overflow:auto}.wuic-mobile-card-slot{padding-bottom:18px;box-sizing:border-box}.wuic-mobile-card-toolbar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:4px 0;border-bottom:1px solid color-mix(in srgb,var(--wuic-grid-base) 80%,var(--wuic-grid-accent) 20%);margin-bottom:4px}.wuic-mobile-card{display:flex;flex-direction:column;gap:6px;padding:14px 16px;border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 75%, var(--wuic-grid-accent) 25%));border-radius:12px;background:var(--p-content-background, var(--wuic-grid-row-odd));color:var(--p-text-color, inherit);box-shadow:0 4px 14px #0f172a14}:host-context(.theme-dark) .wuic-mobile-card{box-shadow:0 2px 8px #00000059}.wuic-mobile-card.wuic-row-selected{background:var(--wuic-grid-row-selected-bg);color:var(--wuic-grid-row-selected-text);outline:2px solid var(--wuic-grid-row-selected-ring)}:host ::ng-deep .wuic-mobile-card-actions{display:flex;justify-content:flex-end;align-items:center;margin:0 -4px 4px;padding-bottom:6px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-body{display:flex;flex-direction:column;gap:0}:host ::ng-deep .wuic-mobile-card-row{display:grid;grid-template-columns:minmax(110px,38%) 1fr;column-gap:12px;align-items:baseline;padding:8px 2px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-row:last-child{border-bottom:none}:host ::ng-deep .wuic-mobile-card-label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:currentColor;opacity:.75;line-height:1.3;word-break:break-word}:host ::ng-deep .wuic-mobile-card-value{font-size:.95rem;font-weight:500;text-align:right;word-break:break-word;min-width:0;color:currentColor}:host ::ng-deep .wuic-mobile-card-value a{color:var(--p-primary-color, #3b82f6);text-decoration:underline}.wuic-mobile-card-empty{padding:24px 12px;text-align:center;color:#6b7280;font-style:italic}p-menu:not(.p-menu-overlay-visible){display:none}@media(max-width:420px){:host ::ng-deep .wuic-mobile-card-row{grid-template-columns:1fr;row-gap:2px}:host ::ng-deep .wuic-mobile-card-value{text-align:left}}\n"] }]
37998
+ args: [{ selector: 'wuic-list-grid', imports: [DataSourceComponent, forwardRef(() => DataRepeaterComponent), TableModule, ButtonModule, SplitButtonModule, DialogModule, ContextMenuModule, MenuModule, PaginatorModule, ScrollerModule, Tabs, TabList, Tab, TabPanels, TabPanel, NgClass, AsyncPipe, FormsModule, InputTextModule, FieldFilterComponent, NgComponentOutlet, TranslateModule, VisibleFieldListPipe, MetadataEditorComponent, ImportExportButtonComponent], providers: [Title], template: "<wuic-data-source #metadataColumnsDatasource [hardcodedRoute]=\"' metadati colonne'\"\r\n style=\"display: none;\"></wuic-data-source>\r\n<p-contextMenu #columnCtxMenu [model]=\"columnContextMenuItems\" appendTo=\"body\"></p-contextMenu>\r\n\r\n<div class=\"list-grid-container\" #gridContainer\r\n [class.wuic-list-grid-mobile]=\"gridLayoutIsMobile\">\r\n @if (rowTemplate) {\r\n <p-table #dt1 [columns]=\"cols\" [value]=\"records\" [scrollable]=\"true\"\r\n [scrollHeight]=\"metaInfo.tableMetadata.md_grid_scroll_height ? metaInfo.tableMetadata.md_grid_scroll_height : 'flex'\"\r\n [virtualScroll]=\"isListVirtualizationEnabled()\" [virtualScrollItemSize]=\"getListVirtualizationItemSize()\"\r\n [resizableColumns]=\"true\" [reorderableColumns]=\"true\" columnResizeMode=\"expand\" [rows]=\"pageSize\"\r\n [first]=\"rowNumber\" [lazy]=\"metaInfo.tableMetadata.md_server_side_operations\" [sortField]=\"orderColumn || null\"\r\n [sortOrder]=\"orderColumn ? (orderDir === 'desc' ? -1 : 1) : 0\" [(selection)]=\"selectedItems\"\r\n [dataKey]=\"metaInfo.pKey?.mc_nome_colonna\" [paginator]=\"metaInfo.tableMetadata.md_pageable\"\r\n [rowsPerPageOptions]=\"metaInfo.rowsPerPageOptions\" [totalRecords]=\"totalRecords\"\r\n [showPageLinks]=\"!datasource?.value?.cursorMode\" [showFirstLastIcon]=\"!datasource?.value?.cursorMode\"\r\n [showCurrentPageReport]=\"metaInfo.tableMetadata.md_show_record_count && !datasource?.value?.cursorMode\"\r\n [loading]=\"datasource?.value?.loading | async\"\r\n [currentPageReportTemplate]=\"'Showing {first} to {last} of {totalRecords} entries' | translate\"\r\n (onPage)=\"pageFilterChange($event)\" (onFilter)=\"pageFilterChange($event)\" (onSort)=\"pageFilterChange($event)\"\r\n (selectionChange)=\"onSelectionChange($event)\" (onColResize)=\"onColumnResize($event)\"\r\n (onColReorder)=\"onColumnReorder($event)\" [expandedRowKeys]=\"expandedRows\" (onRowExpand)=\"onRowExpand($event)\"\r\n (onRowCollapse)=\"onRowCollapse($event)\" [selectionMode]=\"rowCustomSelect ? 'single' : undefined\"\r\n [tableStyleClass]=\"getTableStyleClass()\">\r\n <ng-template pTemplate=\"colgroup\" let-columns>\r\n <colgroup>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <col class=\"nested-grid-column\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <col class=\"multiple-selection-grid-column\" />\r\n }\r\n @if (showActionColumn()) {\r\n <col class=\"command-grid-column\" />\r\n }\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n <col [style.width]=\"getColumnWidthCss(col)\" [style.min-width]=\"columnMinWidthCss\" />\r\n }\r\n </colgroup>\r\n </ng-template>\r\n <ng-template pTemplate=\"caption\">\r\n @if (!hideToolbar) {\r\n <div class=\"grid-caption-bar\">\r\n <div class=\"grid-caption-left\">\r\n @if (!metaInfo.tableMetadata.md_hide_refresh) {\r\n <p-button icon=\"pi pi-refresh\" label=\"{{'refresh_grid' | translate}}\" (onClick)=\"refresh()\" />\r\n }\r\n @if (metaInfo.tableMetadata.md_insertable) {\r\n <p-button icon=\"pi pi-plus\" label=\"{{'add_grid' | translate}}\" (onClick)=\"addRecord()\" />\r\n }\r\n @if (tableActionMenuItems?.length) {\r\n <p-button icon=\"pi pi-bars\" [label]=\"'actions' | translate\" (onClick)=\"tableActionsMenu.toggle($event)\" />\r\n <p-menu #tableActionsMenu [popup]=\"true\" [model]=\"tableActionMenuItems\" appendTo=\"body\"\r\n (onShow)=\"reEvaluateActionEnabledStates()\"></p-menu>\r\n }\r\n <wuic-import-export-button [routeName]=\"routeName\"\r\n [tableMetadata]=\"metaInfo?.tableMetadata\"\r\n [filterInfoProvider]=\"getCurrentFilterInfoForExport\"></wuic-import-export-button>\r\n @if (reportMenuItems?.length) {\r\n <p-splitButton icon=\"pi pi-chart-bar\" [label]=\"'list_grid.reports' | translate\" [model]=\"reportMenuItems\"\r\n [menuButtonProps]=\"{ ariaLabel: ('list_grid.reports' | translate) }\"\r\n (onClick)=\"reportMenuItems[0]?.command({})\" />\r\n }\r\n @if (canToggleClientSideCrud()) {\r\n @if (!isClientSideCrudActive()) {\r\n <p-button icon=\"pi pi-database\" [label]=\"'client_side_crud_enable' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"toggleClientSideCrud()\" />\r\n } @else {\r\n <p-button icon=\"pi pi-cloud-upload\" [label]=\"'client_side_crud_sync' | translate\"\r\n [disabled]=\"clientSideCrudToggleBusy\" (onClick)=\"syncAndDisableClientSideCrud()\" />\r\n <p-button icon=\"pi pi-ban\" [label]=\"'client_side_crud_disable_without_sync' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"clientSideCrudToggleBusy\"\r\n (onClick)=\"discardLocalAndDisableClientSideCrud()\" />\r\n }\r\n }\r\n @if (isInlineBatchSaveEnabled() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <p-button icon=\"pi pi-save\" [label]=\"'list_grid.save_changes' | translate\"\r\n [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\" (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button icon=\"pi pi-times\" [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\"\r\n [outlined]=\"true\" [disabled]=\"changesDialogBusy || !hasDatasourceChanges()\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n }\r\n @if (hasDatasourceChanges() && !metaInfo?.tableMetadata?.extraProps?.toolbar?.hideBatchActions) {\r\n <button type=\"button\" class=\"grid-changes-indicator\"\r\n [title]=\"'Changes in memory: ' + getDatasourceChangesCount()\" (click)=\"openChangesDialog()\">\r\n <i class=\"pi pi-pencil\"></i>\r\n <span class=\"grid-changes-count\">{{ getDatasourceChangesCount() }}</span>\r\n </button>\r\n }\r\n </div>\r\n <div class=\"grid-caption-right\">\r\n @if (!metaInfo?.tableMetadata?.extraProps?.toolbar?.hideManageState) {\r\n @if (currentRouteSavedStates?.length) {\r\n <select class=\"p-inputtext p-component wuic-grid-state-select\" [(ngModel)]=\"selectedSavedStateId\"\r\n (change)=\"onSavedStateDropdownChange($event)\">\r\n <option value=\"\"></option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n }\r\n <p-button [label]=\"'list_grid.manage_state' | translate\" icon=\"pi pi-bookmark\" iconPos=\"left\"\r\n (onClick)=\"onSaveStateMenuToggle($event, saveStateMenu)\" />\r\n <p-menu #saveStateMenu [popup]=\"true\" [model]=\"saveStateMenuItems\" appendTo=\"body\"></p-menu>\r\n }\r\n @if (!isAdvancedFilterModeEnabled()) {\r\n <p-button [label]=\"'clear_filters' | translate\" icon=\"pi pi-filter-slash\" (onClick)=\"clearFilter(dt1)\" />\r\n }\r\n <!-- <i class=\"pi pi-search\"></i>\r\n <input type=\"text\" pInputText [(ngModel)]=\"searchValue\"\r\n class=\"p-inputtext p-component p-element ng-pristine ng-valid ng-touched\"\r\n (input)=\"dt1.filterGlobal(getTgtVal($event.target), 'contains')\"\r\n [placeholder]=\"'search_keyword' | translate\" /> -->\r\n </div>\r\n </div>\r\n }\r\n </ng-template>\r\n <ng-template pTemplate=\"header\" let-columns>\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n <th class=\"nested-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <th class=\"multiple-selection-grid-column\" role=\"presentation\" pFrozenColumn [frozen]=\"true\" alignFrozen=\"left\"\r\n class=\"wuic-selection-th\">\r\n <p-tableHeaderCheckbox [ariaLabel]=\"'Seleziona tutte le righe'\" />\r\n </th>\r\n }\r\n @if (showActionColumn()) {\r\n <th class=\"command-grid-column\" role=\"presentation\" aria-hidden=\"true\" pFrozenColumn [frozen]=\"true\"\r\n alignFrozen=\"left\"></th>\r\n }\r\n <!-- @for (col of columns | visibleFieldList; track col.metaColumn.mc_id) { NON FUNZIONA si spaccano i filter template !!! -->\r\n <!-- *ngFor NON FUNGE SE USI IL trackBy -> si spaccano i filter template -->\r\n @for (col of columns | visibleFieldList; track getStableColumnTrackKey(col, $index)) {\r\n @if (!!col?.frozen) {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n pFrozenColumn [frozen]=\"true\" [alignFrozen]=\"col?.alignFrozen || 'left'\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n }\r\n </div>\r\n </th>\r\n } @else {\r\n <th pResizableColumn pReorderableColumn [attr.data-field]=\"col.field\" [style.width]=\"getColumnWidthCss(col)\"\r\n [style.min-width]=\"columnMinWidthCss\" [class.wuic-column-filter-active]=\"hasActiveFilter(col.metaColumn)\"\r\n (contextmenu)=\"onColumnHeaderContextMenu($event, col, columnCtxMenu)\"\r\n [pSortableColumn]=\"isColumnSortable(col) ? col.field : undefined\">\r\n <div class=\"flex align-items-center\">\r\n <span class=\"wuic-col-header-text\">{{ col.header }}</span>\r\n @if (isColumnSortable(col)) {\r\n <p-sortIcon [field]=\"col.field\" />\r\n }\r\n @if (!isAdvancedFilterModeEnabled() && !col.metaColumn.md_disabilita_filtri &&\r\n col.metaColumn.mc_show_in_filters) {\r\n <!-- <ng-container [ngSwitch]=\"col.metaColumn.mc_ui_column_type\"> -->\r\n <span class=\"wuic-column-filter-wrap\"\r\n [class.wuic-column-filter-wrap-active]=\"hasActiveFilter(col.metaColumn)\">\r\n <p-columnFilter [field]=\"col.metaColumn.mc_nome_colonna\" [matchMode]=\"getPrimeNgMatchMode(col.metaColumn)\"\r\n display=\"menu\" [showMatchModes]=\"false\" [showOperator]=\"false\" [showAddButton]=\"false\"\r\n [showClearButton]=\"false\" and [showApplyButton]=\"false\" #columnFilterRef\r\n [ariaLabel]=\"('filter' | translate) + ' ' + col.header\"\r\n (onShow)=\"onColumnFilterMenuShow(col.metaColumn, columnFilterRef)\">\r\n <ng-template #filter let-value let-filter=\"filterCallback\">\r\n @for (epoch of [getColumnFilterRenderEpoch(col.metaColumn)]; track epoch) {\r\n <wuic-field-filter [record]=\"datasource?.value?.filterDescriptor\" [field]=\"col.metaColumn\"\r\n [metaInfo]=\"metaInfo\" [operator]=\"metaInfo?.operators?.[col.metaColumn.mc_nome_colonna]\"\r\n [datasource]=\"datasource\"></wuic-field-filter>\r\n <!-- Flex layout guarantees the two buttons are always\r\n the same width AND height regardless of label length\r\n or whether bootstrap/primeflex `.row`/`.col-6` are\r\n loaded. `flex: 1 1 0` forces equal widths,\r\n `align-items: stretch` (default) keeps height equal. -->\r\n <div style=\"display: flex; gap: 8px; margin-top: 8px; align-items: stretch;\">\r\n <p-button label=\"{{'apply_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterApply(filter, col.metaColumn, columnFilterRef, $event)\"\r\n class=\"p-button-success\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n <p-button label=\"{{'clear_filter' | translate}}\"\r\n (onClick)=\"onColumnFilterClear(filter, col.metaColumn, columnFilterRef)\"\r\n class=\"p-button-danger\" styleClass=\"w-full h-full\"\r\n [style]=\"{ flex: '1 1 0', width: '100%', height: '100%' }\"></p-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </p-columnFilter>\r\n </span>\r\n <!-- <p-columnFilter *ngSwitchDefault type=\"text\" [field]=\"col.field\" display=\"menu\" /> -->\r\n <!-- </ng-container> -->\r\n }\r\n </div>\r\n </th>\r\n }\r\n }\r\n <!-- } -->\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"body\" let-rowData let-columns=\"columns\" let-expanded=\"expanded\" let-rowIndex=\"rowIndex\">\r\n <ng-container pTemplate=\"body\"\r\n *ngComponentOutlet=\"rowTemplate; inputs: { columns: columns, rowData: rowData, expanded: expanded, rowIndex: rowIndex, actionButtonRowIsVisible: isActionButtonRowVisible, isListVirtualizationEnabled: isListVirtualizationEnabled.bind(this), inlineCellEditingEnabled: isInlineCellEditingEnabled(), onInlineCellEditorBlur: onInlineCellEditorBlur.bind(this), onInlineCellEditorValueChange: onInlineCellEditorValueChange.bind(this), getRuntimeGridFieldMeta: getRuntimeGridFieldMetaFn, metaInfo: metaInfo, datasource: datasource, MetadatiColonna: MetadatiColonna, dt: dt1, toggleRow: toggleRow, rowSelect: rowSelect, rowCustomSelect: rowCustomSelect }\"></ng-container>\r\n </ng-template>\r\n <ng-template pTemplate=\"expandedrow\" let-rowData let-columns=\"columns\">\r\n <tr>\r\n <td\r\n [attr.colspan]=\"(columns | visibleFieldList).length + (metaInfo.tableMetadata.md_nested_grid_routes ? 1 : 0) + (metaInfo.tableMetadata.md_multiple_selection ? 1 : 0) + (showActionColumn() ? 1 : 0)\">\r\n @if (metaInfo.nestedRoutes?.length == 1) {\r\n <div [ngClass]=\"metaInfo.nestedRoutes[0].nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"metaInfo.nestedRoutes[0].route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <div class=\"list-grid-long-description-container\"\r\n [innerHTML]=\"getNestedLongDescriptionSafeHtml(nestedSource)\">\r\n </div>\r\n\r\n <wuic-data-repeater\r\n [hardcodedAction]=\"metaInfo.nestedRoutes[0].action == '' ? 'list' : metaInfo.nestedRoutes[0].action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n }\r\n @if (metaInfo.nestedRoutes?.length > 1) {\r\n <div>\r\n <p-tabs [value]=\"0\">\r\n <p-tablist>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tab [value]=\"i\">{{ nestedRoute.nestedTabCaption }}</p-tab>\r\n }\r\n </p-tablist>\r\n <p-tabpanels>\r\n @for (nestedRoute of metaInfo.nestedRoutes; track nestedRoute; let i = $index) {\r\n <p-tabpanel [value]=\"i\">\r\n <div [ngClass]=\"nestedRoute.nestedGridContainerClass\">\r\n <wuic-data-source #nestedSource [autoload]=\"true\" [routeFromRouting]=\"false\"\r\n [hardcodedRoute]=\"nestedRoute.route\" [parentRecord]=\"rowData\" [parentMetaInfo]=\"metaInfo\"\r\n [loading]=\"loading\"></wuic-data-source>\r\n\r\n @if (nestedSource && userInfo.isCurrentUserAdmin()) {\r\n <wuic-metadata-editor [hardcodedDatasource]=\"nestedSource\"></wuic-metadata-editor>\r\n }\r\n\r\n <wuic-data-repeater [hardcodedAction]=\"nestedRoute.action == '' ? 'list' : nestedRoute.action\"\r\n [hardcodedDatasource]=\"nestedSource\"></wuic-data-repeater>\r\n </div>\r\n </p-tabpanel>\r\n }\r\n </p-tabpanels>\r\n </p-tabs>\r\n </div>\r\n }\r\n </td>\r\n </tr>\r\n </ng-template>\r\n <ng-template pTemplate=\"footer\">\r\n @if (metaInfo.hasFooter) {\r\n <tr>\r\n @if (metaInfo.tableMetadata.md_nested_grid_routes) {\r\n @if (showActionColumn()) {\r\n <td></td>\r\n }\r\n }\r\n @if (metaInfo.tableMetadata.md_multiple_selection) {\r\n <td></td>\r\n }\r\n <td></td>\r\n @for (col of metaInfo.columnMetadata | visibleFieldList; track col.mc_id) {\r\n <td>\r\n @if (col.mc_aggregation) {\r\n <table>\r\n @for (agg of getAggregations(col); track agg.field + '#' + agg.aggregation) {\r\n <tr>\r\n <td>\r\n {{agg.aggregation | translate}}:\r\n </td>\r\n <td>\r\n {{agg.aggregateValue}}\r\n </td>\r\n </tr>\r\n }\r\n </table>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </ng-template>\r\n </p-table>\r\n }\r\n\r\n <!--\r\n Mobile: la list-grid usa la STESSA <p-table> del desktop, sopra.\r\n Il row template (compilato via `DynamicRowTemplateComponent.\r\n getComponentFromTemplate`) cambia contenuto in base a\r\n `deviceAwareness.isMobile` \u2014 vedi `getEffectiveGridRowTemplate` in\r\n list-grid.component.ts: quando mobile, emette una singola <td colspan>\r\n con layout card-style invece di <td> per ogni colonna. CSS\r\n `@media (max-width: 768px)` nel file .scss nasconde <thead>, rende\r\n `display: block` le <tr>/<td> per liberarsi della semantica table.\r\n Vantaggio: un solo code path (riusa il JIT compile del row template\r\n gia' testato) eliminando il bug `*ngComponentOutlet [inputs]` che\r\n affliggeva il percorso mobile-card separato.\r\n -->\r\n</div>\r\n\r\n<p-dialog [header]=\"'list_grid.save_state_dialog_header' | translate\" [(visible)]=\"saveGridStateDialogVisible\"\r\n [modal]=\"true\" [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-save-state-select\">{{ 'list_grid.state' | translate }}</label>\r\n <select id=\"wuic-save-state-select\" class=\"p-inputtext p-component\" [(ngModel)]=\"saveGridStateDialogSelectedId\"\r\n (change)=\"onSaveGridStateDialogSelectionChange()\">\r\n <option [value]=\"NEW_GRID_STATE_OPTION_ID\">{{ 'new' | translate }}</option>\r\n @for (s of currentRouteSavedStates; track s.id) {\r\n <option [value]=\"s.id\">\r\n {{ s.name }}\r\n </option>\r\n }\r\n </select>\r\n\r\n @if (saveGridStateDialogSelectedId === NEW_GRID_STATE_OPTION_ID) {\r\n <label for=\"wuic-save-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-save-state-name\" type=\"text\" pInputText [(ngModel)]=\"saveGridStateDialogNewName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n }\r\n\r\n <label for=\"wuic-save-state-default\" class=\"wuic-column-layout-check\">\r\n <input id=\"wuic-save-state-default\" type=\"checkbox\" [(ngModel)]=\"saveGridStateDialogSetAsDefault\" />\r\n <span>{{ 'list_grid.set_as_default' | translate }}</span>\r\n </label>\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"saveGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveCurrentRouteGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.rename_state' | translate\" [(visible)]=\"renameGridStateDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '30rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-save-state-dialog\">\r\n <label for=\"wuic-rename-state-name\">{{ 'list_grid.new_state_name' | translate }}</label>\r\n <input id=\"wuic-rename-state-name\" type=\"text\" pInputText [(ngModel)]=\"renameGridStateDialogName\"\r\n [placeholder]=\"'list_grid.new_state_name_placeholder' | translate\" />\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"renameGridStateDialogVisible = false\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"renameSelectedGridState()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.configure_columns' | translate\" [(visible)]=\"columnLayoutDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '36rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-column-layout-dialog\">\r\n @for (item of columnLayoutDraft; track item.field + '#' + $index; let i = $index) {\r\n <div class=\"wuic-column-layout-row\">\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.visible\" />\r\n <span>{{ item.header }}</span>\r\n </label>\r\n <div class=\"wuic-column-layout-actions\">\r\n <p-button icon=\"pi pi-arrow-up\" [outlined]=\"true\" [disabled]=\"i === 0\"\r\n (onClick)=\"moveColumnLayoutDraft(i, -1)\" />\r\n <p-button icon=\"pi pi-arrow-down\" [outlined]=\"true\" [disabled]=\"i === (columnLayoutDraft.length - 1)\"\r\n (onClick)=\"moveColumnLayoutDraft(i, 1)\" />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"columnLayoutDialogVisible = false\" />\r\n <p-button [label]=\"'list_grid.reset_metadata' | translate\" [outlined]=\"true\" icon=\"pi pi-refresh\"\r\n (onClick)=\"resetColumnLayoutDraftAndWidths()\" />\r\n <p-button [label]=\"'save' | translate\" (onClick)=\"saveColumnLayoutFromDialog()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.report_parameters' | translate\" [(visible)]=\"reportVariableDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '34rem', maxWidth: '95vw' }\" (onHide)=\"cancelReportVariableDialog()\">\r\n <div class=\"wuic-save-state-dialog\">\r\n @for (variable of selectedReportVariables; track variable.name; let i = $index) {\r\n <label [for]=\"'wuic-report-var-' + i\">{{ variable.alias || variable.name }}</label>\r\n <input [id]=\"'wuic-report-var-' + i\" [name]=\"'wuic-report-var-name-' + i\" type=\"text\" pInputText\r\n [(ngModel)]=\"variable.value\" />\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'cancel' | translate\" [outlined]=\"true\" (onClick)=\"cancelReportVariableDialog()\" />\r\n <p-button [label]=\"'list_grid.open_report' | translate\" (onClick)=\"applyReportVariablesAndOpenViewer()\" />\r\n </ng-template>\r\n</p-dialog>\r\n\r\n<p-dialog [header]=\"'list_grid.pending_changes' | translate\" [(visible)]=\"changesDialogVisible\" [modal]=\"true\"\r\n [closable]=\"true\" [style]=\"{ width: '44rem', maxWidth: '95vw' }\">\r\n <div class=\"wuic-changes-dialog\">\r\n @if (!pendingChangeItems.length) {\r\n <div>{{ 'list_grid.no_pending_changes' | translate }}</div>\r\n } @else {\r\n @for (item of pendingChangeItems; track item.id) {\r\n <label class=\"wuic-column-layout-check\">\r\n <input type=\"checkbox\" [(ngModel)]=\"item.selected\" />\r\n <span><strong>{{ item.label }}</strong> - {{ item.details }}</span>\r\n </label>\r\n }\r\n }\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"'list_grid.save_all' | translate\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveAllPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.save_selected' | translate\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !getSelectedPendingChangesCount() || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"saveSelectedPendingChanges()\" />\r\n <p-button [label]=\"'list_grid.discard_changes' | translate\" severity=\"danger\" [outlined]=\"true\"\r\n [disabled]=\"$any(changesDialogBusy) || !pendingChangeItems.length || !metaInfo?.tableMetadata?.md_batch_save\"\r\n (onClick)=\"cancelAllPendingChanges()\" />\r\n </ng-template>\r\n</p-dialog>", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;max-height:100%;min-height:0;overflow:hidden;--wuic-grid-row-odd: #ffffff;--wuic-grid-row-even: #f6f8fc;--wuic-grid-row-hover: #edf2ff;--wuic-grid-row-selected-bg: #dbe8ff;--wuic-grid-row-selected-text: #0f2348;--wuic-grid-row-selected-ring: rgba(59, 130, 246, .55);--wuic-grid-accent: var(--p-primary-color, #3b82f6);--wuic-grid-base: var(--p-surface-0, #ffffff)}:host-context(.theme-dark){--wuic-grid-row-odd: #1f2430;--wuic-grid-row-even: #252b38;--wuic-grid-row-hover: #31394b;--wuic-grid-row-selected-bg: #284774;--wuic-grid-row-selected-text: #eaf2ff;--wuic-grid-row-selected-ring: rgba(120, 170, 255, .65);--wuic-grid-base: var(--p-surface-900, #1f2430)}@supports (color: color-mix(in srgb,white,black)){:host{--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 96%, var(--wuic-grid-accent) 4%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 90%, var(--wuic-grid-accent) 10%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 82%, var(--wuic-grid-accent) 18%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 56%, var(--wuic-grid-accent) 44%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 72%, transparent)}:host-context(.theme-dark){--wuic-grid-row-odd: color-mix(in srgb, var(--wuic-grid-base) 92%, var(--wuic-grid-accent) 8%);--wuic-grid-row-even: color-mix(in srgb, var(--wuic-grid-base) 84%, var(--wuic-grid-accent) 16%);--wuic-grid-row-hover: color-mix(in srgb, var(--wuic-grid-base) 72%, var(--wuic-grid-accent) 28%);--wuic-grid-row-selected-bg: color-mix(in srgb, var(--wuic-grid-base) 36%, var(--wuic-grid-accent) 64%);--wuic-grid-row-selected-ring: color-mix(in srgb, var(--wuic-grid-accent) 82%, transparent)}}.list-grid-container{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-view{flex:1 1 auto;min-height:0}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-wrapper,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-table-container{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;height:auto}:host ::ng-deep .p-datatable-table-container:has(>table.wuic-nested-grid),:host ::ng-deep .p-datatable:has(>.p-datatable-table-container>table.wuic-nested-grid),:host ::ng-deep .list-grid-container:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid:has(table.wuic-nested-grid),:host ::ng-deep wuic-list-grid-lazy:has(table.wuic-nested-grid),:host ::ng-deep .data-repeater-body:has(table.wuic-nested-grid),:host ::ng-deep td:has(wuic-data-repeater table.wuic-nested-grid){overflow:visible!important;max-height:none!important}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead{position:sticky!important;top:var(--wuic-outer-thead-h, 0px);z-index:3}:host ::ng-deep table.wuic-nested-grid>thead.p-datatable-thead>tr>th{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(odd)>td{background:var(--wuic-grid-row-odd)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):nth-child(2n)>td{background:var(--wuic-grid-row-even)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):hover>td{background:var(--wuic-grid-row-hover)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-style-applied:not(.p-highlight):not(.wuic-row-selected)>td{background:transparent!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr:not(.p-highlight):not(.wuic-row-selected)>td.wuic-cell-style-applied{background:transparent}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td{background:var(--wuic-grid-row-selected-bg)!important;color:var(--wuic-grid-row-selected-text);box-shadow:inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected>td:first-child{box-shadow:inset 4px 0 0 var(--wuic-grid-accent),inset 0 1px 0 var(--wuic-grid-row-selected-ring),inset 0 -1px 0 var(--wuic-grid-row-selected-ring)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr.wuic-row-selected:hover>td{background:var(--wuic-grid-row-selected-bg)!important}:host ::ng-deep td.p-datatable-frozen-column,:host ::ng-deep th.p-datatable-frozen-column{border-right:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep td.wuic-action-cell,:host ::ng-deep th.command-grid-column{width:64px!important;min-width:64px!important;max-width:64px!important;box-sizing:border-box;text-align:center;vertical-align:middle}:host ::ng-deep td.wuic-action-cell wuic-data-action-button-lazy,:host ::ng-deep td.wuic-action-cell wuic-data-action-button{display:inline-flex;align-items:center;justify-content:center}:host ::ng-deep .p-datatable-tbody>tr>td:first-child,:host ::ng-deep .p-datatable-thead>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-box,:host ::ng-deep .p-datatable-tbody>tr>td input.p-checkbox-box{width:18px;height:18px;border:1px solid #9ca3af;border-radius:4px;background:#fff;box-sizing:border-box;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}:host ::ng-deep .p-datatable-thead>tr>th.wuic-selection-th{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td{text-align:center;padding-left:.25rem;padding-right:.25rem}:host ::ng-deep .p-datatable-tbody>tr>td.wuic-selection-td>input.p-checkbox-box{margin-left:auto!important;margin-right:auto!important;display:block}:host ::ng-deep .p-datatable-thead>tr>th .p-checkbox .p-checkbox-icon{font-size:.72rem}:host ::ng-deep .p-datatable-table.width_defined{display:table}:host ::ng-deep .p-datatable-table{display:table}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table{width:100%!important;min-width:100%}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot{background:var(--wuic-grid-base)!important}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>td,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr:first-child>th{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>td:first-child,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-scrollable-table>.p-datatable-tfoot>tr>th:first-child{border-left:solid 1px var(--p-divider-color, #dcdcdc)}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td{min-width:0;overflow:hidden}:host ::ng-deep .list-grid-container .p-datatable>.p-paginator{border-top:solid 1px var(--p-divider-color, #dcdcdc)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator{border-top:2px solid currentColor;background:var(--p-content-background, #fff);font-weight:600;padding:.5rem .75rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page{min-width:2.25rem;min-height:2.25rem;border:2px solid currentColor!important;border-radius:4px;margin:0 2px;font-weight:700;background:transparent;color:currentColor}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:hover:not(:disabled),:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:hover:not(:disabled){background:currentColor;color:var(--p-content-background, #fff)}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:focus-visible{outline:3px solid currentColor;outline-offset:2px}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-first:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-prev:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-next:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-last:disabled,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page:disabled{opacity:.45;cursor:not-allowed}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:var(--p-primary-color, #2563eb)!important;color:var(--p-primary-contrast-color, #fff)!important;border-color:var(--p-primary-color, #2563eb)!important;border-width:3px!important;font-weight:800}@media(forced-colors:active){:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page],:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-paginator-page-selected,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page.p-highlight,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-page[aria-current=page]{background:Highlight!important;color:HighlightText!important;border-color:Highlight!important;forced-color-adjust:none}}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-current{font-weight:700;padding:0 .5rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select{border:2px solid currentColor!important;font-weight:700;min-height:2.25rem}:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(body.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-paginator-rpp-options:focus-within,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-visible,:host-context(html.theme-high-contrast) ::ng-deep .list-grid-container .p-paginator .p-select:focus-within{outline:3px solid currentColor;outline-offset:2px}:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td .list-grid-cell-text-content,:host ::ng-deep .list-grid-container .p-datatable .p-datatable-tbody>tr>td>a{display:block;width:0;min-width:100%;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap!important;overflow-wrap:anywhere}.wuic-save-state-dialog{display:flex;flex-direction:column;gap:.6rem}.wuic-save-state-dialog select,.wuic-save-state-dialog input{width:100%}.wuic-column-layout-dialog{display:flex;flex-direction:column;gap:.45rem;max-height:55vh;overflow:auto}.wuic-column-layout-row{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.35rem 0}.wuic-column-layout-check{display:inline-flex;align-items:center;gap:.5rem}.wuic-column-layout-actions{display:inline-flex;gap:.35rem}.wuic-changes-dialog{display:flex;flex-direction:column;gap:.5rem;max-height:55vh;overflow:auto}.wuic-export-progress-dialog{display:flex;flex-direction:column;gap:.65rem}::ng-deep .wuic-progress-dialog .p-progressbar{--p-progressbar-value-transition: none}::ng-deep .wuic-progress-dialog .p-progressbar .p-progressbar-value,::ng-deep .wuic-progress-dialog .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar .p-progressbar-value,:host ::ng-deep .wuic-export-progress-dialog .p-progressbar-value{transition:none!important;animation:none!important}.wuic-export-progress-detail{font-size:.9rem;font-weight:600;text-align:right}.wuic-import-dialog{display:flex;flex-direction:column;gap:.75rem}.wuic-import-grid{display:grid;grid-template-columns:1fr;gap:.35rem}.wuic-import-file-row{display:flex;align-items:center;gap:.65rem}.grid-caption-bar{display:flex;align-items:stretch;justify-content:space-between;gap:.75rem;flex-wrap:nowrap}.grid-caption-left,.grid-caption-right{display:flex;align-items:stretch;gap:.5rem;flex-wrap:nowrap;min-height:2.25rem;height:2.25rem}.grid-caption-right{margin-left:auto}.grid-caption-left>*,.grid-caption-right>*{margin:0;align-self:stretch}.grid-changes-indicator{display:inline-flex;align-items:center;gap:.3rem;padding:0 .55rem;border:1px solid #f59e0b;border-radius:999px;background:#fffbeb;color:#b45309;font-size:.82rem;font-weight:700;cursor:pointer;appearance:none}.grid-changes-count{min-width:1.2rem;text-align:center}.wuic-grid-state-select{min-width:220px}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button,:host ::ng-deep .grid-caption-bar .p-inputtext{min-height:2.25rem;height:2.25rem;padding-top:.45rem;padding-bottom:.45rem;margin:0;box-sizing:border-box;line-height:1.1rem;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button,:host ::ng-deep .grid-caption-bar p-splitbutton{display:inline-flex;align-items:stretch;margin:0;vertical-align:middle}:host ::ng-deep .grid-caption-bar p-button>.p-button{min-height:2.25rem;height:2.25rem;display:inline-flex;align-items:center}:host ::ng-deep .grid-caption-bar .p-button,:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{padding-left:.75rem;padding-right:.75rem}:host ::ng-deep .grid-caption-bar .p-splitbutton{display:inline-flex;align-items:stretch;margin:0}:host ::ng-deep .grid-caption-bar .p-splitbutton .p-button{height:100%}:host ::ng-deep .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .grid-caption-bar .p-button .p-button-icon{line-height:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td{padding-top:.35rem;padding-bottom:.35rem;vertical-align:middle;position:relative}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within{overflow:visible!important;z-index:50}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .editor-wapper>div{overflow:visible!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{position:relative;z-index:100;min-width:360px!important;max-width:80vw!important;background:var(--p-content-background, var(--p-surface-0, #fff));box-shadow:0 4px 12px #0000002e;border:1px solid var(--p-primary-color, #3b82f6);border-radius:4px}:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-inputnumber .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-autocomplete .p-inputtext,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker,:host-context(.theme-dark) ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within .p-datepicker .p-inputtext{background:var(--p-surface-900, #1f2430)}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{display:block;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td wuic-field-editor{display:flex!important;align-items:center!important;min-height:2.2rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper{margin:0!important;padding:0!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .caption-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{width:100%!important;max-width:100%!important;min-width:0!important;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .editor-wapper{display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-autocomplete .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-select,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-multiselect{min-height:2.2rem;height:2.2rem;line-height:1.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-inputnumber .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-datepicker .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .p-autocomplete .p-inputtext{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) .disabled-textfield{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete>svg[data-p-icon]{display:none!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:not(:focus-within) p-autocomplete .p-autocomplete-input{padding-right:.5rem!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td:focus-within p-autocomplete>svg[data-p-icon]{z-index:101!important}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker{display:flex;align-items:stretch}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-input,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-datepicker .p-datepicker-dropdown{min-height:2.2rem;height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-disabled .p-inputtext,:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .p-inputtext:disabled{opacity:1}:host ::ng-deep .list-grid-container .wuic-inline-cell-editing .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor-lazy,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td wuic-field-editor{display:flex;align-items:center;min-height:2.2rem}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .caption-wrapper{display:none!important;height:0!important;min-height:0!important;margin:0!important;padding:0!important;overflow:hidden!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-wrapper,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .data-field-field,:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper{margin:0!important;padding:0!important;width:100%!important;max-width:100%!important;min-width:0!important;display:flex;align-items:center}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .editor-wapper>div{min-height:0!important;height:auto!important;width:100%!important;max-width:100%!important;min-width:0!important}:host ::ng-deep .list-grid-container .wuic-inline-editing:not(.wuic-inline-cell-editing) .p-datatable-tbody>tr>td .disabled-textfield{margin:0!important;padding:.5rem .75rem!important;min-height:2.2rem!important;height:2.2rem!important;display:flex!important;align-items:center!important;border-radius:6px;box-sizing:border-box}.command-grid-column{width:65px}.multiple-selection-grid-column{width:50px}.nested-grid-column{width:30px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-thead{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tfoot,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-footer,:host ::ng-deep .wuic-list-grid-mobile tfoot{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-resizable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table{display:block}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody{display:block;width:100%;padding-top:12px}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr{display:block;width:100%;box-sizing:border-box;margin-bottom:12px;border-radius:12px;overflow:hidden;background:var(--p-content-background, var(--wuic-grid-row-odd));border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 80%, var(--wuic-grid-accent) 20%))}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-tbody>tr:last-child{margin-bottom:0}:host ::ng-deep .wuic-list-grid-mobile td.wuic-mobile-card-cell{display:block;width:100%;padding:14px;border-bottom:1px solid var(--surface-border, #e5e7eb);box-sizing:border-box}:host ::ng-deep .wuic-list-grid-mobile .p-datatable-scrollable-table,:host ::ng-deep .wuic-list-grid-mobile .p-datatable-table{width:100%!important;min-width:unset!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-label,:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-splitbutton .p-button .p-button-label{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button{padding-left:.5rem!important;padding-right:.5rem!important}:host ::ng-deep .wuic-list-grid-mobile .grid-caption-bar .p-button .p-button-icon{margin-right:0!important;margin-left:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator{display:flex!important;flex-wrap:nowrap;justify-content:center;align-items:center;gap:4px;padding:8px 4px}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-current{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-pages{display:none!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-first,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-prev,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-next,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-last{min-width:36px!important;height:36px!important;margin:0!important}:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-paginator-rpp-options,:host ::ng-deep .wuic-list-grid-mobile .p-paginator .p-select{min-width:56px!important;height:36px!important;margin-left:4px!important}.wuic-mobile-card-list{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:18px;padding:14px;overflow:auto}.wuic-mobile-card-slot{padding-bottom:18px;box-sizing:border-box}.wuic-mobile-card-toolbar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:4px 0;border-bottom:1px solid color-mix(in srgb,var(--wuic-grid-base) 80%,var(--wuic-grid-accent) 20%);margin-bottom:4px}.wuic-mobile-card{display:flex;flex-direction:column;gap:6px;padding:14px 16px;border:1px solid var(--p-content-border-color, color-mix(in srgb, var(--wuic-grid-base) 75%, var(--wuic-grid-accent) 25%));border-radius:12px;background:var(--p-content-background, var(--wuic-grid-row-odd));color:var(--p-text-color, inherit);box-shadow:0 4px 14px #0f172a14}:host-context(.theme-dark) .wuic-mobile-card{box-shadow:0 2px 8px #00000059}.wuic-mobile-card.wuic-row-selected{background:var(--wuic-grid-row-selected-bg);color:var(--wuic-grid-row-selected-text);outline:2px solid var(--wuic-grid-row-selected-ring)}:host ::ng-deep .wuic-mobile-card-actions{display:flex;justify-content:flex-end;align-items:center;margin:0 -4px 4px;padding-bottom:6px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-body{display:flex;flex-direction:column;gap:0}:host ::ng-deep .wuic-mobile-card-row{display:grid;grid-template-columns:minmax(110px,38%) 1fr;column-gap:12px;align-items:baseline;padding:8px 2px;border-bottom:1px solid var(--p-content-border-color, currentColor)}:host ::ng-deep .wuic-mobile-card-row:last-child{border-bottom:none}:host ::ng-deep .wuic-mobile-card-label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:currentColor;opacity:.75;line-height:1.3;word-break:break-word}:host ::ng-deep .wuic-mobile-card-value{font-size:.95rem;font-weight:500;text-align:right;word-break:break-word;min-width:0;color:currentColor}:host ::ng-deep .wuic-mobile-card-value a{color:var(--p-primary-color, #3b82f6);text-decoration:underline}.wuic-mobile-card-empty{padding:24px 12px;text-align:center;color:#6b7280;font-style:italic}p-menu:not(.p-menu-overlay-visible){display:none}@media(max-width:420px){:host ::ng-deep .wuic-mobile-card-row{grid-template-columns:1fr;row-gap:2px}:host ::ng-deep .wuic-mobile-card-value{text-align:left}}\n"] }]
37474
37999
  }], ctorParameters: () => [{ type: MetadataProviderService }, { type: i1$5.DomSanitizer }, { type: i1$2.ActivatedRoute }, { type: i1$2.Router }, { type: i1$3.Location }, { type: i1$5.Title }, { type: TranslationManagerService }, { type: i0.ChangeDetectorRef }, { type: UserInfoService }, { type: i0.ElementRef }, { type: DeviceAwarenessService }, { type: CustomAttributesService, decorators: [{
37475
38000
  type: Optional
37476
38001
  }] }], propDecorators: { hardcodedRoute: [{
@@ -41916,6 +42441,9 @@ class AppSettingsEditorComponent {
41916
42441
  this.sections.set(resp.sections || []);
41917
42442
  this.filePath.set(resp.filePath || '');
41918
42443
  this.rebuildMaps(resp.sections || []);
42444
+ // Popola le dropdown a opzioni dinamiche (es. model list in base al provider).
42445
+ // Fire-and-forget: lo schema renderizza subito, le options arrivano async.
42446
+ this.refreshDynamicOptions();
41919
42447
  // Ripristina il tab attivo dal localStorage (per `name`, non index).
41920
42448
  // Senza questa logica ogni reload (incluso quello post-save+restart)
41921
42449
  // resetta a tab 0, perdendo il contesto dell'utente che stava editando
@@ -42000,10 +42528,54 @@ class AppSettingsEditorComponent {
42000
42528
  onFieldChange(key, newValue) {
42001
42529
  this.current.set(key, newValue);
42002
42530
  this.changeTick.update((t) => t + 1);
42531
+ // Se questo campo guida opzioni dinamiche di un altro (es. provider -> model list),
42532
+ // ri-fetcha le options di quei campi col nuovo valore.
42533
+ const shortKey = key.includes(':') ? key.substring(key.indexOf(':') + 1) : key;
42534
+ for (const sec of this.sections())
42535
+ for (const f of sec.fields)
42536
+ if (f.optionsEndpoint && f.optionsDependsOn === shortKey)
42537
+ this.fetchFieldOptions(f, String(newValue ?? ''));
42003
42538
  }
42004
42539
  fieldValue(key) {
42005
42540
  return this.current.get(key);
42006
42541
  }
42542
+ /** Per ogni field con `optionsEndpoint`, fetcha le options usando il valore corrente
42543
+ * del campo `optionsDependsOn`. Chiamato dopo ogni loadSchema. */
42544
+ refreshDynamicOptions() {
42545
+ for (const sec of this.sections()) {
42546
+ for (const f of sec.fields) {
42547
+ if (!f.optionsEndpoint || !f.optionsDependsOn)
42548
+ continue;
42549
+ this.fetchFieldOptions(f, this.dependsOnValue(f.optionsDependsOn));
42550
+ }
42551
+ }
42552
+ }
42553
+ /** Valore corrente del campo identificato da `dependsOnKey` (con/senza prefisso AppSettings:). */
42554
+ dependsOnValue(dependsOnKey) {
42555
+ for (const sec of this.sections())
42556
+ for (const f of sec.fields)
42557
+ if (f.key === dependsOnKey || f.key.endsWith(':' + dependsOnKey))
42558
+ return String(this.fieldValue(f.key) ?? '');
42559
+ return '';
42560
+ }
42561
+ /** GET `<endpoint>?<dependsOn>=<depVal>` -> popola `f.options`. Best-effort: su errore
42562
+ * lascia il campo come testo libero (options resta vuoto). */
42563
+ fetchFieldOptions(f, depVal) {
42564
+ if (!f.optionsEndpoint || !f.optionsDependsOn)
42565
+ return;
42566
+ const ep = f.optionsEndpoint.replace(/^\/?api\//, ''); // base gia' include /api/
42567
+ const url = `${this.buildApiBaseUrl()}${ep}?${encodeURIComponent(f.optionsDependsOn)}=${encodeURIComponent(depVal)}`;
42568
+ this.http.get(url, { withCredentials: true }).subscribe({
42569
+ next: (opts) => {
42570
+ if (Array.isArray(opts)) {
42571
+ f.options = opts;
42572
+ // bump del signal sections cosi' il <p-select> ri-legge f.options (CD signal-safe)
42573
+ this.sections.update((arr) => [...arr]);
42574
+ }
42575
+ },
42576
+ error: () => { },
42577
+ });
42578
+ }
42007
42579
  /**
42008
42580
  * Ritorna `true` se almeno uno dei changes coinvolge una chiave licenza
42009
42581
  * (`license-email` / `license-payload` / `license-signature`). Usato dal save
@@ -45350,7 +45922,7 @@ class LazyFirstRunWizardComponent {
45350
45922
  this.cdr = cdr;
45351
45923
  }
45352
45924
  async ngOnInit() {
45353
- firstRunWizardComponentPromise ??= import('./wuic-framework-lib-first-run-wizard.component-CaGePYLi.mjs').then((m) => m.WuicFirstRunWizardComponent);
45925
+ firstRunWizardComponentPromise ??= import('./wuic-framework-lib-first-run-wizard.component-lW7-xMm3.mjs').then((m) => m.WuicFirstRunWizardComponent);
45354
45926
  const componentClass = await firstRunWizardComponentPromise;
45355
45927
  this.componentRef = this.vcr.createComponent(componentClass);
45356
45928
  // Forward output del componente reale al wrapper. Type assertion necessaria
@@ -56994,7 +57566,7 @@ class LazyCodeEditorComponent {
56994
57566
  this.cdr = cdr;
56995
57567
  }
56996
57568
  async ngOnInit() {
56997
- codeEditorComponentPromise ??= import('./wuic-framework-lib-code-editor.component-4N95qt3D.mjs').then((m) => m.CodeEditorComponent);
57569
+ codeEditorComponentPromise ??= import('./wuic-framework-lib-code-editor.component-BZNLr0Jj.mjs').then((m) => m.CodeEditorComponent);
56998
57570
  this.loadedComponent = await codeEditorComponentPromise;
56999
57571
  this.cdr.markForCheck();
57000
57572
  }
@@ -58781,7 +59353,7 @@ class DesignerRouteComponent {
58781
59353
  * Inizializza il componente preparando stato, sottoscrizioni e primi caricamenti richiesti.
58782
59354
  */
58783
59355
  async ngOnInit() {
58784
- designerComponentPromise ??= import('./wuic-framework-lib-designer.component-pW1YK-oY.mjs').then((m) => m.DesignerComponent);
59356
+ designerComponentPromise ??= import('./wuic-framework-lib-designer.component-N_CfbGnr.mjs').then((m) => m.DesignerComponent);
58785
59357
  this.loadedComponent = await designerComponentPromise;
58786
59358
  }
58787
59359
  /**
@@ -58858,7 +59430,7 @@ class LazyPivotBuilderComponent {
58858
59430
  this.cdr = cdr;
58859
59431
  }
58860
59432
  async ngOnInit() {
58861
- pivotBuilderComponentPromise ??= import('./wuic-framework-lib-pivot-builder.component-VIs0CIH_.mjs').then((m) => m.PivotBuilderComponent);
59433
+ pivotBuilderComponentPromise ??= import('./wuic-framework-lib-pivot-builder.component-0gJz9RWr.mjs').then((m) => m.PivotBuilderComponent);
58862
59434
  this.loadedComponent = await pivotBuilderComponentPromise;
58863
59435
  this.cdr.markForCheck();
58864
59436
  }
@@ -58897,7 +59469,7 @@ class WorkflowDesignerRouteComponent {
58897
59469
  * Inizializza il componente preparando stato, sottoscrizioni e primi caricamenti richiesti.
58898
59470
  */
58899
59471
  async ngOnInit() {
58900
- workflowDesignerComponentPromise ??= import('./wuic-framework-lib-workflow-designer.component-4RC2EkGA.mjs').then((m) => m.WorkflowDesignerComponent);
59472
+ workflowDesignerComponentPromise ??= import('./wuic-framework-lib-workflow-designer.component-epj3oKq3.mjs').then((m) => m.WorkflowDesignerComponent);
58901
59473
  this.loadedComponent = await workflowDesignerComponentPromise;
58902
59474
  }
58903
59475
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WorkflowDesignerRouteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -58954,7 +59526,7 @@ class LazyReportDesignerComponent {
58954
59526
  this.cdr = cdr;
58955
59527
  }
58956
59528
  async ngOnInit() {
58957
- reportDesignerComponentPromise ??= import('./wuic-framework-lib-report-designer.component-FiAwNkPM.mjs').then((m) => m.ReportDesignerComponent);
59529
+ reportDesignerComponentPromise ??= import('./wuic-framework-lib-report-designer.component-DcpdzFjh.mjs').then((m) => m.ReportDesignerComponent);
58958
59530
  this.loadedComponent = await reportDesignerComponentPromise;
58959
59531
  this.cdr.markForCheck();
58960
59532
  }
@@ -59009,7 +59581,7 @@ class LazyReportViewerComponent {
59009
59581
  this.cdr = cdr;
59010
59582
  }
59011
59583
  async ngOnInit() {
59012
- reportViewerComponentPromise ??= import('./wuic-framework-lib-report-viewer.component-DY0dGMrt.mjs').then((m) => m.ReportViewerComponent);
59584
+ reportViewerComponentPromise ??= import('./wuic-framework-lib-report-viewer.component-BHpbGpp9.mjs').then((m) => m.ReportViewerComponent);
59013
59585
  this.loadedComponent = await reportViewerComponentPromise;
59014
59586
  this.cdr.markForCheck();
59015
59587
  }
@@ -60713,6 +61285,7 @@ class WuicRagService {
60713
61285
  query,
60714
61286
  top_k: options.topK ?? 8,
60715
61287
  use_lora: options.useLora ?? true,
61288
+ profile: options.profile,
60716
61289
  };
60717
61290
  return this.http.post(this.buildUrl('Query'), payload, { withCredentials: true });
60718
61291
  }
@@ -60734,7 +61307,10 @@ class WuicRagService {
60734
61307
  query,
60735
61308
  history,
60736
61309
  top_k: options.topK ?? 5,
60737
- model: options.model ?? 'claude-haiku-4-5-20251001',
61310
+ // Vuoto = il backend usa il model configurato (`rag-llm-default-chat-model`),
61311
+ // provider-appropriate. Non forzare un model Anthropic hardcoded.
61312
+ model: options.model ?? '',
61313
+ profile: options.profile,
60738
61314
  };
60739
61315
  return this.http.post(this.buildUrl('Chat'), payload, { withCredentials: true });
60740
61316
  }
@@ -60818,6 +61394,55 @@ class WuicRagService {
60818
61394
  askAsync(req) {
60819
61395
  return firstValueFrom(this.ask(req));
60820
61396
  }
61397
+ /**
61398
+ * Applica la proposta d'azione precedentemente emessa dall'LLM sul messaggio
61399
+ * indicato. Server-side: re-legge la `proposed_action_json` dal DB (non dal
61400
+ * client: prevenzione tampering del `callback_js`), valida, fa INSERT sul
61401
+ * metadata corrispondente + `InvalidateMetadataCachesAndSetVersion`.
61402
+ *
61403
+ * Gating: solo `isSuperAdmin` (server torna `403 admin-required` in caso).
61404
+ * Esiti tipici (`error`): `not-authenticated` | `admin-required` |
61405
+ * `message-not-found` | `no-proposed-action-on-message` | `already-applied` |
61406
+ * `unsupported-kind: <x>` | `apply-failed`.
61407
+ */
61408
+ applyProposedAction(req) {
61409
+ return this.http.post(this.buildUrl('ApplyAction'), req, { withCredentials: true });
61410
+ }
61411
+ applyProposedActionAsync(req) {
61412
+ return firstValueFrom(this.applyProposedAction(req));
61413
+ }
61414
+ /**
61415
+ * Sovrascrive il `proposed_action_json` di un messaggio assistant con il
61416
+ * payload editato dall'utente in UI (campo "Classe/Stile" + textarea callback).
61417
+ * Va chiamato PRIMA di `applyProposedActionAsync` quando l'utente modifica
61418
+ * qualcosa, cosi' che il backend ApplyAction rilegga dal DB il payload giusto.
61419
+ * Filtri di sicurezza nel backend: ownership user_id + non gia' applicata.
61420
+ */
61421
+ updateProposedAction(req) {
61422
+ return this.http.post(this.buildUrl('UpdateProposedAction'), req, { withCredentials: true });
61423
+ }
61424
+ updateProposedActionAsync(req) {
61425
+ return firstValueFrom(this.updateProposedAction(req));
61426
+ }
61427
+ /**
61428
+ * Compatta la sessione: chiama il backend che a sua volta chiama il rag_server
61429
+ * per produrre un summary della history. Persiste su `_rag_chat_sessions`
61430
+ * + avanza `summary_up_to_message_id`. Le future call /Ask passeranno il
61431
+ * summary nel system prompt al posto dei turn coperti.
61432
+ */
61433
+ compactSession(req) {
61434
+ return this.http.post(this.buildUrl('Compact'), req, { withCredentials: true });
61435
+ }
61436
+ compactSessionAsync(req) {
61437
+ return firstValueFrom(this.compactSession(req));
61438
+ }
61439
+ /** Rinomina il titolo di una session (mostrato nella sessions list). */
61440
+ renameSession(req) {
61441
+ return this.http.post(this.buildUrl('RenameSession'), req, { withCredentials: true });
61442
+ }
61443
+ renameSessionAsync(req) {
61444
+ return firstValueFrom(this.renameSession(req));
61445
+ }
60821
61446
  // ----- private helpers -----
60822
61447
  /**
60823
61448
  * Costruisce l'URL `<api_base>Rag/<action>` rispettando l'eventuale
@@ -60846,6 +61471,134 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
60846
61471
  args: [{ providedIn: 'root' }]
60847
61472
  }], ctorParameters: () => [{ type: i1$1.HttpClient }] });
60848
61473
 
61474
+ class ChatbotHostRegistryService {
61475
+ handlers = new Map();
61476
+ stateProviders = new Map();
61477
+ /**
61478
+ * Registra (o sostituisce) il handler per un dato `kind`. L'ultima
61479
+ * registrazione vince - utile per scenari edge in cui due viste
61480
+ * concorrenti gestiscono lo stesso kind (raro, ma documentato).
61481
+ */
61482
+ register(kind, handler) {
61483
+ if (!kind || typeof handler !== 'function')
61484
+ return;
61485
+ this.handlers.set(kind, handler);
61486
+ }
61487
+ /**
61488
+ * Rimuove il handler per un dato `kind`. No-op se non registrato.
61489
+ * Da chiamare in ngOnDestroy dell'host per evitare retain.
61490
+ */
61491
+ unregister(kind) {
61492
+ if (!kind)
61493
+ return;
61494
+ this.handlers.delete(kind);
61495
+ }
61496
+ /**
61497
+ * Restituisce il handler registrato per `kind`, o `null` se nessuno
61498
+ * lo gestisce client-side. Il chatbot usa questo per decidere se
61499
+ * bypassare il backend o no.
61500
+ */
61501
+ get(kind) {
61502
+ if (!kind)
61503
+ return null;
61504
+ return this.handlers.get(kind) || null;
61505
+ }
61506
+ /**
61507
+ * Helper diagnostico: lista tutti i kind attualmente registrati.
61508
+ * Usato dai test e dal debug panel.
61509
+ */
61510
+ registeredKinds() {
61511
+ return Array.from(this.handlers.keys());
61512
+ }
61513
+ // ===========================================================================
61514
+ // State Providers - per iniettare snapshot state nel route_context del LLM
61515
+ // ===========================================================================
61516
+ /**
61517
+ * Registra (o sostituisce) un state provider per un dato `kind`.
61518
+ * Il provider verra' chiamato dal chatbot in `buildRouteContext()` per
61519
+ * arricchire il system prompt del LLM con lo state corrente dell'host
61520
+ * (es. designer.dashboardElements serializzati come tree compatto).
61521
+ *
61522
+ * Esempio (designer.component):
61523
+ * this.chatbotHostRegistry.registerStateProvider(
61524
+ * 'designer_inject',
61525
+ * () => this.buildCompactStateDump()
61526
+ * );
61527
+ */
61528
+ registerStateProvider(kind, provider) {
61529
+ if (!kind || typeof provider !== 'function')
61530
+ return;
61531
+ this.stateProviders.set(kind, provider);
61532
+ }
61533
+ /**
61534
+ * Rimuove il state provider. Da chiamare in ngOnDestroy.
61535
+ */
61536
+ unregisterStateProvider(kind) {
61537
+ if (!kind)
61538
+ return;
61539
+ this.stateProviders.delete(kind);
61540
+ }
61541
+ /**
61542
+ * Invoca il state provider registrato per `kind` e ritorna il dump.
61543
+ * Se non registrato, ritorna `null`. Errori nel provider sono
61544
+ * swallowed e logged (best-effort: il chatbot deve sempre funzionare
61545
+ * anche se l'host crasha durante il dump).
61546
+ */
61547
+ getStateDump(kind) {
61548
+ if (!kind)
61549
+ return null;
61550
+ const provider = this.stateProviders.get(kind);
61551
+ if (!provider)
61552
+ return null;
61553
+ try {
61554
+ const dump = provider();
61555
+ return (typeof dump === 'string' && dump.length > 0) ? dump : null;
61556
+ }
61557
+ catch (err) {
61558
+ try {
61559
+ console.warn('[ChatbotHostRegistry] stateProvider failed for', kind, err);
61560
+ }
61561
+ catch { /* ignore */ }
61562
+ return null;
61563
+ }
61564
+ }
61565
+ /**
61566
+ * Helper diagnostico: lista tutti i kind con state provider attivo.
61567
+ */
61568
+ registeredStateKinds() {
61569
+ return Array.from(this.stateProviders.keys());
61570
+ }
61571
+ /**
61572
+ * Invoca SYNC tutti gli state provider registrati ignorandone il risultato.
61573
+ *
61574
+ * Scopo: dare ai provider la possibilita' di triggerare i loro fetch lazy
61575
+ * (es. il designer prefetcha `getAvailableRoutes` solo quando lo state
61576
+ * provider viene invocato — altrimenti spreca chiamate al mount). Chiamato
61577
+ * dal chatbot in ngOnInit: la finestra "apertura chatbot" → "primo prompt
61578
+ * inviato" e' di almeno 1-2 secondi (l'utente deve digitare), tempo
61579
+ * sufficiente perche' la cache sia pronta al primo Ask. Senza warm-up,
61580
+ * il primo prompt vedrebbe state dump 'caricamento' e il LLM
61581
+ * potrebbe hallucinare nomi alternativi inventati.
61582
+ *
61583
+ * Errori dei provider vengono swallowed (lo stesso pattern di getStateDump).
61584
+ */
61585
+ warmUpAllStateProviders() {
61586
+ const kinds = this.registeredStateKinds();
61587
+ for (const k of kinds) {
61588
+ try {
61589
+ this.getStateDump(k);
61590
+ }
61591
+ catch { /* ignore */ }
61592
+ }
61593
+ }
61594
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChatbotHostRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
61595
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChatbotHostRegistryService, providedIn: 'root' });
61596
+ }
61597
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChatbotHostRegistryService, decorators: [{
61598
+ type: Injectable,
61599
+ args: [{ providedIn: 'root' }]
61600
+ }] });
61601
+
60849
61602
  /**
60850
61603
  * Componente standalone che espone una chat sopra il RAG del codebase WUIC.
60851
61604
  *
@@ -60869,9 +61622,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
60869
61622
  class WuicRagChatbotComponent {
60870
61623
  ragService;
60871
61624
  cdr;
61625
+ chatbotHostRegistry;
60872
61626
  route;
60873
61627
  router;
60874
61628
  notificationRT;
61629
+ metaSrv;
60875
61630
  /** Titolo mostrato nell'header del chatbot. */
60876
61631
  title = 'Assistente codebase WUIC';
60877
61632
  /** Modalita' operativa: vedi nota nella classDoc. */
@@ -60884,12 +61639,52 @@ class WuicRagChatbotComponent {
60884
61639
  maxHistory = 50;
60885
61640
  /** Placeholder dell'input. */
60886
61641
  placeholder = 'Chiedi qualcosa sul codebase WUIC (italiano o inglese)...';
60887
- /** Modello Claude da usare in modalita' chat (default haiku 4.5). */
60888
- model = 'claude-haiku-4-5-20251001';
61642
+ /** Modello LLM in modalita' chat. Vuoto (default) = usa il model configurato lato
61643
+ * backend (`rag-llm-default-chat-model`), provider-appropriate. Non hardcodare un
61644
+ * model Anthropic: su provider openrouter/openai manderebbe un ID invalido (HTTP 400
61645
+ * -> retrieval-only). Valorizzare solo per forzare un model specifico. */
61646
+ model = '';
60889
61647
  /** Altezza fissa dell'area chat in CSS units (default `420px`). */
60890
61648
  chatHeight = '420px';
60891
61649
  /** Mostra il bottone "Svuota cronologia" nell'header. */
60892
61650
  showClearButton = true;
61651
+ /**
61652
+ * Persiste l'id della sessione corrente in `localStorage` e lo ripristina al
61653
+ * boot del componente (es. refresh della pagina) quando NON arriva un
61654
+ * `?sessionId` dalla route. La cronologia completa viene poi ricaricata dal DB
61655
+ * via `/api/Rag/History` (che ritorna TUTTI i messaggi della sessione, non un
61656
+ * sottoinsieme) -> il refresh non svuota piu' la chat. Default true.
61657
+ */
61658
+ persistSession = true;
61659
+ /**
61660
+ * Se `true` (default), prima di ogni `Ask` il componente costruisce un
61661
+ * **route context** sintetico (route corrente + tabella metadata + colonne)
61662
+ * dal `MetadataProviderService` e lo invia al rag_server come `routeContext`
61663
+ * nel payload. Il server lo inietta nel system prompt dell'LLM cosi' che le
61664
+ * risposte usino i NOMI REALI delle colonne della pagina corrente invece di
61665
+ * placeholder generici.
61666
+ *
61667
+ * Disabilitare a `false` per la route full-page `/rag-chatbot` (il chatbot
61668
+ * stesso non e' una "business route" — niente context utile da iniettare) o
61669
+ * per UI di privacy avanzata.
61670
+ */
61671
+ sendRouteContext = true;
61672
+ /** Chiave `localStorage` dell'ultima sessione (scoped per browser). */
61673
+ static LAST_SESSION_KEY = 'wuic_rag_last_session_id';
61674
+ /**
61675
+ * Mostra il toggle "Vista Dev / Vista Cliente" nell'header (default OFF).
61676
+ * Da abilitare SOLO nelle pagine dev/demo: la chat spedita al cliente NON
61677
+ * deve esporlo. E' comunque leak-safe (il server applica strictest-of: il
61678
+ * toggle puo' solo aumentare la redazione, mai diminuirla).
61679
+ */
61680
+ allowProfileToggle = false;
61681
+ /**
61682
+ * Profilo di redazione corrente scelto dal toggle UI.
61683
+ * - 'internal' = Vista Dev (full source);
61684
+ * - 'release' = Vista Cliente (firme + niente skill, come on-prem).
61685
+ * Inviato al server solo se {@link allowProfileToggle} e' true.
61686
+ */
61687
+ profileOverride = 'internal';
60893
61688
  // ----- Outputs -----
60894
61689
  /** Emette quando l'utente clicca su un source chip. */
60895
61690
  resultSelected = new EventEmitter();
@@ -60920,6 +61715,35 @@ class WuicRagChatbotComponent {
60920
61715
  processedNotificationIds = new Set();
60921
61716
  /** Sub a notificationRT.notifications$ + ActivatedRoute.queryParams. */
60922
61717
  subs = new Subscription();
61718
+ /**
61719
+ * Context window usage del modello corrente (ultima stima dal backend, 0-100%).
61720
+ * Aggiornato a ogni completion. Driver del cerchio colorato in header:
61721
+ * verde <60% / giallo 60-80% / arancione 80-90% / rosso >90%.
61722
+ */
61723
+ contextUsagePercent = 0;
61724
+ /** Token usati (dall'ultima call) / context window max. Mostrati in tooltip. */
61725
+ contextUsedTokens = null;
61726
+ contextWindowMax = null;
61727
+ /** True quando il dialog "context-overflow" e' aperto: l'ultima call ha
61728
+ * fallito perche' la history sfora il context. UX: 2 bottoni (Compatta / Nuova). */
61729
+ contextOverflowDialogOpen = false;
61730
+ /** True mentre il /compact e' in-flight (loading nel bottone). */
61731
+ compactingSession = false;
61732
+ /**
61733
+ * True per tutto il flow `onClearHistory`: apertura prompt di conferma
61734
+ * (PrimeNG Dialog lazy-load, ~300-800ms al primo click) + DELETE backend +
61735
+ * reset stato locale. Driver dello spinner sul bottone cestino in header.
61736
+ * Re-entry guard: blocca click multipli mentre il flow e' in corso.
61737
+ */
61738
+ clearingHistory = false;
61739
+ /** True quando il dialog "Sessioni" e' aperto. Lazy-load lista al toggle. */
61740
+ sessionsDialogOpen = false;
61741
+ sessionsList = [];
61742
+ sessionsLoading = false;
61743
+ /** Map sessionId -> bool: true se quella riga e' in modalita' rinomina. */
61744
+ sessionRenameEditing = {};
61745
+ /** Map sessionId -> string: valore temporaneo del titolo durante l'edit inline. */
61746
+ sessionRenameDraft = {};
60923
61747
  /**
60924
61748
  * Source selezionato per il dialog preview. Null = dialog chiuso.
60925
61749
  * Il dialog mostra SOLO lo snippet embedded in RagSource (max 500 char dal
@@ -60929,12 +61753,14 @@ class WuicRagChatbotComponent {
60929
61753
  selectedSource = null;
60930
61754
  inputTextarea;
60931
61755
  scrollContainer;
60932
- constructor(ragService, cdr, route, router, notificationRT) {
61756
+ constructor(ragService, cdr, chatbotHostRegistry, route, router, notificationRT, metaSrv) {
60933
61757
  this.ragService = ragService;
60934
61758
  this.cdr = cdr;
61759
+ this.chatbotHostRegistry = chatbotHostRegistry;
60935
61760
  this.route = route;
60936
61761
  this.router = router;
60937
61762
  this.notificationRT = notificationRT;
61763
+ this.metaSrv = metaSrv;
60938
61764
  }
60939
61765
  ngOnInit() {
60940
61766
  // 1. Probe health del backend (esistente)
@@ -60944,17 +61770,47 @@ class WuicRagChatbotComponent {
60944
61770
  next: (info) => { this.healthInfo = info; this.cdr.markForCheck(); },
60945
61771
  error: (err) => { this.healthError = this.extractErrorMessage(err); this.cdr.markForCheck(); },
60946
61772
  });
61773
+ // Warm-up degli state provider degli host (es. designer): da' loro la
61774
+ // possibilita' di triggerare i loro fetch lazy (es. lista route metadata)
61775
+ // PRIMA del primo prompt dell'utente. Senza warm-up, il primo prompt
61776
+ // vedrebbe state dump 'caricamento' e il LLM potrebbe hallucinare nomi
61777
+ // alternativi inventati (visto in produzione: utente chiede "grid bindata
61778
+ // a provincie", LLM elenca "provinces/states/regions" che NON sono nella
61779
+ // whitelist reale -> utente sceglie "provinces", al secondo prompt il LLM
61780
+ // vede la whitelist vera che contiene "stateprovinces" e fa fuzzy match
61781
+ // silenzioso provinces->stateprovinces, confondendo l'utente).
61782
+ try {
61783
+ this.chatbotHostRegistry.warmUpAllStateProviders();
61784
+ }
61785
+ catch { /* ignore */ }
60947
61786
  // 2. Deep-link da notifica: ?sessionId=X(&messageId=Y) -> carica history.
60948
- // Se manca, lazy: la sessione viene creata al primo Ask.
61787
+ // Refresh pagina senza query param -> ripristina l'ultima sessione da
61788
+ // localStorage (se persistSession). Se non c'e' nulla, lazy: la sessione
61789
+ // viene creata al primo Ask.
61790
+ let restoreAttempted = false;
60949
61791
  if (this.route) {
60950
61792
  this.subs.add(this.route.queryParamMap.subscribe((qp) => {
60951
61793
  const sid = Number(qp.get('sessionId') || 0);
60952
61794
  const mid = Number(qp.get('messageId') || 0);
60953
61795
  if (sid > 0 && sid !== this.currentSessionId) {
61796
+ restoreAttempted = true;
60954
61797
  this.loadSession(sid, mid > 0 ? mid : null);
60955
61798
  }
61799
+ else if (sid <= 0 && !restoreAttempted && this.persistSession && this.currentSessionId == null) {
61800
+ // Nessun sessionId in URL (refresh diretto): recupera l'ultima sessione.
61801
+ restoreAttempted = true;
61802
+ const saved = this.readLastSession();
61803
+ if (saved)
61804
+ this.loadSession(saved, null);
61805
+ }
60956
61806
  }));
60957
61807
  }
61808
+ else if (this.persistSession && this.currentSessionId == null) {
61809
+ // Componente usato fuori da contesto routing: restore best-effort.
61810
+ const saved = this.readLastSession();
61811
+ if (saved)
61812
+ this.loadSession(saved, null);
61813
+ }
60958
61814
  // 3. Subscribe alle notifiche real-time. Filtriamo type rag.message.* +
60959
61815
  // payload.sessionId match + dedup via processedNotificationIds.
60960
61816
  // handleNotification -> refreshSingleMessage -> applyMessageToTurn
@@ -60972,6 +61828,13 @@ class WuicRagChatbotComponent {
60972
61828
  }
60973
61829
  ngOnDestroy() {
60974
61830
  this.subs.unsubscribe();
61831
+ // Cleanup delle subscription al BehaviorSubject del Monaco editor per
61832
+ // ogni proposta (vedi getProposedActionField). Senza, ogni messageId
61833
+ // tenuto in cache lascerebbe un listener orfano sul record subject.
61834
+ for (const cached of this.proposedActionEditorCache.values()) {
61835
+ cached.bodySubscription?.unsubscribe();
61836
+ }
61837
+ this.proposedActionEditorCache.clear();
60975
61838
  }
60976
61839
  // ----- Computed getters per template -----
60977
61840
  get effectiveMode() {
@@ -60999,6 +61862,19 @@ class WuicRagChatbotComponent {
60999
61862
  get sendDisabled() {
61000
61863
  return this.loading || !this.inputValue.trim() || !!this.healthError;
61001
61864
  }
61865
+ /** Label del toggle profilo. */
61866
+ get profileToggleLabel() {
61867
+ return this.profileOverride === 'release' ? 'Vista Cliente' : 'Vista Dev';
61868
+ }
61869
+ /**
61870
+ * Flippa il profilo di redazione (Dev <-> Cliente). Le risposte gia' in
61871
+ * cronologia restano com'erano (il profilo e' per-richiesta); i prossimi
61872
+ * invii usano il nuovo profilo.
61873
+ */
61874
+ toggleProfile() {
61875
+ this.profileOverride = this.profileOverride === 'release' ? 'internal' : 'release';
61876
+ this.cdr.markForCheck();
61877
+ }
61002
61878
  // ----- Azioni utente -----
61003
61879
  /**
61004
61880
  * Invia il prompt corrente via flow async:
@@ -61012,6 +61888,14 @@ class WuicRagChatbotComponent {
61012
61888
  const text = this.inputValue.trim();
61013
61889
  if (!text || this.loading || this.healthError)
61014
61890
  return;
61891
+ // Slash command `/compact`: intercetta PRIMA del send. Comportamento identico
61892
+ // al click sul context cue. Niente call Ask -> niente turn in history.
61893
+ if (text === '/compact' || text.toLowerCase() === '/compatta') {
61894
+ this.inputValue = '';
61895
+ this.cdr.markForCheck();
61896
+ await this.onContextCueClick();
61897
+ return;
61898
+ }
61015
61899
  const userTurn = { role: 'user', content: text };
61016
61900
  const assistantTurn = {
61017
61901
  role: 'assistant',
@@ -61026,6 +61910,16 @@ class WuicRagChatbotComponent {
61026
61910
  this.loading = true;
61027
61911
  this.cdr.markForCheck();
61028
61912
  this.scheduleScrollToBottom();
61913
+ // Costruisce il route context PRIMA del send (best-effort: errore => null).
61914
+ // Il server lo inietta nel system prompt dell'LLM cosi' che le risposte
61915
+ // possano usare i nomi REALI delle colonne della pagina corrente.
61916
+ let routeContext = null;
61917
+ if (this.sendRouteContext) {
61918
+ try {
61919
+ routeContext = await this.buildRouteContext();
61920
+ }
61921
+ catch { /* best-effort: niente context, ma il send va lo stesso */ }
61922
+ }
61029
61923
  try {
61030
61924
  const ack = await this.ragService.askAsync({
61031
61925
  prompt: text,
@@ -61033,9 +61927,12 @@ class WuicRagChatbotComponent {
61033
61927
  mode: this.mode,
61034
61928
  topK: this.topK,
61035
61929
  model: this.model,
61930
+ profile: this.allowProfileToggle ? this.profileOverride : undefined,
61931
+ routeContext: routeContext,
61036
61932
  });
61037
61933
  // Salva la sessione corrente (puo' essere stata appena creata)
61038
61934
  this.currentSessionId = ack.sessionId;
61935
+ this.saveLastSession(ack.sessionId);
61039
61936
  userTurn.messageId = ack.userMessageId;
61040
61937
  assistantTurn.messageId = ack.assistantMessageId;
61041
61938
  assistantTurn.correlationId = ack.correlationId;
@@ -61060,37 +61957,57 @@ class WuicRagChatbotComponent {
61060
61957
  /**
61061
61958
  * Svuota la cronologia chat (soft-delete della sessione corrente sul DB) +
61062
61959
  * reset stato locale.
61960
+ *
61961
+ * Il flag `clearingHistory` resta `true` per tutto il flow (dall'apertura
61962
+ * del dialog di conferma fino al completamento DELETE+reset). Driver dello
61963
+ * spinner sul bottone cestino in header — necessario perche' il PRIMO click
61964
+ * subisce ~300-800ms di lazy-load del bundle PrimeNG Dialog
61965
+ * (`promptDialog` apre un `p-dialog` dinamico, non gia' istanziato). Senza
61966
+ * feedback l'utente cliccava 2-3 volte pensando che non rispondesse.
61063
61967
  */
61064
61968
  async onClearHistory() {
61065
61969
  if (this.history.length === 0 && !this.currentSessionId)
61066
61970
  return;
61067
- let confirmed = true;
61971
+ if (this.clearingHistory)
61972
+ return; // re-entry guard
61973
+ this.clearingHistory = true;
61974
+ this.cdr.markForCheck();
61068
61975
  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) {
61976
+ let confirmed = true;
61086
61977
  try {
61087
- await this.ragService.deleteSessionAsync(this.currentSessionId);
61978
+ const tools = WtoolboxService;
61979
+ if (typeof tools.promptDialog === 'function') {
61980
+ const ts = WtoolboxService.translationService;
61981
+ const tr = (k, fallback) => (ts && typeof ts.instant === 'function' ? ts.instant(k) : fallback);
61982
+ const res = await tools.promptDialog({
61983
+ title: tr('rag_chatbot.confirm.delete_session.title', 'Svuota cronologia'),
61984
+ message: tr('rag_chatbot.confirm.delete_session', 'Eliminare la cronologia di questa chat?'),
61985
+ confirmLabel: tr('rag_chatbot.confirm.delete_session.confirm', 'Elimina'),
61986
+ cancelLabel: tr('rag_chatbot.confirm.cancel', 'Annulla'),
61987
+ });
61988
+ confirmed = !!res && res.confirmed !== false;
61989
+ }
61088
61990
  }
61089
- catch { /* best effort */ }
61991
+ catch {
61992
+ confirmed = true;
61993
+ }
61994
+ if (!confirmed)
61995
+ return;
61996
+ if (this.currentSessionId) {
61997
+ try {
61998
+ await this.ragService.deleteSessionAsync(this.currentSessionId);
61999
+ }
62000
+ catch { /* best effort */ }
62001
+ }
62002
+ this.history = [];
62003
+ this.pendingMap.clear();
62004
+ this.currentSessionId = null;
62005
+ this.saveLastSession(null);
62006
+ }
62007
+ finally {
62008
+ this.clearingHistory = false;
62009
+ this.cdr.markForCheck();
61090
62010
  }
61091
- this.history = [];
61092
- this.pendingMap.clear();
61093
- this.currentSessionId = null;
61094
62011
  }
61095
62012
  /**
61096
62013
  * Click dispatcher:
@@ -61388,9 +62305,14 @@ class WuicRagChatbotComponent {
61388
62305
  async loadSession(sessionId, highlightMessageId) {
61389
62306
  try {
61390
62307
  const snap = await this.ragService.historyAsync(sessionId);
61391
- if (!snap || !snap.session)
62308
+ if (!snap || !snap.session) {
62309
+ // Sessione salvata non piu' valida (cancellata/altro utente): pulisci il
62310
+ // riferimento persistito cosi' al prossimo boot non si ritenta.
62311
+ this.saveLastSession(null);
61392
62312
  return;
62313
+ }
61393
62314
  this.currentSessionId = snap.session.id;
62315
+ this.saveLastSession(snap.session.id);
61394
62316
  this.history = (snap.messages || []).map((m) => this.dtoToTurn(m));
61395
62317
  this.pendingMap.clear();
61396
62318
  // Re-popola pendingMap per i messaggi assistant ancora in attesa
@@ -61399,6 +62321,19 @@ class WuicRagChatbotComponent {
61399
62321
  this.pendingMap.set(turn.messageId, turn);
61400
62322
  }
61401
62323
  }
62324
+ // Aggiorna il context cue dal piu' recente assistant turn completato:
62325
+ // riflette lo stato corrente del context window a sessione caricata.
62326
+ const lastCtx = [...(snap.messages || [])].reverse()
62327
+ .find(m => m.role === 'assistant' && m.contextUsedTokens != null && m.contextWindowMax != null);
62328
+ if (lastCtx) {
62329
+ this.updateContextCue(lastCtx.contextUsedTokens, lastCtx.contextWindowMax);
62330
+ }
62331
+ else {
62332
+ // Reset: nuova session o session senza turn assistant
62333
+ this.contextUsagePercent = 0;
62334
+ this.contextUsedTokens = null;
62335
+ this.contextWindowMax = null;
62336
+ }
61402
62337
  this.cdr.markForCheck();
61403
62338
  this.scheduleScrollToBottom();
61404
62339
  if (highlightMessageId) {
@@ -61407,6 +62342,27 @@ class WuicRagChatbotComponent {
61407
62342
  }
61408
62343
  }
61409
62344
  catch (err) {
62345
+ // Caso comune: la session salvata in localStorage punta a un record
62346
+ // che non esiste piu' sul backend (es. cambio DBMS, DB pulito, riga
62347
+ // cancellata o appartenente a un altro tenant). Backend risponde 404
62348
+ // con `{error:"session-not-found"}`. Degradazione gracefully:
62349
+ // - rimuovi la lastSession persistita (evita re-tentativo al prossimo boot)
62350
+ // - resetta lo state interno a "nuova session vuota"
62351
+ // - NIENTE alert/healthError invadente.
62352
+ // Per altri tipi di errore (rete, 500, auth) mostriamo healthError come prima.
62353
+ const isSessionNotFound = (err && typeof err === 'object' && 'status' in err && err.status === 404)
62354
+ || /session-not-found|404/i.test(this.extractErrorMessage(err) || '');
62355
+ if (isSessionNotFound) {
62356
+ this.saveLastSession(null);
62357
+ this.currentSessionId = null;
62358
+ this.history = [];
62359
+ this.pendingMap.clear();
62360
+ this.contextUsagePercent = 0;
62361
+ this.contextUsedTokens = null;
62362
+ this.contextWindowMax = null;
62363
+ this.cdr.markForCheck();
62364
+ return;
62365
+ }
61410
62366
  this.healthError = this.extractErrorMessage(err);
61411
62367
  this.cdr.markForCheck();
61412
62368
  }
@@ -61426,10 +62382,13 @@ class WuicRagChatbotComponent {
61426
62382
  loading: m.status === 'pending',
61427
62383
  mode: m.mode || null,
61428
62384
  sources,
61429
- errorMessage: m.status === 'error' ? (m.errorMessage || 'Errore sconosciuto') : null,
62385
+ errorMessage: m.status === 'error' ? (m.errorMessage || 'rag_chatbot.error.unknown') : null,
61430
62386
  tokensIn: m.tokensIn,
61431
62387
  tokensOut: m.tokensOut,
61432
62388
  correlationId: m.correlationId,
62389
+ proposedAction: this.parseProposedAction(m.proposedActionJson),
62390
+ proposedActionAppliedAt: m.proposedActionAppliedAt || null,
62391
+ followupQuestions: this.parseFollowupQuestions(m.followupQuestionsJson),
61433
62392
  };
61434
62393
  if (turn.role === 'assistant' && turn.loading && (!turn.content || turn.content === '')) {
61435
62394
  turn.content = '';
@@ -61456,13 +62415,48 @@ class WuicRagChatbotComponent {
61456
62415
  target.errorMessage = null;
61457
62416
  }
61458
62417
  else if (m.status === 'error') {
61459
- target.errorMessage = m.errorMessage || 'Errore sconosciuto';
62418
+ target.errorMessage = m.errorMessage || 'rag_chatbot.error.unknown';
62419
+ // Errore strutturato `context-overflow`: il backend l'ha settato perche'
62420
+ // la history sfora il context window del modello. Apri il dialog dedicato.
62421
+ if (m.errorMessage === 'context-overflow') {
62422
+ this.contextOverflowDialogOpen = true;
62423
+ }
61460
62424
  }
61461
62425
  target.tokensIn = m.tokensIn;
61462
62426
  target.tokensOut = m.tokensOut;
62427
+ // Aggiorna la proposta (puo' arrivare solo al complete: prima e' null su pending)
62428
+ target.proposedAction = this.parseProposedAction(m.proposedActionJson);
62429
+ target.proposedActionAppliedAt = m.proposedActionAppliedAt || null;
62430
+ target.followupQuestions = this.parseFollowupQuestions(m.followupQuestionsJson);
62431
+ // Aggiorna il context cue (cerchio colorato in header) usando i valori
62432
+ // dell'assistant turn appena ricevuto: e' lo stato corrente del context window.
62433
+ if (m.role === 'assistant' && m.contextUsedTokens != null && m.contextWindowMax != null) {
62434
+ this.updateContextCue(m.contextUsedTokens, m.contextWindowMax);
62435
+ }
61463
62436
  if (!target.loading) {
61464
62437
  this.pendingMap.delete(m.id);
61465
62438
  }
62439
+ // Auto-sync del badge mode (chip 'retrieval' / 'RAG + LLM' in header):
62440
+ // `healthInfo` viene fetchato SOLO in ngOnInit -> dopo restart del
62441
+ // rag_server (rebuild RAG, fresh deploy, crash recovery) il backend
62442
+ // re-pusha la API key e il rag_server passa a `llm_enabled:true`, MA
62443
+ // il componente continua a mostrare 'retrieval' fino al refresh pagina.
62444
+ //
62445
+ // Detection: se l'assistant turn completato ha `target.mode='rag-llm'`
62446
+ // (= il backend ha effettivamente girato LLM) ma `healthInfo.llm_enabled`
62447
+ // e' ancora false, c'e' un mismatch -> rifetcho /health silente.
62448
+ // Simmetricamente per il caso opposto (key rimossa: llm passa a false).
62449
+ // Cost: ~1 call /health per turn quando c'e' divergenza, zero quando OK.
62450
+ if (m.role === 'assistant' && m.status === 'completed' && target.mode) {
62451
+ const turnIsLlm = target.mode === 'rag-llm';
62452
+ const healthIsLlm = !!this.healthInfo?.llm_enabled;
62453
+ if (turnIsLlm !== healthIsLlm) {
62454
+ this.ragService.health().subscribe({
62455
+ next: (info) => { this.healthInfo = info; this.cdr.markForCheck(); },
62456
+ error: () => { }
62457
+ });
62458
+ }
62459
+ }
61466
62460
  // markForCheck: la mutation avviene su un oggetto esistente dentro
61467
62461
  // `this.history` (nested property), invisibile ad Angular con
61468
62462
  // ChangeDetectionStrategy.OnPush dei componenti ancestor (es. quando
@@ -61481,11 +62475,1006 @@ class WuicRagChatbotComponent {
61481
62475
  el.scrollIntoView({ behavior: 'smooth', block: 'center' });
61482
62476
  }
61483
62477
  // ----- Helpers (esistenti, riutilizzati) -----
62478
+ /**
62479
+ * Persiste (o cancella se id null) l'ultima sessione in localStorage.
62480
+ * Best-effort: guard SSR + try/catch (private-mode / quota / storage disabled).
62481
+ */
62482
+ saveLastSession(id) {
62483
+ if (!this.persistSession || typeof window === 'undefined' || !window.localStorage)
62484
+ return;
62485
+ try {
62486
+ const key = WuicRagChatbotComponent.LAST_SESSION_KEY;
62487
+ if (id && id > 0)
62488
+ window.localStorage.setItem(key, String(id));
62489
+ else
62490
+ window.localStorage.removeItem(key);
62491
+ }
62492
+ catch { /* storage non disponibile: la persistenza e' best-effort */ }
62493
+ }
62494
+ /** Legge l'ultima sessione salvata (>0) o null. */
62495
+ readLastSession() {
62496
+ if (typeof window === 'undefined' || !window.localStorage)
62497
+ return null;
62498
+ try {
62499
+ const raw = window.localStorage.getItem(WuicRagChatbotComponent.LAST_SESSION_KEY);
62500
+ const id = Number(raw || 0);
62501
+ return id > 0 ? id : null;
62502
+ }
62503
+ catch {
62504
+ return null;
62505
+ }
62506
+ }
62507
+ // ---------------------------------------------------------------------
62508
+ // Proposed actions (LLM tool-use)
62509
+ // ---------------------------------------------------------------------
62510
+ /** Parse JSON del field `proposed_action_json` -> oggetto tipato (o null). */
62511
+ parseProposedAction(raw) {
62512
+ if (!raw)
62513
+ return null;
62514
+ try {
62515
+ const parsed = JSON.parse(raw);
62516
+ return (parsed && typeof parsed === 'object' && typeof parsed.kind === 'string')
62517
+ ? parsed
62518
+ : null;
62519
+ }
62520
+ catch {
62521
+ return null;
62522
+ }
62523
+ }
62524
+ /** Parse JSON del field `followup_questions_json` -> array di stringhe (o null). */
62525
+ parseFollowupQuestions(raw) {
62526
+ if (!raw)
62527
+ return null;
62528
+ try {
62529
+ const parsed = JSON.parse(raw);
62530
+ if (!Array.isArray(parsed))
62531
+ return null;
62532
+ const cleaned = parsed.filter(q => typeof q === 'string' && q.trim().length > 0).slice(0, 3);
62533
+ return cleaned.length > 0 ? cleaned : null;
62534
+ }
62535
+ catch {
62536
+ return null;
62537
+ }
62538
+ }
62539
+ /**
62540
+ * Click su un chip follow-up: popola l'input box con la domanda (NON invia
62541
+ * automaticamente; l'utente puo' editare prima di premere invio). Coerente
62542
+ * con la scelta UX di non lanciare query "per sbaglio".
62543
+ */
62544
+ applyFollowup(question) {
62545
+ if (!question)
62546
+ return;
62547
+ this.inputValue = question;
62548
+ this.cdr.markForCheck();
62549
+ // focus input cosi' l'utente puo' editare subito o premere invio
62550
+ this.focusInput();
62551
+ }
62552
+ // -------------------------------------------------------------------
62553
+ // Context cue (cerchio colorato in header) + /compact + overflow dialog
62554
+ // -------------------------------------------------------------------
62555
+ /** Aggiorna lo stato del cerchio cue dato (usedTokens, windowMax). */
62556
+ updateContextCue(usedTokens, windowMax) {
62557
+ if (usedTokens == null || windowMax == null || windowMax <= 0)
62558
+ return;
62559
+ this.contextUsedTokens = usedTokens;
62560
+ this.contextWindowMax = windowMax;
62561
+ this.contextUsagePercent = Math.min(100, Math.round((usedTokens / windowMax) * 100));
62562
+ this.cdr.markForCheck();
62563
+ }
62564
+ /** Colore del cerchio basato sul `contextUsagePercent`. */
62565
+ get contextCueColor() {
62566
+ const p = this.contextUsagePercent;
62567
+ if (p < 60)
62568
+ return 'var(--p-green-500, #22c55e)';
62569
+ if (p < 80)
62570
+ return 'var(--p-yellow-500, #eab308)';
62571
+ if (p < 90)
62572
+ return 'var(--p-orange-500, #f97316)';
62573
+ return 'var(--p-red-500, #ef4444)';
62574
+ }
62575
+ get contextCueTooltip() {
62576
+ // Guard: il getter e' chiamato AD OGNI Change Detection (binding di
62577
+ // [pTooltip]). `WtoolboxService.translationService` e' uno static
62578
+ // assegnato in bootstrap; durante una route transition (es. click
62579
+ // su un source chip → router navigation) un CD cycle puo' partire
62580
+ // prima che lo static sia popolato → `undefined.instant(...)` boom
62581
+ // (TypeError "Cannot read properties of undefined (reading 'instant')"
62582
+ // visto in stack trace su WuicRagChatbotComponent_Conditional_10_Template).
62583
+ // Senza guard, l'errore si propaga al global error handler e mostra il
62584
+ // dialog "Si e' verificato un errore inatteso" che blocca l'utente.
62585
+ const ts = WtoolboxService.translationService;
62586
+ if (!ts || typeof ts.instant !== 'function')
62587
+ return '';
62588
+ if (this.contextWindowMax == null || this.contextUsedTokens == null) {
62589
+ return ts.instant('rag_chatbot.context_cue.tooltip_waiting');
62590
+ }
62591
+ const usedK = Math.round(this.contextUsedTokens / 1000);
62592
+ const maxK = Math.round(this.contextWindowMax / 1000);
62593
+ // WtoolboxService.translationService.instant(key) accetta SOLO 1 argomento.
62594
+ // Niente interpolation built-in: replace manuale dei placeholder -> valore.
62595
+ // Regex accetta SIA `{var}` (single brace, format usato nello script
62596
+ // upsert-wuic-translations.ps1) SIA `{{var}}` (double brace, format
62597
+ // tipico Angular template) per robustezza. Storicamente le traduzioni
62598
+ // nel DB hanno usato single brace -> match falliva su double-only.
62599
+ const tpl = ts.instant('rag_chatbot.context_cue.tooltip');
62600
+ return String(tpl)
62601
+ .replace(/\{\{?\s*percent\s*\}?\}/g, String(this.contextUsagePercent))
62602
+ .replace(/\{\{?\s*used\s*\}?\}/g, String(usedK))
62603
+ .replace(/\{\{?\s*max\s*\}?\}/g, String(maxK));
62604
+ }
62605
+ /**
62606
+ * Click sul cerchio context cue: conferma + lancia /compact. Disponibile sempre
62607
+ * (anche <60%): l'utente puo' compattare on-demand.
62608
+ */
62609
+ async onContextCueClick() {
62610
+ if (!this.currentSessionId || this.compactingSession)
62611
+ return;
62612
+ // Conferma via WtoolboxService.confirm (framework). Fallback su no-op confirm
62613
+ // se non disponibile (es. tests).
62614
+ let confirmed = true;
62615
+ try {
62616
+ const tools = WtoolboxService;
62617
+ if (typeof tools.confirm === 'function') {
62618
+ confirmed = await tools.confirm({
62619
+ header: WtoolboxService.translationService.instant('rag_chatbot.confirm.compact_session.header'),
62620
+ message: WtoolboxService.translationService.instant('rag_chatbot.confirm.compact_session.message'),
62621
+ icon: 'pi pi-exclamation-triangle',
62622
+ });
62623
+ }
62624
+ }
62625
+ catch {
62626
+ confirmed = true;
62627
+ }
62628
+ if (!confirmed)
62629
+ return;
62630
+ await this.doCompactSession();
62631
+ }
62632
+ /**
62633
+ * Chiama l'endpoint /api/Rag/Compact. Aggiorna lo stato dopo successo.
62634
+ * Riusato da: contextCue click, slash-command /compact, dialog overflow "Compatta ora".
62635
+ */
62636
+ async doCompactSession() {
62637
+ if (!this.currentSessionId || this.compactingSession)
62638
+ return;
62639
+ this.compactingSession = true;
62640
+ this.cdr.markForCheck();
62641
+ try {
62642
+ const resp = await this.ragService.compactSessionAsync({ sessionId: this.currentSessionId });
62643
+ if (resp?.ok && !resp.noop) {
62644
+ WtoolboxService.messageNotificationService?.add?.({
62645
+ severity: 'success',
62646
+ summary: WtoolboxService.translationService.instant('rag_chatbot.toast.compact_success.summary'),
62647
+ detail: `Summary di ${resp.summaryLength} caratteri, fino al messaggio #${resp.summaryUpToMessageId}.`,
62648
+ });
62649
+ // Ricarica history per riflettere il nuovo summary (alcuni client potrebbero
62650
+ // voler mostrare un divider "--- compattato ---" prima dei msg coperti).
62651
+ if (this.currentSessionId)
62652
+ await this.loadSession(this.currentSessionId, null);
62653
+ // Reset overflow dialog se era aperto
62654
+ this.contextOverflowDialogOpen = false;
62655
+ }
62656
+ else if (resp?.noop) {
62657
+ WtoolboxService.messageNotificationService?.add?.({
62658
+ severity: 'info',
62659
+ summary: WtoolboxService.translationService.instant('rag_chatbot.toast.compact_noop.summary'),
62660
+ detail: resp.reason || 'La sessione non ha turn completati da riassumere.',
62661
+ });
62662
+ }
62663
+ else {
62664
+ WtoolboxService.messageNotificationService?.add?.({
62665
+ severity: 'error',
62666
+ summary: WtoolboxService.translationService.instant('rag_chatbot.toast.compact_failed.summary'),
62667
+ detail: resp?.error || WtoolboxService.translationService.instant('rag_chatbot.toast.generic_error'),
62668
+ });
62669
+ }
62670
+ }
62671
+ catch (err) {
62672
+ const msg = this.extractErrorMessage(err);
62673
+ WtoolboxService.messageNotificationService?.add?.({
62674
+ severity: 'error',
62675
+ summary: WtoolboxService.translationService.instant('rag_chatbot.toast.compact_failed.summary'),
62676
+ detail: msg,
62677
+ });
62678
+ }
62679
+ finally {
62680
+ this.compactingSession = false;
62681
+ this.cdr.markForCheck();
62682
+ }
62683
+ }
62684
+ /**
62685
+ * Nuova sessione: chiude la corrente (clear localStorage), resetta history, ricomincia.
62686
+ * Chiamato dal bottone "Nuova sessione" del dialog overflow.
62687
+ */
62688
+ startNewSession() {
62689
+ this.history = [];
62690
+ this.pendingMap.clear();
62691
+ this.currentSessionId = null;
62692
+ this.saveLastSession(null);
62693
+ this.contextUsagePercent = 0;
62694
+ this.contextUsedTokens = null;
62695
+ this.contextOverflowDialogOpen = false;
62696
+ this.cdr.markForCheck();
62697
+ }
62698
+ // -------------------------------------------------------------------
62699
+ // Sessions popup (lista + rinomina + switch)
62700
+ // -------------------------------------------------------------------
62701
+ async openSessionsDialog() {
62702
+ this.sessionsDialogOpen = true;
62703
+ this.sessionsLoading = true;
62704
+ this.cdr.markForCheck();
62705
+ try {
62706
+ const resp = await this.ragService.sessionsAsync();
62707
+ this.sessionsList = (resp?.sessions || []).slice().sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''));
62708
+ }
62709
+ catch (err) {
62710
+ this.sessionsList = [];
62711
+ WtoolboxService.messageNotificationService?.add?.({
62712
+ severity: 'error',
62713
+ summary: WtoolboxService.translationService.instant('rag_chatbot.toast.sessions_load_failed.summary'),
62714
+ detail: this.extractErrorMessage(err),
62715
+ });
62716
+ }
62717
+ finally {
62718
+ this.sessionsLoading = false;
62719
+ this.cdr.markForCheck();
62720
+ }
62721
+ }
62722
+ closeSessionsDialog() {
62723
+ this.sessionsDialogOpen = false;
62724
+ this.sessionRenameEditing = {};
62725
+ this.sessionRenameDraft = {};
62726
+ }
62727
+ /** Switch alla session selezionata (carica history). */
62728
+ async selectSession(s) {
62729
+ if (!s || !s.id || s.id === this.currentSessionId) {
62730
+ this.closeSessionsDialog();
62731
+ return;
62732
+ }
62733
+ this.closeSessionsDialog();
62734
+ await this.loadSession(s.id, null);
62735
+ }
62736
+ /** Apre l'edit inline del titolo. */
62737
+ startRenameSession(s, event) {
62738
+ if (event) {
62739
+ event.stopPropagation();
62740
+ event.preventDefault();
62741
+ }
62742
+ if (!s?.id)
62743
+ return;
62744
+ this.sessionRenameEditing[s.id] = true;
62745
+ this.sessionRenameDraft[s.id] = s.title || '';
62746
+ this.cdr.markForCheck();
62747
+ }
62748
+ async confirmRenameSession(s, event) {
62749
+ if (event) {
62750
+ event.stopPropagation();
62751
+ event.preventDefault();
62752
+ }
62753
+ if (!s?.id)
62754
+ return;
62755
+ const newTitle = (this.sessionRenameDraft[s.id] || '').trim();
62756
+ if (!newTitle || newTitle === s.title) {
62757
+ this.cancelRenameSession(s);
62758
+ return;
62759
+ }
62760
+ try {
62761
+ const resp = await this.ragService.renameSessionAsync({ sessionId: s.id, title: newTitle });
62762
+ if (resp?.ok) {
62763
+ s.title = resp.title || newTitle;
62764
+ }
62765
+ else {
62766
+ WtoolboxService.messageNotificationService?.add?.({
62767
+ severity: 'error',
62768
+ summary: WtoolboxService.translationService.instant('rag_chatbot.toast.rename_failed.summary'),
62769
+ detail: resp?.error || WtoolboxService.translationService.instant('rag_chatbot.toast.generic_error'),
62770
+ });
62771
+ }
62772
+ }
62773
+ catch (err) {
62774
+ WtoolboxService.messageNotificationService?.add?.({
62775
+ severity: 'error',
62776
+ summary: WtoolboxService.translationService.instant('rag_chatbot.toast.rename_failed.summary'),
62777
+ detail: this.extractErrorMessage(err),
62778
+ });
62779
+ }
62780
+ finally {
62781
+ delete this.sessionRenameEditing[s.id];
62782
+ delete this.sessionRenameDraft[s.id];
62783
+ this.cdr.markForCheck();
62784
+ }
62785
+ }
62786
+ cancelRenameSession(s) {
62787
+ if (!s?.id)
62788
+ return;
62789
+ delete this.sessionRenameEditing[s.id];
62790
+ delete this.sessionRenameDraft[s.id];
62791
+ this.cdr.markForCheck();
62792
+ }
62793
+ /**
62794
+ * Cancella una sessione dalla cronologia (chiamato dall'icona cestino
62795
+ * sulla riga della lista sessioni). Confirm via WtoolboxService.promptDialog
62796
+ * (regola 12 AGENTS: mai window.confirm). Backend soft-delete via
62797
+ * POST /api/Rag/DeleteSession (gia' esistente).
62798
+ *
62799
+ * Se la sessione cancellata e' quella CORRENTE: clear della view (history
62800
+ * + currentSessionId reset) e remove da localStorage. Se non corrente:
62801
+ * solo splice dalla lista locale senza toccare la chat aperta.
62802
+ *
62803
+ * Best-effort: errori backend swallowed (la lista si refresha al prossimo
62804
+ * open del dialog comunque, e l'utente puo' riprovare).
62805
+ */
62806
+ async onDeleteSessionFromList(s, event) {
62807
+ if (event)
62808
+ event.stopPropagation();
62809
+ if (!s?.id)
62810
+ return;
62811
+ let confirmed = true;
62812
+ try {
62813
+ const tools = WtoolboxService;
62814
+ if (typeof tools.promptDialog === 'function') {
62815
+ const titlePreview = this.sessionPreview(s) || WtoolboxService.translationService.instant('rag_chatbot.sessions_dialog.untitled');
62816
+ const tmpl = WtoolboxService.translationService.instant('rag_chatbot.sessions_dialog.delete_confirm_message');
62817
+ // Accetta sia {title} sia {{title}} per coerenza col resto del codebase.
62818
+ const message = String(tmpl).replace(/\{\{?\s*title\s*\}?\}/g, titlePreview);
62819
+ const res = await tools.promptDialog({
62820
+ title: WtoolboxService.translationService.instant('rag_chatbot.sessions_dialog.delete_confirm_title'),
62821
+ message,
62822
+ confirmLabel: WtoolboxService.translationService.instant('rag_chatbot.sessions_dialog.delete_confirm_yes'),
62823
+ cancelLabel: WtoolboxService.translationService.instant('rag_chatbot.confirm.cancel'),
62824
+ });
62825
+ confirmed = !!res && res.confirmed !== false;
62826
+ }
62827
+ }
62828
+ catch {
62829
+ confirmed = true;
62830
+ }
62831
+ if (!confirmed)
62832
+ return;
62833
+ const sessionId = s.id;
62834
+ try {
62835
+ await this.ragService.deleteSessionAsync(sessionId);
62836
+ }
62837
+ catch { /* best-effort, prosegui col refresh UI */ }
62838
+ // Rimuove dalla lista locale (immediate UI feedback senza re-fetch).
62839
+ const idx = this.sessionsList.findIndex(x => x.id === sessionId);
62840
+ if (idx >= 0) {
62841
+ this.sessionsList = [...this.sessionsList.slice(0, idx), ...this.sessionsList.slice(idx + 1)];
62842
+ }
62843
+ // Se era la sessione corrente: clear view + localStorage.
62844
+ if (this.currentSessionId === sessionId) {
62845
+ this.history = [];
62846
+ this.pendingMap.clear();
62847
+ this.currentSessionId = null;
62848
+ this.saveLastSession(null);
62849
+ }
62850
+ this.cdr.markForCheck();
62851
+ }
62852
+ /** Truncate del primo prompt a 100 char per anteprima list (full su tooltip). */
62853
+ sessionPreview(s) {
62854
+ const t = s?.title || '';
62855
+ if (!t)
62856
+ return '(senza titolo)';
62857
+ return t.length > 100 ? t.substring(0, 100) + '…' : t;
62858
+ }
62859
+ /** Formato data sessione (locale). */
62860
+ sessionDate(s) {
62861
+ const raw = s?.updatedAt || s?.createdAt;
62862
+ if (!raw)
62863
+ return '';
62864
+ try {
62865
+ const d = new Date(raw);
62866
+ return d.toLocaleString();
62867
+ }
62868
+ catch {
62869
+ return raw;
62870
+ }
62871
+ }
62872
+ /** Label umana per il bottone/header in base al `kind`. */
62873
+ proposedActionTitle(a) {
62874
+ if (!a)
62875
+ return '';
62876
+ // Stessa guard di `contextCueTooltip`: chiamato come binding del template,
62877
+ // soggetto al CD durante route transition con WtoolboxService.translationService
62878
+ // potenzialmente undefined. Fallback al kind raw cosi' l'utente vede comunque
62879
+ // qualcosa di sensato anche prima del bootstrap i18n.
62880
+ const ts = WtoolboxService.translationService;
62881
+ if (!ts || typeof ts.instant !== 'function')
62882
+ return String(a.kind ?? '');
62883
+ // Chiave i18n per ognuno dei 10 kind documentati in callback-cookbook.md.
62884
+ // Per kind sconosciuti (futuri o malformati) fallback a una chiave
62885
+ // generica con interpolation {kind}.
62886
+ const knownKinds = new Set([
62887
+ 'toolbar_action', 'row_action', 'table_style', 'column_style',
62888
+ 'display_formula', 'form_title_formula', 'default_value_callback',
62889
+ 'custom_validation', 'selection_changed', 'lifecycle_callback',
62890
+ 'simple_metadata_update', 'designer_inject',
62891
+ 'sql_metadata_field', 'metadata_column_create',
62892
+ ]);
62893
+ if (knownKinds.has(a.kind)) {
62894
+ return ts.instant(`rag_chatbot.action_box.title.${a.kind}`);
62895
+ }
62896
+ // instant(key) accetta SOLO 1 argomento: interpolation manuale.
62897
+ // Regex accetta sia {kind} (single brace, format script upsert)
62898
+ // sia {{kind}} (double brace, format Angular template) per robustezza.
62899
+ const tpl = ts.instant('rag_chatbot.action_box.title.unknown');
62900
+ return String(tpl).replace(/\{\{?\s*kind\s*\}?\}/g, String(a.kind ?? ''));
62901
+ }
62902
+ /**
62903
+ * Estrae il body del code/template della proposta indipendentemente dal kind:
62904
+ * - toolbar_action / row_action / default_value / custom_validation /
62905
+ * selection_changed / lifecycle / form_title → `callback_js` / `formula_js`
62906
+ * - table_style / column_style → `condition_js`
62907
+ * - display_formula → `template_html` (Angular markup)
62908
+ * o `formula_js` (legacy backward-compat)
62909
+ * - sql_metadata_field → `sql_snippet` (SQL fragment)
62910
+ * Ritorna stringa vuota se nessun campo body e' presente.
62911
+ */
62912
+ proposedActionJsBody(a) {
62913
+ if (!a)
62914
+ return '';
62915
+ return a.callback_js
62916
+ || a.condition_js
62917
+ || a.template_html
62918
+ || a.formula_js
62919
+ || a.sql_snippet
62920
+ || '';
62921
+ }
62922
+ /**
62923
+ * Campi NON-body della proposta che e' utile mostrare all'utente accanto
62924
+ * al codice (es. per `table_style` la classe CSS o l'inline-style che verra'
62925
+ * applicato alla `<tr>` quando il callback ritorna true; per `toolbar_action`
62926
+ * la label del bottone; per `sql_metadata_field` il field target). Per ogni
62927
+ * kind ritorna 0..N coppie `{label, value}`. Il template li renderizza in
62928
+ * lettura sopra la textarea del body. Mantenere allineato alla definizione
62929
+ * tool in `rag_tools.json`: quando si aggiunge un campo importante a un kind
62930
+ * (es. `inline_style` su `propose_table_style`), aggiungerlo anche qui per
62931
+ * non far perdere visibilita' all'utente sulla decisione del LLM.
62932
+ */
62933
+ proposedActionExtraFields(a) {
62934
+ if (!a)
62935
+ return [];
62936
+ const out = [];
62937
+ const get = (k) => a[k];
62938
+ switch (a.kind) {
62939
+ case 'table_style':
62940
+ case 'column_style': {
62941
+ const cssClass = get('css_class');
62942
+ const isInline = !!cssClass && cssClass.startsWith('style:');
62943
+ out.push({
62944
+ label: isInline ? 'Stile inline' : 'Classe CSS',
62945
+ fieldName: 'css_class',
62946
+ value: isInline ? cssClass.substring('style:'.length).trim() : (cssClass || ''),
62947
+ isInlineStyle: isInline,
62948
+ });
62949
+ break;
62950
+ }
62951
+ case 'toolbar_action':
62952
+ case 'row_action': {
62953
+ out.push({ label: 'Etichetta', fieldName: 'label', value: get('label') || '' });
62954
+ out.push({ label: 'Icona', fieldName: 'icon', value: get('icon') || '' });
62955
+ break;
62956
+ }
62957
+ case 'sql_metadata_field': {
62958
+ out.push({ label: 'Campo metadata', fieldName: 'field_name', value: get('field_name') || '' });
62959
+ out.push({ label: 'Tabella', fieldName: 'target_table', value: get('target_table') || '' });
62960
+ break;
62961
+ }
62962
+ case 'metadata_column_create': {
62963
+ out.push({ label: 'Colonna', fieldName: 'column_name', value: get('column_name') || '' });
62964
+ out.push({ label: 'Display', fieldName: 'display_string', value: get('display_string') || '' });
62965
+ out.push({ label: 'Tipo UI', fieldName: 'ui_column_type', value: get('ui_column_type') || '' });
62966
+ break;
62967
+ }
62968
+ }
62969
+ return out;
62970
+ }
62971
+ /**
62972
+ * Nome del campo nella `RagProposedAction` che porta il body editabile della
62973
+ * textarea (callback / template / sql snippet). Speculare a
62974
+ * `proposedActionJsBody` ma ritorna l'identificatore del field, cosi' il
62975
+ * `(ngModelChange)` puo' scrivere su `a[name]`. Default `callback_js` per i
62976
+ * kind che non hanno un body editabile (no-op all'edit).
62977
+ */
62978
+ proposedActionJsBodyFieldName(a) {
62979
+ if (!a)
62980
+ return 'callback_js';
62981
+ switch (a.kind) {
62982
+ case 'table_style':
62983
+ case 'column_style':
62984
+ return 'condition_js';
62985
+ case 'display_formula':
62986
+ // Backward-compat: template_html nuovo, formula_js legacy.
62987
+ return a.template_html !== undefined ? 'template_html' : 'formula_js';
62988
+ case 'form_title_formula':
62989
+ return 'formula_js';
62990
+ case 'sql_metadata_field':
62991
+ return 'sql_snippet';
62992
+ default:
62993
+ return 'callback_js';
62994
+ }
62995
+ }
62996
+ /**
62997
+ * Setter chiamato da `(ngModelChange)` su un campo extra (es. classe/stile).
62998
+ * Per `css_class` con `isInlineStyle=true` ri-prepende il prefisso `style:`
62999
+ * cosi' la convenzione persistita resta omogenea (vedi
63000
+ * `dynamic-template.component.ts:getTableInlineStyle`). Segna il turno come
63001
+ * `proposedActionDirty=true` cosi' `onApplyAction` chiama il PATCH backend
63002
+ * prima dell'Apply.
63003
+ */
63004
+ onProposedExtraFieldEdit(turn, extra, newValue) {
63005
+ if (!turn?.proposedAction)
63006
+ return;
63007
+ let stored = newValue;
63008
+ if (extra.fieldName === 'css_class' && extra.isInlineStyle) {
63009
+ stored = 'style:' + (newValue || '').trim();
63010
+ }
63011
+ turn.proposedAction[extra.fieldName] = stored;
63012
+ turn.proposedActionDirty = true;
63013
+ }
63014
+ /**
63015
+ * Setter per la textarea del body (callback_js / condition_js / template_html
63016
+ * / formula_js / sql_snippet). Aggiorna il field corretto in base al kind.
63017
+ */
63018
+ onProposedBodyEdit(turn, newValue) {
63019
+ if (!turn?.proposedAction)
63020
+ return;
63021
+ const fieldName = this.proposedActionJsBodyFieldName(turn.proposedAction);
63022
+ turn.proposedAction[fieldName] = newValue;
63023
+ turn.proposedActionDirty = true;
63024
+ }
63025
+ /**
63026
+ * Stessa `codeContext` che il metadata-editor usa per editare il callback
63027
+ * sullo stesso campo metadata. Permette al Monaco TS provider di applicare
63028
+ * autocomplete + type-check come se l'utente stesse editando in metadata-editor.
63029
+ * Centralizzato qui per kind: future aggiunte = entry nella map.
63030
+ */
63031
+ proposedActionCodeContext(a) {
63032
+ if (!a)
63033
+ return '';
63034
+ // Le firme corrispondono a quelle documentate in docs/pages/callback-cookbook.md
63035
+ // (tabella "Contratto dei callback").
63036
+ switch (a.kind) {
63037
+ case 'toolbar_action':
63038
+ // (datasource, metaInfo, record, event, wtoolbox)
63039
+ return 'var datasource: DataSourceComponent; var metaInfo: MetaInfo; var record: {[key: string]: BehaviorSubject<any>}; var event: any; var wtoolbox: typeof WtoolboxService';
63040
+ case 'row_action':
63041
+ // (datasource, record, event, field, wtoolbox)
63042
+ return 'var datasource: DataSourceComponent; var record: {[key: string]: BehaviorSubject<any>}; var event: any; var field: MetadatiColonna; var wtoolbox: typeof WtoolboxService';
63043
+ case 'display_formula':
63044
+ // mc_ui_grid_column_data_template scope: Angular template markup, scope=rowData (oggetto riga raw, NO BehaviorSubject).
63045
+ // Monaco lo renderizza come HTML/Angular template - il "type context" qui e' solo informativo per autocomplete fields.
63046
+ return 'var rowData: {[key: string]: any}';
63047
+ case 'form_title_formula':
63048
+ // mddisplayformula scope: (metaInfo, record, datasource, wtoolbox)
63049
+ return 'var metaInfo: MetaInfo; var record: {[key: string]: BehaviorSubject<any>}; var datasource: DataSourceComponent; var wtoolbox: typeof WtoolboxService';
63050
+ case 'default_value_callback':
63051
+ // mcdefaultvaluecallback scope: (metaInfo, record, datasource, wtoolbox)
63052
+ return 'var metaInfo: MetaInfo; var record: {[key: string]: BehaviorSubject<any>}; var datasource: DataSourceComponent; var wtoolbox: typeof WtoolboxService';
63053
+ case 'custom_validation':
63054
+ // mc_validation_custom_callback scope: (valore, record, validateResult, ...)
63055
+ return 'var valore: any; var record: {[key: string]: BehaviorSubject<any>}; var validateResult: (ok: boolean, message?: string) => void';
63056
+ case 'selection_changed':
63057
+ // mcslctionchangedcustomfunction scope: (record, value, datasource, wtoolbox)
63058
+ return 'var record: {[key: string]: BehaviorSubject<any>}; var value: any; var datasource: DataSourceComponent; var wtoolbox: typeof WtoolboxService';
63059
+ case 'lifecycle_callback':
63060
+ // mdbeforesave/mdaftersave/mdafterload scope: (record, datasource, wtoolbox)
63061
+ return 'var record: {[key: string]: BehaviorSubject<any>}; var datasource: DataSourceComponent; var wtoolbox: typeof WtoolboxService';
63062
+ case 'table_style':
63063
+ case 'column_style':
63064
+ // mustattributevalue / musc_attribute_value scope: solo record (dataItem)
63065
+ return 'var record: any';
63066
+ default:
63067
+ return '';
63068
+ }
63069
+ }
63070
+ /**
63071
+ * Cache field+record sintetici per `<wuic-code-editor>` indicizzata per messageId.
63072
+ * MOTIVATION: il code-editor accetta `field: MetadatiColonna` e
63073
+ * `record: { [k]: BehaviorSubject<any> }`; ricostruirli ad ogni ciclo di CD
63074
+ * causerebbe re-init pesanti del Monaco (riload types, ecc). Cache stabile per
63075
+ * messageId -> stesse referenze -> niente re-init.
63076
+ */
63077
+ proposedActionEditorCache = new Map();
63078
+ /**
63079
+ * Costruisce (o riusa cached) il `MetadatiColonna` sintetico da passare al
63080
+ * `<wuic-code-editor>` per il box proposta. Inietta il `codeContext` corretto
63081
+ * per il `kind` cosi' il TSProvider Monaco applica autocomplete come nel
63082
+ * metadata-editor stesso. L'editor e' EDITABILE (readOnly:false): le modifiche
63083
+ * vengono propagate a `turn.proposedAction[bodyFieldName]` tramite subscription
63084
+ * al BehaviorSubject del record, e segnano il turno come dirty cosi'
63085
+ * `onApplyAction` chiama il PATCH backend prima dell'Apply.
63086
+ */
63087
+ getProposedActionField(turn) {
63088
+ if (!turn?.messageId || !turn.proposedAction)
63089
+ return new MetadatiColonna('');
63090
+ const cached = this.proposedActionEditorCache.get(turn.messageId);
63091
+ if (cached)
63092
+ return cached.field;
63093
+ const field = new MetadatiColonna('');
63094
+ field.mc_nome_colonna = 'callback_js';
63095
+ field.mc_ui_column_type = 'code_editor';
63096
+ field.extras = {
63097
+ customEditorConfig: {
63098
+ language: 'typescript',
63099
+ codeContext: this.proposedActionCodeContext(turn.proposedAction),
63100
+ readOnly: false,
63101
+ }
63102
+ };
63103
+ // Body JS unificato: per i kind che usano `condition_js` (table_style)
63104
+ // o `formula_js` (display_formula) il Monaco editor riceve comunque il
63105
+ // contenuto giusto, e il `field.mc_nome_colonna='callback_js'` resta
63106
+ // arbitrario (e' solo l'etichetta della cella nel record).
63107
+ const jsBody = this.proposedActionJsBody(turn.proposedAction);
63108
+ const recordSubj = new BehaviorSubject(jsBody);
63109
+ const record = { callback_js: recordSubj };
63110
+ // Subscription: ogni edit Monaco emette sul BehaviorSubject. Propaghiamo a
63111
+ // `turn.proposedAction[bodyFieldName]` cosi' textarea e Monaco condividono
63112
+ // la stessa fonte di verita'. Skip della prima emit (BehaviorSubject emette
63113
+ // subito il valore iniziale, che e' gia' nel proposedAction).
63114
+ let firstEmit = true;
63115
+ const bodySubscription = recordSubj.subscribe((val) => {
63116
+ if (firstEmit) {
63117
+ firstEmit = false;
63118
+ return;
63119
+ }
63120
+ if (!turn.proposedAction)
63121
+ return;
63122
+ const fieldName = this.proposedActionJsBodyFieldName(turn.proposedAction);
63123
+ const current = turn.proposedAction[fieldName];
63124
+ if (current === val)
63125
+ return; // no-op se non e' cambiato (evita dirty spurio)
63126
+ turn.proposedAction[fieldName] = val;
63127
+ turn.proposedActionDirty = true;
63128
+ this.cdr.markForCheck();
63129
+ });
63130
+ this.proposedActionEditorCache.set(turn.messageId, { field, record, bodySubscription });
63131
+ return field;
63132
+ }
63133
+ /** Compagno di `getProposedActionField`: ritorna lo stesso `record` cached. */
63134
+ getProposedActionRecord(turn) {
63135
+ if (!turn?.messageId || !turn.proposedAction)
63136
+ return {};
63137
+ const cached = this.proposedActionEditorCache.get(turn.messageId);
63138
+ if (cached)
63139
+ return cached.record;
63140
+ // forza inizializzazione tramite getProposedActionField + ri-prende cached
63141
+ this.getProposedActionField(turn);
63142
+ return this.proposedActionEditorCache.get(turn.messageId)?.record || {};
63143
+ }
63144
+ /**
63145
+ * Toggle textarea (default) <-> Monaco editor avanzato per il callback proposto.
63146
+ * Pattern `<lib-code-area-editor>`: textarea istantanea, Monaco on-demand via
63147
+ * `@defer (when turn.editorMode)`. Entrando in Monaco ri-sincronizza il
63148
+ * BehaviorSubject del record cached col valore corrente di `proposedAction`
63149
+ * (potrebbe essere stato editato dalla textarea nel frattempo, vedi
63150
+ * `onProposedBodyEdit`).
63151
+ */
63152
+ toggleEditorMode(turn) {
63153
+ turn.editorMode = !turn.editorMode;
63154
+ if (turn.editorMode && turn.messageId) {
63155
+ const cached = this.proposedActionEditorCache.get(turn.messageId);
63156
+ if (cached) {
63157
+ const current = this.proposedActionJsBody(turn.proposedAction);
63158
+ // bracket notation (TS4111: index-signature property).
63159
+ const subj = cached.record['callback_js'];
63160
+ if (subj && subj.value !== current) {
63161
+ subj.next(current);
63162
+ }
63163
+ }
63164
+ }
63165
+ this.cdr.markForCheck();
63166
+ }
63167
+ /** True se il turn ha una proposta non ancora applicata (mostra "Applica"). */
63168
+ canApplyAction(turn) {
63169
+ return !!turn?.proposedAction && !turn.proposedActionAppliedAt && !turn.applyingAction;
63170
+ }
63171
+ /**
63172
+ * Bottone "Applica": chiama POST /api/Rag/ApplyAction con conferma. Il server
63173
+ * re-legge la proposta dal DB (anti-tampering del callback_js), checka admin,
63174
+ * INSERT su metadata, InvalidateMetadataCachesAndSetVersion. La proposta viene
63175
+ * marcata come applicata in DB.
63176
+ */
63177
+ async onApplyAction(turn) {
63178
+ if (!this.canApplyAction(turn) || !turn.messageId)
63179
+ return;
63180
+ const action = turn.proposedAction;
63181
+ // ---- Client-only kinds (host registry) ---------------------------------
63182
+ // Alcuni kind (es. `designer_inject`) NON vengono applicati dal backend
63183
+ // ma da un handler registrato da un componente host (es. designer.component)
63184
+ // tramite `ChatbotHostRegistryService.register(kind, handler)`. In tal
63185
+ // caso bypassiamo `/api/Rag/ApplyAction` e deleghiamo l'esecuzione al
63186
+ // handler client. Il `proposedActionAppliedAt` viene marcato qui localmente
63187
+ // perche' il server non sa che l'azione e' stata applicata.
63188
+ const hostHandler = this.chatbotHostRegistry.get(action.kind);
63189
+ // Conferma (best-effort: se promptDialog framework non disponibile, fallback a confirm).
63190
+ // Per host-handler: messaggio piu' chiaro che la modifica e' visibile SOLO al designer
63191
+ // corrente fino al save esplicito (vs. apply backend che e' globale).
63192
+ let confirmed = true;
63193
+ try {
63194
+ const tools = WtoolboxService;
63195
+ if (typeof tools.promptDialog === 'function') {
63196
+ const confirmMsg = hostHandler
63197
+ ? `Applicare '${action.label || action.kind}' al designer corrente? La modifica sara' visibile solo a te fino al save esplicito della dashboard.`
63198
+ : `Applicare '${action.label || action.kind}' sulla route '${action.route || '?'}'? L'azione sara' visibile a tutti gli utenti dopo refresh.`;
63199
+ const r = await tools.promptDialog({
63200
+ title: this.proposedActionTitle(action),
63201
+ message: confirmMsg,
63202
+ confirmLabel: 'Applica',
63203
+ cancelLabel: 'Annulla',
63204
+ });
63205
+ confirmed = !!r && r.confirmed !== false;
63206
+ }
63207
+ }
63208
+ catch {
63209
+ confirmed = true;
63210
+ }
63211
+ if (!confirmed)
63212
+ return;
63213
+ turn.applyingAction = true;
63214
+ turn.applyError = null;
63215
+ this.cdr.markForCheck();
63216
+ try {
63217
+ if (hostHandler) {
63218
+ // ---- Branch CLIENT-ONLY -------------------------------------------
63219
+ const result = await hostHandler(action);
63220
+ if (result?.ok) {
63221
+ turn.proposedActionAppliedAt = new Date().toISOString();
63222
+ try {
63223
+ WtoolboxService.messageNotificationService?.add?.({
63224
+ severity: 'success',
63225
+ summary: 'Azione applicata',
63226
+ detail: result.message || `${action.label || action.kind} applicata al designer.`,
63227
+ });
63228
+ }
63229
+ catch { /* notif opzionale */ }
63230
+ }
63231
+ else {
63232
+ turn.applyError = result?.message || 'host-handler-failed';
63233
+ }
63234
+ }
63235
+ else {
63236
+ // ---- Branch BACKEND (default) -------------------------------------
63237
+ // Pre-flight: se l'utente ha editato classe/stile/callback in UI, sync
63238
+ // il `proposed_action_json` del messaggio sul DB. Il backend ApplyAction
63239
+ // rilegge dal DB (security per evitare tampering, vedi commento in
63240
+ // RagController.ApplyAction). Senza questo PATCH le modifiche utente
63241
+ // andrebbero perse all'apply.
63242
+ if (turn.proposedActionDirty && turn.proposedAction) {
63243
+ try {
63244
+ const upd = await this.ragService.updateProposedActionAsync({
63245
+ messageId: turn.messageId,
63246
+ proposedAction: turn.proposedAction,
63247
+ });
63248
+ if (upd?.ok) {
63249
+ turn.proposedActionDirty = false;
63250
+ }
63251
+ else {
63252
+ turn.applyError = upd?.error || 'proposed-action-update-failed';
63253
+ return;
63254
+ }
63255
+ }
63256
+ catch (patchErr) {
63257
+ turn.applyError = this.extractErrorMessage(patchErr);
63258
+ return;
63259
+ }
63260
+ }
63261
+ const resp = await this.ragService.applyProposedActionAsync({ messageId: turn.messageId });
63262
+ if (resp?.ok) {
63263
+ // Mark applied localmente (il server ha gia' persistito audit)
63264
+ turn.proposedActionAppliedAt = new Date().toISOString();
63265
+ // ─── Invalidate metadata caches (server + client, route-scoped) ─────
63266
+ // Backend RagController.ApplyAction GIA' chiama
63267
+ // RawHelpers.InvalidateMetadataCachesAndSetVersion(true), che bump-a la
63268
+ // projectMetadataVersion server-side. Ma il frontend ha la Dexie
63269
+ // MetadataProviderService.MetaDB persistente che mostra i metadati
63270
+ // vecchi finche' qualcuno non la flusha esplicitamente (bug UX riportato
63271
+ // 2026-06-09: l'utente fa Apply, riapre il dropdown "Metadati correlati"
63272
+ // -> "Stili colonna" mostra "Nessun record trovato" anche se la riga
63273
+ // e' stata salvata server-side).
63274
+ //
63275
+ // SCOPE LIMITATO (richiesta utente):
63276
+ // 1. Eseguiamo il flush SOLO se l'utente sta lavorando in un contesto
63277
+ // route business compatibile con l'action (cioe' buildRouteContext
63278
+ // ha ritornato non-null E action.route corrisponde alla route
63279
+ // corrente). In designer / chatbot fullscreen / route senza
63280
+ // contesto, niente flush: alla prossima navigazione il provider
63281
+ // ricaricherà i metadati comunque via projectMetadataVersion check.
63282
+ // 2. Cancelliamo SOLO le righe Dexie della specifica route impattata
63283
+ // (vedi MetadataProviderService.deleteRouteFromCache: 8 tabelle
63284
+ // filtrate per md_route_name + md_id + mc_id + CG_Id), evitando
63285
+ // il flush totale che e' costoso (~MB di re-fetch su F5).
63286
+ const actionRoute = (action.route || '').trim().toLowerCase();
63287
+ const currentRoute = (() => {
63288
+ try {
63289
+ const url = String(this.router?.url || '').split('?')[0].split('#')[0];
63290
+ return url.replace(/^\/+/, '').split('/')[0].trim().toLowerCase();
63291
+ }
63292
+ catch {
63293
+ return '';
63294
+ }
63295
+ })();
63296
+ const sameRoute = !!actionRoute && actionRoute === currentRoute;
63297
+ if (sameRoute) {
63298
+ // POST invalidateMetadataRuntime: idempotente (server-side ha gia'
63299
+ // bumpato la versione, ritorna OK al volo). Pattern canonico
63300
+ // pivot-builder.component.ts:227.
63301
+ try {
63302
+ await WtoolboxService.http.post(WtoolboxService.appSettings.global_root_url + 'MetaService.invalidateMetadataRuntime', {}, { withCredentials: true }).toPromise();
63303
+ }
63304
+ catch { /* non-fatal: backend ApplyAction l'ha gia' fatto server-side */ }
63305
+ // Flush mirato della Dexie per la sola route impattata.
63306
+ try {
63307
+ await MetadataProviderService.deleteRouteFromCache(actionRoute);
63308
+ }
63309
+ catch { }
63310
+ // Reset cache in-memory custom-attributes (pattern documentato in
63311
+ // custom-attributes.service.ts:199 — "Reset cache chiamato dopo
63312
+ // invalidateMetadataRuntime").
63313
+ try {
63314
+ WtoolboxService.customAttributesService?.clearCache?.();
63315
+ }
63316
+ catch { }
63317
+ }
63318
+ try {
63319
+ WtoolboxService.messageNotificationService?.add?.({
63320
+ severity: 'success',
63321
+ summary: 'Azione applicata',
63322
+ detail: sameRoute
63323
+ ? `${action.label || action.kind} creata su '${action.route}'. Ricarica la pagina (F5) per vederla.`
63324
+ : `${action.label || action.kind} creata su '${action.route}'. Naviga alla route per vederla.`,
63325
+ });
63326
+ }
63327
+ catch { /* notif opzionale */ }
63328
+ }
63329
+ else {
63330
+ turn.applyError = resp?.error || 'apply-failed';
63331
+ }
63332
+ }
63333
+ }
63334
+ catch (err) {
63335
+ turn.applyError = this.extractErrorMessage(err);
63336
+ this.errorOccurred.emit({ message: turn.applyError, details: err });
63337
+ }
63338
+ finally {
63339
+ turn.applyingAction = false;
63340
+ this.cdr.markForCheck();
63341
+ }
63342
+ }
61484
63343
  trimHistory() {
61485
63344
  if (this.history.length > this.maxHistory) {
61486
63345
  this.history = this.history.slice(this.history.length - this.maxHistory);
61487
63346
  }
61488
63347
  }
63348
+ /**
63349
+ * Costruisce un riepilogo testuale della pagina/route corrente che viene
63350
+ * iniettato nel system prompt dell'LLM. Permette di generare snippet che
63351
+ * usano i nomi REALI delle colonne (es. `record.CityName.value`) invece di
63352
+ * placeholder generici.
63353
+ *
63354
+ * Formato (compatto, leggibile dall'LLM):
63355
+ * ```
63356
+ * Route corrente: cities
63357
+ * Tabella: Cities (md_id=42, mdtablename=Cities)
63358
+ * Colonne:
63359
+ * - CityName: type=text, required=yes (real column: CityName)
63360
+ * - StateProvince: type=lookupByID -> StateProvinces
63361
+ * - Location: type=text
63362
+ * - LatestRecordedPopulation: type=number
63363
+ * - btn_send: type=button (row action)
63364
+ * ```
63365
+ *
63366
+ * Ritorna `null` se: router assente, route non resolvibile, route NON e'
63367
+ * una business route (framework-docs/rag-chatbot/auth), o metadata
63368
+ * non disponibile in cache (l'utente probabilmente non e' su una list/edit
63369
+ * della route).
63370
+ */
63371
+ async buildRouteContext() {
63372
+ if (!this.router)
63373
+ return null;
63374
+ const url = String(this.router.url || '').split('?')[0].split('#')[0];
63375
+ // Path completo della pagina SENZA slash iniziale: /cities/list -> 'cities/list',
63376
+ // /designer -> 'designer'. Il route_name (per metadata) e' il primo segment.
63377
+ const pagePath = url.replace(/^\/+/, '').trim();
63378
+ if (!pagePath)
63379
+ return null;
63380
+ // ---- Sezioni indipendenti, concatenate alla fine -----------------------
63381
+ // 1. PAGE SECTION (MINIMALE): SOLO la route/pagina corrente. Le colonne reali,
63382
+ // l'identita' SQL e i dettagli dei lookup NON sono inlineati (gonfiavano OGNI
63383
+ // richiesta a prescindere dal prompt): il LLM li recupera ON-DEMAND via il tool
63384
+ // `request_metadata_detail` (risolto dal backend, RagController.ResolveMetadataDetail).
63385
+ // Cosi' il contesto resta lean e scala a N dimensioni di metadata senza bloat.
63386
+ // 2. HOST STATE SECTION: dump dello state dei componenti host che hanno registrato
63387
+ // uno state provider in ChatbotHostRegistryService (es. designer.component dumpa il
63388
+ // tree dashboardElements). E' state CLIENT-only -> resta inlineato qui (il backend
63389
+ // non puo' risolverlo via retrieval). Permette i riferimenti deittici dell'utente.
63390
+ const sections = [];
63391
+ const pageSection = this.buildMinimalPageContext(pagePath);
63392
+ if (pageSection)
63393
+ sections.push(pageSection);
63394
+ const hostStateSection = this.buildHostStateContext();
63395
+ if (hostStateSection)
63396
+ sections.push(hostStateSection);
63397
+ return sections.length > 0 ? sections.join('\n\n') : null;
63398
+ }
63399
+ /**
63400
+ * Contesto MINIMALE della pagina: SOLO la route/pagina corrente (es. `cities/list`,
63401
+ * `cities/edit`, `designer`). I dettagli di metadata (elenco colonne reali, identita'
63402
+ * SQL schema/tabella, alias di join dei lookup) NON sono inlineati: il LLM li recupera
63403
+ * ON-DEMAND via il tool `request_metadata_detail` (detail='columns'|'lookup_columns'),
63404
+ * risolto lato backend da `RagController.ResolveMetadataDetail`.
63405
+ *
63406
+ * RAZIONALE: inlinare colonne + SQL identity gonfiava OGNI richiesta a prescindere dal
63407
+ * prompt (context window saturato, token sprecati). Col retrieval agentico il model
63408
+ * recupera SOLO cio' che il prompt richiede, e il meccanismo scala a N dimensioni di
63409
+ * metadata (physical_columns, related_routes, enum_values, ...) senza toccare questo
63410
+ * builder. Vale per qualunque pagina, incluse quelle framework (designer, ...): il
63411
+ * model puo' comunque targetizzare una route business via retrieval.
63412
+ */
63413
+ buildMinimalPageContext(pagePath) {
63414
+ if (!pagePath)
63415
+ return null;
63416
+ // route_name = primo segment (la route metadata); l'azione (list/edit/dashboard) e' suffisso.
63417
+ const routeName = decodeURIComponent((pagePath.split('/')[0] || '')).trim();
63418
+ const lines = [];
63419
+ lines.push(`Route/pagina corrente: ${pagePath}`);
63420
+ if (routeName && routeName !== pagePath)
63421
+ lines.push(`route_name (per metadata/tool): ${routeName}`);
63422
+ lines.push(`Contesto minimale: l'elenco colonne, l'identita' SQL e i dettagli dei lookup NON sono qui. ` +
63423
+ `Per ottenerli usa il tool request_metadata_detail: {detail:'columns', route:'<route_name>'} per le ` +
63424
+ `colonne reali e l'identita' SQL (schema/tabella + full qualifier); ` +
63425
+ `{detail:'lookup_columns', route:'<route_name>', column:'<col>'} per l'alias di join e le colonne ` +
63426
+ `correlate di una colonna lookupByID.`);
63427
+ return lines.join('\n');
63428
+ }
63429
+ /**
63430
+ * Concatena lo state dump fornito dai componenti host che hanno registrato
63431
+ * un state provider per un proposed_action kind. Tipicamente designer.component
63432
+ * registra `designer_inject` con un tree dei dashboardElements (uniqueName +
63433
+ * inputs significativi), cosi' il LLM puo' identificare componenti via
63434
+ * uniqueName quando l'utente chiede modifiche tipo "cambia background
63435
+ * del riquadro in alto a destra".
63436
+ *
63437
+ * Ritorna `null` se nessun host ha registrato dump per il turn corrente.
63438
+ */
63439
+ buildHostStateContext() {
63440
+ const kinds = this.chatbotHostRegistry.registeredStateKinds();
63441
+ if (!kinds || kinds.length === 0)
63442
+ return null;
63443
+ const sections = [];
63444
+ for (const kind of kinds) {
63445
+ const dump = this.chatbotHostRegistry.getStateDump(kind);
63446
+ if (dump && dump.trim().length > 0) {
63447
+ // Disambiguation hint: quando il dump contiene componenti del designer
63448
+ // (TABLE_/SPLITTER_/TABVIEW_/ACCORDION_/KPI_/...) prepende una nota
63449
+ // esplicita per il LLM. Senza questa nota, prompt come "aggiungi una
63450
+ // colonna alla tabella" vengono interpretati come "aggiungi una colonna
63451
+ // metadata SQL alla route business corrente" invece che "incrementa
63452
+ // cols sul componente TABLE del canvas designer".
63453
+ const dumpTrim = dump.trim();
63454
+ let header = `HOST STATE (${kind})`;
63455
+ if (kind === 'designer_inject') {
63456
+ header += ':\nNOTA AMBIGUITA\': i componenti elencati sotto sono nel CANVAS DESIGNER corrente. '
63457
+ + 'Quando l\'utente parla di "tabella", "riga", "colonna", "splitter", "tab", "accordion", '
63458
+ + '"kpi", "label", "div", ecc. si riferisce SEMPRE a questi uniqueName del canvas, NON a '
63459
+ + 'tabelle/colonne metadata SQL business. Esempi:\n'
63460
+ + ' - "aggiungi una colonna alla tabella" => set_property cols=cols+1 sul TABLE__N del canvas (NON propose_simple_metadata_update)\n'
63461
+ + ' - "aggiungi una riga alla tabella" => set_property rows=rows+1\n'
63462
+ + ' - "rimuovi una colonna/riga" => set_property cols=cols-1 / rows=rows-1\n'
63463
+ + ' - "aggiungi un\'area allo splitter" => set_property areas=areas+1\n'
63464
+ + ' - "aggiungi un tab al tabview" => inject TABPANEL con parent_unique_name=TABVIEW__N\n'
63465
+ + ' - "aggiungi un item all\'accordion" => set_property items=items+1\n'
63466
+ + ' - "cambia il colore della tabella" => set_property backgroundColor sul TABLE__N\n'
63467
+ + 'Se invece l\'utente menziona ESPLICITAMENTE "metadata", "route", "datasource cities", '
63468
+ + '"colonna SQL", "campo del database" => usa propose_simple_metadata_update.';
63469
+ }
63470
+ else {
63471
+ header += ':';
63472
+ }
63473
+ sections.push(`${header}\n${dumpTrim}`);
63474
+ }
63475
+ }
63476
+ return sections.length > 0 ? sections.join('\n\n') : null;
63477
+ }
61489
63478
  scheduleScrollToBottom() {
61490
63479
  if (typeof window === 'undefined')
61491
63480
  return;
@@ -61514,67 +63503,86 @@ class WuicRagChatbotComponent {
61514
63503
  return `${anyErr.status} ${anyErr.statusText}`;
61515
63504
  return JSON.stringify(err);
61516
63505
  }
61517
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotComponent, deps: [{ token: WuicRagService }, { token: i0.ChangeDetectorRef }, { token: i1$2.ActivatedRoute, optional: true }, { token: i1$2.Router, optional: true }, { token: NotificationRealtimeService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
61518
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: WuicRagChatbotComponent, isStandalone: true, selector: "wuic-rag-chatbot", inputs: { title: "title", mode: "mode", topK: "topK", showSources: "showSources", maxHistory: "maxHistory", placeholder: "placeholder", model: "model", chatHeight: "chatHeight", showClearButton: "showClearButton" }, outputs: { resultSelected: "resultSelected", errorOccurred: "errorOccurred", turnAdded: "turnAdded" }, viewQueries: [{ propertyName: "inputTextarea", first: true, predicate: ["inputTextarea"], descendants: true }, { propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }], ngImport: i0, template: "<div class=\"wuic-rag-chatbot\" [style.--chat-height]=\"chatHeight\">\n\n <!-- Header -->\n <div class=\"wuic-rag-chatbot__header\">\n <div class=\"wuic-rag-chatbot__title\">\n <i class=\"pi pi-comments wuic-rag-chatbot__title-icon\" aria-hidden=\"true\"></i>\n <span>{{ title }}</span>\n </div>\n <div class=\"wuic-rag-chatbot__header-actions\">\n <p-badge\n [value]=\"modeBadgeLabel\"\n [severity]=\"modeBadgeSeverity\"\n [pTooltip]=\"healthInfo\n ? ('chunks indicizzati: ' + healthInfo.docs_loaded + ' | modello: ' + healthInfo.default_model)\n : (healthError || 'connessione al server RAG...')\"\n tooltipPosition=\"bottom\">\n </p-badge>\n @if (showClearButton) {\n <p-button\n icon=\"pi pi-trash\"\n severity=\"secondary\"\n [text]=\"true\"\n [rounded]=\"true\"\n size=\"small\"\n [disabled]=\"history.length === 0 || loading\"\n (onClick)=\"onClearHistory()\"\n pTooltip=\"Svuota cronologia\"\n tooltipPosition=\"bottom\"\n ariaLabel=\"Svuota cronologia chat\">\n </p-button>\n }\n </div>\n </div>\n\n <!-- Health error banner -->\n @if (healthError) {\n <p-message severity=\"error\" [text]=\"'Server RAG non raggiungibile: ' + healthError\" styleClass=\"wuic-rag-chatbot__error-banner\"></p-message>\n }\n\n <!-- Chat history -->\n <div #scrollContainer class=\"wuic-rag-chatbot__history\" role=\"log\" aria-live=\"polite\">\n @if (history.length === 0) {\n <div class=\"wuic-rag-chatbot__empty\">\n <i class=\"pi pi-info-circle wuic-rag-chatbot__empty-icon\" aria-hidden=\"true\"></i>\n <p>Fai una domanda sul codebase WUIC, ad esempio:</p>\n <ul class=\"wuic-rag-chatbot__suggestions\">\n <li>Invalidare la cache metadata runtime sul server</li>\n <li>Spreadsheet per data entry massivo simile a excel</li>\n <li>Salvataggio di un report dal designer</li>\n </ul>\n </div>\n }\n\n @for (turn of history; track turn.messageId || $index) {\n <div class=\"wuic-rag-chatbot__turn\"\n [attr.data-message-id]=\"turn.messageId || null\"\n [class.wuic-rag-chatbot__turn--user]=\"turn.role === 'user'\"\n [class.wuic-rag-chatbot__turn--assistant]=\"turn.role === 'assistant'\">\n\n <div class=\"wuic-rag-chatbot__turn-header\">\n <i class=\"pi\"\n [class.pi-user]=\"turn.role === 'user'\"\n [class.pi-android]=\"turn.role === 'assistant'\"\n aria-hidden=\"true\"></i>\n <span>{{ turn.role === 'user' ? 'Tu' : 'Assistente' }}</span>\n @if (turn.mode) {\n <span class=\"wuic-rag-chatbot__turn-mode\">[{{ turn.mode === 'rag-llm' ? 'RAG+LLM' : 'retrieval' }}]</span>\n }\n </div>\n\n @if (turn.loading) {\n <div class=\"wuic-rag-chatbot__turn-loading\">\n <p-progressSpinner styleClass=\"wuic-rag-chatbot__spinner\" strokeWidth=\"3\" animationDuration=\".9s\"></p-progressSpinner>\n <span>Sto interrogando il codebase...</span>\n </div>\n } @else if (turn.errorMessage) {\n <p-message severity=\"error\" [text]=\"turn.errorMessage\" styleClass=\"wuic-rag-chatbot__turn-error\"></p-message>\n } @else {\n <div class=\"wuic-rag-chatbot__turn-content\">{{ turn.content }}</div>\n @if (turn.warning) {\n <p-message severity=\"warn\" [text]=\"turn.warning\" styleClass=\"wuic-rag-chatbot__turn-warning\"></p-message>\n }\n @if (showSources && turn.role === 'assistant' && turn.sources && turn.sources.length > 0) {\n @let dedupedSources = dedupSources(turn.sources);\n <div class=\"wuic-rag-chatbot__sources\">\n <div class=\"wuic-rag-chatbot__sources-label\">Fonti:</div>\n <div class=\"wuic-rag-chatbot__sources-list\">\n @for (source of dedupedSources; track source.chunk_id || source.rank) {\n <p-chip\n [label]=\"sourceLabel(source, dedupedSources)\"\n [icon]=\"isDocsSource(source) ? 'pi pi-book' : (isSkillSource(source) ? 'pi pi-wrench' : 'pi pi-file-edit')\"\n [removable]=\"false\"\n [pTooltip]=\"sourceTooltip(source)\"\n tooltipPosition=\"top\"\n [styleClass]=\"isDocsSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--docs'\n : (isSkillSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--skill'\n : 'wuic-rag-chatbot__source-chip')\"\n (click)=\"onSourceClick(source)\">\n </p-chip>\n }\n </div>\n </div>\n }\n @if (turn.tokensIn || turn.tokensOut) {\n <div class=\"wuic-rag-chatbot__tokens\">\n tokens in/out: {{ turn.tokensIn ?? '?' }}/{{ turn.tokensOut ?? '?' }}\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Input area -->\n <div class=\"wuic-rag-chatbot__input-area\">\n <textarea #inputTextarea\n pTextarea\n class=\"wuic-rag-chatbot__input\"\n [(ngModel)]=\"inputValue\"\n (keydown)=\"onKeyDown($event)\"\n [placeholder]=\"placeholder\"\n [disabled]=\"loading || !!healthError\"\n [autoResize]=\"true\"\n rows=\"1\"\n aria-label=\"Domanda per il chatbot codebase\">\n </textarea>\n <p-button\n icon=\"pi pi-send\"\n [disabled]=\"sendDisabled\"\n [loading]=\"loading\"\n (onClick)=\"onSend()\"\n severity=\"primary\"\n ariaLabel=\"Invia messaggio\">\n </p-button>\n </div>\n\n <!-- Dialog preview snippet sorgente.\n Mostra SOLO il contenuto del chunk (gia' embedded nella response RAG).\n NON espone il path del file ne' apre editor esterni: il codebase del\n framework vive sul repo dev e non deve essere linkato all'utente finale. -->\n @if (selectedSource) {\n <p-dialog\n [visible]=\"true\"\n [modal]=\"true\"\n [closable]=\"true\"\n [closeOnEscape]=\"true\"\n [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw' }\"\n [header]=\"sourceDialogTitle(selectedSource)\"\n styleClass=\"wuic-rag-chatbot__source-dialog\"\n (onHide)=\"closeSourcePreview()\">\n <div class=\"wuic-rag-chatbot__source-meta\">\n @if (sourceFilePath(selectedSource); as fpath) {\n <span class=\"wuic-rag-chatbot__source-tag wuic-rag-chatbot__source-tag--file\">\n <i class=\"pi pi-file\" aria-hidden=\"true\"></i>\n {{ fpath }}\n </span>\n }\n @if (selectedSource.symbol_type) {\n <span class=\"wuic-rag-chatbot__source-tag\">{{ selectedSource.symbol_type }}</span>\n }\n @if (selectedSource.start_line && selectedSource.end_line) {\n <span class=\"wuic-rag-chatbot__source-tag\">linee {{ selectedSource.start_line }}-{{ selectedSource.end_line }}</span>\n }\n @if (selectedSource.score_vector !== null && selectedSource.score_vector !== undefined) {\n <span class=\"wuic-rag-chatbot__source-tag\">vec {{ selectedSource.score_vector | number:'1.2-2' }}</span>\n }\n </div>\n <pre class=\"wuic-rag-chatbot__source-snippet\"><code>{{ selectedSource.snippet || '(snippet non disponibile)' }}</code></pre>\n </p-dialog>\n }\n\n</div>\n", styles: [".wuic-rag-chatbot{--wuic-rag-chatbot-radius: .5rem;--wuic-rag-chatbot-padding: .75rem;--wuic-rag-chatbot-gap: .5rem;--chat-height: 420px;display:flex;flex-direction:column;width:100%;height:auto;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:var(--wuic-rag-chatbot-radius);background-color:var(--p-content-background, transparent);color:inherit;overflow:hidden;font-family:inherit}.wuic-rag-chatbot__header{display:flex;align-items:center;justify-content:space-between;padding:var(--wuic-rag-chatbot-padding);border-bottom:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__title{display:flex;align-items:center;gap:.5rem;font-weight:600;color:inherit}.wuic-rag-chatbot__title-icon{color:var(--p-primary-color, #3b82f6)}.wuic-rag-chatbot__header-actions{display:flex;align-items:center;gap:.5rem}.wuic-rag-chatbot__error-banner{margin:var(--wuic-rag-chatbot-padding);margin-bottom:0}.wuic-rag-chatbot__history{flex:1 1 auto;height:var(--chat-height);overflow-y:auto;padding:var(--wuic-rag-chatbot-padding);display:flex;flex-direction:column;gap:var(--wuic-rag-chatbot-gap);background-color:transparent}.wuic-rag-chatbot__empty{margin:auto;text-align:center;color:var(--p-text-muted-color, #6b7280);max-width:80%;padding:1.5rem 0}.wuic-rag-chatbot__empty-icon{font-size:1.5rem;margin-bottom:.5rem;display:block}.wuic-rag-chatbot__suggestions{list-style:none;padding:0;margin:.75rem 0 0;display:flex;flex-direction:column;gap:.25rem;text-align:left}.wuic-rag-chatbot__suggestions li{padding:.35rem .75rem;border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:.375rem;font-size:.875rem}.wuic-rag-chatbot__turn{display:flex;flex-direction:column;gap:.35rem;padding:.65rem .85rem;border-radius:var(--wuic-rag-chatbot-radius);max-width:92%;color:inherit;background-color:transparent;border:1px solid var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__turn--user{align-self:flex-end;background-color:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-200, rgba(59, 130, 246, .25))}.wuic-rag-chatbot__turn--assistant{align-self:flex-start;background-color:var(--p-surface-50, rgba(0, 0, 0, .025))}.wuic-rag-chatbot__turn-header{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__turn-mode{margin-left:auto;font-family:ui-monospace,monospace;font-size:.7rem;opacity:.7}.wuic-rag-chatbot__turn-content{white-space:pre-wrap;word-break:break-word;line-height:1.45;font-size:.93rem;color:inherit}.wuic-rag-chatbot__turn-loading{display:flex;align-items:center;gap:.5rem;color:var(--p-text-muted-color, #6b7280);font-style:italic;font-size:.875rem}.wuic-rag-chatbot__spinner{width:1.25rem!important;height:1.25rem!important}.wuic-rag-chatbot__turn-error,.wuic-rag-chatbot__turn-warning{margin-top:.35rem}.wuic-rag-chatbot__sources{display:flex;flex-direction:column;gap:.35rem;margin-top:.5rem;padding-top:.5rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__sources-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.05em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__sources-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__source-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.wuic-rag-chatbot__source-chip:hover{transform:translateY(-1px);box-shadow:0 1px 4px #0000001f}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:var(--p-blue-50, #eff6ff);color:var(--p-blue-700, #1d4ed8);border:1px solid var(--p-blue-200, #bfdbfe)}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:var(--p-blue-600, #2563eb)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:var(--p-green-50, #f0fdf4);color:var(--p-green-700, #15803d);border:1px solid var(--p-green-200, #bbf7d0)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:var(--p-green-600, #16a34a)}.wuic-rag-chatbot__tokens{margin-top:.4rem;font-size:.7rem;color:var(--p-text-muted-color, #6b7280);text-align:right;font-family:ui-monospace,monospace}.wuic-rag-chatbot__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:var(--wuic-rag-chatbot-padding);border-top:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__input{flex:1 1 auto;min-height:2.25rem;max-height:8rem;resize:none;color:inherit;background-color:transparent}.wuic-rag-chatbot__source-meta{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.75rem}.wuic-rag-chatbot__source-tag{display:inline-flex;align-items:center;gap:.25rem;padding:2px 8px;background:var(--p-surface-100, #f3f4f6);color:var(--p-text-color-secondary, #6b7280);border-radius:10px;font-size:.72rem;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.wuic-rag-chatbot__source-tag--file{background:var(--p-surface-0, #fff);color:var(--p-primary-color, #2563eb);border:1px solid var(--p-primary-200, #bfdbfe);font-weight:500;max-width:100%;word-break:break-all;white-space:normal;line-height:1.3}.wuic-rag-chatbot__source-tag--file i{font-size:.7rem;flex-shrink:0}.wuic-rag-chatbot__source-snippet{margin:0;padding:12px 14px;max-height:420px;overflow:auto;background:var(--p-surface-50, #fafafa);border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.78rem;line-height:1.4;white-space:pre-wrap;word-break:break-word;color:var(--p-text-color, #111827)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i7$1.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: BadgeModule }, { kind: "component", type: i7$2.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "ngmodule", type: ChipModule }, { kind: "component", type: i8$2.Chip, selector: "p-chip", inputs: ["label", "icon", "image", "alt", "styleClass", "disabled", "removable", "removeIcon", "chipProps"], outputs: ["onRemove", "onImageError"] }, { kind: "ngmodule", type: ScrollPanelModule }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i9$2.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i7.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant", "motionOptions"], outputs: ["onClose"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }] });
63506
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotComponent, deps: [{ token: WuicRagService }, { token: i0.ChangeDetectorRef }, { token: ChatbotHostRegistryService }, { token: i1$2.ActivatedRoute, optional: true }, { token: i1$2.Router, optional: true }, { token: NotificationRealtimeService, optional: true }, { token: MetadataProviderService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
63507
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: WuicRagChatbotComponent, isStandalone: true, selector: "wuic-rag-chatbot", inputs: { title: "title", mode: "mode", topK: "topK", showSources: "showSources", maxHistory: "maxHistory", placeholder: "placeholder", model: "model", chatHeight: "chatHeight", showClearButton: "showClearButton", persistSession: "persistSession", sendRouteContext: "sendRouteContext", allowProfileToggle: "allowProfileToggle" }, outputs: { resultSelected: "resultSelected", errorOccurred: "errorOccurred", turnAdded: "turnAdded" }, viewQueries: [{ propertyName: "inputTextarea", first: true, predicate: ["inputTextarea"], descendants: true }, { propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }], ngImport: i0, template: "<div class=\"wuic-rag-chatbot\" style=\"height: 100%;\">\n\n <!-- Header -->\n <div class=\"wuic-rag-chatbot__header\">\n <div class=\"wuic-rag-chatbot__title\">\n <i class=\"pi pi-comments wuic-rag-chatbot__title-icon\" aria-hidden=\"true\"></i>\n <span>{{ title }}</span>\n </div>\n <div class=\"wuic-rag-chatbot__header-actions\">\n @if (allowProfileToggle) {\n <p-button [label]=\"profileToggleLabel\" [icon]=\"profileOverride === 'release' ? 'pi pi-eye' : 'pi pi-code'\"\n [severity]=\"profileOverride === 'release' ? 'warn' : 'secondary'\" [outlined]=\"true\" [rounded]=\"true\"\n size=\"small\" (onClick)=\"toggleProfile()\" [pTooltip]=\"profileOverride === 'release'\n ? ('rag_chatbot.profile_toggle.tooltip_release' | translate)\n : ('rag_chatbot.profile_toggle.tooltip_dev' | translate)\" tooltipPosition=\"bottom\"\n [ariaLabel]=\"'rag_chatbot.profile_toggle.aria_label' | translate\">\n </p-button>\n }\n <p-badge [value]=\"modeBadgeLabel\" [severity]=\"modeBadgeSeverity\" [pTooltip]=\"healthInfo\n ? ('chunks indicizzati: ' + healthInfo.docs_loaded + ' | modello: ' + healthInfo.default_model)\n : (healthError || ('rag_chatbot.health_indicator.connecting' | translate))\" tooltipPosition=\"bottom\">\n </p-badge>\n <!-- Context cue: cerchio colorato che riflette % context window usato.\n Click + conferma -> /compact. Tooltip mostra k/k token + %. -->\n @if (contextWindowMax) {\n <button type=\"button\"\n class=\"wuic-rag-chatbot__context-cue\"\n [style.background]=\"contextCueColor\"\n [pTooltip]=\"contextCueTooltip\"\n tooltipPosition=\"bottom\"\n [disabled]=\"compactingSession || !currentSessionId\"\n (click)=\"onContextCueClick()\"\n attr.aria-label=\"Compatta sessione (context {{ contextUsagePercent }}%)\">\n @if (compactingSession) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else {\n <span>{{ contextUsagePercent }}%</span>\n }\n </button>\n }\n <!-- Bottone Sessioni: apre popup con lista sessioni ordinate desc per updatedAt. -->\n <p-button icon=\"pi pi-list\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"openSessionsDialog()\" [pTooltip]=\"'rag_chatbot.sessions_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.sessions_button.aria_label' | translate\">\n </p-button>\n @if (showClearButton) {\n <p-button icon=\"pi pi-trash\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n [disabled]=\"history.length === 0 || loading || clearingHistory\" [loading]=\"clearingHistory\"\n (onClick)=\"onClearHistory()\" [pTooltip]=\"'rag_chatbot.clear_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.clear_button.aria_label' | translate\">\n </p-button>\n }\n </div>\n </div>\n\n <!-- Health error banner -->\n @if (healthError) {\n <p-message severity=\"error\" [text]=\"('rag_chatbot.health_error.banner' | translate) + ': ' + healthError\"\n styleClass=\"wuic-rag-chatbot__error-banner\"></p-message>\n }\n\n <!-- Chat history -->\n <div #scrollContainer class=\"wuic-rag-chatbot__history\" role=\"log\" aria-live=\"polite\">\n @if (history.length === 0) {\n <div class=\"wuic-rag-chatbot__empty\">\n <i class=\"pi pi-info-circle wuic-rag-chatbot__empty-icon\" aria-hidden=\"true\"></i>\n <p>{{ 'rag_chatbot.empty.prompt' | translate }}</p>\n <ul class=\"wuic-rag-chatbot__suggestions\">\n <li>{{ 'rag_chatbot.suggestion.invalidate_cache' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.spreadsheet_massive' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.save_report' | translate }}</li>\n </ul>\n </div>\n }\n\n @for (turn of history; track turn.messageId || $index) {\n <div class=\"wuic-rag-chatbot__turn\" [attr.data-message-id]=\"turn.messageId || null\"\n [class.wuic-rag-chatbot__turn--user]=\"turn.role === 'user'\"\n [class.wuic-rag-chatbot__turn--assistant]=\"turn.role === 'assistant'\">\n\n <div class=\"wuic-rag-chatbot__turn-header\">\n <i class=\"pi\" [class.pi-user]=\"turn.role === 'user'\" [class.pi-android]=\"turn.role === 'assistant'\"\n aria-hidden=\"true\"></i>\n <span>{{ (turn.role === 'user' ? 'rag_chatbot.turn.role_user' : 'rag_chatbot.turn.role_assistant') | translate }}</span>\n @if (turn.mode) {\n <span class=\"wuic-rag-chatbot__turn-mode\">[{{ turn.mode === 'rag-llm' ? 'RAG+LLM' : 'retrieval' }}]</span>\n }\n </div>\n\n @if (turn.loading) {\n <div class=\"wuic-rag-chatbot__turn-loading\">\n <p-progressSpinner styleClass=\"wuic-rag-chatbot__spinner\" strokeWidth=\"3\"\n animationDuration=\".9s\"></p-progressSpinner>\n <span>{{ 'rag_chatbot.turn.loading' | translate }}</span>\n </div>\n } @else if (turn.errorMessage) {\n <p-message severity=\"error\" [text]=\"(turn.errorMessage | translate)\" styleClass=\"wuic-rag-chatbot__turn-error\"></p-message>\n } @else {\n <div class=\"wuic-rag-chatbot__turn-content\">{{ turn.content }}</div>\n @if (turn.warning) {\n <p-message severity=\"warn\" [text]=\"turn.warning\" styleClass=\"wuic-rag-chatbot__turn-warning\"></p-message>\n }\n <!-- Box \"Proposta azione\" emessa dall'LLM via tool-use. Renderizzato solo\n per i turn assistant che hanno proposedAction valorizzata. Il monaco\n editor riusa la stessa codeContext del metadata-editor sulla colonna\n equivalente (autocomplete identico, type-check identico). -->\n @if (turn.role === 'assistant' && turn.proposedAction) {\n <div class=\"wuic-rag-chatbot__action-box\"\n [class.wuic-rag-chatbot__action-box--applied]=\"turn.proposedActionAppliedAt\"\n [class.wuic-rag-chatbot__action-box--error]=\"turn.applyError\">\n <div class=\"wuic-rag-chatbot__action-box-header\">\n <i class=\"pi pi-bolt\" aria-hidden=\"true\"></i>\n <span class=\"wuic-rag-chatbot__action-box-title\">\n {{ proposedActionTitle(turn.proposedAction) }}\n @if (turn.proposedAction.label) {\n <span class=\"wuic-rag-chatbot__action-box-label\">\"{{ turn.proposedAction.label }}\"</span>\n }\n @if (turn.proposedAction.route) {\n <span class=\"wuic-rag-chatbot__action-box-route\">{{ 'rag_chatbot.action_box.label_on_route_prefix' | translate }} <code>{{ turn.proposedAction.route }}</code></span>\n }\n </span>\n </div>\n @if (turn.proposedAction.rationale) {\n <div class=\"wuic-rag-chatbot__action-box-rationale\">{{ turn.proposedAction.rationale }}</div>\n }\n <!-- Hint trasparenza UX: l'utente sa in anticipo che, oltre a creare\n la toolbar action, abiliteremo md_multiple_selection sulla route\n (necessario per le bulk operations che leggono getSelectedRows). -->\n @if (turn.proposedAction.requires_multi_selection) {\n <div class=\"wuic-rag-chatbot__action-box-side-effect\">\n <i class=\"pi pi-info-circle\"></i>\n {{ 'rag_chatbot.action_box.side_effect_multi_selection' | translate }}\n </div>\n }\n <!-- Campi NON-body della proposta (es. per table_style: classe/stile;\n per toolbar/row_action: label+icon; per sql_metadata_field: field\n target). Visibili sopra la textarea del body cosi' l'utente capisce\n a colpo d'occhio COSA viene applicato oltre al CODICE del callback.\n Sorgente: proposedActionExtraFields(kind) nel .ts. -->\n @if (proposedActionExtraFields(turn.proposedAction).length > 0) {\n <div class=\"wuic-rag-chatbot__action-box-extra-fields\">\n @for (extra of proposedActionExtraFields(turn.proposedAction); track extra.fieldName) {\n <div class=\"wuic-rag-chatbot__action-box-extra-field\">\n <label class=\"wuic-rag-chatbot__action-box-extra-field-label\">{{ extra.label }}</label>\n <input\n class=\"wuic-rag-chatbot__action-box-extra-field-value\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"extra.value\"\n (ngModelChange)=\"onProposedExtraFieldEdit(turn, extra, $event)\"\n [name]=\"'extra-' + turn.messageId + '-' + extra.fieldName\">\n </div>\n }\n </div>\n }\n @if (proposedActionJsBody(turn.proposedAction)) {\n <!-- Pattern <lib-code-area-editor>: textarea readonly (zero overhead,\n niente flash nero, contenuto immediatamente visibile) di default.\n Toggle \"Apri editor avanzato\" attiva `editorMode` -> @defer carica\n Monaco con codeContext+autocomplete. Doppio livello di lazy:\n @defer in HTML + LazyCodeEditorComponent fa import() dinamico.\n Risultato: Monaco scaricato SOLO se l'utente lo richiede.\n proposedActionJsBody() unifica callback_js / condition_js / formula_js\n in base al kind: stesso UX per tutti i kind che hanno una body JS. -->\n <div class=\"wuic-rag-chatbot__action-box-editor-toolbar\">\n <p-button\n [label]=\"(turn.editorMode ? 'rag_chatbot.action_box.editor_close' : 'rag_chatbot.action_box.editor_open') | translate\"\n [icon]=\"turn.editorMode ? 'pi pi-times' : 'pi pi-code'\"\n severity=\"secondary\"\n [text]=\"true\"\n size=\"small\"\n (onClick)=\"toggleEditorMode(turn)\">\n </p-button>\n </div>\n @if (!turn.editorMode) {\n <textarea\n class=\"wuic-rag-chatbot__action-box-textarea\"\n rows=\"10\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"proposedActionJsBody(turn.proposedAction)\"\n (ngModelChange)=\"onProposedBodyEdit(turn, $event)\"\n [name]=\"'body-' + turn.messageId\">\n </textarea>\n } @else {\n @defer (when turn.editorMode) {\n <div class=\"wuic-rag-chatbot__action-box-editor\">\n <wuic-code-editor-lazy\n [field]=\"getProposedActionField(turn)\"\n [record]=\"getProposedActionRecord(turn)\"\n style=\"display: block; width: 100%; height: 100%;\">\n </wuic-code-editor-lazy>\n </div>\n } @loading (minimum 200ms) {\n <div class=\"wuic-rag-chatbot__action-box-editor-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.editor_loading' | translate }}\n </div>\n }\n }\n }\n <div class=\"wuic-rag-chatbot__action-box-actions\">\n @if (turn.proposedActionAppliedAt) {\n <span class=\"wuic-rag-chatbot__action-box-applied\">\n <i class=\"pi pi-check-circle\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.applied' | translate }}\n </span>\n } @else {\n <p-button\n [label]=\"'rag_chatbot.action_box.apply' | translate\"\n icon=\"pi pi-check\"\n severity=\"primary\"\n size=\"small\"\n [loading]=\"!!turn.applyingAction\"\n (onClick)=\"onApplyAction(turn)\">\n </p-button>\n }\n @if (turn.applyError) {\n <span class=\"wuic-rag-chatbot__action-box-error\">\n <i class=\"pi pi-times-circle\" aria-hidden=\"true\"></i>\n {{ turn.applyError }}\n </span>\n }\n </div>\n </div>\n }\n @if (showSources && turn.role === 'assistant' && turn.sources && turn.sources.length > 0) {\n @let dedupedSources = dedupSources(turn.sources);\n <div class=\"wuic-rag-chatbot__sources\">\n <div class=\"wuic-rag-chatbot__sources-label\">{{ 'rag_chatbot.sources.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__sources-list\">\n @for (source of dedupedSources; track source.chunk_id || source.rank) {\n <p-chip [label]=\"sourceLabel(source, dedupedSources)\"\n [icon]=\"isDocsSource(source) ? 'pi pi-book' : (isSkillSource(source) ? 'pi pi-wrench' : 'pi pi-file-edit')\"\n [removable]=\"false\" [pTooltip]=\"sourceTooltip(source)\" tooltipPosition=\"top\" [styleClass]=\"isDocsSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--docs'\n : (isSkillSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--skill'\n : 'wuic-rag-chatbot__source-chip')\" (click)=\"onSourceClick(source)\">\n </p-chip>\n }\n </div>\n </div>\n }\n <!-- Follow-up questions emesse dall'LLM via tool `suggest_followups`.\n Renderizzate come chip cliccabili sotto la risposta: click popola\n l'input box (NON invia auto, l'utente puo' editare prima). -->\n @if (turn.role === 'assistant' && turn.followupQuestions && turn.followupQuestions.length > 0) {\n <div class=\"wuic-rag-chatbot__followups\">\n <div class=\"wuic-rag-chatbot__followups-label\">{{ 'rag_chatbot.followups.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__followups-list\">\n @for (q of turn.followupQuestions; track q) {\n <p-chip\n [label]=\"q\"\n icon=\"pi pi-arrow-right\"\n styleClass=\"wuic-rag-chatbot__followup-chip\"\n (click)=\"applyFollowup(q)\">\n </p-chip>\n }\n </div>\n </div>\n }\n @if (turn.tokensIn || turn.tokensOut) {\n <div class=\"wuic-rag-chatbot__tokens\">\n tokens in/out: {{ turn.tokensIn ?? '?' }}/{{ turn.tokensOut ?? '?' }}\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Input area -->\n <div class=\"wuic-rag-chatbot__input-area\">\n <textarea #inputTextarea pTextarea class=\"wuic-rag-chatbot__input\" [(ngModel)]=\"inputValue\"\n (keydown)=\"onKeyDown($event)\" [placeholder]=\"placeholder\" [disabled]=\"loading || !!healthError\"\n [autoResize]=\"true\" rows=\"1\" [attr.aria-label]=\"'rag_chatbot.input.aria_label' | translate\">\n </textarea>\n <p-button icon=\"pi pi-send\" [disabled]=\"sendDisabled\" [loading]=\"loading\" (onClick)=\"onSend()\" severity=\"primary\"\n [ariaLabel]=\"'rag_chatbot.input.send_aria_label' | translate\">\n </p-button>\n </div>\n\n <!-- Dialog preview snippet sorgente.\n Mostra SOLO il contenuto del chunk (gia' embedded nella response RAG).\n NON espone il path del file ne' apre editor esterni: il codebase del\n framework vive sul repo dev e non deve essere linkato all'utente finale. -->\n @if (selectedSource) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw' }\" [header]=\"sourceDialogTitle(selectedSource)\"\n styleClass=\"wuic-rag-chatbot__source-dialog\" (onHide)=\"closeSourcePreview()\">\n <div class=\"wuic-rag-chatbot__source-meta\">\n @if (sourceFilePath(selectedSource); as fpath) {\n <span class=\"wuic-rag-chatbot__source-tag wuic-rag-chatbot__source-tag--file\">\n <i class=\"pi pi-file\" aria-hidden=\"true\"></i>\n {{ fpath }}\n </span>\n }\n @if (selectedSource.symbol_type) {\n <span class=\"wuic-rag-chatbot__source-tag\">{{ selectedSource.symbol_type }}</span>\n }\n @if (selectedSource.start_line && selectedSource.end_line) {\n <span class=\"wuic-rag-chatbot__source-tag\">linee {{ selectedSource.start_line }}-{{ selectedSource.end_line\n }}</span>\n }\n @if (selectedSource.score_vector !== null && selectedSource.score_vector !== undefined) {\n <span class=\"wuic-rag-chatbot__source-tag\">vec {{ selectedSource.score_vector | number:'1.2-2' }}</span>\n }\n </div>\n <pre\n class=\"wuic-rag-chatbot__source-snippet\"><code>{{ selectedSource.snippet || ('rag_chatbot.source.snippet_unavailable' | translate) }}</code></pre>\n </p-dialog>\n }\n\n <!-- Dialog \"Sessioni\": elenco ordinato desc per updatedAt. Per ogni riga:\n data + titolo (truncated 100 char, tooltip full) + bottone rinomina\n inline + click row = switch session. -->\n @if (sessionsDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw', maxHeight: '85vh' }\"\n [header]=\"'rag_chatbot.sessions_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__sessions-dialog\"\n (onHide)=\"closeSessionsDialog()\">\n @if (sessionsLoading) {\n <div class=\"wuic-rag-chatbot__sessions-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i> {{ 'rag_chatbot.sessions_dialog.loading' | translate }}\n </div>\n } @else if (sessionsList.length === 0) {\n <div class=\"wuic-rag-chatbot__sessions-empty\">{{ 'rag_chatbot.sessions_dialog.empty' | translate }}</div>\n } @else {\n <ul class=\"wuic-rag-chatbot__sessions-list\">\n @for (s of sessionsList; track s.id) {\n <li class=\"wuic-rag-chatbot__session-item\"\n [class.wuic-rag-chatbot__session-item--current]=\"s.id === currentSessionId\"\n (click)=\"selectSession(s)\">\n <div class=\"wuic-rag-chatbot__session-date\">{{ sessionDate(s) }}</div>\n @if (sessionRenameEditing[s.id]) {\n <div class=\"wuic-rag-chatbot__session-edit\" (click)=\"$event.stopPropagation()\">\n <input type=\"text\" pInputText\n [(ngModel)]=\"sessionRenameDraft[s.id]\"\n [autofocus]=\"true\"\n maxlength=\"200\"\n class=\"wuic-rag-chatbot__session-edit-input\"\n (keydown.enter)=\"confirmRenameSession(s, $event)\"\n (keydown.escape)=\"cancelRenameSession(s)\">\n <p-button icon=\"pi pi-check\" severity=\"success\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"confirmRenameSession(s, $event)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_confirm_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-times\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"cancelRenameSession(s)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_cancel_aria' | translate\"></p-button>\n </div>\n } @else {\n <div class=\"wuic-rag-chatbot__session-row\">\n <span class=\"wuic-rag-chatbot__session-title\" [pTooltip]=\"s.title || ('rag_chatbot.sessions_dialog.untitled' | translate)\" tooltipPosition=\"top\">\n {{ sessionPreview(s) }}\n </span>\n <p-button icon=\"pi pi-pencil\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"startRenameSession(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.rename' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-trash\" severity=\"danger\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"onDeleteSessionFromList(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.delete' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.delete_aria' | translate\"></p-button>\n </div>\n }\n </li>\n }\n </ul>\n }\n </p-dialog>\n }\n\n <!-- Dialog context-overflow: l'ultima call ha fallito perche' la history\n sfora il context window. 2 azioni: Compatta ora (riassume e prosegue\n stessa session) / Nuova sessione (clear + ricomincia). -->\n @if (contextOverflowDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"false\"\n [style]=\"{ width: '480px', maxWidth: '92vw' }\"\n [header]=\"'rag_chatbot.overflow_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__overflow-dialog\"\n (onHide)=\"contextOverflowDialogOpen = false\">\n <p>{{ 'rag_chatbot.overflow_dialog.intro' | translate }}</p>\n <ul>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_compact_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_compact_desc' | translate }}</li>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_new_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_new_desc' | translate }}</li>\n </ul>\n <div class=\"wuic-rag-chatbot__overflow-actions\">\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_compact_title' | translate\" icon=\"pi pi-compress\" severity=\"primary\"\n [loading]=\"compactingSession\"\n (onClick)=\"doCompactSession()\"></p-button>\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_new_title' | translate\" icon=\"pi pi-refresh\" severity=\"secondary\"\n (onClick)=\"startNewSession()\"></p-button>\n </div>\n </p-dialog>\n }\n\n</div>", styles: [".wuic-rag-chatbot{--wuic-rag-chatbot-radius: .5rem;--wuic-rag-chatbot-padding: .75rem;--wuic-rag-chatbot-gap: .5rem;display:flex;flex-direction:column;width:100%;height:100%;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:var(--wuic-rag-chatbot-radius);background-color:var(--p-content-background, transparent);color:inherit;overflow:hidden;font-family:inherit}.wuic-rag-chatbot__header{display:flex;align-items:center;justify-content:space-between;padding:var(--wuic-rag-chatbot-padding);border-bottom:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__title{display:flex;align-items:center;gap:.5rem;font-weight:600;color:inherit}.wuic-rag-chatbot__title-icon{color:var(--p-primary-color, #3b82f6)}.wuic-rag-chatbot__header-actions{display:flex;align-items:center;gap:.5rem}.wuic-rag-chatbot__context-cue{width:28px;height:28px;border-radius:50%;border:2px solid rgba(255,255,255,.85);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:.62rem;font-weight:700;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.35);box-shadow:0 0 0 1px #0000000f,0 1px 3px #0000001f;transition:transform .12s ease,box-shadow .12s ease,opacity .12s ease;padding:0}.wuic-rag-chatbot__context-cue:hover:not(:disabled){transform:scale(1.08);box-shadow:0 0 0 1px #0000001a,0 2px 6px #0003}.wuic-rag-chatbot__context-cue:disabled{cursor:default;opacity:.65}.wuic-rag-chatbot__context-cue>i{font-size:.85rem}.wuic-rag-chatbot__sessions-loading,.wuic-rag-chatbot__sessions-empty{padding:1.5rem;text-align:center;color:var(--p-text-muted-color, #6b7280);font-style:italic}.wuic-rag-chatbot__sessions-list{list-style:none;margin:0;padding:0;max-height:60vh;overflow-y:auto}.wuic-rag-chatbot__session-item{padding:.6rem .85rem;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;margin-bottom:.4rem;cursor:pointer;transition:background .12s ease,border-color .12s ease}.wuic-rag-chatbot__session-item:hover{background:var(--p-surface-50, #f3f4f6);border-color:var(--p-primary-300, rgba(59, 130, 246, .4))}.wuic-rag-chatbot__session-item--current{background:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-400, rgba(59, 130, 246, .55))}.wuic-rag-chatbot__session-date{font-size:.72rem;color:var(--p-text-muted-color, #6b7280);font-family:ui-monospace,monospace;margin-bottom:.2rem}.wuic-rag-chatbot__session-row{display:flex;align-items:center;gap:.4rem}.wuic-rag-chatbot__session-title{flex:1 1 auto;font-size:.9rem;color:var(--p-text-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wuic-rag-chatbot__session-edit{display:flex;align-items:center;gap:.3rem}.wuic-rag-chatbot__session-edit-input{flex:1 1 auto;font-size:.85rem}.wuic-rag-chatbot__overflow-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1rem}.wuic-rag-chatbot__error-banner{margin:var(--wuic-rag-chatbot-padding);margin-bottom:0}.wuic-rag-chatbot__history{flex:1 1 auto;min-height:0;overflow-y:auto;padding:var(--wuic-rag-chatbot-padding);display:flex;flex-direction:column;gap:var(--wuic-rag-chatbot-gap);background-color:transparent}.wuic-rag-chatbot__empty{margin:auto;text-align:center;color:var(--p-text-muted-color, #6b7280);max-width:80%;padding:1.5rem 0}.wuic-rag-chatbot__empty-icon{font-size:1.5rem;margin-bottom:.5rem;display:block}.wuic-rag-chatbot__suggestions{list-style:none;padding:0;margin:.75rem 0 0;display:flex;flex-direction:column;gap:.25rem;text-align:left}.wuic-rag-chatbot__suggestions li{padding:.35rem .75rem;border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:.375rem;font-size:.875rem}.wuic-rag-chatbot__turn{display:flex;flex-direction:column;gap:.35rem;padding:.65rem .85rem;border-radius:var(--wuic-rag-chatbot-radius);max-width:92%;color:inherit;background-color:transparent;border:1px solid var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__turn--user{align-self:flex-end;background-color:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-200, rgba(59, 130, 246, .25))}.wuic-rag-chatbot__turn--assistant{align-self:flex-start;background-color:var(--p-surface-50, rgba(0, 0, 0, .025))}.wuic-rag-chatbot__turn-header{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__turn-mode{margin-left:auto;font-family:ui-monospace,monospace;font-size:.7rem;opacity:.7}.wuic-rag-chatbot__turn-content{white-space:pre-wrap;word-break:break-word;line-height:1.45;font-size:.93rem;color:inherit}.wuic-rag-chatbot__turn-loading{display:flex;align-items:center;gap:.5rem;color:var(--p-text-muted-color, #6b7280);font-style:italic;font-size:.875rem}.wuic-rag-chatbot__spinner{width:1.25rem!important;height:1.25rem!important}.wuic-rag-chatbot__turn-error,.wuic-rag-chatbot__turn-warning{margin-top:.35rem}.wuic-rag-chatbot__sources{display:flex;flex-direction:column;gap:.35rem;margin-top:.5rem;padding-top:.5rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__sources-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.05em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__sources-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__source-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.wuic-rag-chatbot__source-chip:hover{transform:translateY(-1px);box-shadow:0 1px 4px #0000001f}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:var(--p-blue-50, #eff6ff);color:var(--p-blue-700, #1d4ed8);border:1px solid var(--p-blue-200, #bfdbfe)}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:var(--p-blue-600, #2563eb)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:var(--p-green-50, #f0fdf4);color:var(--p-green-700, #15803d);border:1px solid var(--p-green-200, #bbf7d0)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:var(--p-green-600, #16a34a)}.wuic-rag-chatbot__tokens{margin-top:.4rem;font-size:.7rem;color:var(--p-text-muted-color, #6b7280);text-align:right;font-family:ui-monospace,monospace}.wuic-rag-chatbot__followups{display:flex;flex-direction:column;gap:.3rem;margin-top:.55rem;padding-top:.55rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__followups-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__followups-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__followup-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease,background .12s ease}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip{background:var(--p-primary-50, rgba(59, 130, 246, .08));color:var(--p-primary-700, #1d4ed8);border:1px solid var(--p-primary-200, rgba(59, 130, 246, .25));font-size:.82rem;line-height:1.3;padding:.2rem .5rem}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip-icon{color:var(--p-primary-600, #2563eb);font-size:.75rem}.wuic-rag-chatbot__followup-chip:hover{transform:translateY(-1px);box-shadow:0 2px 6px #3b82f62e}.wuic-rag-chatbot__followup-chip:hover :host ::ng-deep .p-chip{background:var(--p-primary-100, rgba(59, 130, 246, .15))}.wuic-rag-chatbot__action-box{margin-top:.65rem;border:1px solid var(--p-primary-300, rgba(59, 130, 246, .45));border-radius:8px;background:var(--p-primary-50, rgba(59, 130, 246, .05));padding:.6rem .75rem;display:flex;flex-direction:column;gap:.45rem}.wuic-rag-chatbot__action-box--applied{border-color:var(--p-green-300, #86efac);background:var(--p-green-50, #f0fdf4)}.wuic-rag-chatbot__action-box--error{border-color:var(--p-red-300, #fca5a5);background:var(--p-red-50, #fef2f2)}.wuic-rag-chatbot__action-box-header{display:flex;align-items:center;gap:.45rem;font-size:.88rem;color:var(--p-primary-700, #1d4ed8);font-weight:600}.wuic-rag-chatbot__action-box-header>i{font-size:.95rem}.wuic-rag-chatbot__action-box-title{display:inline-flex;align-items:center;gap:.4rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-label{color:var(--p-text-color, #111827);font-weight:700}.wuic-rag-chatbot__action-box-route{color:var(--p-text-muted-color, #6b7280);font-weight:400;font-size:.8rem}.wuic-rag-chatbot__action-box-route>code{background:var(--p-surface-100, #f3f4f6);padding:1px 6px;border-radius:4px;font-family:ui-monospace,monospace;color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-rationale{font-size:.85rem;color:var(--p-text-color, #1f2937);line-height:1.4;font-style:italic}.wuic-rag-chatbot__action-box-side-effect{font-size:.8rem;color:var(--p-blue-700, #1d4ed8);background:var(--p-blue-50, #eff6ff);border-left:3px solid var(--p-blue-400, #60a5fa);padding:.4rem .6rem;border-radius:4px;display:flex;align-items:flex-start;gap:.4rem;line-height:1.35}.wuic-rag-chatbot__action-box-side-effect i{font-size:.9rem;margin-top:.1rem;flex-shrink:0}.wuic-rag-chatbot__action-box-editor-toolbar{display:flex;justify-content:flex-end;align-items:center}.wuic-rag-chatbot__action-box-extra-fields{display:flex;flex-direction:column;gap:6px;margin-bottom:8px}.wuic-rag-chatbot__action-box-extra-field{display:flex;align-items:center;gap:8px}.wuic-rag-chatbot__action-box-extra-field-label{flex:0 0 auto;min-width:100px;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__action-box-extra-field-value{flex:1 1 auto;min-width:0;font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;padding:4px 8px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:4px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-extra-field-value:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-textarea{width:100%;min-height:200px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;padding:8px 10px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827);font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;line-height:1.45;resize:vertical;white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:hidden;overflow-y:auto;tab-size:2}.wuic-rag-chatbot__action-box-textarea:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-editor{height:240px;min-height:240px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;overflow:hidden;background:#1e1e1e}.wuic-rag-chatbot__action-box-editor-loading{display:flex;align-items:center;justify-content:center;gap:.5rem;min-height:100px;color:var(--p-text-muted-color, #6b7280);font-size:.85rem;font-style:italic;background:var(--p-surface-50, #fafafa);border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:6px}.wuic-rag-chatbot__action-box-actions{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-applied{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-green-700, #15803d);font-weight:600;font-size:.85rem}.wuic-rag-chatbot__action-box-error{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-red-700, #b91c1c);font-size:.8rem;font-family:ui-monospace,monospace}.wuic-rag-chatbot__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:var(--wuic-rag-chatbot-padding);border-top:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__input{flex:1 1 auto;min-height:2.25rem;max-height:8rem;resize:none;color:inherit;background-color:transparent}.wuic-rag-chatbot__source-meta{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.75rem}.wuic-rag-chatbot__source-tag{display:inline-flex;align-items:center;gap:.25rem;padding:2px 8px;background:var(--p-surface-100, #f3f4f6);color:var(--p-text-color-secondary, #6b7280);border-radius:10px;font-size:.72rem;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.wuic-rag-chatbot__source-tag--file{background:var(--p-surface-0, #fff);color:var(--p-primary-color, #2563eb);border:1px solid var(--p-primary-200, #bfdbfe);font-weight:500;max-width:100%;word-break:break-all;white-space:normal;line-height:1.3}.wuic-rag-chatbot__source-tag--file i{font-size:.7rem;flex-shrink:0}.wuic-rag-chatbot__source-snippet{margin:0;padding:12px 14px;max-height:420px;overflow:auto;background:var(--p-surface-50, #fafafa);border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.78rem;line-height:1.4;white-space:pre-wrap;word-break:break-word;color:var(--p-text-color, #111827)}:host-context(.theme-dark) .wuic-rag-chatbot__turn--user{background-color:#60a5fa2e;border-color:#60a5fa73}:host-context(.theme-dark) .wuic-rag-chatbot__turn--assistant{background-color:#ffffff0f;border-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__turn-header,:host-context(.theme-dark) .wuic-rag-chatbot__turn-loading,:host-context(.theme-dark) .wuic-rag-chatbot__sources-label,:host-context(.theme-dark) .wuic-rag-chatbot__tokens{color:#ffffffa6}:host-context(.theme-dark) .wuic-rag-chatbot__sources{border-top-color:#ffffff2e}:host-context(.theme-dark) .wuic-rag-chatbot__input-area{border-top-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:#3b82f62e;color:#93c5fd;border:1px solid rgba(59,130,246,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:#bfdbfe}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:#22c55e2e;color:#86efac;border:1px solid rgba(34,197,94,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:#bbf7d0}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag{background:#ffffff14;color:#ffffffc7}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag--file{background:#3b82f624;color:#93c5fd;border-color:#3b82f673}:host-context(.theme-dark) .wuic-rag-chatbot__source-snippet{background:#00000052;border-color:#ffffff1f;color:#ffffffeb}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i7$1.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i13.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: BadgeModule }, { kind: "component", type: i10$1.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "ngmodule", type: ChipModule }, { kind: "component", type: i11$1.Chip, selector: "p-chip", inputs: ["label", "icon", "image", "alt", "styleClass", "disabled", "removable", "removeIcon", "chipProps"], outputs: ["onRemove", "onImageError"] }, { kind: "ngmodule", type: ScrollPanelModule }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i9$2.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i7.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant", "motionOptions"], outputs: ["onClose"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }], deferBlockDependencies: [() => [Promise.resolve().then(function () { return codeEditor_lazy_component; }).then(m => m.LazyCodeEditorComponent)]] });
61519
63508
  }
61520
- i0.ɵɵ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
- }] } });
63509
+ i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotComponent, resolveDeferredDeps: () => [Promise.resolve().then(function () { return codeEditor_lazy_component; }).then(m => m.LazyCodeEditorComponent)], resolveMetadata: LazyCodeEditorComponent => ({ decorators: [{
63510
+ type: Component,
63511
+ args: [{ selector: 'wuic-rag-chatbot', standalone: true, imports: [
63512
+ CommonModule,
63513
+ FormsModule,
63514
+ ButtonModule,
63515
+ TextareaModule,
63516
+ InputTextModule,
63517
+ BadgeModule,
63518
+ ChipModule,
63519
+ ScrollPanelModule,
63520
+ ProgressSpinnerModule,
63521
+ TooltipModule,
63522
+ MessageModule,
63523
+ DialogModule,
63524
+ TranslateModule,
63525
+ LazyCodeEditorComponent,
63526
+ ], template: "<div class=\"wuic-rag-chatbot\" style=\"height: 100%;\">\n\n <!-- Header -->\n <div class=\"wuic-rag-chatbot__header\">\n <div class=\"wuic-rag-chatbot__title\">\n <i class=\"pi pi-comments wuic-rag-chatbot__title-icon\" aria-hidden=\"true\"></i>\n <span>{{ title }}</span>\n </div>\n <div class=\"wuic-rag-chatbot__header-actions\">\n @if (allowProfileToggle) {\n <p-button [label]=\"profileToggleLabel\" [icon]=\"profileOverride === 'release' ? 'pi pi-eye' : 'pi pi-code'\"\n [severity]=\"profileOverride === 'release' ? 'warn' : 'secondary'\" [outlined]=\"true\" [rounded]=\"true\"\n size=\"small\" (onClick)=\"toggleProfile()\" [pTooltip]=\"profileOverride === 'release'\n ? ('rag_chatbot.profile_toggle.tooltip_release' | translate)\n : ('rag_chatbot.profile_toggle.tooltip_dev' | translate)\" tooltipPosition=\"bottom\"\n [ariaLabel]=\"'rag_chatbot.profile_toggle.aria_label' | translate\">\n </p-button>\n }\n <p-badge [value]=\"modeBadgeLabel\" [severity]=\"modeBadgeSeverity\" [pTooltip]=\"healthInfo\n ? ('chunks indicizzati: ' + healthInfo.docs_loaded + ' | modello: ' + healthInfo.default_model)\n : (healthError || ('rag_chatbot.health_indicator.connecting' | translate))\" tooltipPosition=\"bottom\">\n </p-badge>\n <!-- Context cue: cerchio colorato che riflette % context window usato.\n Click + conferma -> /compact. Tooltip mostra k/k token + %. -->\n @if (contextWindowMax) {\n <button type=\"button\"\n class=\"wuic-rag-chatbot__context-cue\"\n [style.background]=\"contextCueColor\"\n [pTooltip]=\"contextCueTooltip\"\n tooltipPosition=\"bottom\"\n [disabled]=\"compactingSession || !currentSessionId\"\n (click)=\"onContextCueClick()\"\n attr.aria-label=\"Compatta sessione (context {{ contextUsagePercent }}%)\">\n @if (compactingSession) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else {\n <span>{{ contextUsagePercent }}%</span>\n }\n </button>\n }\n <!-- Bottone Sessioni: apre popup con lista sessioni ordinate desc per updatedAt. -->\n <p-button icon=\"pi pi-list\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"openSessionsDialog()\" [pTooltip]=\"'rag_chatbot.sessions_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.sessions_button.aria_label' | translate\">\n </p-button>\n @if (showClearButton) {\n <p-button icon=\"pi pi-trash\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n [disabled]=\"history.length === 0 || loading || clearingHistory\" [loading]=\"clearingHistory\"\n (onClick)=\"onClearHistory()\" [pTooltip]=\"'rag_chatbot.clear_button.tooltip' | translate\"\n tooltipPosition=\"bottom\" [ariaLabel]=\"'rag_chatbot.clear_button.aria_label' | translate\">\n </p-button>\n }\n </div>\n </div>\n\n <!-- Health error banner -->\n @if (healthError) {\n <p-message severity=\"error\" [text]=\"('rag_chatbot.health_error.banner' | translate) + ': ' + healthError\"\n styleClass=\"wuic-rag-chatbot__error-banner\"></p-message>\n }\n\n <!-- Chat history -->\n <div #scrollContainer class=\"wuic-rag-chatbot__history\" role=\"log\" aria-live=\"polite\">\n @if (history.length === 0) {\n <div class=\"wuic-rag-chatbot__empty\">\n <i class=\"pi pi-info-circle wuic-rag-chatbot__empty-icon\" aria-hidden=\"true\"></i>\n <p>{{ 'rag_chatbot.empty.prompt' | translate }}</p>\n <ul class=\"wuic-rag-chatbot__suggestions\">\n <li>{{ 'rag_chatbot.suggestion.invalidate_cache' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.spreadsheet_massive' | translate }}</li>\n <li>{{ 'rag_chatbot.suggestion.save_report' | translate }}</li>\n </ul>\n </div>\n }\n\n @for (turn of history; track turn.messageId || $index) {\n <div class=\"wuic-rag-chatbot__turn\" [attr.data-message-id]=\"turn.messageId || null\"\n [class.wuic-rag-chatbot__turn--user]=\"turn.role === 'user'\"\n [class.wuic-rag-chatbot__turn--assistant]=\"turn.role === 'assistant'\">\n\n <div class=\"wuic-rag-chatbot__turn-header\">\n <i class=\"pi\" [class.pi-user]=\"turn.role === 'user'\" [class.pi-android]=\"turn.role === 'assistant'\"\n aria-hidden=\"true\"></i>\n <span>{{ (turn.role === 'user' ? 'rag_chatbot.turn.role_user' : 'rag_chatbot.turn.role_assistant') | translate }}</span>\n @if (turn.mode) {\n <span class=\"wuic-rag-chatbot__turn-mode\">[{{ turn.mode === 'rag-llm' ? 'RAG+LLM' : 'retrieval' }}]</span>\n }\n </div>\n\n @if (turn.loading) {\n <div class=\"wuic-rag-chatbot__turn-loading\">\n <p-progressSpinner styleClass=\"wuic-rag-chatbot__spinner\" strokeWidth=\"3\"\n animationDuration=\".9s\"></p-progressSpinner>\n <span>{{ 'rag_chatbot.turn.loading' | translate }}</span>\n </div>\n } @else if (turn.errorMessage) {\n <p-message severity=\"error\" [text]=\"(turn.errorMessage | translate)\" styleClass=\"wuic-rag-chatbot__turn-error\"></p-message>\n } @else {\n <div class=\"wuic-rag-chatbot__turn-content\">{{ turn.content }}</div>\n @if (turn.warning) {\n <p-message severity=\"warn\" [text]=\"turn.warning\" styleClass=\"wuic-rag-chatbot__turn-warning\"></p-message>\n }\n <!-- Box \"Proposta azione\" emessa dall'LLM via tool-use. Renderizzato solo\n per i turn assistant che hanno proposedAction valorizzata. Il monaco\n editor riusa la stessa codeContext del metadata-editor sulla colonna\n equivalente (autocomplete identico, type-check identico). -->\n @if (turn.role === 'assistant' && turn.proposedAction) {\n <div class=\"wuic-rag-chatbot__action-box\"\n [class.wuic-rag-chatbot__action-box--applied]=\"turn.proposedActionAppliedAt\"\n [class.wuic-rag-chatbot__action-box--error]=\"turn.applyError\">\n <div class=\"wuic-rag-chatbot__action-box-header\">\n <i class=\"pi pi-bolt\" aria-hidden=\"true\"></i>\n <span class=\"wuic-rag-chatbot__action-box-title\">\n {{ proposedActionTitle(turn.proposedAction) }}\n @if (turn.proposedAction.label) {\n <span class=\"wuic-rag-chatbot__action-box-label\">\"{{ turn.proposedAction.label }}\"</span>\n }\n @if (turn.proposedAction.route) {\n <span class=\"wuic-rag-chatbot__action-box-route\">{{ 'rag_chatbot.action_box.label_on_route_prefix' | translate }} <code>{{ turn.proposedAction.route }}</code></span>\n }\n </span>\n </div>\n @if (turn.proposedAction.rationale) {\n <div class=\"wuic-rag-chatbot__action-box-rationale\">{{ turn.proposedAction.rationale }}</div>\n }\n <!-- Hint trasparenza UX: l'utente sa in anticipo che, oltre a creare\n la toolbar action, abiliteremo md_multiple_selection sulla route\n (necessario per le bulk operations che leggono getSelectedRows). -->\n @if (turn.proposedAction.requires_multi_selection) {\n <div class=\"wuic-rag-chatbot__action-box-side-effect\">\n <i class=\"pi pi-info-circle\"></i>\n {{ 'rag_chatbot.action_box.side_effect_multi_selection' | translate }}\n </div>\n }\n <!-- Campi NON-body della proposta (es. per table_style: classe/stile;\n per toolbar/row_action: label+icon; per sql_metadata_field: field\n target). Visibili sopra la textarea del body cosi' l'utente capisce\n a colpo d'occhio COSA viene applicato oltre al CODICE del callback.\n Sorgente: proposedActionExtraFields(kind) nel .ts. -->\n @if (proposedActionExtraFields(turn.proposedAction).length > 0) {\n <div class=\"wuic-rag-chatbot__action-box-extra-fields\">\n @for (extra of proposedActionExtraFields(turn.proposedAction); track extra.fieldName) {\n <div class=\"wuic-rag-chatbot__action-box-extra-field\">\n <label class=\"wuic-rag-chatbot__action-box-extra-field-label\">{{ extra.label }}</label>\n <input\n class=\"wuic-rag-chatbot__action-box-extra-field-value\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"extra.value\"\n (ngModelChange)=\"onProposedExtraFieldEdit(turn, extra, $event)\"\n [name]=\"'extra-' + turn.messageId + '-' + extra.fieldName\">\n </div>\n }\n </div>\n }\n @if (proposedActionJsBody(turn.proposedAction)) {\n <!-- Pattern <lib-code-area-editor>: textarea readonly (zero overhead,\n niente flash nero, contenuto immediatamente visibile) di default.\n Toggle \"Apri editor avanzato\" attiva `editorMode` -> @defer carica\n Monaco con codeContext+autocomplete. Doppio livello di lazy:\n @defer in HTML + LazyCodeEditorComponent fa import() dinamico.\n Risultato: Monaco scaricato SOLO se l'utente lo richiede.\n proposedActionJsBody() unifica callback_js / condition_js / formula_js\n in base al kind: stesso UX per tutti i kind che hanno una body JS. -->\n <div class=\"wuic-rag-chatbot__action-box-editor-toolbar\">\n <p-button\n [label]=\"(turn.editorMode ? 'rag_chatbot.action_box.editor_close' : 'rag_chatbot.action_box.editor_open') | translate\"\n [icon]=\"turn.editorMode ? 'pi pi-times' : 'pi pi-code'\"\n severity=\"secondary\"\n [text]=\"true\"\n size=\"small\"\n (onClick)=\"toggleEditorMode(turn)\">\n </p-button>\n </div>\n @if (!turn.editorMode) {\n <textarea\n class=\"wuic-rag-chatbot__action-box-textarea\"\n rows=\"10\"\n [disabled]=\"!!turn.proposedActionAppliedAt\"\n [ngModel]=\"proposedActionJsBody(turn.proposedAction)\"\n (ngModelChange)=\"onProposedBodyEdit(turn, $event)\"\n [name]=\"'body-' + turn.messageId\">\n </textarea>\n } @else {\n @defer (when turn.editorMode) {\n <div class=\"wuic-rag-chatbot__action-box-editor\">\n <wuic-code-editor-lazy\n [field]=\"getProposedActionField(turn)\"\n [record]=\"getProposedActionRecord(turn)\"\n style=\"display: block; width: 100%; height: 100%;\">\n </wuic-code-editor-lazy>\n </div>\n } @loading (minimum 200ms) {\n <div class=\"wuic-rag-chatbot__action-box-editor-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.editor_loading' | translate }}\n </div>\n }\n }\n }\n <div class=\"wuic-rag-chatbot__action-box-actions\">\n @if (turn.proposedActionAppliedAt) {\n <span class=\"wuic-rag-chatbot__action-box-applied\">\n <i class=\"pi pi-check-circle\" aria-hidden=\"true\"></i>\n {{ 'rag_chatbot.action_box.applied' | translate }}\n </span>\n } @else {\n <p-button\n [label]=\"'rag_chatbot.action_box.apply' | translate\"\n icon=\"pi pi-check\"\n severity=\"primary\"\n size=\"small\"\n [loading]=\"!!turn.applyingAction\"\n (onClick)=\"onApplyAction(turn)\">\n </p-button>\n }\n @if (turn.applyError) {\n <span class=\"wuic-rag-chatbot__action-box-error\">\n <i class=\"pi pi-times-circle\" aria-hidden=\"true\"></i>\n {{ turn.applyError }}\n </span>\n }\n </div>\n </div>\n }\n @if (showSources && turn.role === 'assistant' && turn.sources && turn.sources.length > 0) {\n @let dedupedSources = dedupSources(turn.sources);\n <div class=\"wuic-rag-chatbot__sources\">\n <div class=\"wuic-rag-chatbot__sources-label\">{{ 'rag_chatbot.sources.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__sources-list\">\n @for (source of dedupedSources; track source.chunk_id || source.rank) {\n <p-chip [label]=\"sourceLabel(source, dedupedSources)\"\n [icon]=\"isDocsSource(source) ? 'pi pi-book' : (isSkillSource(source) ? 'pi pi-wrench' : 'pi pi-file-edit')\"\n [removable]=\"false\" [pTooltip]=\"sourceTooltip(source)\" tooltipPosition=\"top\" [styleClass]=\"isDocsSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--docs'\n : (isSkillSource(source)\n ? 'wuic-rag-chatbot__source-chip wuic-rag-chatbot__source-chip--skill'\n : 'wuic-rag-chatbot__source-chip')\" (click)=\"onSourceClick(source)\">\n </p-chip>\n }\n </div>\n </div>\n }\n <!-- Follow-up questions emesse dall'LLM via tool `suggest_followups`.\n Renderizzate come chip cliccabili sotto la risposta: click popola\n l'input box (NON invia auto, l'utente puo' editare prima). -->\n @if (turn.role === 'assistant' && turn.followupQuestions && turn.followupQuestions.length > 0) {\n <div class=\"wuic-rag-chatbot__followups\">\n <div class=\"wuic-rag-chatbot__followups-label\">{{ 'rag_chatbot.followups.label' | translate }}</div>\n <div class=\"wuic-rag-chatbot__followups-list\">\n @for (q of turn.followupQuestions; track q) {\n <p-chip\n [label]=\"q\"\n icon=\"pi pi-arrow-right\"\n styleClass=\"wuic-rag-chatbot__followup-chip\"\n (click)=\"applyFollowup(q)\">\n </p-chip>\n }\n </div>\n </div>\n }\n @if (turn.tokensIn || turn.tokensOut) {\n <div class=\"wuic-rag-chatbot__tokens\">\n tokens in/out: {{ turn.tokensIn ?? '?' }}/{{ turn.tokensOut ?? '?' }}\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Input area -->\n <div class=\"wuic-rag-chatbot__input-area\">\n <textarea #inputTextarea pTextarea class=\"wuic-rag-chatbot__input\" [(ngModel)]=\"inputValue\"\n (keydown)=\"onKeyDown($event)\" [placeholder]=\"placeholder\" [disabled]=\"loading || !!healthError\"\n [autoResize]=\"true\" rows=\"1\" [attr.aria-label]=\"'rag_chatbot.input.aria_label' | translate\">\n </textarea>\n <p-button icon=\"pi pi-send\" [disabled]=\"sendDisabled\" [loading]=\"loading\" (onClick)=\"onSend()\" severity=\"primary\"\n [ariaLabel]=\"'rag_chatbot.input.send_aria_label' | translate\">\n </p-button>\n </div>\n\n <!-- Dialog preview snippet sorgente.\n Mostra SOLO il contenuto del chunk (gia' embedded nella response RAG).\n NON espone il path del file ne' apre editor esterni: il codebase del\n framework vive sul repo dev e non deve essere linkato all'utente finale. -->\n @if (selectedSource) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw' }\" [header]=\"sourceDialogTitle(selectedSource)\"\n styleClass=\"wuic-rag-chatbot__source-dialog\" (onHide)=\"closeSourcePreview()\">\n <div class=\"wuic-rag-chatbot__source-meta\">\n @if (sourceFilePath(selectedSource); as fpath) {\n <span class=\"wuic-rag-chatbot__source-tag wuic-rag-chatbot__source-tag--file\">\n <i class=\"pi pi-file\" aria-hidden=\"true\"></i>\n {{ fpath }}\n </span>\n }\n @if (selectedSource.symbol_type) {\n <span class=\"wuic-rag-chatbot__source-tag\">{{ selectedSource.symbol_type }}</span>\n }\n @if (selectedSource.start_line && selectedSource.end_line) {\n <span class=\"wuic-rag-chatbot__source-tag\">linee {{ selectedSource.start_line }}-{{ selectedSource.end_line\n }}</span>\n }\n @if (selectedSource.score_vector !== null && selectedSource.score_vector !== undefined) {\n <span class=\"wuic-rag-chatbot__source-tag\">vec {{ selectedSource.score_vector | number:'1.2-2' }}</span>\n }\n </div>\n <pre\n class=\"wuic-rag-chatbot__source-snippet\"><code>{{ selectedSource.snippet || ('rag_chatbot.source.snippet_unavailable' | translate) }}</code></pre>\n </p-dialog>\n }\n\n <!-- Dialog \"Sessioni\": elenco ordinato desc per updatedAt. Per ogni riga:\n data + titolo (truncated 100 char, tooltip full) + bottone rinomina\n inline + click row = switch session. -->\n @if (sessionsDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"true\"\n [style]=\"{ width: '640px', maxWidth: '92vw', maxHeight: '85vh' }\"\n [header]=\"'rag_chatbot.sessions_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__sessions-dialog\"\n (onHide)=\"closeSessionsDialog()\">\n @if (sessionsLoading) {\n <div class=\"wuic-rag-chatbot__sessions-loading\">\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i> {{ 'rag_chatbot.sessions_dialog.loading' | translate }}\n </div>\n } @else if (sessionsList.length === 0) {\n <div class=\"wuic-rag-chatbot__sessions-empty\">{{ 'rag_chatbot.sessions_dialog.empty' | translate }}</div>\n } @else {\n <ul class=\"wuic-rag-chatbot__sessions-list\">\n @for (s of sessionsList; track s.id) {\n <li class=\"wuic-rag-chatbot__session-item\"\n [class.wuic-rag-chatbot__session-item--current]=\"s.id === currentSessionId\"\n (click)=\"selectSession(s)\">\n <div class=\"wuic-rag-chatbot__session-date\">{{ sessionDate(s) }}</div>\n @if (sessionRenameEditing[s.id]) {\n <div class=\"wuic-rag-chatbot__session-edit\" (click)=\"$event.stopPropagation()\">\n <input type=\"text\" pInputText\n [(ngModel)]=\"sessionRenameDraft[s.id]\"\n [autofocus]=\"true\"\n maxlength=\"200\"\n class=\"wuic-rag-chatbot__session-edit-input\"\n (keydown.enter)=\"confirmRenameSession(s, $event)\"\n (keydown.escape)=\"cancelRenameSession(s)\">\n <p-button icon=\"pi pi-check\" severity=\"success\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"confirmRenameSession(s, $event)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_confirm_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-times\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"cancelRenameSession(s)\" [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_cancel_aria' | translate\"></p-button>\n </div>\n } @else {\n <div class=\"wuic-rag-chatbot__session-row\">\n <span class=\"wuic-rag-chatbot__session-title\" [pTooltip]=\"s.title || ('rag_chatbot.sessions_dialog.untitled' | translate)\" tooltipPosition=\"top\">\n {{ sessionPreview(s) }}\n </span>\n <p-button icon=\"pi pi-pencil\" severity=\"secondary\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"startRenameSession(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.rename' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.rename_aria' | translate\"></p-button>\n <p-button icon=\"pi pi-trash\" severity=\"danger\" [text]=\"true\" [rounded]=\"true\" size=\"small\"\n (onClick)=\"onDeleteSessionFromList(s, $event)\" [pTooltip]=\"'rag_chatbot.sessions_dialog.delete' | translate\" tooltipPosition=\"left\"\n [ariaLabel]=\"'rag_chatbot.sessions_dialog.delete_aria' | translate\"></p-button>\n </div>\n }\n </li>\n }\n </ul>\n }\n </p-dialog>\n }\n\n <!-- Dialog context-overflow: l'ultima call ha fallito perche' la history\n sfora il context window. 2 azioni: Compatta ora (riassume e prosegue\n stessa session) / Nuova sessione (clear + ricomincia). -->\n @if (contextOverflowDialogOpen) {\n <p-dialog [visible]=\"true\" [modal]=\"true\" [closable]=\"true\" [closeOnEscape]=\"true\" [dismissableMask]=\"false\"\n [style]=\"{ width: '480px', maxWidth: '92vw' }\"\n [header]=\"'rag_chatbot.overflow_dialog.header' | translate\"\n styleClass=\"wuic-rag-chatbot__overflow-dialog\"\n (onHide)=\"contextOverflowDialogOpen = false\">\n <p>{{ 'rag_chatbot.overflow_dialog.intro' | translate }}</p>\n <ul>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_compact_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_compact_desc' | translate }}</li>\n <li><strong>{{ 'rag_chatbot.overflow_dialog.option_new_title' | translate }}</strong> \u2014 {{ 'rag_chatbot.overflow_dialog.option_new_desc' | translate }}</li>\n </ul>\n <div class=\"wuic-rag-chatbot__overflow-actions\">\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_compact_title' | translate\" icon=\"pi pi-compress\" severity=\"primary\"\n [loading]=\"compactingSession\"\n (onClick)=\"doCompactSession()\"></p-button>\n <p-button [label]=\"'rag_chatbot.overflow_dialog.option_new_title' | translate\" icon=\"pi pi-refresh\" severity=\"secondary\"\n (onClick)=\"startNewSession()\"></p-button>\n </div>\n </p-dialog>\n }\n\n</div>", styles: [".wuic-rag-chatbot{--wuic-rag-chatbot-radius: .5rem;--wuic-rag-chatbot-padding: .75rem;--wuic-rag-chatbot-gap: .5rem;display:flex;flex-direction:column;width:100%;height:100%;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:var(--wuic-rag-chatbot-radius);background-color:var(--p-content-background, transparent);color:inherit;overflow:hidden;font-family:inherit}.wuic-rag-chatbot__header{display:flex;align-items:center;justify-content:space-between;padding:var(--wuic-rag-chatbot-padding);border-bottom:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__title{display:flex;align-items:center;gap:.5rem;font-weight:600;color:inherit}.wuic-rag-chatbot__title-icon{color:var(--p-primary-color, #3b82f6)}.wuic-rag-chatbot__header-actions{display:flex;align-items:center;gap:.5rem}.wuic-rag-chatbot__context-cue{width:28px;height:28px;border-radius:50%;border:2px solid rgba(255,255,255,.85);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:.62rem;font-weight:700;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.35);box-shadow:0 0 0 1px #0000000f,0 1px 3px #0000001f;transition:transform .12s ease,box-shadow .12s ease,opacity .12s ease;padding:0}.wuic-rag-chatbot__context-cue:hover:not(:disabled){transform:scale(1.08);box-shadow:0 0 0 1px #0000001a,0 2px 6px #0003}.wuic-rag-chatbot__context-cue:disabled{cursor:default;opacity:.65}.wuic-rag-chatbot__context-cue>i{font-size:.85rem}.wuic-rag-chatbot__sessions-loading,.wuic-rag-chatbot__sessions-empty{padding:1.5rem;text-align:center;color:var(--p-text-muted-color, #6b7280);font-style:italic}.wuic-rag-chatbot__sessions-list{list-style:none;margin:0;padding:0;max-height:60vh;overflow-y:auto}.wuic-rag-chatbot__session-item{padding:.6rem .85rem;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;margin-bottom:.4rem;cursor:pointer;transition:background .12s ease,border-color .12s ease}.wuic-rag-chatbot__session-item:hover{background:var(--p-surface-50, #f3f4f6);border-color:var(--p-primary-300, rgba(59, 130, 246, .4))}.wuic-rag-chatbot__session-item--current{background:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-400, rgba(59, 130, 246, .55))}.wuic-rag-chatbot__session-date{font-size:.72rem;color:var(--p-text-muted-color, #6b7280);font-family:ui-monospace,monospace;margin-bottom:.2rem}.wuic-rag-chatbot__session-row{display:flex;align-items:center;gap:.4rem}.wuic-rag-chatbot__session-title{flex:1 1 auto;font-size:.9rem;color:var(--p-text-color, #111827);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wuic-rag-chatbot__session-edit{display:flex;align-items:center;gap:.3rem}.wuic-rag-chatbot__session-edit-input{flex:1 1 auto;font-size:.85rem}.wuic-rag-chatbot__overflow-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1rem}.wuic-rag-chatbot__error-banner{margin:var(--wuic-rag-chatbot-padding);margin-bottom:0}.wuic-rag-chatbot__history{flex:1 1 auto;min-height:0;overflow-y:auto;padding:var(--wuic-rag-chatbot-padding);display:flex;flex-direction:column;gap:var(--wuic-rag-chatbot-gap);background-color:transparent}.wuic-rag-chatbot__empty{margin:auto;text-align:center;color:var(--p-text-muted-color, #6b7280);max-width:80%;padding:1.5rem 0}.wuic-rag-chatbot__empty-icon{font-size:1.5rem;margin-bottom:.5rem;display:block}.wuic-rag-chatbot__suggestions{list-style:none;padding:0;margin:.75rem 0 0;display:flex;flex-direction:column;gap:.25rem;text-align:left}.wuic-rag-chatbot__suggestions li{padding:.35rem .75rem;border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:.375rem;font-size:.875rem}.wuic-rag-chatbot__turn{display:flex;flex-direction:column;gap:.35rem;padding:.65rem .85rem;border-radius:var(--wuic-rag-chatbot-radius);max-width:92%;color:inherit;background-color:transparent;border:1px solid var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__turn--user{align-self:flex-end;background-color:var(--p-primary-50, rgba(59, 130, 246, .08));border-color:var(--p-primary-200, rgba(59, 130, 246, .25))}.wuic-rag-chatbot__turn--assistant{align-self:flex-start;background-color:var(--p-surface-50, rgba(0, 0, 0, .025))}.wuic-rag-chatbot__turn-header{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__turn-mode{margin-left:auto;font-family:ui-monospace,monospace;font-size:.7rem;opacity:.7}.wuic-rag-chatbot__turn-content{white-space:pre-wrap;word-break:break-word;line-height:1.45;font-size:.93rem;color:inherit}.wuic-rag-chatbot__turn-loading{display:flex;align-items:center;gap:.5rem;color:var(--p-text-muted-color, #6b7280);font-style:italic;font-size:.875rem}.wuic-rag-chatbot__spinner{width:1.25rem!important;height:1.25rem!important}.wuic-rag-chatbot__turn-error,.wuic-rag-chatbot__turn-warning{margin-top:.35rem}.wuic-rag-chatbot__sources{display:flex;flex-direction:column;gap:.35rem;margin-top:.5rem;padding-top:.5rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__sources-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.05em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__sources-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__source-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.wuic-rag-chatbot__source-chip:hover{transform:translateY(-1px);box-shadow:0 1px 4px #0000001f}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:var(--p-blue-50, #eff6ff);color:var(--p-blue-700, #1d4ed8);border:1px solid var(--p-blue-200, #bfdbfe)}.wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:var(--p-blue-600, #2563eb)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:var(--p-green-50, #f0fdf4);color:var(--p-green-700, #15803d);border:1px solid var(--p-green-200, #bbf7d0)}.wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:var(--p-green-600, #16a34a)}.wuic-rag-chatbot__tokens{margin-top:.4rem;font-size:.7rem;color:var(--p-text-muted-color, #6b7280);text-align:right;font-family:ui-monospace,monospace}.wuic-rag-chatbot__followups{display:flex;flex-direction:column;gap:.3rem;margin-top:.55rem;padding-top:.55rem;border-top:1px dashed var(--p-content-border-color, #e5e7eb)}.wuic-rag-chatbot__followups-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color, #6b7280);font-weight:600}.wuic-rag-chatbot__followups-list{display:flex;flex-wrap:wrap;gap:.35rem}.wuic-rag-chatbot__followup-chip{cursor:pointer;transition:transform .12s ease,box-shadow .12s ease,background .12s ease}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip{background:var(--p-primary-50, rgba(59, 130, 246, .08));color:var(--p-primary-700, #1d4ed8);border:1px solid var(--p-primary-200, rgba(59, 130, 246, .25));font-size:.82rem;line-height:1.3;padding:.2rem .5rem}.wuic-rag-chatbot__followup-chip :host ::ng-deep .p-chip-icon{color:var(--p-primary-600, #2563eb);font-size:.75rem}.wuic-rag-chatbot__followup-chip:hover{transform:translateY(-1px);box-shadow:0 2px 6px #3b82f62e}.wuic-rag-chatbot__followup-chip:hover :host ::ng-deep .p-chip{background:var(--p-primary-100, rgba(59, 130, 246, .15))}.wuic-rag-chatbot__action-box{margin-top:.65rem;border:1px solid var(--p-primary-300, rgba(59, 130, 246, .45));border-radius:8px;background:var(--p-primary-50, rgba(59, 130, 246, .05));padding:.6rem .75rem;display:flex;flex-direction:column;gap:.45rem}.wuic-rag-chatbot__action-box--applied{border-color:var(--p-green-300, #86efac);background:var(--p-green-50, #f0fdf4)}.wuic-rag-chatbot__action-box--error{border-color:var(--p-red-300, #fca5a5);background:var(--p-red-50, #fef2f2)}.wuic-rag-chatbot__action-box-header{display:flex;align-items:center;gap:.45rem;font-size:.88rem;color:var(--p-primary-700, #1d4ed8);font-weight:600}.wuic-rag-chatbot__action-box-header>i{font-size:.95rem}.wuic-rag-chatbot__action-box-title{display:inline-flex;align-items:center;gap:.4rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-label{color:var(--p-text-color, #111827);font-weight:700}.wuic-rag-chatbot__action-box-route{color:var(--p-text-muted-color, #6b7280);font-weight:400;font-size:.8rem}.wuic-rag-chatbot__action-box-route>code{background:var(--p-surface-100, #f3f4f6);padding:1px 6px;border-radius:4px;font-family:ui-monospace,monospace;color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-rationale{font-size:.85rem;color:var(--p-text-color, #1f2937);line-height:1.4;font-style:italic}.wuic-rag-chatbot__action-box-side-effect{font-size:.8rem;color:var(--p-blue-700, #1d4ed8);background:var(--p-blue-50, #eff6ff);border-left:3px solid var(--p-blue-400, #60a5fa);padding:.4rem .6rem;border-radius:4px;display:flex;align-items:flex-start;gap:.4rem;line-height:1.35}.wuic-rag-chatbot__action-box-side-effect i{font-size:.9rem;margin-top:.1rem;flex-shrink:0}.wuic-rag-chatbot__action-box-editor-toolbar{display:flex;justify-content:flex-end;align-items:center}.wuic-rag-chatbot__action-box-extra-fields{display:flex;flex-direction:column;gap:6px;margin-bottom:8px}.wuic-rag-chatbot__action-box-extra-field{display:flex;align-items:center;gap:8px}.wuic-rag-chatbot__action-box-extra-field-label{flex:0 0 auto;min-width:100px;font-size:.78rem;color:var(--p-text-muted-color, #6b7280);font-weight:500}.wuic-rag-chatbot__action-box-extra-field-value{flex:1 1 auto;min-width:0;font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;padding:4px 8px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:4px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827)}.wuic-rag-chatbot__action-box-extra-field-value:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-textarea{width:100%;min-height:200px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;padding:8px 10px;background:var(--p-surface-50, #fafafa);color:var(--p-text-color, #111827);font-family:ui-monospace,SFMono-Regular,Consolas,Cascadia Code,monospace;font-size:.78rem;line-height:1.45;resize:vertical;white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:hidden;overflow-y:auto;tab-size:2}.wuic-rag-chatbot__action-box-textarea:focus{outline:2px solid var(--p-primary-300, rgba(59, 130, 246, .4));outline-offset:-1px}.wuic-rag-chatbot__action-box-editor{height:240px;min-height:240px;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;overflow:hidden;background:#1e1e1e}.wuic-rag-chatbot__action-box-editor-loading{display:flex;align-items:center;justify-content:center;gap:.5rem;min-height:100px;color:var(--p-text-muted-color, #6b7280);font-size:.85rem;font-style:italic;background:var(--p-surface-50, #fafafa);border:1px dashed var(--p-content-border-color, #e5e7eb);border-radius:6px}.wuic-rag-chatbot__action-box-actions{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}.wuic-rag-chatbot__action-box-applied{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-green-700, #15803d);font-weight:600;font-size:.85rem}.wuic-rag-chatbot__action-box-error{display:inline-flex;align-items:center;gap:.35rem;color:var(--p-red-700, #b91c1c);font-size:.8rem;font-family:ui-monospace,monospace}.wuic-rag-chatbot__input-area{display:flex;align-items:flex-end;gap:.5rem;padding:var(--wuic-rag-chatbot-padding);border-top:1px solid var(--p-content-border-color, #e5e7eb);background-color:transparent}.wuic-rag-chatbot__input{flex:1 1 auto;min-height:2.25rem;max-height:8rem;resize:none;color:inherit;background-color:transparent}.wuic-rag-chatbot__source-meta{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.75rem}.wuic-rag-chatbot__source-tag{display:inline-flex;align-items:center;gap:.25rem;padding:2px 8px;background:var(--p-surface-100, #f3f4f6);color:var(--p-text-color-secondary, #6b7280);border-radius:10px;font-size:.72rem;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.wuic-rag-chatbot__source-tag--file{background:var(--p-surface-0, #fff);color:var(--p-primary-color, #2563eb);border:1px solid var(--p-primary-200, #bfdbfe);font-weight:500;max-width:100%;word-break:break-all;white-space:normal;line-height:1.3}.wuic-rag-chatbot__source-tag--file i{font-size:.7rem;flex-shrink:0}.wuic-rag-chatbot__source-snippet{margin:0;padding:12px 14px;max-height:420px;overflow:auto;background:var(--p-surface-50, #fafafa);border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.78rem;line-height:1.4;white-space:pre-wrap;word-break:break-word;color:var(--p-text-color, #111827)}:host-context(.theme-dark) .wuic-rag-chatbot__turn--user{background-color:#60a5fa2e;border-color:#60a5fa73}:host-context(.theme-dark) .wuic-rag-chatbot__turn--assistant{background-color:#ffffff0f;border-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__turn-header,:host-context(.theme-dark) .wuic-rag-chatbot__turn-loading,:host-context(.theme-dark) .wuic-rag-chatbot__sources-label,:host-context(.theme-dark) .wuic-rag-chatbot__tokens{color:#ffffffa6}:host-context(.theme-dark) .wuic-rag-chatbot__sources{border-top-color:#ffffff2e}:host-context(.theme-dark) .wuic-rag-chatbot__input-area{border-top-color:#ffffff1f}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip{background:#3b82f62e;color:#93c5fd;border:1px solid rgba(59,130,246,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--docs :host ::ng-deep .p-chip-icon{color:#bfdbfe}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip{background:#22c55e2e;color:#86efac;border:1px solid rgba(34,197,94,.4)}:host-context(.theme-dark) .wuic-rag-chatbot__source-chip--skill :host ::ng-deep .p-chip-icon{color:#bbf7d0}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag{background:#ffffff14;color:#ffffffc7}:host-context(.theme-dark) .wuic-rag-chatbot__source-tag--file{background:#3b82f624;color:#93c5fd;border-color:#3b82f673}:host-context(.theme-dark) .wuic-rag-chatbot__source-snippet{background:#00000052;border-color:#ffffff1f;color:#ffffffeb}\n"] }]
63527
+ }], ctorParameters: () => [{ type: WuicRagService }, { type: i0.ChangeDetectorRef }, { type: ChatbotHostRegistryService }, { type: i1$2.ActivatedRoute, decorators: [{
63528
+ type: Optional
63529
+ }] }, { type: i1$2.Router, decorators: [{
63530
+ type: Optional
63531
+ }] }, { type: NotificationRealtimeService, decorators: [{
63532
+ type: Optional
63533
+ }] }, { type: MetadataProviderService, decorators: [{
63534
+ type: Optional
63535
+ }] }], propDecorators: { title: [{
63536
+ type: Input
63537
+ }], mode: [{
63538
+ type: Input
63539
+ }], topK: [{
63540
+ type: Input
63541
+ }], showSources: [{
63542
+ type: Input
63543
+ }], maxHistory: [{
63544
+ type: Input
63545
+ }], placeholder: [{
63546
+ type: Input
63547
+ }], model: [{
63548
+ type: Input
63549
+ }], chatHeight: [{
63550
+ type: Input
63551
+ }], showClearButton: [{
63552
+ type: Input
63553
+ }], persistSession: [{
63554
+ type: Input
63555
+ }], sendRouteContext: [{
63556
+ type: Input
63557
+ }], allowProfileToggle: [{
63558
+ type: Input
63559
+ }], resultSelected: [{
63560
+ type: Output
63561
+ }], errorOccurred: [{
63562
+ type: Output
63563
+ }], turnAdded: [{
63564
+ type: Output
63565
+ }], inputTextarea: [{
63566
+ type: ViewChild,
63567
+ args: ['inputTextarea']
63568
+ }], scrollContainer: [{
63569
+ type: ViewChild,
63570
+ args: ['scrollContainer']
63571
+ }] } }) });
61572
63572
 
61573
63573
  var ragChatbot_component = /*#__PURE__*/Object.freeze({
61574
63574
  __proto__: null,
61575
63575
  WuicRagChatbotComponent: WuicRagChatbotComponent
61576
63576
  });
61577
63577
 
63578
+ /**
63579
+ * Route principale che renderizza il chatbot full-page (vedi `app.routes.ts`:
63580
+ * path 'rag-chatbot' → WuicRagChatbotComponent). Quando l'utente e' gia' su
63581
+ * quella route, il FAB diventa ridondante (e visivamente sovrapposto al chatbot
63582
+ * stesso) → lo nascondiamo. Il match e' su prefisso per coprire eventuali
63583
+ * sub-route future tipo `/rag-chatbot/session/<id>`.
63584
+ */
63585
+ const RAG_CHATBOT_ROUTE_PREFIX = '/rag-chatbot';
61578
63586
  /**
61579
63587
  * Floating Action Button (FAB) bottom-right che apre il chatbot RAG come
61580
63588
  * dialog overlay non-modale. Riusa il componente esistente
@@ -61604,6 +63612,7 @@ class WuicRagChatbotFabComponent {
61604
63612
  licenseService;
61605
63613
  notificationRT;
61606
63614
  cdr;
63615
+ router;
61607
63616
  /** Tooltip dell'icona FAB. */
61608
63617
  fabTooltip = 'Chiedi al codebase WUIC';
61609
63618
  /** Titolo del dialog (passato anche al chatbot interno). */
@@ -61639,11 +63648,20 @@ class WuicRagChatbotFabComponent {
61639
63648
  licenseAllowed = false;
61640
63649
  /** Counter unread per il badge sul FAB. */
61641
63650
  unreadRagCount = 0;
63651
+ /**
63652
+ * True quando la route corrente e' `/rag-chatbot` (o un suo sub-path).
63653
+ * Nascondiamo il FAB in quel caso perche' renderebbe un launcher duplicato
63654
+ * sopra il chatbot gia' aperto a tutta pagina. Aggiornato reattivamente
63655
+ * ai `NavigationEnd` del Router, e inizializzato dal `router.url` corrente
63656
+ * cosi' il primo render (es. F5 mentre sei gia' sulla route) e' coerente.
63657
+ */
63658
+ onRagChatbotRoute = false;
61642
63659
  subs = new Subscription();
61643
- constructor(licenseService, notificationRT, cdr) {
63660
+ constructor(licenseService, notificationRT, cdr, router) {
61644
63661
  this.licenseService = licenseService;
61645
63662
  this.notificationRT = notificationRT;
61646
63663
  this.cdr = cdr;
63664
+ this.router = router;
61647
63665
  }
61648
63666
  ngOnInit() {
61649
63667
  // Subscribe al license status. La status$() emette null finche' refresh()
@@ -61664,11 +63682,34 @@ class WuicRagChatbotFabComponent {
61664
63682
  this.cdr.markForCheck();
61665
63683
  }
61666
63684
  }));
63685
+ // Track route corrente: nascondi il FAB quando sei gia' sul chatbot
63686
+ // full-page. `router.url` e' valorizzato anche prima del primo NavigationEnd
63687
+ // (es. F5 con URL `#/rag-chatbot` -> router lo ha gia' parsato a costruzione
63688
+ // del component), quindi inizializziamo subito + restiamo in sync via events.
63689
+ this.updateOnRagChatbotRoute(this.router.url);
63690
+ this.subs.add(this.router.events
63691
+ .pipe(filter((e) => e instanceof NavigationEnd))
63692
+ .subscribe((e) => this.updateOnRagChatbotRoute(e.urlAfterRedirects || e.url)));
63693
+ }
63694
+ updateOnRagChatbotRoute(url) {
63695
+ const u = String(url || '').split('?')[0].split('#')[0];
63696
+ // match esatto o sub-path: `/rag-chatbot` oppure `/rag-chatbot/...`
63697
+ const next = u === RAG_CHATBOT_ROUTE_PREFIX || u.startsWith(RAG_CHATBOT_ROUTE_PREFIX + '/');
63698
+ if (next !== this.onRagChatbotRoute) {
63699
+ this.onRagChatbotRoute = next;
63700
+ this.cdr.markForCheck();
63701
+ }
61667
63702
  }
61668
63703
  ngOnDestroy() {
61669
63704
  this.subs.unsubscribe();
61670
63705
  }
61671
63706
  get visible() {
63707
+ // Anche con licenza valida o `forceVisible=true`, il FAB resta nascosto
63708
+ // quando si e' gia' sulla route full-page del chatbot: aprirebbe un
63709
+ // launcher duplicato sopra il chatbot stesso.
63710
+ if (this.onRagChatbotRoute) {
63711
+ return false;
63712
+ }
61672
63713
  return this.forceVisible || this.licenseAllowed;
61673
63714
  }
61674
63715
  /**
@@ -61719,8 +63760,8 @@ class WuicRagChatbotFabComponent {
61719
63760
  this.dialogVisible = false;
61720
63761
  this.cdr.markForCheck();
61721
63762
  }
61722
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotFabComponent, deps: [{ token: LicenseFeatureService }, { token: NotificationRealtimeService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
61723
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: WuicRagChatbotFabComponent, isStandalone: true, selector: "wuic-rag-chatbot-fab", inputs: { fabTooltip: "fabTooltip", dialogTitle: "dialogTitle", dialogWidth: "dialogWidth", chatHeight: "chatHeight", forceVisible: "forceVisible" }, ngImport: i0, template: "@if (visible) {\n <!-- FAB bottom-right (position:fixed via .scss). z-index alto per stare sopra\n a navbar, dialog modali leggeri, ecc. ma sotto agli overlay critici (es.\n PrimeNG p-dialog modal stack). -->\n <button\n type=\"button\"\n class=\"wuic-rag-fab\"\n [class.wuic-rag-fab--dialog-open]=\"dialogVisible\"\n [attr.title]=\"fabTooltip\"\n aria-label=\"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 });
63763
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotFabComponent, deps: [{ token: LicenseFeatureService }, { token: NotificationRealtimeService }, { token: i0.ChangeDetectorRef }, { token: i1$2.Router }], target: i0.ɵɵFactoryTarget.Component });
63764
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: WuicRagChatbotFabComponent, isStandalone: true, selector: "wuic-rag-chatbot-fab", inputs: { fabTooltip: "fabTooltip", dialogTitle: "dialogTitle", dialogWidth: "dialogWidth", chatHeight: "chatHeight", forceVisible: "forceVisible" }, ngImport: i0, template: "@if (visible) {\n <!-- FAB bottom-right (position:fixed via .scss). z-index alto per stare sopra\n a navbar, dialog modali leggeri, ecc. ma sotto agli overlay critici (es.\n PrimeNG p-dialog modal stack). -->\n <button\n type=\"button\"\n class=\"wuic-rag-fab\"\n [class.wuic-rag-fab--dialog-open]=\"dialogVisible\"\n [attr.title]=\"fabTooltip\"\n [attr.aria-label]=\"'rag_chatbot_fab.aria_label' | translate\"\n (click)=\"toggleDialog()\">\n <i class=\"pi pi-comments\" aria-hidden=\"true\"></i>\n @if (unreadRagCount > 0 && !dialogVisible) {\n <span class=\"wuic-rag-fab__badge\" [attr.data-count]=\"unreadRagCount\">\n {{ unreadRagCount > 9 ? '9+' : unreadRagCount }}\n </span>\n }\n </button>\n\n <!-- Dialog non-modal: l'utente puo' continuare a usare l'app sotto.\n contentStyle limita l'altezza interna al chatHeight + header.\n [draggable]=\"true\" + [resizable]=\"true\" per UX power-user.\n [appendTo]=\"'body'\" sposta il dialog nel body document: evita che\n layout/scroll interactions con componenti heavy sottostanti (Syncfusion\n Spreadsheet con N-mila record, list-grid virtualized, ecc.) freezino la\n pagina quando il dialog apre/chiude. Il parent container NON subisce\n re-layout causato dalla presenza del dialog nel proprio subtree. -->\n <p-dialog\n [(visible)]=\"dialogVisible\"\n [modal]=\"false\"\n [draggable]=\"true\"\n [resizable]=\"true\"\n [closable]=\"true\"\n [closeOnEscape]=\"true\"\n [dismissableMask]=\"false\"\n [appendTo]=\"'body'\"\n [style]=\"{ width: dialogWidth }\"\n [contentStyle]=\"{ height: chatHeight }\"\n [header]=\"dialogTitle\"\n styleClass=\"wuic-rag-fab__dialog\"\n (onHide)=\"onDialogHide()\">\n <!-- Lazy-mount MANUALE del chatbot: PrimeNG p-dialog v17+ NON lazy-monta\n il content projettato (lo istanzia eagerly al creation del parent).\n Senza il guard `@if (dialogOpened)` il chatbot sarebbe istanziato al\n refresh \u2192 ngOnInit \u2192 `ragService.health()` \u2192 `GET /api/Rag/Health`\n anche se l'utente non apre mai il FAB (errore rosso in console se\n il RAG server Python e' offline, 503 Service Unavailable).\n `dialogOpened` e' sticky (setato dal primo `toggleDialog()`, mai\n resetto): una volta aperto, il chatbot resta montato e le\n ri-aperture sono istantanee (PrimeNG controlla solo la visibilita'\n DOM, niente rimonto ngOnInit). -->\n @if (dialogOpened) {\n <wuic-rag-chatbot\n [title]=\"dialogTitle\"\n [chatHeight]=\"chatHeight\"\n [showClearButton]=\"true\">\n </wuic-rag-chatbot>\n }\n </p-dialog>\n}\n", styles: [".wuic-rag-fab{position:fixed;bottom:24px;right:24px;width:56px;height:56px;border-radius:50%;border:none;cursor:pointer;background:var(--p-primary-color, #2563eb);color:var(--p-primary-color-text, #fff);box-shadow:0 4px 12px #0000002e,0 2px 4px #0000001f;display:flex;align-items:center;justify-content:center;z-index:1100;transform:translate(calc(100% + 16px));opacity:.45;transition:transform .28s cubic-bezier(.16,1,.3,1) .25s,opacity .28s ease-out .25s,box-shadow .18s ease-out 0s,background-color .18s ease-out 0s}.wuic-rag-fab i{font-size:24px;line-height:1}.wuic-rag-fab:hover,.wuic-rag-fab:focus-visible{transform:translate(0) scale(1.05);opacity:1;box-shadow:0 6px 18px #00000038,0 3px 6px #00000024;transition:transform .28s cubic-bezier(.16,1,.3,1) 0s,opacity .28s ease-out 0s,box-shadow .18s ease-out 0s,background-color .18s ease-out 0s}.wuic-rag-fab:hover:after,.wuic-rag-fab:focus-visible:after{content:\"\";position:absolute;top:-8px;bottom:-8px;left:100%;width:96px;background:transparent;pointer-events:auto}.wuic-rag-fab:active{transform:translate(0) scale(.98);opacity:1}.wuic-rag-fab:focus-visible{outline:2px solid var(--p-primary-color, #2563eb);outline-offset:3px}.wuic-rag-fab.wuic-rag-fab--dialog-open{transform:translate(0);opacity:1}.wuic-rag-fab__badge{position:absolute;top:-4px;right:-4px;min-width:22px;height:22px;padding:0 6px;border-radius:11px;background:#e11d48;color:#fff;font-size:11px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;box-shadow:0 1px 3px #0000004d;pointer-events:none}.wuic-rag-fab__dialog{position:fixed!important;inset:auto 16px 96px auto!important;margin:0!important;max-height:calc(100vh - 112px)}.wuic-rag-fab__dialog .p-dialog-content{padding:0;overflow:hidden;display:flex;flex-direction:column;height:480px;max-height:calc(100vh - 160px)}.wuic-rag-fab__dialog wuic-rag-chatbot{display:flex;flex:1 1 auto;min-height:0;width:100%}.wuic-rag-fab__dialog .p-dialog-header{padding:12px 18px}.p-dialog-mask:has(>.wuic-rag-fab__dialog){pointer-events:none;background:transparent!important}.p-dialog-mask:has(>.wuic-rag-fab__dialog)>.wuic-rag-fab__dialog{pointer-events:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "ngmodule", type: BadgeModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: WuicRagChatbotComponent, selector: "wuic-rag-chatbot", inputs: ["title", "mode", "topK", "showSources", "maxHistory", "placeholder", "model", "chatHeight", "showClearButton", "persistSession", "sendRouteContext", "allowProfileToggle"], outputs: ["resultSelected", "errorOccurred", "turnAdded"] }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
61724
63765
  }
61725
63766
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicRagChatbotFabComponent, decorators: [{
61726
63767
  type: Component,
@@ -61730,9 +63771,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
61730
63771
  DialogModule,
61731
63772
  TooltipModule,
61732
63773
  BadgeModule,
63774
+ TranslateModule,
61733
63775
  WuicRagChatbotComponent,
61734
- ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if (visible) {\n <!-- FAB bottom-right (position:fixed via .scss). z-index alto per stare sopra\n a navbar, dialog modali leggeri, ecc. ma sotto agli overlay critici (es.\n PrimeNG p-dialog modal stack). -->\n <button\n type=\"button\"\n class=\"wuic-rag-fab\"\n [class.wuic-rag-fab--dialog-open]=\"dialogVisible\"\n [attr.title]=\"fabTooltip\"\n aria-label=\"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: [{
63776
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if (visible) {\n <!-- FAB bottom-right (position:fixed via .scss). z-index alto per stare sopra\n a navbar, dialog modali leggeri, ecc. ma sotto agli overlay critici (es.\n PrimeNG p-dialog modal stack). -->\n <button\n type=\"button\"\n class=\"wuic-rag-fab\"\n [class.wuic-rag-fab--dialog-open]=\"dialogVisible\"\n [attr.title]=\"fabTooltip\"\n [attr.aria-label]=\"'rag_chatbot_fab.aria_label' | translate\"\n (click)=\"toggleDialog()\">\n <i class=\"pi pi-comments\" aria-hidden=\"true\"></i>\n @if (unreadRagCount > 0 && !dialogVisible) {\n <span class=\"wuic-rag-fab__badge\" [attr.data-count]=\"unreadRagCount\">\n {{ unreadRagCount > 9 ? '9+' : unreadRagCount }}\n </span>\n }\n </button>\n\n <!-- Dialog non-modal: l'utente puo' continuare a usare l'app sotto.\n contentStyle limita l'altezza interna al chatHeight + header.\n [draggable]=\"true\" + [resizable]=\"true\" per UX power-user.\n [appendTo]=\"'body'\" sposta il dialog nel body document: evita che\n layout/scroll interactions con componenti heavy sottostanti (Syncfusion\n Spreadsheet con N-mila record, list-grid virtualized, ecc.) freezino la\n pagina quando il dialog apre/chiude. Il parent container NON subisce\n re-layout causato dalla presenza del dialog nel proprio subtree. -->\n <p-dialog\n [(visible)]=\"dialogVisible\"\n [modal]=\"false\"\n [draggable]=\"true\"\n [resizable]=\"true\"\n [closable]=\"true\"\n [closeOnEscape]=\"true\"\n [dismissableMask]=\"false\"\n [appendTo]=\"'body'\"\n [style]=\"{ width: dialogWidth }\"\n [contentStyle]=\"{ height: chatHeight }\"\n [header]=\"dialogTitle\"\n styleClass=\"wuic-rag-fab__dialog\"\n (onHide)=\"onDialogHide()\">\n <!-- Lazy-mount MANUALE del chatbot: PrimeNG p-dialog v17+ NON lazy-monta\n il content projettato (lo istanzia eagerly al creation del parent).\n Senza il guard `@if (dialogOpened)` il chatbot sarebbe istanziato al\n refresh \u2192 ngOnInit \u2192 `ragService.health()` \u2192 `GET /api/Rag/Health`\n anche se l'utente non apre mai il FAB (errore rosso in console se\n il RAG server Python e' offline, 503 Service Unavailable).\n `dialogOpened` e' sticky (setato dal primo `toggleDialog()`, mai\n resetto): una volta aperto, il chatbot resta montato e le\n ri-aperture sono istantanee (PrimeNG controlla solo la visibilita'\n DOM, niente rimonto ngOnInit). -->\n @if (dialogOpened) {\n <wuic-rag-chatbot\n [title]=\"dialogTitle\"\n [chatHeight]=\"chatHeight\"\n [showClearButton]=\"true\">\n </wuic-rag-chatbot>\n }\n </p-dialog>\n}\n", styles: [".wuic-rag-fab{position:fixed;bottom:24px;right:24px;width:56px;height:56px;border-radius:50%;border:none;cursor:pointer;background:var(--p-primary-color, #2563eb);color:var(--p-primary-color-text, #fff);box-shadow:0 4px 12px #0000002e,0 2px 4px #0000001f;display:flex;align-items:center;justify-content:center;z-index:1100;transform:translate(calc(100% + 16px));opacity:.45;transition:transform .28s cubic-bezier(.16,1,.3,1) .25s,opacity .28s ease-out .25s,box-shadow .18s ease-out 0s,background-color .18s ease-out 0s}.wuic-rag-fab i{font-size:24px;line-height:1}.wuic-rag-fab:hover,.wuic-rag-fab:focus-visible{transform:translate(0) scale(1.05);opacity:1;box-shadow:0 6px 18px #00000038,0 3px 6px #00000024;transition:transform .28s cubic-bezier(.16,1,.3,1) 0s,opacity .28s ease-out 0s,box-shadow .18s ease-out 0s,background-color .18s ease-out 0s}.wuic-rag-fab:hover:after,.wuic-rag-fab:focus-visible:after{content:\"\";position:absolute;top:-8px;bottom:-8px;left:100%;width:96px;background:transparent;pointer-events:auto}.wuic-rag-fab:active{transform:translate(0) scale(.98);opacity:1}.wuic-rag-fab:focus-visible{outline:2px solid var(--p-primary-color, #2563eb);outline-offset:3px}.wuic-rag-fab.wuic-rag-fab--dialog-open{transform:translate(0);opacity:1}.wuic-rag-fab__badge{position:absolute;top:-4px;right:-4px;min-width:22px;height:22px;padding:0 6px;border-radius:11px;background:#e11d48;color:#fff;font-size:11px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;box-shadow:0 1px 3px #0000004d;pointer-events:none}.wuic-rag-fab__dialog{position:fixed!important;inset:auto 16px 96px auto!important;margin:0!important;max-height:calc(100vh - 112px)}.wuic-rag-fab__dialog .p-dialog-content{padding:0;overflow:hidden;display:flex;flex-direction:column;height:480px;max-height:calc(100vh - 160px)}.wuic-rag-fab__dialog wuic-rag-chatbot{display:flex;flex:1 1 auto;min-height:0;width:100%}.wuic-rag-fab__dialog .p-dialog-header{padding:12px 18px}.p-dialog-mask:has(>.wuic-rag-fab__dialog){pointer-events:none;background:transparent!important}.p-dialog-mask:has(>.wuic-rag-fab__dialog)>.wuic-rag-fab__dialog{pointer-events:auto}\n"] }]
63777
+ }], ctorParameters: () => [{ type: LicenseFeatureService }, { type: NotificationRealtimeService }, { type: i0.ChangeDetectorRef }, { type: i1$2.Router }], propDecorators: { fabTooltip: [{
61736
63778
  type: Input
61737
63779
  }], dialogTitle: [{
61738
63780
  type: Input
@@ -62020,7 +64062,7 @@ const designerPendingChangesGuard = async (component) => {
62020
64062
  const routes = [
62021
64063
  {
62022
64064
  path: 'framework-docs',
62023
- loadComponent: () => import('./wuic-framework-lib-framework-docs.component-m0IlS0Sl.mjs').then((m) => m.FrameworkDocsComponent),
64065
+ loadComponent: () => import('./wuic-framework-lib-framework-docs.component-CGQbmSkv.mjs').then((m) => m.FrameworkDocsComponent),
62024
64066
  canMatch: [menuRouteAccessCanMatchGuard],
62025
64067
  canActivate: [menuRouteAccessCanActivateGuard],
62026
64068
  data: {
@@ -62029,7 +64071,7 @@ const routes = [
62029
64071
  },
62030
64072
  {
62031
64073
  path: 'framework-docs/:slug',
62032
- loadComponent: () => import('./wuic-framework-lib-framework-docs.component-m0IlS0Sl.mjs').then((m) => m.FrameworkDocsComponent),
64074
+ loadComponent: () => import('./wuic-framework-lib-framework-docs.component-CGQbmSkv.mjs').then((m) => m.FrameworkDocsComponent),
62033
64075
  canMatch: [menuRouteAccessCanMatchGuard],
62034
64076
  canActivate: [menuRouteAccessCanActivateGuard],
62035
64077
  data: {
@@ -62047,7 +64089,7 @@ const routes = [
62047
64089
  },
62048
64090
  {
62049
64091
  path: ':route/report-designer',
62050
- loadComponent: () => import('./wuic-framework-lib-report-designer.component-FiAwNkPM.mjs').then((m) => m.ReportDesignerComponent),
64092
+ loadComponent: () => import('./wuic-framework-lib-report-designer.component-DcpdzFjh.mjs').then((m) => m.ReportDesignerComponent),
62051
64093
  canMatch: [menuRouteAccessCanMatchGuard, featureRouteCanMatchGuard],
62052
64094
  canActivate: [menuRouteAccessCanActivateGuard, featureRouteCanActivateGuard],
62053
64095
  data: {
@@ -62057,7 +64099,7 @@ const routes = [
62057
64099
  },
62058
64100
  {
62059
64101
  path: ':route/report-viewer',
62060
- loadComponent: () => import('./wuic-framework-lib-report-viewer.component-DY0dGMrt.mjs').then((m) => m.ReportViewerComponent),
64102
+ loadComponent: () => import('./wuic-framework-lib-report-viewer.component-BHpbGpp9.mjs').then((m) => m.ReportViewerComponent),
62061
64103
  canMatch: [menuRouteAccessCanMatchGuard],
62062
64104
  canActivate: [menuRouteAccessCanActivateGuard],
62063
64105
  data: {
@@ -62077,7 +64119,7 @@ const routes = [
62077
64119
  },
62078
64120
  {
62079
64121
  path: ':route/pivot-builder',
62080
- loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-VIs0CIH_.mjs').then((m) => m.PivotBuilderComponent),
64122
+ loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-0gJz9RWr.mjs').then((m) => m.PivotBuilderComponent),
62081
64123
  canMatch: [menuRouteAccessCanMatchGuard, featureRouteCanMatchGuard],
62082
64124
  canActivate: [menuRouteAccessCanActivateGuard, featureRouteCanActivateGuard],
62083
64125
  data: {
@@ -62087,7 +64129,7 @@ const routes = [
62087
64129
  },
62088
64130
  {
62089
64131
  path: 'pivot-builder',
62090
- loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-VIs0CIH_.mjs').then((m) => m.PivotBuilderComponent),
64132
+ loadComponent: () => import('./wuic-framework-lib-pivot-builder.component-0gJz9RWr.mjs').then((m) => m.PivotBuilderComponent),
62091
64133
  canMatch: [menuRouteAccessCanMatchGuard, featureRouteCanMatchGuard],
62092
64134
  canActivate: [menuRouteAccessCanActivateGuard, featureRouteCanActivateGuard],
62093
64135
  data: {
@@ -62345,5 +64387,5 @@ function getThemePrimaryHex(value) {
62345
64387
  * Generated bundle index. Do not edit.
62346
64388
  */
62347
64389
 
62348
- export { 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
64390
+ export { IsSelectedRowPipe as $, ArchetypeConfiguratorComponent as A, BooleanEditorComponent as B, ChatbotHostRegistryService as C, DynamicGenericTemplateComponent as D, ColorEditorComponent as E, FilterBarComponent as F, GlobalHandler as G, CrashReporterService as H, ImportExportButtonComponent as I, CustomAttributesService as J, CustomException as K, LazyParametricDialogComponent as L, MetadataProviderService as M, DataActionButtonComponent as N, DataProviderOdataService as O, PagerComponent as P, DateEditorComponent as Q, DesignerRouteComponent as R, DictionaryEditorComponent as S, TranslationManagerService as T, UserInfoService as U, DynamicRowTemplateComponent as V, WtoolboxService as W, FormatGridViewValuePipe as X, GetSrcUploadPreviewPipe as Y, HtmlEditorComponent as Z, ImageWrapperComponent as _, DynamicCompilerService as a, loadHtmlEditorComponent as a$, KanbanListComponent as a0, KanbanOptions as a1, KanbanStatusColumnItem as a2, LazyChartListComponent as a3, LazyCodeEditorComponent as a4, LazyDataActionButtonComponent as a5, LazyDataSourceComponent as a6, LazyFieldEditorComponent as a7, LazyFirstRunWizardComponent as a8, LazyImageWrapperComponent as a9, ValueChangedPayload as aA, VisibleFieldListPipe as aB, WidgetDefinition as aC, WorkflowDesignerRouteComponent as aD, WorkflowRunnerComponent as aE, WuicAziendaSwitcherComponent as aF, WuicErrorDialogComponent as aG, WuicErrorInterceptor as aH, WuicFeatureDirective as aI, WuicFrozenColumnDirective as aJ, WuicRagChatbotComponent as aK, WuicRagChatbotFabComponent as aL, WuicRagService as aM, WuicRowTogglerDirective as aN, _PRIMENG_TABLE_DIRECTIVES_NAMES as aO, authExpiredInterceptor as aP, boundedRepeaterPendingChangesGuard as aQ, featureRouteCanActivateGuard as aR, featureRouteCanMatchGuard as aS, getThemePrimaryHex as aT, inferTemplateFieldFromBaseClass as aU, loadBooleanEditorComponent as aV, loadButtonEditorComponent as aW, loadCodeAreaEditorComponent as aX, loadColorEditorComponent as aY, loadDateEditorComponent as aZ, loadDictionaryEditorComponent as a_, LazyKanbanListComponent as aa, LazyMetaMenuComponent as ab, LazyPivotBuilderComponent as ac, LazyReportDesignerComponent as ad, LazyReportViewerComponent as ae, LazySchedulerListComponent as af, LazySpreadsheetListSfComponent as ag, ListGridComponent as ah, LookupEditorComponent as ai, MapListComponent as aj, MetaMenuComponent as ak, MetadatiTabella as al, NotificationBellComponent as am, NotificationRealtimeService as an, NumberEditorComponent as ao, PRIMARY_PALETTES as ap, PropertyArrayEditorComponent as aq, PropertyObjectEditorComponent as ar, THEME_OPTIONS as as, TextAreaEditorComponent as at, TextEditorComponent as au, TreeListComponent as av, TreeViewSelectorComponent as aw, UploadDialogComponent as ax, UploadEditorComponent as ay, ValidationRule as az, DataSourceComponent as b, loadLookupEditorComponent as b0, loadNumberEditorComponent as b1, loadPropertyArrayEditorComponent as b2, loadPropertyObjectEditorComponent as b3, loadTextAreaEditorComponent as b4, loadTextEditorComponent as b5, loadTreeViewSelectorComponent as b6, loadUploadEditorComponent as b7, menuRouteAccessCanActivateGuard as b8, menuRouteAccessCanMatchGuard as b9, routes as ba, wuicErrorInterceptor as bb, DataRepeaterComponent as c, ParametricDialogComponent as d, MetadatiColonna as e, DataProviderService as f, MetadataEditorService as g, WorkflowRuntimeMetadataService as h, FieldEditorComponent as i, MetadataEditorComponent as j, WuicClientException as k, WuicErrorCodes as l, MetaInfo as m, FilterInfo as n, LicenseFeatureService as o, FilterItem as p, getThemeOptions as q, AUTH_REDIRECT_REASON_KEY as r, AppSettingsEditorComponent as s, AuthSessionService as t, BoundedRepeaterComponent as u, ButtonEditorComponent as v, CallbackPipe as w, CallbackPipe2 as x, CarouselListComponent as y, CodeAreaEditorComponent as z };
64391
+ //# sourceMappingURL=wuic-framework-lib-wuic-framework-lib-Deudd4MY.mjs.map