uni-manager 0.0.78 → 0.1.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.
@@ -1,6 +1,25 @@
1
- import { BehaviorSubject, switchMap, mergeMap, concatMap, exhaustMap, throwError, of, EMPTY, defer, from, tap, catchError, finalize, timer, distinctUntilChanged, map } from 'rxjs';
2
- import { isHttpErrorBody, UniHttpError } from 'uni-error';
3
- import { isEqual } from 'lodash-es';
1
+ import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
2
+ import { map } from 'rxjs/internal/operators/map';
3
+ import { tap } from 'rxjs/internal/operators/tap';
4
+ import { UniToastManager as UniToastManager$1 } from 'uni-manager/toast';
5
+ import isEqual from 'lodash-es/isEqual';
6
+ import { defer } from 'rxjs/internal/observable/defer';
7
+ import { from } from 'rxjs/internal/observable/from';
8
+ import { timer } from 'rxjs/internal/observable/timer';
9
+ import { catchError } from 'rxjs/internal/operators/catchError';
10
+ import { distinctUntilChanged } from 'rxjs/internal/operators/distinctUntilChanged';
11
+ import { finalize } from 'rxjs/internal/operators/finalize';
12
+ import { UniErrorManager as UniErrorManager$1 } from 'uni-manager/error';
13
+ import { EMPTY } from 'rxjs/internal/observable/empty';
14
+ import { of } from 'rxjs/internal/observable/of';
15
+ import { throwError } from 'rxjs/internal/observable/throwError';
16
+ import { concatMap } from 'rxjs/internal/operators/concatMap';
17
+ import { exhaustMap } from 'rxjs/internal/operators/exhaustMap';
18
+ import { mergeMap } from 'rxjs/internal/operators/mergeMap';
19
+ import { switchMap } from 'rxjs/internal/operators/switchMap';
20
+ import { UniHttpError, isHttpErrorBody } from 'uni-error/http';
21
+ import { UniTypeDateManager as UniTypeDateManager$1 } from 'uni-manager/type';
22
+ import { UniLocaleManager as UniLocaleManager$1 } from 'uni-manager/locale';
4
23
 
5
24
  class UniErrorManager {
6
25
  /* ------------------------------------------------------------------------------- */
@@ -169,401 +188,6 @@ var EmitValueMode;
169
188
  EmitValueMode[EmitValueMode["EVERY_TIME"] = 1] = "EVERY_TIME";
170
189
  })(EmitValueMode || (EmitValueMode = {}));
171
190
 
172
- class UniLocaleManager {
173
- /* ------------------------------------------------------------------------------- */
174
- /* ----------------------------------- Config ------------------------------------ */
175
- /* ------------------------------------------------------------------------------- */
176
- /** Codice lingua corrente (es. 'it-IT', 'en-US') usato per formattare date e numeri */
177
- static { this._locale = navigator.language ?? 'en-US'; }
178
- /** Elenco dei codici lingua supportati dall'applicazione (es. ['it-IT', 'en-US']) */
179
- static { this._localesSupported = []; }
180
- /** Prefisso globale applicato a tutte le chiavi di traduzione (es. 'APP_') */
181
- static { this._prefix = undefined; }
182
- /* ------------------------------------------------------------------------------- */
183
- /* --------------------------------- Metodi: get --------------------------------- */
184
- /* ------------------------------------------------------------------------------- */
185
- /** Restituisce il codice lingua corrente */
186
- static get locale() {
187
- return this._locale;
188
- }
189
- /** Restituisce l'array contenente tutti i codici locale supportati.*/
190
- static get localesSupported() {
191
- return this._localesSupported;
192
- }
193
- /** Restituisce solo la lingua (es. 'it') */
194
- static get language() {
195
- return this._locale.split('-')[0];
196
- }
197
- /** Restituisce solo il paese (es. 'IT') */
198
- static get region() {
199
- const parts = this._locale.split('-');
200
- return parts.length > 1 ? parts[1] : '';
201
- }
202
- /* ------------------------------------------------------------------------------- */
203
- /* ------------------------------------ Store ------------------------------------ */
204
- /* ------------------------------------------------------------------------------- */
205
- /** Store privato (Subject) */
206
- static { this.store = new BehaviorSubject({}); }
207
- /** Store pubblico (Observable) */
208
- static { this.store$ = this.store.asObservable(); }
209
- /** Ottiene il dizionario attuale senza sottoscrizione */
210
- static get currentValue() {
211
- return this.store.getValue();
212
- }
213
- /* ------------------------------------------------------------------------------- */
214
- /* -------------------------------- Metodi: setup -------------------------------- */
215
- /* ------------------------------------------------------------------------------- */
216
- /**
217
- * Inizializza la configurazione di rete del manager.
218
- * Deve essere chiamato prima di effettuare qualsiasi richiesta HTTP.
219
- */
220
- static setup(locale, prefix) {
221
- this._locale = locale ?? 'en-US';
222
- this._prefix = prefix;
223
- }
224
- /** Imposta l'elenco dei codici lingua supportati dall'applicazione */
225
- static setLocalesSupported(locales) {
226
- this._localesSupported = locales ?? [];
227
- }
228
- /**
229
- * Aggiorna lo store locale con il dizionario delle traduzioni fornito.
230
- * Se viene passato undefined, lo store viene inizializzato come oggetto vuoto.
231
- */
232
- static setTranslations(translations) {
233
- this.store.next(translations ?? {});
234
- }
235
- /* ------------------------------------------------------------------------------- */
236
- /* ----------------------------- Metodi: traduzioni ------------------------------ */
237
- /* ------------------------------------------------------------------------------- */
238
- /**
239
- * Traduce una label in base al dizionario caricato.
240
- * Gestisce la composizione della chiave, i parametri dinamici (interpolazione) e il fallback.
241
- */
242
- static translate(key, prefix = 'lbl', interpolationParams) {
243
- if (!key)
244
- return '-';
245
- // Costruzione chiave: prefissoGlobale + prefissoLocale + LabelConInizialeMaiuscola
246
- const keyParts = key.trim().split(/(?=[A-Z])/);
247
- const rootKeyParts = keyParts.filter((p) => p.toLowerCase() !== prefix.toLowerCase());
248
- const cleanKey = rootKeyParts.length > 0 ? rootKeyParts.join('') : undefined;
249
- const capitalizedKey = cleanKey ? cleanKey.charAt(0).toUpperCase() + cleanKey.slice(1) : '';
250
- const finalKey = `${this._prefix ?? ''}${prefix}${capitalizedKey}`;
251
- // Cerca la chiave in minuscolo (standardizzazione)
252
- let translation = this.currentValue?.[finalKey.toLowerCase()];
253
- // Log e fallback se la traduzione manca
254
- if (!translation) {
255
- console.warn(`Translation missing for key: ${finalKey}`);
256
- return `🔑 ${finalKey}`;
257
- }
258
- // Interpolazione variabili
259
- if (interpolationParams) {
260
- for (const [key, value] of Object.entries(interpolationParams)) {
261
- const displayValue = value instanceof Date ? value.toLocaleDateString(this._locale) : String(value);
262
- translation = translation.replaceAll(`{{${key}}}`, displayValue);
263
- }
264
- }
265
- return translation;
266
- }
267
- /**
268
- * Traduce una stringa che contiene parametri separati da un carattere specifico.
269
- * Supporta ora un numero arbitrario di parametri in formato "label/param1/param2"
270
- * o semplicemente "label".
271
- */
272
- static translateInlineParams(keyWithParams, splitChar) {
273
- if (!keyWithParams)
274
- return '-';
275
- const [label, ...params] = keyWithParams.split(splitChar);
276
- // Se non ci sono parametri, esegui una traduzione semplice
277
- if (params.length === 0) {
278
- return this.translate(label);
279
- }
280
- // Mappa i parametri in un oggetto di interpolazione: { inlineParam0: val, inlineParam1: val, ... }
281
- const interpolationParameters = {};
282
- for (const [index, val] of params.entries()) {
283
- interpolationParameters[`param${index}`] = val;
284
- }
285
- return this.translate(label, 'lbl', interpolationParameters);
286
- }
287
- /* ------------------------------------------------------------------------------- */
288
- /* ------------------------------- Metodi: numeri -------------------------------- */
289
- /* ------------------------------------------------------------------------------- */
290
- /**
291
- * Converte un valore numerico in una stringa formattata secondo il locale impostato.
292
- * Disabilita i separatori delle migliaia e forza un numero fisso di decimali.
293
- */
294
- static toStringNumber(value, decimal) {
295
- return new Intl.NumberFormat(this._locale, {
296
- useGrouping: true,
297
- minimumFractionDigits: decimal,
298
- maximumFractionDigits: decimal,
299
- numberingSystem: 'latn',
300
- }).format(value);
301
- }
302
- /* ------------------------------------------------------------------------------- */
303
- /* -------------------------------- Metodi: date --------------------------------- */
304
- /* ------------------------------------------------------------------------------- */
305
- /**
306
- * Formatta una data o una stringa in base al locale corrente.
307
- * Gestisce tre modalità predefinite (date, time, full) e accetta opzioni personalizzate Intl.
308
- */
309
- static toDate(date, mode = 'full', force) {
310
- const dt = new Date(date);
311
- // Controllo: validità data
312
- if (Number.isNaN(dt.getTime())) {
313
- console.error(`[UniLocaleManager] Data non valida fornita a formatDateTime:`, date);
314
- return '';
315
- }
316
- // Controllo: locale da forzare
317
- const locale = force && this._locale.startsWith(force.oldLang) ? force.newLocale : this._locale;
318
- switch (mode) {
319
- case 'date': {
320
- return dt.toLocaleDateString(locale);
321
- }
322
- case 'time': {
323
- return dt.toLocaleTimeString(locale);
324
- }
325
- case 'full': {
326
- return dt.toLocaleString(locale);
327
- }
328
- }
329
- }
330
- }
331
-
332
- /* eslint-disable @typescript-eslint/no-explicit-any */
333
- class UniToastManager {
334
- /* ------------------------------------------------------------------------------- */
335
- /* -------------------------------- Metodi: setup -------------------------------- */
336
- /* ------------------------------------------------------------------------------- */
337
- /**
338
- * Inizializza il manager con una serie di operazioni
339
- */
340
- static setup(operations) {
341
- this.manager = operations;
342
- }
343
- /* ------------------------------------------------------------------------------- */
344
- /* -------------------------------- Metodi: show --------------------------------- */
345
- /* ------------------------------------------------------------------------------- */
346
- /**
347
- * Mostra un toast di successo basato su una risposta HTTP e una label di traduzione.
348
- */
349
- static show(config) {
350
- /* Messaggio tradotto */
351
- const msg = UniLocaleManager.translate(config.label, 'toast', config.params);
352
- const msgFixed = `${config.prefix ?? ''}${msg}${config.suffix ?? ''}`;
353
- this.manager[config.type ?? 'info'](msgFixed, config);
354
- }
355
- /**
356
- * Mostra un toast di successo basato su una risposta HTTP e una label di traduzione.
357
- */
358
- static showHttp(config, res) {
359
- const { interpolationParams, resParams, resLengthParam, formatters } = config;
360
- /* Parametri statici di base */
361
- const allParams = { ...interpolationParams };
362
- /* Config parametri risposta: estrazione parametri dalla risposta (se esiste ed è un oggetto) */
363
- if (resParams?.length && res && typeof res === 'object') {
364
- for (const param of resParams ?? []) {
365
- if (param in res)
366
- continue;
367
- // Se esiste un formatter per questa chiave lo si usa, altrimenti valore grezzo
368
- const rawValue = res[param];
369
- allParams[param] = formatters?.[param] ? formatters[param](rawValue) : rawValue;
370
- }
371
- }
372
- /* Config parametro lunghezza risposta: conteggio array */
373
- if (resLengthParam && Array.isArray(res)) {
374
- allParams[resLengthParam] = res.length;
375
- }
376
- /* Messaggio tradotto */
377
- this.show({ ...config, params: allParams });
378
- }
379
- }
380
-
381
- async function execute(url, init) {
382
- try {
383
- /* Esegue chiamata http */
384
- const res = await fetch(url, init);
385
- /* Nessun contenuto cons status 204 */
386
- if (res.status === 204) {
387
- return undefined;
388
- }
389
- /* Recupero se è un json */
390
- const contentType = res.headers.get('content-type') ?? '';
391
- const isJson = contentType.startsWith('application/json');
392
- /* Errore HTTP (quindi risposta ricevuta ma non ok) */
393
- if (!res.ok) {
394
- let errorBody;
395
- try {
396
- /* Clona risposta solo se c'è errore, per leggerla senza consumare la originale */
397
- const resClone = res.clone();
398
- /* Aggiorna body con struttura errore */
399
- errorBody = isJson ? await resClone.json() : await resClone.text();
400
- }
401
- catch {
402
- errorBody = await res.text();
403
- }
404
- if (isHttpErrorBody(errorBody)) {
405
- const type = errorBody.exceptionType.includes('HttpRequestException') ? 'be' : 'ice';
406
- throw new UniHttpError(type, res.status, url, errorBody);
407
- }
408
- else {
409
- const error = new Error(`HTTP ${res.statusText}`);
410
- throw new UniHttpError('base', res.status, url, {
411
- exceptionMessage: error.message,
412
- exceptionType: 'ErrorBase',
413
- message: error.message,
414
- stackTrace: error.stack ?? '',
415
- });
416
- }
417
- }
418
- /* Risposta ok */
419
- return res;
420
- }
421
- catch (error) {
422
- if (error instanceof TypeError) {
423
- throw new UniHttpError('network', -1, url, {
424
- exceptionMessage: `${error.name}: ${error.message}\n${error.stack}`,
425
- exceptionType: error.name,
426
- message: error.message,
427
- stackTrace: error.stack ?? '',
428
- });
429
- }
430
- // Fallback
431
- throw error;
432
- }
433
- }
434
- async function executeHttp(url, init) {
435
- /* Esegue la chiamata HTTP e restituisce il corpo decodificato come JSON */
436
- const res = await execute(url, init);
437
- if (!res)
438
- return undefined;
439
- /* Estrae il contenuto come testo */
440
- const text = await res.text();
441
- /* Controllo: se il testo è vuoto (''), ritorna undefined */
442
- if (!text || text.trim().length === 0) {
443
- return undefined;
444
- }
445
- /* Parsa manualmente il testo, dato che lo stream è stato già letto */
446
- return JSON.parse(text);
447
- }
448
- async function executeBlob(url, init) {
449
- /* Esegue la chiamata HTTP */
450
- const res = await execute(url, init);
451
- if (!res)
452
- return undefined;
453
- /* Estrae il contenuto come Blob direttamente */
454
- const blob = await res.blob();
455
- /* Controllo: se il blob è vuoto (0 byte), ritorna undefined */
456
- if (blob.size === 0) {
457
- return undefined;
458
- }
459
- /* Estrae il nome del file dall'header */
460
- const disposition = res.headers.get('Content-Disposition');
461
- let fileName = 'download.pdf'; // Fallback di default
462
- if (disposition) {
463
- const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(disposition);
464
- if (!!matches && matches[1]) {
465
- fileName = matches[1].replaceAll(/['"]/g, '');
466
- }
467
- }
468
- /* Genera l'URL temporaneo dal blob */
469
- const fileUrl = URL.createObjectURL(blob);
470
- return { url: fileUrl, name: fileName };
471
- }
472
-
473
- /**
474
- * Aggiunge una nuova ref nello store solo se non è già presente.
475
- * Se l'ID esiste già, l'operazione viene interrotta per preservare il dato originale.
476
- */
477
- function add(id, url, type) {
478
- // Recupera l'ultimo stato (Map)
479
- const oldMap = UniHttpManager.currentValue;
480
- // Controllo: se è presente l'item allora termina
481
- if (oldMap.get(id))
482
- return;
483
- // Crea il nuovo oggetto
484
- const newItemMap = {
485
- type,
486
- lineId: undefined,
487
- url,
488
- hasError: false,
489
- pendingCount: 0,
490
- };
491
- // Crea una nuova istanza della Map
492
- const newMap = new Map(oldMap);
493
- newMap.set(id, newItemMap);
494
- // Aggiorna il nuovo stato notificando l'observer
495
- UniHttpManager.store.next(newMap);
496
- }
497
- /**
498
- * Rimuove un http ref tramite ID
499
- */
500
- function remove(id) {
501
- // Recupera l'ultimo stato (Map)
502
- const oldMap = UniHttpManager.currentValue;
503
- // Controllo: se non è presente l'item allora termina
504
- if (!oldMap.has(id))
505
- return;
506
- // Aggiorna store
507
- const newMap = new Map(oldMap);
508
- newMap.delete(id);
509
- UniHttpManager.store.next(newMap);
510
- }
511
- /**
512
- * Aggiorna il contatore delle chiamate pendenti per una specifica ref.
513
- * Incrementa o decrementa 'pendingCount' garantendo che non scenda mai sotto lo zero.
514
- * Se la ref non esiste, l'operazione viene ignorata.
515
- */
516
- function updateIsLoading(id, delta) {
517
- // Recupera l'ultimo stato (Map)
518
- const oldMap = UniHttpManager.currentValue;
519
- // Controllo: se non è presente l'item allora termina
520
- const oldItem = oldMap.get(id);
521
- if (!oldItem)
522
- return;
523
- // Aggiorna l'oggetto
524
- const newItemMap = {
525
- ...oldItem,
526
- pendingCount: Math.max(0, oldItem.pendingCount + delta),
527
- };
528
- // Crea una nuova istanza della Map
529
- const newMap = new Map(oldMap);
530
- newMap.set(id, newItemMap);
531
- // Aggiorna il nuovo stato notificando l'observer
532
- UniHttpManager.store.next(newMap);
533
- }
534
- /**
535
- * Aggiorna lo stato di errore per una specifica ref.
536
- * Se il valore di 'hasError' è identico a quello attuale o se la ref non esiste,
537
- * l'operazione viene interrotta per evitare aggiornamenti inutili.
538
- */
539
- function updateHasError(id, hasError) {
540
- // Recupera l'ultimo stato (Map)
541
- const oldMap = UniHttpManager.currentValue;
542
- // Controllo: se non è presente l'item allora termina
543
- const oldItem = oldMap.get(id);
544
- if (!oldItem)
545
- return;
546
- // Controllo: se con lo stesso valore, salta
547
- if (oldItem.hasError === hasError)
548
- return;
549
- // Aggiorna l'oggetto
550
- const newItemMap = { ...oldItem, hasError };
551
- // Crea una nuova istanza della Map
552
- const newMap = new Map(oldMap);
553
- newMap.set(id, newItemMap);
554
- // Aggiorna il nuovo stato notificando l'observer
555
- UniHttpManager.store.next(newMap);
556
- }
557
- /**
558
- * Svuota completamente la lista delle refs
559
- */
560
- function removeAll() {
561
- // Crea una nuova istanza della Map
562
- const newMap = new Map();
563
- // Aggiorna il nuovo stato notificando l'observer
564
- UniHttpManager.store.next(newMap);
565
- }
566
-
567
191
  function operatorHandler(operator) {
568
192
  // Gestione della concorrenza in base al parametro
569
193
  switch (operator) {
@@ -597,18 +221,23 @@ function errorHandler(err, errorMode, ref) {
597
221
  return EMPTY;
598
222
  }
599
223
  case PollingErrorMode.IGNORE_WITH_ERROR: {
600
- {
601
- UniErrorManager.add(ref, err);
602
- return of();
603
- }
224
+ UniErrorManager$1.add(ref, err);
225
+ return of();
604
226
  }
605
227
  case PollingErrorMode.STOP: {
606
- UniErrorManager.add(ref, err);
228
+ UniErrorManager$1.add(ref, err);
607
229
  return throwError(() => err);
608
230
  }
609
231
  }
610
232
  }
611
233
 
234
+ /* ------------------------------------------------------------------------------- */
235
+ /* -------------------------------- Funzioni Core RxJS -------------------------- */
236
+ /* ------------------------------------------------------------------------------- */
237
+ /**
238
+ * Gestisce l'esecuzione e il ciclo di vita di una singola richiesta HTTP.
239
+ * Si occupa della registrazione della reference, della gestione dei loader, dei toast di successo e dell'intercettazione degli errori.
240
+ */
612
241
  function http$(url, refType, config, promiseFactory) {
613
242
  /* Recupero configurazione */
614
243
  const { ref, toast, hasLoader } = config;
@@ -630,7 +259,7 @@ function http$(url, refType, config, promiseFactory) {
630
259
  next: (res) => {
631
260
  /* Mostra toast (se presente) */
632
261
  if (toast) {
633
- UniToastManager.showHttp(toast, res);
262
+ UniToastManager$1.showHttp(toast, res);
634
263
  }
635
264
  },
636
265
  }), catchError((err) => {
@@ -644,6 +273,10 @@ function http$(url, refType, config, promiseFactory) {
644
273
  }));
645
274
  });
646
275
  }
276
+ /**
277
+ * Avvia e coordina un ciclo di polling a intervalli regolari.
278
+ * Gestisce la concorrenza tramite operatori RxJS configurabili, la rimozione dei popup, di errore nelle iterazioni successive e i comportamenti custom al primo avvio.
279
+ */
647
280
  function httpPolling$(url, config, promiseFactory) {
648
281
  /* Recupero configurazione */
649
282
  const { interval, ref, operator = MapOperator.SWITCH_MAP, errorMode = PollingErrorMode.STOP, emitValueMode = EmitValueMode.ON_NEW_DATA, firstIteration, } = config;
@@ -665,14 +298,14 @@ function httpPolling$(url, config, promiseFactory) {
665
298
  return defer(() => {
666
299
  const http$ = from(promiseFactory());
667
300
  return http$.pipe(tap((res) => {
668
- /* Rimozione popup di errore nel caso di errorMode = 'IGNORE_WITH_ERROR' */
301
+ /* Meccanismo di ripristino automatico: se il polling torna in salute, cancella l'errore dallo store e nasconde i relativi messaggi a schermo */
669
302
  if (errorMode === PollingErrorMode.IGNORE_WITH_ERROR) {
670
303
  updateHasError(ref, false);
671
- UniErrorManager.remove(ref);
304
+ UniErrorManager$1.remove(ref);
672
305
  }
673
306
  /* Prima risposta */
674
307
  if (index === 0 && firstIteration?.toast) {
675
- UniToastManager.showHttp(firstIteration.toast, res);
308
+ UniToastManager$1.showHttp(firstIteration.toast, res);
676
309
  }
677
310
  }), catchError((err) => {
678
311
  /* Gestione errore */
@@ -689,148 +322,193 @@ function httpPolling$(url, config, promiseFactory) {
689
322
  ? source$.pipe(distinctUntilChanged((prev, cur) => isEqual(prev, cur)))
690
323
  : source$;
691
324
  }
692
-
325
+ /* ------------------------------------------------------------------------------- */
326
+ /* ------------------------------------ Utils ------------------------------------ */
327
+ /* ------------------------------------------------------------------------------- */
328
+ /**
329
+ * Aggiunge una nuova ref nello store solo se non è già presente.
330
+ * Se l'ID esiste già, l'operazione viene interrotta per preservare il dato originale.
331
+ */
332
+ function add(id, url, type) {
333
+ // Recupera l'ultimo stato (Map)
334
+ const oldMap = UniHttpManager.currentValue;
335
+ // Controllo: se è presente l'item allora termina
336
+ if (oldMap.get(id))
337
+ return;
338
+ // Crea il nuovo oggetto
339
+ const newItemMap = {
340
+ type,
341
+ lineId: undefined,
342
+ url,
343
+ hasError: false,
344
+ pendingCount: 0,
345
+ };
346
+ // Crea una nuova istanza della Map
347
+ const newMap = new Map(oldMap);
348
+ newMap.set(id, newItemMap);
349
+ // Aggiorna il nuovo stato notificando l'observer
350
+ UniHttpManager.store.next(newMap);
351
+ }
352
+ /**
353
+ * Rimuove un http ref tramite ID
354
+ */
355
+ function remove(id) {
356
+ // Recupera l'ultimo stato (Map)
357
+ const oldMap = UniHttpManager.currentValue;
358
+ // Controllo: se non è presente l'item allora termina
359
+ if (!oldMap.has(id))
360
+ return;
361
+ // Aggiorna store
362
+ const newMap = new Map(oldMap);
363
+ newMap.delete(id);
364
+ UniHttpManager.store.next(newMap);
365
+ }
693
366
  /**
694
- * Classe di utilità per la gestione e formattazione delle date.
695
- * Fornisce metodi per convertire in modo sicuro valori di tipo Date, stringa o null in formati standardizzati.
367
+ * Aggiorna il contatore delle chiamate pendenti per una specifica ref.
368
+ * Incrementa o decrementa 'pendingCount' garantendo che non scenda mai sotto lo zero.
369
+ * Se la ref non esiste, l'operazione viene ignorata.
696
370
  */
697
- class UniTypeDateManager {
698
- static toYYYYMMDD(date) {
699
- if (!date)
700
- return '';
701
- const d = new Date(date);
702
- if (Number.isNaN(d.getTime()))
703
- return '';
704
- const year = d.getFullYear();
705
- const month = String(d.getMonth() + 1).padStart(2, '0');
706
- const day = String(d.getDate()).padStart(2, '0');
707
- return `${year}-${month}-${day}`;
708
- }
371
+ function updateIsLoading(id, delta) {
372
+ // Recupera l'ultimo stato (Map)
373
+ const oldMap = UniHttpManager.currentValue;
374
+ // Controllo: se non è presente l'item allora termina
375
+ const oldItem = oldMap.get(id);
376
+ if (!oldItem)
377
+ return;
378
+ // Aggiorna l'oggetto
379
+ const newItemMap = {
380
+ ...oldItem,
381
+ pendingCount: Math.max(0, oldItem.pendingCount + delta),
382
+ };
383
+ // Crea una nuova istanza della Map
384
+ const newMap = new Map(oldMap);
385
+ newMap.set(id, newItemMap);
386
+ // Aggiorna il nuovo stato notificando l'observer
387
+ UniHttpManager.store.next(newMap);
709
388
  }
710
-
711
389
  /**
712
- * Utility per la gestione e formattazione di valori numerici.
390
+ * Aggiorna lo stato di errore per una specifica ref.
391
+ * Se il valore di 'hasError' è identico a quello attuale o se la ref non esiste,
392
+ * l'operazione viene interrotta per evitare aggiornamenti inutili.
713
393
  */
714
- class UniTypeNumberManager {
715
- /** Applica il padding a un numero o una stringa */
716
- static toPad(value, count, character = '0') {
717
- // Se il valore è null/undefined, riempiamo l'intero campo con il carattere scelto
718
- if (value === null || value === undefined) {
719
- return ''.padStart(count, character);
720
- }
721
- // Convertiamo in stringa e applichiamo il padding
722
- return value.toString().padStart(count, character);
723
- }
724
- /** Formatta un numero in una versione leggibile (es: 1000 -> 1K, 1000000 -> 1M) */
725
- static toTruncateAndAdUdm(value, decimalDigits = 0, maxIntegerDigits = 3) {
726
- // Nessun limite impostato o numero inferiore alla soglia definita
727
- if (Math.abs(value) < Math.pow(10, maxIntegerDigits)) {
728
- return value.toFixed(decimalDigits);
729
- }
730
- // Definizione delle scale di riduzione per grandi numeri
731
- const scales = [
732
- { threshold: 1e12, suffix: 'T' }, // Trilioni
733
- { threshold: 1e9, suffix: 'B' }, // Miliardi
734
- { threshold: 1e6, suffix: 'M' }, // Milioni
735
- { threshold: 1e3, suffix: 'K' }, // Migliaia
736
- ];
737
- // Itera dalla scala più grande alla più piccola per trovare la soglia corretta
738
- for (const { threshold, suffix } of scales) {
739
- if (Math.abs(value) >= threshold) {
740
- const reduced = value / threshold;
741
- // parseFloat(toFixed()) rimuove gli zeri decimali superflui (es: 1.50 -> 1.5)
742
- // aggiungendo poi il relativo suffisso (K, M, B, T)
743
- return Number.parseFloat(reduced.toFixed(decimalDigits)).toString() + suffix;
744
- }
745
- }
746
- // Fallback: se il numero è grande ma non rientra nelle scale (caso raro con la logica attuale)
747
- return value.toFixed(decimalDigits);
748
- }
394
+ function updateHasError(id, hasError) {
395
+ // Recupera l'ultimo stato (Map)
396
+ const oldMap = UniHttpManager.currentValue;
397
+ // Controllo: se non è presente l'item allora termina
398
+ const oldItem = oldMap.get(id);
399
+ if (!oldItem)
400
+ return;
401
+ // Controllo: se con lo stesso valore, salta
402
+ if (oldItem.hasError === hasError)
403
+ return;
404
+ // Aggiorna l'oggetto
405
+ const newItemMap = { ...oldItem, hasError };
406
+ // Crea una nuova istanza della Map
407
+ const newMap = new Map(oldMap);
408
+ newMap.set(id, newItemMap);
409
+ // Aggiorna il nuovo stato notificando l'observer
410
+ UniHttpManager.store.next(newMap);
749
411
  }
750
-
751
412
  /**
752
- * Utility per la manipolazione di stringhe
413
+ * Svuota completamente la lista delle refs
753
414
  */
754
- class UniTypeStringManager {
755
- /** Converte una stringa in formato lblPascalCase (es. "user_id" -> "lblUserId") */
756
- static toLabelize(key, prefix = 'lbl') {
757
- // Se vuoto restituisce vuoto
758
- if (!key)
759
- return '';
760
- // Pulizia chiave
761
- const fixedKey = key.trim();
762
- // Unisce il prefisso
763
- return prefix + this.toPascalCase(fixedKey);
764
- }
765
- /** Converte una stringa in PascalCase (es. "user_id" -> "UserId") */
766
- static toPascalCase(key) {
767
- // Se vuoto restituisce vuoto
768
- if (!key)
769
- return '';
770
- // Pulizia chiave
771
- const fixedKey = key.trim();
772
- // Normalizza e pulisce
773
- const parts = this.splitString(fixedKey);
774
- if (parts.length === 0)
775
- return '';
776
- // Converte in PascalCase
777
- const pascalCased = this.toPascalCaseParts(parts);
778
- return pascalCased;
779
- }
780
- /** Converte una stringa in camelCase (es. "user_id" -> "userId") */
781
- static toCamelCase(key) {
782
- // Se vuoto restituisce vuoto
783
- if (!key)
784
- return '';
785
- // Pulizia chiave
786
- const fixedKey = key.trim();
787
- // Normalizza e pulisce
788
- const parts = this.splitString(fixedKey);
789
- if (parts.length === 0)
790
- return '';
791
- // La prima parola resta minuscola, le successive PascalCase
792
- const first = parts[0].toLowerCase();
793
- const rest = parts.slice(1);
794
- return first + this.toPascalCaseParts(rest);
795
- }
796
- /** Capitalizza solo la prima lettera della stringa */
797
- static toCapitalize(key) {
798
- // Se vuoto restituisce vuoto
799
- if (!key)
800
- return '';
801
- // Pulizia chiave
802
- const fixedKey = key.trim();
803
- return fixedKey.charAt(0).toUpperCase() + fixedKey.slice(1);
415
+ function removeAll() {
416
+ // Crea una nuova istanza della Map
417
+ const newMap = new Map();
418
+ // Aggiorna il nuovo stato notificando l'observer
419
+ UniHttpManager.store.next(newMap);
420
+ }
421
+
422
+ async function execute(url, init) {
423
+ try {
424
+ /* Esegue chiamata http */
425
+ const res = await fetch(url, init);
426
+ /* Nessun contenuto cons status 204 */
427
+ if (res.status === 204) {
428
+ return undefined;
429
+ }
430
+ /* Recupero se è un json */
431
+ const contentType = res.headers.get('content-type') ?? '';
432
+ const isJson = contentType.startsWith('application/json');
433
+ /* Errore HTTP (quindi risposta ricevuta ma non ok) */
434
+ if (!res.ok) {
435
+ let errorBody;
436
+ try {
437
+ /* Clona risposta solo se c'è errore, per leggerla senza consumare la originale */
438
+ const resClone = res.clone();
439
+ /* Aggiorna body con struttura errore */
440
+ errorBody = isJson ? await resClone.json() : await resClone.text();
441
+ }
442
+ catch {
443
+ errorBody = await res.text();
444
+ }
445
+ if (isHttpErrorBody(errorBody)) {
446
+ const type = errorBody.exceptionType.includes('HttpRequestException') ? 'be' : 'ice';
447
+ throw new UniHttpError(type, res.status, url, errorBody);
448
+ }
449
+ else {
450
+ const error = new Error(`HTTP ${res.statusText}`);
451
+ throw new UniHttpError('base', res.status, url, {
452
+ exceptionMessage: error.message,
453
+ exceptionType: 'ErrorBase',
454
+ message: error.message,
455
+ stackTrace: error.stack ?? '',
456
+ });
457
+ }
458
+ }
459
+ /* Risposta ok */
460
+ return res;
804
461
  }
805
- /** Sostituisce sotto-stringhe all'interno della stringa */
806
- static toReplace(key, values) {
807
- // Se vuoto restituisce vuoto
808
- if (!key)
809
- return '';
810
- // Pulizia chiave
811
- let fixedKey = key.trim();
812
- for (const value of values) {
813
- fixedKey = fixedKey.replaceAll(value.searchValue, value.replaceValue);
462
+ catch (error) {
463
+ if (error instanceof TypeError) {
464
+ throw new UniHttpError('network', -1, url, {
465
+ exceptionMessage: `${error.name}: ${error.message}\n${error.stack}`,
466
+ exceptionType: error.name,
467
+ message: error.message,
468
+ stackTrace: error.stack ?? '',
469
+ });
814
470
  }
815
- return fixedKey;
471
+ // Fallback
472
+ throw error;
816
473
  }
817
- /* ----------------- Utils ----------------- */
818
- static splitString(key) {
819
- return (key
820
- // Pulisce eventuali caratteri di separazione rimasti in testa (es. "_tab_name" -> "tab_name")
821
- .replace(/^[-_\s]+/, '')
822
- // Isola il CamelCase inserendo un underscore tra minuscole e maiuscole (es. "userId" -> "user_Id")
823
- .replaceAll(/([a-z])([A-Z])/g, '$1_$2')
824
- // Isola gli acronimi attaccati a parole Normali (es. "VARAna" -> "VAR_Ana" grazie alla minuscola "na")
825
- .replaceAll(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
826
- // Applica il taglio definitivo usando come riferimento i trattini, gli underscore e gli spazi
827
- .split(/[-_\s]+/)
828
- // Rimuove dall'array finale eventuali stringhe vuote generate da separatori consecutivi
829
- .filter(Boolean));
474
+ }
475
+ async function executeHttp(url, init) {
476
+ /* Esegue la chiamata HTTP e restituisce il corpo decodificato come JSON */
477
+ const res = await execute(url, init);
478
+ if (!res)
479
+ return undefined;
480
+ /* Estrae il contenuto come testo */
481
+ const text = await res.text();
482
+ /* Controllo: se il testo è vuoto (''), ritorna undefined */
483
+ if (!text || text.trim().length === 0) {
484
+ return undefined;
830
485
  }
831
- static toPascalCaseParts(parts) {
832
- return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join('');
486
+ /* Parsa manualmente il testo, dato che lo stream è stato già letto */
487
+ return JSON.parse(text);
488
+ }
489
+ async function executeBlob(url, init) {
490
+ /* Esegue la chiamata HTTP */
491
+ const res = await execute(url, init);
492
+ if (!res)
493
+ return undefined;
494
+ /* Estrae il contenuto come Blob direttamente */
495
+ const blob = await res.blob();
496
+ /* Controllo: se il blob è vuoto (0 byte), ritorna undefined */
497
+ if (blob.size === 0) {
498
+ return undefined;
499
+ }
500
+ /* Estrae il nome del file dall'header */
501
+ const disposition = res.headers.get('Content-Disposition');
502
+ let fileName = 'download.pdf'; // Fallback di default
503
+ if (disposition) {
504
+ const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(disposition);
505
+ if (!!matches && matches[1]) {
506
+ fileName = matches[1].replaceAll(/['"]/g, '');
507
+ }
833
508
  }
509
+ /* Genera l'URL temporaneo dal blob */
510
+ const fileUrl = URL.createObjectURL(blob);
511
+ return { url: fileUrl, name: fileName };
834
512
  }
835
513
 
836
514
  /**
@@ -859,14 +537,14 @@ function getUrl(hostname, port, config) {
859
537
  let formattedValue;
860
538
  // Caso: Date nativo
861
539
  if (item instanceof Date) {
862
- formattedValue = UniTypeDateManager.toYYYYMMDD(item);
540
+ formattedValue = UniTypeDateManager$1.toYYYYMMDD(item);
863
541
  }
864
542
  // Caso: Date formato ISO string
865
543
  else if (typeof item === 'string' && isoDateRegex.test(item)) {
866
544
  const parsedDate = new Date(item);
867
545
  formattedValue = Number.isNaN(parsedDate.getTime())
868
546
  ? item
869
- : UniTypeDateManager.toYYYYMMDD(parsedDate);
547
+ : UniTypeDateManager$1.toYYYYMMDD(parsedDate);
870
548
  }
871
549
  // Default
872
550
  else {
@@ -916,7 +594,10 @@ function normalizeHttpBody(body, isRoot = true) {
916
594
  return Object.fromEntries(entries);
917
595
  }
918
596
 
919
- const CONFIG_TOAST_DEFAULT = { type: 'success', label: 'OperationCompleted' };
597
+ const CONFIG_TOAST_DEFAULT = {
598
+ type: 'success',
599
+ label: 'OperationCompleted',
600
+ };
920
601
  class UniHttpManager {
921
602
  /* ------------------------------------------------------------------------------- */
922
603
  /* --------------------------------- Metodi: get --------------------------------- */
@@ -947,12 +628,6 @@ class UniHttpManager {
947
628
  this.hostname = hostname;
948
629
  this.port = port;
949
630
  }
950
- /**
951
- * Imposta o resetta lo stato di errore per una specifica reference.
952
- */
953
- static updateHasError(id, hasError) {
954
- updateHasError(id, hasError);
955
- }
956
631
  /* ------------------------------------------------------------------------------- */
957
632
  /* -------------------------------- Metodi: CRUD --------------------------------- */
958
633
  /* ------------------------------------------------------------------------------- */
@@ -970,7 +645,7 @@ class UniHttpManager {
970
645
  const url = getUrl(this.hostname, this.port, config);
971
646
  return http$(url, 'one', config, () => executeHttp(url, initCustom)).pipe(tap((res) => {
972
647
  if (Array.isArray(res) && config.toast === undefined) {
973
- UniToastManager.show({
648
+ UniToastManager$1.show({
974
649
  label: 'ItemsFound',
975
650
  params: { count: res.length },
976
651
  });
@@ -1043,97 +718,447 @@ class UniHttpManager {
1043
718
  return http$(url, 'one', configCustom, () => executeHttp(url, initCustom));
1044
719
  }
1045
720
  /* ------------------------------------------------------------------------------- */
1046
- /* -------------------------- Metodi: CRUD file/image ---------------------------- */
721
+ /* -------------------------- Metodi: CRUD file/image ---------------------------- */
722
+ /* ------------------------------------------------------------------------------- */
723
+ /**
724
+ * Recupera un'immagine tramite una richiesta GET e la trasforma in un formato gestibile (es. Blob o Base64).
725
+ * Delega la logica di conversione alla funzione executeImage.
726
+ */
727
+ static readImage$(config) {
728
+ /* Config */
729
+ const initCustom = {
730
+ ...config.init,
731
+ method: 'GET',
732
+ };
733
+ /* Chiama API */
734
+ const url = getUrl(this.hostname, this.port, config);
735
+ return http$(url, 'one', config, () => executeBlob(url, initCustom));
736
+ }
737
+ /**
738
+ * Recupera un file (es. PDF) tramite una richiesta GET e restituisce un Object URL temporaneo.
739
+ * Delega la logica di conversione alla funzione executeFile.
740
+ */
741
+ static readFile$(config) {
742
+ /* Config */
743
+ const initCustom = {
744
+ ...config.init,
745
+ method: 'GET',
746
+ };
747
+ /* Toast di default */
748
+ const configCustom = {
749
+ ...config,
750
+ toast: config.toast === null ? undefined : (config.toast ?? CONFIG_TOAST_DEFAULT),
751
+ };
752
+ /* Chiama API */
753
+ const url = getUrl(this.hostname, this.port, config);
754
+ return http$(url, 'file', configCustom, () => executeBlob(url, initCustom));
755
+ }
756
+ /* ------------------------------------------------------------------------------- */
757
+ /* ---------------------------- Metodi: CRUD polling ----------------------------- */
758
+ /* ------------------------------------------------------------------------------- */
759
+ /**
760
+ * Avvia un ciclo di polling basato su richieste GET.
761
+ * Continua a emettere valori in base alla configurazione di intervallo definita in HttpConfigPolling.
762
+ */
763
+ static readPolling$(config) {
764
+ /* Config */
765
+ const initCustom = {
766
+ ...config.init,
767
+ method: 'GET',
768
+ };
769
+ /* Chiama API */
770
+ const url = getUrl(this.hostname, this.port, config);
771
+ return httpPolling$(url, config, () => executeHttp(url, initCustom));
772
+ }
773
+ /**
774
+ * Avvia un ciclo di polling basato su richieste POST.
775
+ * Invia il body specificato a ogni iterazione del ciclo.
776
+ */
777
+ static createPolling$(config, body) {
778
+ /* Config */
779
+ const initCustom = {
780
+ ...config.init,
781
+ method: 'POST',
782
+ headers: { 'Content-Type': 'application/json' },
783
+ body: JSON.stringify(body),
784
+ };
785
+ /* Chiama API */
786
+ const url = getUrl(this.hostname, this.port, config);
787
+ return httpPolling$(url, config, () => executeHttp(url, initCustom));
788
+ }
789
+ /**
790
+ * Avvia un ciclo di polling basato su richieste PUT.
791
+ */
792
+ static updatePolling$(config, body) {
793
+ /* Config */
794
+ const initCustom = {
795
+ ...config.init,
796
+ method: 'PUT',
797
+ };
798
+ if (body !== undefined) {
799
+ initCustom.headers = {
800
+ ...initCustom.headers,
801
+ 'Content-Type': 'application/json',
802
+ };
803
+ initCustom.body = JSON.stringify(body);
804
+ }
805
+ /* Chiama API */
806
+ const url = getUrl(this.hostname, this.port, config);
807
+ return httpPolling$(url, config, () => executeHttp(url, initCustom));
808
+ }
809
+ }
810
+
811
+ class UniLocaleManager {
812
+ /* ------------------------------------------------------------------------------- */
813
+ /* ----------------------------------- Config ------------------------------------ */
814
+ /* ------------------------------------------------------------------------------- */
815
+ /** Codice lingua corrente (es. 'it-IT', 'en-US') usato per formattare date e numeri */
816
+ static { this._locale = navigator.language ?? 'en-US'; }
817
+ /** Elenco dei codici lingua supportati dall'applicazione (es. ['it-IT', 'en-US']) */
818
+ static { this._localesSupported = []; }
819
+ /** Prefisso globale applicato a tutte le chiavi di traduzione (es. 'APP_') */
820
+ static { this._prefix = undefined; }
821
+ /* ------------------------------------------------------------------------------- */
822
+ /* --------------------------------- Metodi: get --------------------------------- */
823
+ /* ------------------------------------------------------------------------------- */
824
+ /** Restituisce il codice lingua corrente */
825
+ static get locale() {
826
+ return this._locale;
827
+ }
828
+ /** Restituisce l'array contenente tutti i codici locale supportati.*/
829
+ static get localesSupported() {
830
+ return this._localesSupported;
831
+ }
832
+ /** Restituisce solo la lingua (es. 'it') */
833
+ static get language() {
834
+ return this._locale.split('-')[0];
835
+ }
836
+ /** Restituisce solo il paese (es. 'IT') */
837
+ static get region() {
838
+ const parts = this._locale.split('-');
839
+ return parts.length > 1 ? parts[1] : '';
840
+ }
841
+ /* ------------------------------------------------------------------------------- */
842
+ /* ------------------------------------ Store ------------------------------------ */
843
+ /* ------------------------------------------------------------------------------- */
844
+ /** Store privato (Subject) */
845
+ static { this.store = new BehaviorSubject({}); }
846
+ /** Store pubblico (Observable) */
847
+ static { this.store$ = this.store.asObservable(); }
848
+ /** Ottiene il dizionario attuale senza sottoscrizione */
849
+ static get currentValue() {
850
+ return this.store.getValue();
851
+ }
852
+ /* ------------------------------------------------------------------------------- */
853
+ /* -------------------------------- Metodi: setup -------------------------------- */
854
+ /* ------------------------------------------------------------------------------- */
855
+ /**
856
+ * Inizializza la configurazione di rete del manager.
857
+ * Deve essere chiamato prima di effettuare qualsiasi richiesta HTTP.
858
+ */
859
+ static setup(locale, prefix) {
860
+ this._locale = locale ?? 'en-US';
861
+ this._prefix = prefix;
862
+ }
863
+ /** Imposta l'elenco dei codici lingua supportati dall'applicazione */
864
+ static setLocalesSupported(locales) {
865
+ this._localesSupported = locales ?? [];
866
+ }
867
+ /**
868
+ * Aggiorna lo store locale con il dizionario delle traduzioni fornito.
869
+ * Se viene passato undefined, lo store viene inizializzato come oggetto vuoto.
870
+ */
871
+ static setTranslations(translations) {
872
+ this.store.next(translations ?? {});
873
+ }
874
+ /* ------------------------------------------------------------------------------- */
875
+ /* ----------------------------- Metodi: traduzioni ------------------------------ */
876
+ /* ------------------------------------------------------------------------------- */
877
+ /**
878
+ * Traduce una label in base al dizionario caricato.
879
+ * Gestisce la composizione della chiave, i parametri dinamici (interpolazione) e il fallback.
880
+ */
881
+ static translate(key, prefix = 'lbl', interpolationParams) {
882
+ if (!key)
883
+ return '-';
884
+ // Costruzione chiave: prefissoGlobale + prefissoLocale + LabelConInizialeMaiuscola
885
+ const keyParts = key.trim().split(/(?=[A-Z])/);
886
+ const rootKeyParts = keyParts.filter((p) => p.toLowerCase() !== prefix.toLowerCase());
887
+ const cleanKey = rootKeyParts.length > 0 ? rootKeyParts.join('') : undefined;
888
+ const capitalizedKey = cleanKey ? cleanKey.charAt(0).toUpperCase() + cleanKey.slice(1) : '';
889
+ const finalKey = `${this._prefix ?? ''}${prefix}${capitalizedKey}`;
890
+ // Cerca la chiave in minuscolo (standardizzazione)
891
+ let translation = this.currentValue?.[finalKey.toLowerCase()];
892
+ // Log e fallback se la traduzione manca
893
+ if (!translation) {
894
+ console.warn(`Translation missing for key: ${finalKey}`);
895
+ return `🔑 ${finalKey}`;
896
+ }
897
+ // Interpolazione variabili
898
+ if (interpolationParams) {
899
+ for (const [key, value] of Object.entries(interpolationParams)) {
900
+ const displayValue = value instanceof Date ? value.toLocaleDateString(this._locale) : String(value);
901
+ translation = translation.replaceAll(`{{${key}}}`, displayValue);
902
+ }
903
+ }
904
+ return translation;
905
+ }
906
+ /**
907
+ * Traduce una stringa che contiene parametri separati da un carattere specifico.
908
+ * Supporta ora un numero arbitrario di parametri in formato "label/param1/param2"
909
+ * o semplicemente "label".
910
+ */
911
+ static translateInlineParams(keyWithParams, splitChar) {
912
+ if (!keyWithParams)
913
+ return '-';
914
+ const [label, ...params] = keyWithParams.split(splitChar);
915
+ // Se non ci sono parametri, esegui una traduzione semplice
916
+ if (params.length === 0) {
917
+ return this.translate(label);
918
+ }
919
+ // Mappa i parametri in un oggetto di interpolazione: { inlineParam0: val, inlineParam1: val, ... }
920
+ const interpolationParameters = {};
921
+ for (const [index, val] of params.entries()) {
922
+ interpolationParameters[`param${index}`] = val;
923
+ }
924
+ return this.translate(label, 'lbl', interpolationParameters);
925
+ }
926
+ /* ------------------------------------------------------------------------------- */
927
+ /* ------------------------------- Metodi: numeri -------------------------------- */
928
+ /* ------------------------------------------------------------------------------- */
929
+ /**
930
+ * Converte un valore numerico in una stringa formattata secondo il locale impostato.
931
+ * Disabilita i separatori delle migliaia e forza un numero fisso di decimali.
932
+ */
933
+ static toStringNumber(value, decimal) {
934
+ return new Intl.NumberFormat(this._locale, {
935
+ useGrouping: true,
936
+ minimumFractionDigits: decimal,
937
+ maximumFractionDigits: decimal,
938
+ numberingSystem: 'latn',
939
+ }).format(value);
940
+ }
941
+ /* ------------------------------------------------------------------------------- */
942
+ /* -------------------------------- Metodi: date --------------------------------- */
943
+ /* ------------------------------------------------------------------------------- */
944
+ /**
945
+ * Formatta una data o una stringa in base al locale corrente.
946
+ * Gestisce tre modalità predefinite (date, time, full) e accetta opzioni personalizzate Intl.
947
+ */
948
+ static toDate(date, mode = 'full', force) {
949
+ const dt = new Date(date);
950
+ // Controllo: validità data
951
+ if (Number.isNaN(dt.getTime())) {
952
+ console.error(`[UniLocaleManager] Data non valida fornita a formatDateTime:`, date);
953
+ return '';
954
+ }
955
+ // Controllo: locale da forzare
956
+ const locale = force && this._locale.startsWith(force.oldLang) ? force.newLocale : this._locale;
957
+ switch (mode) {
958
+ case 'date': {
959
+ return dt.toLocaleDateString(locale);
960
+ }
961
+ case 'time': {
962
+ return dt.toLocaleTimeString(locale);
963
+ }
964
+ case 'full': {
965
+ return dt.toLocaleString(locale);
966
+ }
967
+ }
968
+ }
969
+ }
970
+
971
+ /* eslint-disable @typescript-eslint/no-explicit-any */
972
+ class UniToastManager {
973
+ /* ------------------------------------------------------------------------------- */
974
+ /* -------------------------------- Metodi: setup -------------------------------- */
1047
975
  /* ------------------------------------------------------------------------------- */
1048
976
  /**
1049
- * Recupera un'immagine tramite una richiesta GET e la trasforma in un formato gestibile (es. Blob o Base64).
1050
- * Delega la logica di conversione alla funzione executeImage.
1051
- */
1052
- static readImage$(config) {
1053
- /* Config */
1054
- const initCustom = {
1055
- ...config.init,
1056
- method: 'GET',
1057
- };
1058
- /* Chiama API */
1059
- const url = getUrl(this.hostname, this.port, config);
1060
- return http$(url, 'one', config, () => executeBlob(url, initCustom));
1061
- }
1062
- /**
1063
- * Recupera un file (es. PDF) tramite una richiesta GET e restituisce un Object URL temporaneo.
1064
- * Delega la logica di conversione alla funzione executeFile.
977
+ * Inizializza il manager con una serie di operazioni
1065
978
  */
1066
- static readFile$(config) {
1067
- /* Config */
1068
- const initCustom = {
1069
- ...config.init,
1070
- method: 'GET',
1071
- };
1072
- /* Toast di default */
1073
- const configCustom = {
1074
- ...config,
1075
- toast: config.toast === null ? undefined : (config.toast ?? CONFIG_TOAST_DEFAULT),
1076
- };
1077
- /* Chiama API */
1078
- const url = getUrl(this.hostname, this.port, config);
1079
- return http$(url, 'file', configCustom, () => executeBlob(url, initCustom));
979
+ static setup(operations) {
980
+ this.manager = operations;
1080
981
  }
1081
982
  /* ------------------------------------------------------------------------------- */
1082
- /* ---------------------------- Metodi: CRUD polling ----------------------------- */
983
+ /* -------------------------------- Metodi: show --------------------------------- */
1083
984
  /* ------------------------------------------------------------------------------- */
1084
985
  /**
1085
- * Avvia un ciclo di polling basato su richieste GET.
1086
- * Continua a emettere valori in base alla configurazione di intervallo definita in HttpConfigPolling.
986
+ * Mostra un toast di successo basato su una risposta HTTP e una label di traduzione.
1087
987
  */
1088
- static readPolling$(config) {
1089
- /* Config */
1090
- const initCustom = {
1091
- ...config.init,
1092
- method: 'GET',
1093
- };
1094
- /* Chiama API */
1095
- const url = getUrl(this.hostname, this.port, config);
1096
- return httpPolling$(url, config, () => executeHttp(url, initCustom));
988
+ static show(config) {
989
+ /* Messaggio tradotto */
990
+ const msg = UniLocaleManager$1.translate(config.label, 'toast', config.params);
991
+ const msgFixed = `${config.prefix ?? ''}${msg}${config.suffix ?? ''}`;
992
+ this.manager[config.type ?? 'info'](msgFixed, config);
1097
993
  }
1098
994
  /**
1099
- * Avvia un ciclo di polling basato su richieste POST.
1100
- * Invia il body specificato a ogni iterazione del ciclo.
995
+ * Mostra un toast di successo basato su una risposta HTTP e una label di traduzione.
1101
996
  */
1102
- static createPolling$(config, body) {
1103
- /* Config */
1104
- const initCustom = {
1105
- ...config.init,
1106
- method: 'POST',
1107
- headers: { 'Content-Type': 'application/json' },
1108
- body: JSON.stringify(body),
1109
- };
1110
- /* Chiama API */
1111
- const url = getUrl(this.hostname, this.port, config);
1112
- return httpPolling$(url, config, () => executeHttp(url, initCustom));
997
+ static showHttp(config, res) {
998
+ const { interpolationParams, resParams, resLengthParam, formatters } = config;
999
+ /* Parametri statici di base */
1000
+ const allParams = { ...interpolationParams };
1001
+ /* Config parametri risposta: estrazione parametri dalla risposta (se esiste ed è un oggetto) */
1002
+ if (resParams?.length && res && typeof res === 'object') {
1003
+ for (const param of resParams ?? []) {
1004
+ if (param in res)
1005
+ continue;
1006
+ // Se esiste un formatter per questa chiave lo si usa, altrimenti valore grezzo
1007
+ const rawValue = res[param];
1008
+ allParams[param] = formatters?.[param] ? formatters[param](rawValue) : rawValue;
1009
+ }
1010
+ }
1011
+ /* Config parametro lunghezza risposta: conteggio array */
1012
+ if (resLengthParam && Array.isArray(res)) {
1013
+ allParams[resLengthParam] = res.length;
1014
+ }
1015
+ /* Messaggio tradotto */
1016
+ this.show({ ...config, params: allParams });
1113
1017
  }
1114
- /**
1115
- * Avvia un ciclo di polling basato su richieste PUT.
1116
- */
1117
- static updatePolling$(config, body) {
1118
- /* Config */
1119
- const initCustom = {
1120
- ...config.init,
1121
- method: 'PUT',
1122
- };
1123
- if (body !== undefined) {
1124
- initCustom.headers = {
1125
- ...initCustom.headers,
1126
- 'Content-Type': 'application/json',
1127
- };
1128
- initCustom.body = JSON.stringify(body);
1018
+ }
1019
+
1020
+ /**
1021
+ * Classe di utilità per la gestione e formattazione delle date.
1022
+ * Fornisce metodi per convertire in modo sicuro valori di tipo Date, stringa o null in formati standardizzati.
1023
+ */
1024
+ class UniTypeDateManager {
1025
+ static toYYYYMMDD(date) {
1026
+ if (!date)
1027
+ return '';
1028
+ const d = new Date(date);
1029
+ if (Number.isNaN(d.getTime()))
1030
+ return '';
1031
+ const year = d.getFullYear();
1032
+ const month = String(d.getMonth() + 1).padStart(2, '0');
1033
+ const day = String(d.getDate()).padStart(2, '0');
1034
+ return `${year}-${month}-${day}`;
1035
+ }
1036
+ }
1037
+
1038
+ /**
1039
+ * Utility per la gestione e formattazione di valori numerici.
1040
+ */
1041
+ class UniTypeNumberManager {
1042
+ /** Applica il padding a un numero o una stringa */
1043
+ static toPad(value, count, character = '0') {
1044
+ // Se il valore è null/undefined, riempiamo l'intero campo con il carattere scelto
1045
+ if (value === null || value === undefined) {
1046
+ return ''.padStart(count, character);
1129
1047
  }
1130
- /* Chiama API */
1131
- const url = getUrl(this.hostname, this.port, config);
1132
- return httpPolling$(url, config, () => executeHttp(url, initCustom));
1048
+ // Convertiamo in stringa e applichiamo il padding
1049
+ return value.toString().padStart(count, character);
1050
+ }
1051
+ /** Formatta un numero in una versione leggibile (es: 1000 -> 1K, 1000000 -> 1M) */
1052
+ static toTruncateAndAdUdm(value, decimalDigits = 0, maxIntegerDigits = 3) {
1053
+ // Nessun limite impostato o numero inferiore alla soglia definita
1054
+ if (Math.abs(value) < Math.pow(10, maxIntegerDigits)) {
1055
+ return value.toFixed(decimalDigits);
1056
+ }
1057
+ // Definizione delle scale di riduzione per grandi numeri
1058
+ const scales = [
1059
+ { threshold: 1e12, suffix: 'T' }, // Trilioni
1060
+ { threshold: 1e9, suffix: 'B' }, // Miliardi
1061
+ { threshold: 1e6, suffix: 'M' }, // Milioni
1062
+ { threshold: 1e3, suffix: 'K' }, // Migliaia
1063
+ ];
1064
+ // Itera dalla scala più grande alla più piccola per trovare la soglia corretta
1065
+ for (const { threshold, suffix } of scales) {
1066
+ if (Math.abs(value) >= threshold) {
1067
+ const reduced = value / threshold;
1068
+ // parseFloat(toFixed()) rimuove gli zeri decimali superflui (es: 1.50 -> 1.5)
1069
+ // aggiungendo poi il relativo suffisso (K, M, B, T)
1070
+ return Number.parseFloat(reduced.toFixed(decimalDigits)).toString() + suffix;
1071
+ }
1072
+ }
1073
+ // Fallback: se il numero è grande ma non rientra nelle scale (caso raro con la logica attuale)
1074
+ return value.toFixed(decimalDigits);
1133
1075
  }
1134
1076
  }
1135
1077
 
1136
- /* eslint-disable @typescript-eslint/no-explicit-any */
1078
+ /**
1079
+ * Utility per la manipolazione di stringhe
1080
+ */
1081
+ class UniTypeStringManager {
1082
+ /** Converte una stringa in formato lblPascalCase (es. "user_id" -> "lblUserId") */
1083
+ static toLabelize(key, prefix = 'lbl') {
1084
+ // Se vuoto restituisce vuoto
1085
+ if (!key)
1086
+ return '';
1087
+ // Pulizia chiave
1088
+ const fixedKey = key.trim();
1089
+ // Unisce il prefisso
1090
+ return prefix + this.toPascalCase(fixedKey);
1091
+ }
1092
+ /** Converte una stringa in PascalCase (es. "user_id" -> "UserId") */
1093
+ static toPascalCase(key) {
1094
+ // Se vuoto restituisce vuoto
1095
+ if (!key)
1096
+ return '';
1097
+ // Pulizia chiave
1098
+ const fixedKey = key.trim();
1099
+ // Normalizza e pulisce
1100
+ const parts = this.splitString(fixedKey);
1101
+ if (parts.length === 0)
1102
+ return '';
1103
+ // Converte in PascalCase
1104
+ const pascalCased = this.toPascalCaseParts(parts);
1105
+ return pascalCased;
1106
+ }
1107
+ /** Converte una stringa in camelCase (es. "user_id" -> "userId") */
1108
+ static toCamelCase(key) {
1109
+ // Se vuoto restituisce vuoto
1110
+ if (!key)
1111
+ return '';
1112
+ // Pulizia chiave
1113
+ const fixedKey = key.trim();
1114
+ // Normalizza e pulisce
1115
+ const parts = this.splitString(fixedKey);
1116
+ if (parts.length === 0)
1117
+ return '';
1118
+ // La prima parola resta minuscola, le successive PascalCase
1119
+ const first = parts[0].toLowerCase();
1120
+ const rest = parts.slice(1);
1121
+ return first + this.toPascalCaseParts(rest);
1122
+ }
1123
+ /** Capitalizza solo la prima lettera della stringa */
1124
+ static toCapitalize(key) {
1125
+ // Se vuoto restituisce vuoto
1126
+ if (!key)
1127
+ return '';
1128
+ // Pulizia chiave
1129
+ const fixedKey = key.trim();
1130
+ return fixedKey.charAt(0).toUpperCase() + fixedKey.slice(1);
1131
+ }
1132
+ /** Sostituisce sotto-stringhe all'interno della stringa */
1133
+ static toReplace(key, values) {
1134
+ // Se vuoto restituisce vuoto
1135
+ if (!key)
1136
+ return '';
1137
+ // Pulizia chiave
1138
+ let fixedKey = key.trim();
1139
+ for (const value of values) {
1140
+ fixedKey = fixedKey.replaceAll(value.searchValue, value.replaceValue);
1141
+ }
1142
+ return fixedKey;
1143
+ }
1144
+ /* ----------------- Utils ----------------- */
1145
+ static splitString(key) {
1146
+ return (key
1147
+ // Pulisce eventuali caratteri di separazione rimasti in testa (es. "_tab_name" -> "tab_name")
1148
+ .replace(/^[-_\s]+/, '')
1149
+ // Isola il CamelCase inserendo un underscore tra minuscole e maiuscole (es. "userId" -> "user_Id")
1150
+ .replaceAll(/([a-z])([A-Z])/g, '$1_$2')
1151
+ // Isola gli acronimi attaccati a parole Normali (es. "VARAna" -> "VAR_Ana" grazie alla minuscola "na")
1152
+ .replaceAll(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
1153
+ // Applica il taglio definitivo usando come riferimento i trattini, gli underscore e gli spazi
1154
+ .split(/[-_\s]+/)
1155
+ // Rimuove dall'array finale eventuali stringhe vuote generate da separatori consecutivi
1156
+ .filter(Boolean));
1157
+ }
1158
+ static toPascalCaseParts(parts) {
1159
+ return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join('');
1160
+ }
1161
+ }
1137
1162
 
1138
1163
  /*
1139
1164
  * Public API Surface of uni-manager