@topconsultnpm/sdkui-react 6.22.0-dev3.15 → 6.22.0-dev3.17

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.
@@ -434,229 +434,229 @@ export default TMDataGrid;
434
434
  // (padding, altezza minima, font e dimensione dei pulsanti di pagina).
435
435
  // Con compactPager = false non emette nessuna regola: width/height restano inline come in precedenza,
436
436
  // così il rendering è identico a quello del div originale.
437
- const StyledDataGridContainer = styled.div `
438
- ${({ $compactPager }) => $compactPager && css `
439
- & .dx-datagrid-pager {
440
- padding: 2px 4px !important;
441
- min-height: 0 !important;
442
- font-size: 11px;
443
- }
444
-
445
- & .dx-datagrid-pager .dx-page,
446
- & .dx-datagrid-pager .dx-page-size,
447
- & .dx-datagrid-pager .dx-selection,
448
- & .dx-datagrid-pager .dx-navigate-button {
449
- min-width: 18px !important;
450
- min-height: 18px !important;
451
- height: auto !important;
452
- padding: 1px 5px !important;
453
- line-height: 16px !important;
454
- font-size: 11px;
455
- }
456
-
457
- & .dx-datagrid-pager .dx-pages,
458
- & .dx-datagrid-pager .dx-page-sizes,
459
- & .dx-datagrid-pager .dx-info,
460
- & .dx-datagrid-pager .dx-page-index,
461
- & .dx-datagrid-pager .dx-pages .dx-info {
462
- font-size: 11px;
463
- margin-top: 0 !important;
464
- margin-bottom: 0 !important;
465
- }
466
-
467
- & .dx-datagrid-pager .dx-info {
468
- margin-left: 4px !important;
469
- margin-right: 4px !important;
470
- }
471
-
472
- /* Editor del numero di pagina (displayMode compact) */
473
- & .dx-datagrid-pager .dx-page-index .dx-texteditor-input,
474
- & .dx-datagrid-pager .dx-page-sizes .dx-texteditor-input {
475
- min-height: 18px !important;
476
- height: 18px !important;
477
- padding: 0 4px !important;
478
- font-size: 11px;
479
- }
480
- `}
437
+ const StyledDataGridContainer = styled.div `
438
+ ${({ $compactPager }) => $compactPager && css `
439
+ & .dx-datagrid-pager {
440
+ padding: 2px 4px !important;
441
+ min-height: 0 !important;
442
+ font-size: 11px;
443
+ }
444
+
445
+ & .dx-datagrid-pager .dx-page,
446
+ & .dx-datagrid-pager .dx-page-size,
447
+ & .dx-datagrid-pager .dx-selection,
448
+ & .dx-datagrid-pager .dx-navigate-button {
449
+ min-width: 18px !important;
450
+ min-height: 18px !important;
451
+ height: auto !important;
452
+ padding: 1px 5px !important;
453
+ line-height: 16px !important;
454
+ font-size: 11px;
455
+ }
456
+
457
+ & .dx-datagrid-pager .dx-pages,
458
+ & .dx-datagrid-pager .dx-page-sizes,
459
+ & .dx-datagrid-pager .dx-info,
460
+ & .dx-datagrid-pager .dx-page-index,
461
+ & .dx-datagrid-pager .dx-pages .dx-info {
462
+ font-size: 11px;
463
+ margin-top: 0 !important;
464
+ margin-bottom: 0 !important;
465
+ }
466
+
467
+ & .dx-datagrid-pager .dx-info {
468
+ margin-left: 4px !important;
469
+ margin-right: 4px !important;
470
+ }
471
+
472
+ /* Editor del numero di pagina (displayMode compact) */
473
+ & .dx-datagrid-pager .dx-page-index .dx-texteditor-input,
474
+ & .dx-datagrid-pager .dx-page-sizes .dx-texteditor-input {
475
+ min-height: 18px !important;
476
+ height: 18px !important;
477
+ padding: 0 4px !important;
478
+ font-size: 11px;
479
+ }
480
+ `}
481
481
  `;
482
482
  // Stile del pannello del filtro d'intestazione (ricerca, elenco valori, OK/Annulla).
483
483
  // Il popup è in portale sul body, fuori da StyledDataGridContainer: le regole sono globali
484
484
  // ma tutte sotto .dx-header-filter-menu, classe che DevExtreme applica solo a questo popup.
485
- const HeaderFilterMenuGlobalStyle = createGlobalStyle `
486
- .dx-header-filter-menu {
487
-
488
- &.dx-popup-wrapper > .dx-overlay-content.dx-popup-normal {
489
- border: 1px solid #E4E8EE;
490
- border-radius: 8px;
491
- box-shadow: 0 2px 6px rgba(18, 44, 76, .08), 0 12px 28px rgba(18, 44, 76, .14);
492
- overflow: hidden;
493
- }
494
-
495
- &.dx-popup-wrapper .dx-popup-content {
496
- padding: 10px 10px 4px;
497
- }
498
-
499
- /* Campo di ricerca: TextBox mode "search" resa da List o da TreeView (filtri gerarchici) */
500
- .dx-searchbox.dx-list-search,
501
- .dx-searchbox.dx-treeview-search {
502
- margin-bottom: 8px;
503
- border-radius: 6px;
504
- border: 1px solid #CDD2D8;
505
- background-color: #F4F6F9;
506
- box-shadow: none;
507
- transition: border-color .15s ease, background-color .15s ease, box-shadow .15s ease;
508
-
509
- /* fluent disegna qui la barretta di focus, sostituita da bordo + alone */
510
- &::before,
511
- &::after {
512
- display: none;
513
- }
514
-
515
- &.dx-state-hover:not(.dx-state-focused) {
516
- border-color: #AEB6C1;
517
- background-color: #EEF2F7;
518
- }
519
-
520
- &.dx-state-focused {
521
- border-color: #2559A5;
522
- background-color: #FFFFFF;
523
- box-shadow: 0 0 0 2px rgba(37, 89, 165, .16);
524
- }
525
-
526
- .dx-icon-search {
527
- width: 28px;
528
- color: #8A929B;
529
- transition: color .15s ease;
530
- }
531
-
532
- &.dx-state-focused .dx-icon-search {
533
- color: #2559A5;
534
- }
535
-
536
- .dx-texteditor-input {
537
- min-height: 28px;
538
- padding-inline-start: 28px;
539
- padding-inline-end: 8px;
540
- font-size: 12px;
541
- background-color: transparent;
542
- }
543
-
544
- .dx-placeholder {
545
- color: #9AA3AD;
546
- font-size: 12px;
547
-
548
- &::before {
549
- padding-inline-start: 28px;
550
- padding-inline-end: 8px;
551
- }
552
- }
553
-
554
- .dx-clear-button-area .dx-icon-clear {
555
- color: #8A929B;
556
- transition: color .15s ease;
557
- }
558
-
559
- .dx-clear-button-area:hover .dx-icon-clear {
560
- color: #2559A5;
561
- }
562
- }
563
-
564
- .dx-list-item {
565
- border-radius: 4px;
566
- transition: background-color .12s ease;
567
- }
568
-
569
- .dx-list-item-content {
570
- padding: 4px 6px;
571
- }
572
-
573
- .dx-list-item.dx-state-hover,
574
- .dx-list-item.dx-state-focused {
575
- background-color: #F1F6FD;
576
- color: #122C4C;
577
- }
578
-
579
- .dx-list-select-all {
580
- padding: 2px 0 6px;
581
- margin-bottom: 4px;
582
- border-bottom: 1px solid #ECEFF3;
583
- }
584
-
585
- .dx-list-select-all-label {
586
- font-weight: 600;
587
- color: #122C4C;
588
- }
589
-
590
- .dx-checkbox-checked .dx-checkbox-icon,
591
- .dx-checkbox-indeterminate .dx-checkbox-icon {
592
- border-color: #2559A5;
593
- background-color: #2559A5;
594
- }
595
-
596
- .dx-checkbox.dx-state-hover:not(.dx-checkbox-checked) .dx-checkbox-icon {
597
- border-color: #2559A5;
598
- }
599
-
600
- /* il :not() lascia intatto il fade-out nativo della scrollbar */
601
- .dx-scrollable-scroll:not(.dx-state-invisible) .dx-scrollable-scroll-content {
602
- background-color: rgba(18, 44, 76, .28);
603
- border-radius: 3px;
604
- }
605
-
606
- &.dx-popup-wrapper .dx-popup-bottom.dx-toolbar {
607
- padding: 8px 10px 10px;
608
- border-top: 1px solid #ECEFF3;
609
- }
610
-
611
- .dx-popup-bottom .dx-button {
612
- border-radius: 5px;
613
- min-width: 76px;
614
- margin: 0;
615
- }
616
-
617
- .dx-popup-bottom .dx-toolbar-button + .dx-toolbar-button {
618
- margin-inline-start: 6px;
619
-
620
- .dx-button {
621
- margin: 0;
622
- }
623
- }
624
-
625
- /* DevExtreme aggiunge prima OK e poi Annulla: individuarli per posizione li rende indipendenti dalla lingua */
626
- .dx-popup-bottom .dx-toolbar-after > .dx-toolbar-button:first-child .dx-button {
627
- border-color: transparent;
628
- background-color: #2559A5;
629
-
630
- .dx-button-text {
631
- color: #FFFFFF;
632
- }
633
-
634
- &.dx-state-hover {
635
- background-color: #1E4A8C;
636
- }
637
-
638
- &.dx-state-active {
639
- background-color: #1A3F78;
640
- }
641
- }
642
-
643
- .dx-popup-bottom .dx-toolbar-after > .dx-toolbar-button + .dx-toolbar-button .dx-button {
644
- border-color: transparent;
645
- background-color: transparent;
646
-
647
- .dx-button-text {
648
- color: #44506B;
649
- }
650
-
651
- &.dx-state-hover {
652
- background-color: #F0F3F7;
653
- }
654
-
655
- &.dx-state-active {
656
- background-color: #E6EBF2;
657
- }
658
- }
659
- }
485
+ const HeaderFilterMenuGlobalStyle = createGlobalStyle `
486
+ .dx-header-filter-menu {
487
+
488
+ &.dx-popup-wrapper > .dx-overlay-content.dx-popup-normal {
489
+ border: 1px solid #E4E8EE;
490
+ border-radius: 8px;
491
+ box-shadow: 0 2px 6px rgba(18, 44, 76, .08), 0 12px 28px rgba(18, 44, 76, .14);
492
+ overflow: hidden;
493
+ }
494
+
495
+ &.dx-popup-wrapper .dx-popup-content {
496
+ padding: 10px 10px 4px;
497
+ }
498
+
499
+ /* Campo di ricerca: TextBox mode "search" resa da List o da TreeView (filtri gerarchici) */
500
+ .dx-searchbox.dx-list-search,
501
+ .dx-searchbox.dx-treeview-search {
502
+ margin-bottom: 8px;
503
+ border-radius: 6px;
504
+ border: 1px solid #CDD2D8;
505
+ background-color: #F4F6F9;
506
+ box-shadow: none;
507
+ transition: border-color .15s ease, background-color .15s ease, box-shadow .15s ease;
508
+
509
+ /* fluent disegna qui la barretta di focus, sostituita da bordo + alone */
510
+ &::before,
511
+ &::after {
512
+ display: none;
513
+ }
514
+
515
+ &.dx-state-hover:not(.dx-state-focused) {
516
+ border-color: #AEB6C1;
517
+ background-color: #EEF2F7;
518
+ }
519
+
520
+ &.dx-state-focused {
521
+ border-color: #2559A5;
522
+ background-color: #FFFFFF;
523
+ box-shadow: 0 0 0 2px rgba(37, 89, 165, .16);
524
+ }
525
+
526
+ .dx-icon-search {
527
+ width: 28px;
528
+ color: #8A929B;
529
+ transition: color .15s ease;
530
+ }
531
+
532
+ &.dx-state-focused .dx-icon-search {
533
+ color: #2559A5;
534
+ }
535
+
536
+ .dx-texteditor-input {
537
+ min-height: 28px;
538
+ padding-inline-start: 28px;
539
+ padding-inline-end: 8px;
540
+ font-size: 12px;
541
+ background-color: transparent;
542
+ }
543
+
544
+ .dx-placeholder {
545
+ color: #9AA3AD;
546
+ font-size: 12px;
547
+
548
+ &::before {
549
+ padding-inline-start: 28px;
550
+ padding-inline-end: 8px;
551
+ }
552
+ }
553
+
554
+ .dx-clear-button-area .dx-icon-clear {
555
+ color: #8A929B;
556
+ transition: color .15s ease;
557
+ }
558
+
559
+ .dx-clear-button-area:hover .dx-icon-clear {
560
+ color: #2559A5;
561
+ }
562
+ }
563
+
564
+ .dx-list-item {
565
+ border-radius: 4px;
566
+ transition: background-color .12s ease;
567
+ }
568
+
569
+ .dx-list-item-content {
570
+ padding: 4px 6px;
571
+ }
572
+
573
+ .dx-list-item.dx-state-hover,
574
+ .dx-list-item.dx-state-focused {
575
+ background-color: #F1F6FD;
576
+ color: #122C4C;
577
+ }
578
+
579
+ .dx-list-select-all {
580
+ padding: 2px 0 6px;
581
+ margin-bottom: 4px;
582
+ border-bottom: 1px solid #ECEFF3;
583
+ }
584
+
585
+ .dx-list-select-all-label {
586
+ font-weight: 600;
587
+ color: #122C4C;
588
+ }
589
+
590
+ .dx-checkbox-checked .dx-checkbox-icon,
591
+ .dx-checkbox-indeterminate .dx-checkbox-icon {
592
+ border-color: #2559A5;
593
+ background-color: #2559A5;
594
+ }
595
+
596
+ .dx-checkbox.dx-state-hover:not(.dx-checkbox-checked) .dx-checkbox-icon {
597
+ border-color: #2559A5;
598
+ }
599
+
600
+ /* il :not() lascia intatto il fade-out nativo della scrollbar */
601
+ .dx-scrollable-scroll:not(.dx-state-invisible) .dx-scrollable-scroll-content {
602
+ background-color: rgba(18, 44, 76, .28);
603
+ border-radius: 3px;
604
+ }
605
+
606
+ &.dx-popup-wrapper .dx-popup-bottom.dx-toolbar {
607
+ padding: 8px 10px 10px;
608
+ border-top: 1px solid #ECEFF3;
609
+ }
610
+
611
+ .dx-popup-bottom .dx-button {
612
+ border-radius: 5px;
613
+ min-width: 76px;
614
+ margin: 0;
615
+ }
616
+
617
+ .dx-popup-bottom .dx-toolbar-button + .dx-toolbar-button {
618
+ margin-inline-start: 6px;
619
+
620
+ .dx-button {
621
+ margin: 0;
622
+ }
623
+ }
624
+
625
+ /* DevExtreme aggiunge prima OK e poi Annulla: individuarli per posizione li rende indipendenti dalla lingua */
626
+ .dx-popup-bottom .dx-toolbar-after > .dx-toolbar-button:first-child .dx-button {
627
+ border-color: transparent;
628
+ background-color: #2559A5;
629
+
630
+ .dx-button-text {
631
+ color: #FFFFFF;
632
+ }
633
+
634
+ &.dx-state-hover {
635
+ background-color: #1E4A8C;
636
+ }
637
+
638
+ &.dx-state-active {
639
+ background-color: #1A3F78;
640
+ }
641
+ }
642
+
643
+ .dx-popup-bottom .dx-toolbar-after > .dx-toolbar-button + .dx-toolbar-button .dx-button {
644
+ border-color: transparent;
645
+ background-color: transparent;
646
+
647
+ .dx-button-text {
648
+ color: #44506B;
649
+ }
650
+
651
+ &.dx-state-hover {
652
+ background-color: #F0F3F7;
653
+ }
654
+
655
+ &.dx-state-active {
656
+ background-color: #E6EBF2;
657
+ }
658
+ }
659
+ }
660
660
  `;
661
661
  const getRecordCount = (dataSource) => {
662
662
  if (Array.isArray(dataSource)) {
@@ -22,12 +22,17 @@ const TMArchive = ({ onDcmtTypeSelect = undefined, inputTID, inputFile = null, c
22
22
  const pendingMidsRef = React.useRef(null);
23
23
  const deviceType = useDeviceType();
24
24
  useEffect(() => { setMruTIDs(SDKUI_Globals.userSettings.archivingSettings.mruTIDs); }, []);
25
+ // Applica inputTID solo quando cambia davvero: non deve sovrascrivere il tipo documento
26
+ // selezionato dall'utente.
25
27
  useEffect(() => {
26
28
  if (!inputTID)
27
29
  return;
28
30
  setCurrentTID(inputTID);
29
- setCurrentMruTID(mruTIDs.includes(inputTID) ? inputTID : 0);
30
- }, [inputTID, mruTIDs]);
31
+ }, [inputTID]);
32
+ // Evidenzia la scorciatoia del tipo documento corrente, anche quando mruTIDs cambia.
33
+ useEffect(() => {
34
+ setCurrentMruTID(currentTID && mruTIDs.includes(currentTID) ? currentTID : 0);
35
+ }, [currentTID, mruTIDs]);
31
36
  useEffect(() => {
32
37
  pendingMidsRef.current = inputMids;
33
38
  if (currentTID && currentTID > 0 && inputMids && inputMids.length > 0) {
@@ -20,12 +20,6 @@ import ShowAlert from '../../base/TMAlert';
20
20
  import { TMColors } from '../../../utils/theme';
21
21
  import TMDownloadRelationViewerSection from './TMDownloadRelationViewerSection';
22
22
  import { buildInitialDownloadSettings, fileExists, generateTargetFileName, generateUniqueFileName, getDcmtInfosToDownload, getFloatingLabelStyle, isDirectoryPickerSupported, } from './copyAndMergeDcmtsShared';
23
- /**
24
- * Numero di documenti scaricati in parallelo.
25
- * I file vengono consumati via callback (scrittura su cartella o raccolta per lo ZIP),
26
- * quindi il parallelismo è sicuro; l'ordine finale è garantito a valle del download.
27
- */
28
- const COPY_DOWNLOAD_CONCURRENCY = 5;
29
23
  /**
30
24
  * Form per la copia/zip dei documenti in una cartella locale.
31
25
  * Logica condivisa con TMMergeToPdfForm è esternalizzata in copyAndMergeDcmtsShared
@@ -117,23 +111,9 @@ const TMCopyToFolderForm = ({ mode, selectedDcmtInfos, onClose, showTMRelationVi
117
111
  const handleSeparatorChange = (e) => {
118
112
  updateSettings('separatorChar', e.target.value.slice(0, 1));
119
113
  };
120
- // ---- Serializzazione delle scritture su filesystem ----
121
- // I download sono paralleli, le scritture no: fileExists/generateUniqueFileName/
122
- // getFileHandle sono check-then-act sulla stessa cartella e due scritture contemporanee
123
- // potrebbero sovrascriversi o calcolare lo stesso nome "univoco".
124
- const writeChainRef = useRef(Promise.resolve());
125
- const runExclusive = (action) => {
126
- const result = writeChainRef.current.then(action);
127
- // La catena non deve interrompersi se una scrittura fallisce: l'errore viene
128
- // comunque propagato al chiamante tramite il promise restituito.
129
- writeChainRef.current = result.catch(() => undefined);
130
- return result;
131
- };
132
114
  // ---- Scrittura file nel filesystem locale ----
133
115
  const writeFileToFolder = async (file, dcmtInfo) => {
134
116
  try {
135
- // Fuori dal lock: con la denominazione da metadati questa fa una chiamata al
136
- // server per documento e deve poter andare in parallelo agli altri download.
137
117
  const baseFileName = await generateTargetFileName(file, dcmtInfo, {
138
118
  fileNamingMode: settings.fileNamingMode,
139
119
  separatorChar: settings.separatorChar,
@@ -152,29 +132,25 @@ const TMCopyToFolderForm = ({ mode, selectedDcmtInfos, onClose, showTMRelationVi
152
132
  URL.revokeObjectURL(url);
153
133
  return;
154
134
  }
155
- // Dentro il lock: controllo esistenza, calcolo del nome univoco e scrittura
156
- // devono restare un'unica operazione atomica sulla cartella.
157
135
  const folderHandle = folderHandleRef.current;
158
- await runExclusive(async () => {
159
- let targetFileName = baseFileName;
160
- const exists = await fileExists(folderHandle, targetFileName);
161
- if (exists) {
162
- switch (settings.fileExistsMode) {
163
- case 'skip':
164
- return;
165
- case 'rename':
166
- targetFileName = await generateUniqueFileName(folderHandle, targetFileName);
167
- break;
168
- case 'overwrite':
169
- default:
170
- break;
171
- }
136
+ let targetFileName = baseFileName;
137
+ const exists = await fileExists(folderHandle, targetFileName);
138
+ if (exists) {
139
+ switch (settings.fileExistsMode) {
140
+ case 'skip':
141
+ return;
142
+ case 'rename':
143
+ targetFileName = await generateUniqueFileName(folderHandle, targetFileName);
144
+ break;
145
+ case 'overwrite':
146
+ default:
147
+ break;
172
148
  }
173
- const fileHandle = await folderHandle.getFileHandle(targetFileName, { create: true });
174
- const writable = await fileHandle.createWritable();
175
- await writable.write(file);
176
- await writable.close();
177
- });
149
+ }
150
+ const fileHandle = await folderHandle.getFileHandle(targetFileName, { create: true });
151
+ const writable = await fileHandle.createWritable();
152
+ await writable.write(file);
153
+ await writable.close();
178
154
  }
179
155
  catch (err) {
180
156
  console.error('Errore nella scrittura del file:', err);
@@ -202,10 +178,7 @@ const TMCopyToFolderForm = ({ mode, selectedDcmtInfos, onClose, showTMRelationVi
202
178
  retrieveOptions.invoiceRetrieveFormat = settings.invoiceFormat ?? SDKUI_Globals.userSettings.searchSettings.invoiceRetrieveFormat;
203
179
  retrieveOptions.orderRetrieveFormat = settings.orderFormat ?? SDKUI_Globals.userSettings.searchSettings.orderRetrieveFormat;
204
180
  if (settings.exportMode === 'copy') {
205
- // Senza handle della cartella ogni file viene salvato con un click su <a download>:
206
- // in quel caso si resta sequenziali, perché il browser blocca i click ravvicinati.
207
- const copyConcurrency = folderHandleRef.current ? COPY_DOWNLOAD_CONCURRENCY : 1;
208
- await downloadDcmtsAsync({ inputDcmts: dcmtInfosToDownload, downloadType: DownloadTypes.Dcmt, downloadMode: 'download', onFileDownloaded: writeFileToFolder, skipConfirmation: true, retrieveOptions, useCache: false, downloadConcurrency: copyConcurrency });
181
+ await downloadDcmtsAsync({ inputDcmts: dcmtInfosToDownload, downloadType: DownloadTypes.Dcmt, downloadMode: 'download', onFileDownloaded: writeFileToFolder, skipConfirmation: true, retrieveOptions, useCache: false });
209
182
  }
210
183
  else if (settings.exportMode === 'zip') {
211
184
  const fileNameCounts = new Map();
@@ -220,9 +193,8 @@ const TMCopyToFolderForm = ({ mode, selectedDcmtInfos, onClose, showTMRelationVi
220
193
  const ext = dotIndex > 0 ? fileName.substring(dotIndex) : '';
221
194
  return `${baseName} (${count})${ext}`;
222
195
  };
223
- // Con i download paralleli i file arrivano in ordine di completamento: si raccolgono
224
- // in mappa (nome incluso, calcolato in parallelo) e l'archivio viene costruito dopo
225
- // nell'ordine di selezione, così i suffissi di deduplica restano quelli sequenziali.
196
+ // I file scaricati si raccolgono in mappa (nome incluso) e l'archivio viene costruito
197
+ // dopo, nell'ordine di selezione, così i suffissi di deduplica restano coerenti.
226
198
  const downloadedEntries = new Map();
227
199
  const collectFileForZip = async (file, dcmtInfo) => {
228
200
  const targetFileName = await generateTargetFileName(file, dcmtInfo, {
@@ -231,7 +203,7 @@ const TMCopyToFolderForm = ({ mode, selectedDcmtInfos, onClose, showTMRelationVi
231
203
  });
232
204
  downloadedEntries.set(`${dcmtInfo.TID}_${dcmtInfo.DID}`, { file, targetFileName });
233
205
  };
234
- await downloadDcmtsAsync({ inputDcmts: dcmtInfosToDownload, downloadType: DownloadTypes.Dcmt, downloadMode: 'download', onFileDownloaded: collectFileForZip, skipConfirmation: true, retrieveOptions, useCache: false, downloadConcurrency: COPY_DOWNLOAD_CONCURRENCY });
206
+ await downloadDcmtsAsync({ inputDcmts: dcmtInfosToDownload, downloadType: DownloadTypes.Dcmt, downloadMode: 'download', onFileDownloaded: collectFileForZip, skipConfirmation: true, retrieveOptions, useCache: false });
235
207
  const zipEntries = [];
236
208
  for (const dcmtInfo of dcmtInfosToDownload) {
237
209
  const entry = downloadedEntries.get(`${dcmtInfo.TID}_${dcmtInfo.DID}`);
@@ -18,12 +18,6 @@ import ShowAlert from '../../base/TMAlert';
18
18
  import TMTooltip from '../../base/TMTooltip';
19
19
  import MergePdfManager from '../../../helper/MergePdfManager';
20
20
  import { TMResultManager } from '../../forms/TMResultDialog';
21
- /**
22
- * Numero di documenti scaricati in parallelo durante l'unione.
23
- * I file vengono consumati via callback (nessun click su <a download>), quindi il
24
- * parallelismo è sicuro; il riordino finale è garantito da pdfFilesMap.
25
- */
26
- const MERGE_DOWNLOAD_CONCURRENCY = 5;
27
21
  /**
28
22
  * Converte il formato di visualizzazione fatture impostato dall'utente nel corrispondente formato PDF:
29
23
  * il foglio di stile SDI resta SDI, in tutti gli altri casi si usa ASW.
@@ -206,7 +200,7 @@ const TMMergeToPdfForm = (props) => {
206
200
  const collectFileForMerge = async (file, dcmtInfo) => {
207
201
  pdfFilesMap.set(`${dcmtInfo.TID}_${dcmtInfo.DID}`, file);
208
202
  };
209
- await downloadDcmtsAsync({ inputDcmts: dcmtsWithFile, downloadType: DownloadTypes.Dcmt, downloadMode: 'download', onFileDownloaded: collectFileForMerge, skipConfirmation: true, retrieveOptions: rfo, useCache: false, downloadConcurrency: MERGE_DOWNLOAD_CONCURRENCY });
203
+ await downloadDcmtsAsync({ inputDcmts: dcmtsWithFile, downloadType: DownloadTypes.Dcmt, downloadMode: 'download', onFileDownloaded: collectFileForMerge, skipConfirmation: true, retrieveOptions: rfo, useCache: false });
210
204
  }
211
205
  // Ricostruisci l'array pdfFiles nell'ordine originale di selezione
212
206
  const pdfFiles = [];
@@ -1518,9 +1518,9 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1518
1518
  return _jsxs("div", { style: { width: "100%", height: "100%" }, children: [!isDataGridReady && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', width: '100%', gap: '10px' }, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsx("div", { children: SDKUI_Localizator.Loading })] })), isDataGridReady && _jsx(TMDataGrid, { ref: dataGridRef, id: "tm-search-result", keyExpr: "rowIndex", dataColumns: dataColumns, dataSource: dataSource, customizeColumns: customizeColumns, repaintChangesOnly: true, selectedRowKeys: selectedRowKeys, focusedRowKey: disableAutoFocus ? undefined : Number(focusedItem?.rowIndex ?? 0), showSearchPanel: showSearchTMDatagrid, showFilterPanel: true, sorting: gridSorting, selection: gridSelection, scrolling: gridScrolling, pageSize: pageSize, compactPager: compactPager, onSelectionChanged: handleSelectionChange, onFocusedRowChanged: handleFocusedRowChange, onRowDblClick: onRowDblClick, onContentReady: onContentReady, showHeaderColumnChooser: true, onKeyDown: onKeyDown, customContextMenuItems: operationItems, counterConfig: gridCounterConfig })] });
1519
1519
  };
1520
1520
  //#region TMSearchResultSelector
1521
- const StyledItemTemplate = styled.div `
1522
- background: ${(props) => props.$isSelected ? 'oklch(from var(--dx-color-primary) l c h / .2) !important' : 'transparent'};
1523
- cursor: pointer;
1521
+ const StyledItemTemplate = styled.div `
1522
+ background: ${(props) => props.$isSelected ? 'oklch(from var(--dx-color-primary) l c h / .2) !important' : 'transparent'};
1523
+ cursor: pointer;
1524
1524
  `;
1525
1525
  const MemoizedStyledItemTemplate = React.memo(StyledItemTemplate);
1526
1526
  const TMSearchResultSelector = ({ searchResults = [], disableAccordionIfSingleCategory = false, selectedTID, selectedSearchResult, autoSelectFirst = true, onSelectionChanged }) => {
@@ -1727,85 +1727,85 @@ const TMDcmtPreviewWrapper = ({ refreshPreviewTrigger, currentDcmt, isVisible, i
1727
1727
  return (_jsx(TMDcmtPreview, { dcmtData: currentDcmt, onClosePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('tmDcmtPreview', false) : undefined, onBack: onBack, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onMaximizePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => toggleMaximize("tmDcmtPreview") : undefined, isResizingActive: isResizingActive, isVisible: isVisible && isPageVisible }, refreshPreviewTrigger));
1728
1728
  };
1729
1729
  // Styled Components
1730
- const StyledPlaceholder = styled.div `
1731
- padding: 20px;
1732
- text-align: center;
1733
- color: #888;
1730
+ const StyledPlaceholder = styled.div `
1731
+ padding: 20px;
1732
+ text-align: center;
1733
+ color: #888;
1734
1734
  `;
1735
- const StyledIndexingInfoSection = styled.div `
1736
- padding: 15px;
1737
- border-top: 1px solid #e0e0e0;
1738
- background: linear-gradient(to bottom, #f9f9f9, #f5f5f5);
1739
- display: flex;
1740
- flex-direction: column;
1741
- gap: 10px;
1735
+ const StyledIndexingInfoSection = styled.div `
1736
+ padding: 15px;
1737
+ border-top: 1px solid #e0e0e0;
1738
+ background: linear-gradient(to bottom, #f9f9f9, #f5f5f5);
1739
+ display: flex;
1740
+ flex-direction: column;
1741
+ gap: 10px;
1742
1742
  `;
1743
- const StyledIndexingToggle = styled.button `
1744
- display: flex;
1745
- align-items: center;
1746
- justify-content: space-between;
1747
- width: 100%;
1748
- padding: 10px 16px;
1749
- background: white;
1750
- border: 1px solid #d0d0d0;
1751
- border-radius: 6px;
1752
- cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
1753
- transition: all 0.2s ease;
1754
- font-size: 14px;
1755
- font-weight: 500;
1756
- color: #333;
1757
- opacity: ${props => props.disabled ? 0.6 : 1};
1758
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
1759
-
1760
- &:hover:not(:disabled) {
1761
- background: #f8f8f8;
1762
- border-color: #2196F3;
1763
- box-shadow: 0 2px 4px rgba(33, 150, 243, 0.2);
1764
- }
1765
-
1766
- &:active:not(:disabled) {
1767
- transform: translateY(1px);
1768
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
1769
- }
1743
+ const StyledIndexingToggle = styled.button `
1744
+ display: flex;
1745
+ align-items: center;
1746
+ justify-content: space-between;
1747
+ width: 100%;
1748
+ padding: 10px 16px;
1749
+ background: white;
1750
+ border: 1px solid #d0d0d0;
1751
+ border-radius: 6px;
1752
+ cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
1753
+ transition: all 0.2s ease;
1754
+ font-size: 14px;
1755
+ font-weight: 500;
1756
+ color: #333;
1757
+ opacity: ${props => props.disabled ? 0.6 : 1};
1758
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
1759
+
1760
+ &:hover:not(:disabled) {
1761
+ background: #f8f8f8;
1762
+ border-color: #2196F3;
1763
+ box-shadow: 0 2px 4px rgba(33, 150, 243, 0.2);
1764
+ }
1765
+
1766
+ &:active:not(:disabled) {
1767
+ transform: translateY(1px);
1768
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
1769
+ }
1770
1770
  `;
1771
- const StyledLeftContent = styled.div `
1772
- display: flex;
1773
- align-items: center;
1774
- gap: 8px;
1771
+ const StyledLeftContent = styled.div `
1772
+ display: flex;
1773
+ align-items: center;
1774
+ gap: 8px;
1775
1775
  `;
1776
- const StyledRightContent = styled.div `
1777
- display: flex;
1778
- align-items: center;
1779
- gap: 8px;
1776
+ const StyledRightContent = styled.div `
1777
+ display: flex;
1778
+ align-items: center;
1779
+ gap: 8px;
1780
1780
  `;
1781
- const StyledChevron = styled.span `
1782
- transition: transform 0.2s ease;
1783
- transform: ${props => props.$isOpen ? 'rotate(180deg)' : 'rotate(0deg)'};
1784
- color: #666;
1785
- font-size: 12px;
1781
+ const StyledChevron = styled.span `
1782
+ transition: transform 0.2s ease;
1783
+ transform: ${props => props.$isOpen ? 'rotate(180deg)' : 'rotate(0deg)'};
1784
+ color: #666;
1785
+ font-size: 12px;
1786
1786
  `;
1787
- const StyledIndexingInfoBox = styled.div `
1788
- position: relative;
1789
- background: white;
1790
- border: 1px solid #e0e0e0;
1791
- border-radius: 6px;
1792
- padding: 12px;
1793
- max-height: 200px;
1794
- overflow: auto;
1795
- font-family: 'Courier New', monospace;
1796
- font-size: 12px;
1797
- color: #333;
1798
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1799
- animation: slideDown 0.2s ease;
1800
-
1801
- @keyframes slideDown {
1802
- from {
1803
- opacity: 0;
1804
- transform: translateY(-10px);
1805
- }
1806
- to {
1807
- opacity: 1;
1808
- transform: translateY(0);
1809
- }
1810
- }
1787
+ const StyledIndexingInfoBox = styled.div `
1788
+ position: relative;
1789
+ background: white;
1790
+ border: 1px solid #e0e0e0;
1791
+ border-radius: 6px;
1792
+ padding: 12px;
1793
+ max-height: 200px;
1794
+ overflow: auto;
1795
+ font-family: 'Courier New', monospace;
1796
+ font-size: 12px;
1797
+ color: #333;
1798
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1799
+ animation: slideDown 0.2s ease;
1800
+
1801
+ @keyframes slideDown {
1802
+ from {
1803
+ opacity: 0;
1804
+ transform: translateY(-10px);
1805
+ }
1806
+ to {
1807
+ opacity: 1;
1808
+ transform: translateY(0);
1809
+ }
1810
+ }
1811
1811
  `;
@@ -49,6 +49,11 @@ const TMTreeSelector = ({ layoutMode = LayoutModes.Update, isVisible, onSelected
49
49
  const loadTreesAsync = useCallback(async (forceRefresh = false) => {
50
50
  const isSearchContext = layoutMode == LayoutModes.Update;
51
51
  if (forceRefresh) {
52
+ // Gli alberi arrivano dal server filtrati sui tipi documento della sessione: senza rileggerli
53
+ // prima (RetrieveAll con refreshCache rigenera anche la cache lato server) un tipo documento
54
+ // appena creato da Designer non comparirebbe tra gli elementi dell'albero
55
+ DcmtTypeListCacheService.RemoveAllWithoutMetadata();
56
+ await DcmtTypeListCacheService.GetAllWithoutMetadataAsync();
52
57
  if (isSearchContext)
53
58
  TreeCacheService.RemoveAllSearch();
54
59
  else
@@ -25,7 +25,7 @@ const TMResultDialog = ({ title, result, id1Caption, id2Caption }) => {
25
25
  TMExceptionBoxManager.show({ exception: e.data.description });
26
26
  }
27
27
  };
28
- return (_jsx(TMModal, { height: '65%', width: '75%', title: `${SDKUI_Localizator.OperationResult} - ${title}`, children: _jsxs(TMLayoutContainer, { children: [_jsxs("div", { style: { display: 'flex', flexDirection: 'row', gap: '5px', padding: '5px', justifyContent: 'center', alignItems: 'center' }, children: [_jsx("p", { children: `${SDKUI_Localizator.ProcessedItems}: ${result.length}` }), _jsx(IconSuccessCirlce, { fontSize: 22, color: TMColors.success }), _jsx("p", { children: result.filter(o => o.resultType == ResultTypes.SUCCESS).length ?? 0 }), _jsx(IconWarning, { fontSize: 22, color: TMColors.warning }), _jsx("p", { children: result.filter(o => o.resultType == ResultTypes.WARNING).length ?? 0 }), _jsx(IconCloseCircle, { fontSize: 22, color: TMColors.error }), _jsx("p", { children: result.filter(o => o.resultType == ResultTypes.ERROR).length ?? 0 })] }), _jsxs(DataGrid, { dataSource: result.filter(o => o.resultType != ResultTypes.SUCCESS), showColumnLines: Number(SDKUI_Globals.userSettings?.themeSettings.gridSettings.showRowLines) === 1, showRowLines: Number(SDKUI_Globals.userSettings?.themeSettings.gridSettings.showColumnLines) === 1, keyExpr: "rowIndex", width: "100%", height: "100%", columnResizingMode: "widget", allowColumnResizing: true, focusedRowEnabled: true, hoverStateEnabled: true, rowAlternationEnabled: true, onRowDblClick: handleRowDblClick, children: [_jsx(Column, { dataField: 'resultType', caption: '', width: "5%", allowResizing: true, allowSorting: true, cellRender: cellRender }), _jsx(Column, { dataField: 'id1', caption: id1Caption, width: "10%" }), id2Caption && _jsx(Column, { dataField: 'id2', caption: id2Caption, width: "10%" }), _jsx(Column, { dataField: 'description', allowResizing: true, caption: SDKUI_Localizator.Description, allowSorting: true, width: id2Caption ? "75%" : "85%" }), _jsx(HeaderFilter, { visible: true }), _jsx(Paging, { enabled: true, pageSize: 30 }), _jsx(Pager, { visible: true, showInfo: true, showNavigationButtons: true }), _jsx(Scrolling, { mode: 'standard', useNative: Number(SDKUI_Globals.userSettings?.themeSettings.gridSettings.useNativeScrollbar) === 1 })] })] }) }));
28
+ return (_jsx(TMModal, { height: '65%', width: '75%', title: `${SDKUI_Localizator.OperationResult} - ${title}`, children: _jsxs(TMLayoutContainer, { children: [_jsxs("div", { style: { display: 'flex', flexDirection: 'row', gap: '5px', padding: '5px', justifyContent: 'center', alignItems: 'center', flexShrink: 0 }, children: [_jsx("p", { children: `${SDKUI_Localizator.ProcessedItems}: ${result.length}` }), _jsx(IconSuccessCirlce, { fontSize: 22, color: TMColors.success }), _jsx("p", { children: result.filter(o => o.resultType == ResultTypes.SUCCESS).length ?? 0 }), _jsx(IconWarning, { fontSize: 22, color: TMColors.warning }), _jsx("p", { children: result.filter(o => o.resultType == ResultTypes.WARNING).length ?? 0 }), _jsx(IconCloseCircle, { fontSize: 22, color: TMColors.error }), _jsx("p", { children: result.filter(o => o.resultType == ResultTypes.ERROR).length ?? 0 })] }), _jsx("div", { style: { flex: '1 1 auto', minHeight: 0, width: '100%', overflow: 'hidden' }, children: _jsxs(DataGrid, { dataSource: result.filter(o => o.resultType != ResultTypes.SUCCESS), showColumnLines: Number(SDKUI_Globals.userSettings?.themeSettings.gridSettings.showRowLines) === 1, showRowLines: Number(SDKUI_Globals.userSettings?.themeSettings.gridSettings.showColumnLines) === 1, keyExpr: "rowIndex", width: "100%", height: "100%", columnResizingMode: "widget", allowColumnResizing: true, focusedRowEnabled: true, hoverStateEnabled: true, rowAlternationEnabled: true, onRowDblClick: handleRowDblClick, children: [_jsx(Column, { dataField: 'resultType', caption: '', width: "5%", allowResizing: true, allowSorting: true, cellRender: cellRender }), _jsx(Column, { dataField: 'id1', caption: id1Caption, width: "10%" }), id2Caption && _jsx(Column, { dataField: 'id2', caption: id2Caption, width: "10%" }), _jsx(Column, { dataField: 'description', allowResizing: true, caption: SDKUI_Localizator.Description, allowSorting: true, width: id2Caption ? "75%" : "85%" }), _jsx(HeaderFilter, { visible: true }), _jsx(Paging, { enabled: true, pageSize: 30 }), _jsx(Pager, { visible: true, showInfo: true, showNavigationButtons: true }), _jsx(Scrolling, { mode: 'standard', useNative: Number(SDKUI_Globals.userSettings?.themeSettings.gridSettings.useNativeScrollbar) === 1 })] }) })] }) }));
29
29
  };
30
30
  export default TMResultDialog;
31
31
  export class TMResultManager {
@@ -24,13 +24,6 @@ export interface DownloadDcmtsAsyncParams {
24
24
  * - false (default): usa il nome file recuperato dal backend (file.name) come priorità
25
25
  */
26
26
  useInputFileName?: boolean;
27
- /**
28
- * Numero di download eseguiti in parallelo (default 1 = sequenziale, comportamento preesistente).
29
- * Valori > 1 vanno usati solo quando il chiamante consuma i file via
30
- * `onFileDownloaded`: nel percorso senza callback ogni file genera un click su un
31
- * tag <a download>, e più click ravvicinati vengono bloccati dal browser.
32
- */
33
- downloadConcurrency?: number;
34
27
  }
35
28
  export interface UseDcmtOperationsReturn {
36
29
  abortController: AbortController;
@@ -9,8 +9,6 @@ import { importCertificatesAsync } from '../helper/certificateImportHelper';
9
9
  import { isXMLFileExt } from '../helper/dcmtsHelper';
10
10
  import { ShowConfirm } from '../components/base/TMConfirm';
11
11
  import { generateTargetFileName } from '../components/features/documents/copyAndMergeDcmtsShared';
12
- /** Limite massimo di download paralleli accettato da `downloadDcmtsAsync`. */
13
- const MAX_DOWNLOAD_CONCURRENCY = 8;
14
12
  /** Intervallo minimo (ms) fra due aggiornamenti della progress bar di download. */
15
13
  const DOWNLOAD_PROGRESS_THROTTLE_MS = 100;
16
14
  const isScannerLicenseConfigured = () => {
@@ -261,7 +259,7 @@ export const useDcmtOperations = () => {
261
259
  ShowAlert({ message: SDKUI_Localizator.ZipCreatedSavedInFolder.replaceParams(zipFileName, "Zip creato con successo"), mode: 'success', duration: 5000, title: SDKUI_Localizator.CompressToZipFile });
262
260
  }
263
261
  };
264
- const _downloadDcmtsAsync = async (inputDcmts, downloadMode = "download", onFileDownloaded, skipConfirmation = false, retrieveOptions, useCache = true, showSuccessAlert = true, useInputFileName = false, downloadConcurrency = 1) => {
262
+ const _downloadDcmtsAsync = async (inputDcmts, downloadMode = "download", onFileDownloaded, skipConfirmation = false, retrieveOptions, useCache = true, showSuccessAlert = true, useInputFileName = false) => {
265
263
  if (inputDcmts === undefined)
266
264
  return;
267
265
  if (inputDcmts.length <= 0)
@@ -290,29 +288,22 @@ export const useDcmtOperations = () => {
290
288
  setShowSecondary(true);
291
289
  setWaitPanelTitle(operationTitle);
292
290
  abortController = new AbortController();
293
- // Riferimento const: il narrowing su inputDcmts non sopravvive dentro le closure del pool.
291
+ // Riferimento const: il narrowing su inputDcmts non sopravvive dentro le closure.
294
292
  const dcmts = inputDcmts;
295
- /**
296
- * Ogni richiesta usa la propria istanza di RetrieveFileOptions: con il pool più
297
- * download sono in volo contemporaneamente e non possono condividere lo stesso oggetto.
298
- */
299
- const buildRetrieveFileOptions = (dcmt) => {
300
- const rfo = new RetrieveFileOptions();
301
- rfo.retrieveReason = retrieveOptions?.retrieveReason ?? DcmtOpers.ShowFile;
302
- if (retrieveOptions?.cvtFormat !== undefined)
303
- rfo.cvtFormat = retrieveOptions.cvtFormat;
304
- if (retrieveOptions?.generalRetrieveFormat !== undefined)
305
- rfo.generalRetrieveFormat = retrieveOptions.generalRetrieveFormat;
306
- rfo.invoiceRetrieveFormat = retrieveOptions?.invoiceRetrieveFormat ?? SDKUI_Globals.userSettings.searchSettings.invoiceRetrieveFormat;
307
- rfo.orderRetrieveFormat = retrieveOptions?.orderRetrieveFormat ?? SDKUI_Globals.userSettings.searchSettings.orderRetrieveFormat;
308
- return rfo;
309
- };
310
- const results = new Array(dcmts.length);
293
+ const rfo = new RetrieveFileOptions();
294
+ rfo.retrieveReason = retrieveOptions?.retrieveReason ?? DcmtOpers.ShowFile;
295
+ if (retrieveOptions?.cvtFormat !== undefined)
296
+ rfo.cvtFormat = retrieveOptions.cvtFormat;
297
+ if (retrieveOptions?.generalRetrieveFormat !== undefined)
298
+ rfo.generalRetrieveFormat = retrieveOptions.generalRetrieveFormat;
299
+ rfo.invoiceRetrieveFormat = retrieveOptions?.invoiceRetrieveFormat ?? SDKUI_Globals.userSettings.searchSettings.invoiceRetrieveFormat;
300
+ rfo.orderRetrieveFormat = retrieveOptions?.orderRetrieveFormat ?? SDKUI_Globals.userSettings.searchSettings.orderRetrieveFormat;
301
+ const results = [];
311
302
  setWaitPanelMaxValuePrimary(dcmts.length);
312
- // ---- Progress secondaria: aggregata sui download in volo e throttlata ----
303
+ // ---- Progress secondaria: throttlata ----
313
304
  // Gli eventi di avanzamento di axios arrivano a ogni chunk: senza throttle ognuno
314
305
  // provoca un re-render dell'intera form (relation viewer compreso).
315
- const inFlightBytes = new Map();
306
+ let currentBytes;
316
307
  let pendingSecondary;
317
308
  let secondaryTimer;
318
309
  const flushSecondaryProgress = () => {
@@ -325,41 +316,33 @@ export const useDcmtOperations = () => {
325
316
  pendingSecondary = undefined;
326
317
  };
327
318
  const publishSecondaryProgress = () => {
328
- let loaded = 0;
329
- let total = 0;
330
- for (const bytes of inFlightBytes.values()) {
331
- loaded += bytes.loaded;
332
- total += bytes.total;
333
- }
319
+ const loaded = currentBytes?.loaded ?? 0;
320
+ const total = currentBytes?.total ?? 0;
334
321
  pendingSecondary = total > 0
335
322
  ? { value: loaded, max: total, text: `Downloading... ${Globalization.getNumberDisplayValue(loaded, true)} / ${Globalization.getNumberDisplayValue(total, true)}` }
336
323
  : { value: 0, max: 0, text: '' };
337
324
  if (secondaryTimer === undefined)
338
325
  secondaryTimer = setTimeout(flushSecondaryProgress, DOWNLOAD_PROGRESS_THROTTLE_MS);
339
326
  };
340
- // ---- Pool di download ----
341
- let nextIndex = 0;
342
- let started = 0;
327
+ // ---- Download sequenziale: un documento per volta ----
343
328
  let completed = 0;
344
- let abortReported = false;
345
329
  const downloadOne = async (i) => {
346
- started++;
347
- setWaitPanelTextPrimary(`Download file ${started} di ${dcmts.length}`);
330
+ setWaitPanelTextPrimary(`Download file ${i + 1} di ${dcmts.length}`);
348
331
  let file;
349
332
  const cacheKey = `${dcmts[i].TID}-${dcmts[i].DID}`;
350
333
  if (useCache && dcmtsFileCacheDownload.has(cacheKey)) {
351
334
  file = dcmtsFileCacheDownload.get(cacheKey);
352
335
  }
353
336
  else {
354
- inFlightBytes.set(i, { loaded: 0, total: 0 });
337
+ currentBytes = { loaded: 0, total: 0 };
355
338
  try {
356
- file = await SDK_Globals.tmSession?.NewSearchEngine().RetrieveFileAsync(dcmts[i].TID, dcmts[i].DID, buildRetrieveFileOptions(dcmts[i]), abortController.signal, (pd) => {
357
- inFlightBytes.set(i, { loaded: pd.ProgressBarValue, total: pd.ProgressBarMaximum ?? 0 });
339
+ file = await SDK_Globals.tmSession?.NewSearchEngine().RetrieveFileAsync(dcmts[i].TID, dcmts[i].DID, rfo, abortController.signal, (pd) => {
340
+ currentBytes = { loaded: pd.ProgressBarValue, total: pd.ProgressBarMaximum ?? 0 };
358
341
  publishSecondaryProgress();
359
342
  });
360
343
  }
361
344
  finally {
362
- inFlightBytes.delete(i);
345
+ currentBytes = undefined;
363
346
  publishSecondaryProgress();
364
347
  }
365
348
  }
@@ -400,40 +383,26 @@ export const useDcmtOperations = () => {
400
383
  }
401
384
  dcmtsFileCacheDownload.set(cacheKey, file);
402
385
  }
403
- results[i] = { rowIndex: i, id1: dcmts[i].TID, id2: dcmts[i].DID, resultType: ResultTypes.SUCCESS };
386
+ results.push({ rowIndex: i, id1: dcmts[i].TID, id2: dcmts[i].DID, resultType: ResultTypes.SUCCESS });
404
387
  };
405
- const runWorker = async () => {
406
- while (true) {
407
- const i = nextIndex++;
408
- if (i >= dcmts.length)
409
- return;
410
- if (abortController.signal.aborted) {
411
- // Un solo avviso di interruzione anche quando più worker si fermano insieme.
412
- if (!abortReported) {
413
- abortReported = true;
414
- results[i] = { rowIndex: i, id1: dcmts[i].TID, id2: dcmts[i].DID, resultType: ResultTypes.WARNING, description: `Operazione interrotta. Elaborati ${completed} documenti` };
415
- }
416
- return;
417
- }
418
- try {
419
- await downloadOne(i);
420
- }
421
- catch (ex) {
422
- results[i] = { rowIndex: i, id1: dcmts[i].TID, id2: dcmts[i].DID, resultType: ResultTypes.ERROR, description: getExceptionMessage(ex) };
423
- const err = ex;
424
- if (err.name === 'CanceledError') {
425
- ShowAlert({ message: err.message, mode: 'warning', duration: 3000, title: 'Abort' });
426
- }
388
+ for (let i = 0; i < dcmts.length; i++) {
389
+ if (abortController.signal.aborted) {
390
+ results.push({ rowIndex: i, id1: dcmts[i].TID, id2: dcmts[i].DID, resultType: ResultTypes.WARNING, description: `Operazione interrotta. Elaborati ${completed} documenti` });
391
+ break;
392
+ }
393
+ try {
394
+ await downloadOne(i);
395
+ }
396
+ catch (ex) {
397
+ results.push({ rowIndex: i, id1: dcmts[i].TID, id2: dcmts[i].DID, resultType: ResultTypes.ERROR, description: getExceptionMessage(ex) });
398
+ const err = ex;
399
+ if (err.name === 'CanceledError') {
400
+ ShowAlert({ message: err.message, mode: 'warning', duration: 3000, title: 'Abort' });
427
401
  }
428
402
  }
429
- };
430
- // `|| 1` intercetta NaN/0/negativi: senza, Array.from({length: NaN}) non creerebbe
431
- // alcun worker e il download terminerebbe in silenzio senza scaricare nulla.
432
- const workers = Math.max(1, Math.min(Math.trunc(downloadConcurrency) || 1, MAX_DOWNLOAD_CONCURRENCY, dcmts.length));
433
- await Promise.all(Array.from({ length: workers }, () => runWorker()));
403
+ }
434
404
  if (secondaryTimer !== undefined)
435
405
  clearTimeout(secondaryTimer);
436
- const result = results.filter((r) => r !== undefined);
437
406
  setWaitPanelTextPrimary('');
438
407
  setWaitPanelMaxValuePrimary(0);
439
408
  setWaitPanelValuePrimary(0);
@@ -441,7 +410,7 @@ export const useDcmtOperations = () => {
441
410
  setWaitPanelMaxValueSecondary(0);
442
411
  setWaitPanelValueSecondary(0);
443
412
  setShowWaitPanel(false);
444
- TMResultManager.show(result, operationTitle, "TID", "DID", undefined, undefined, showSuccessAlert);
413
+ TMResultManager.show(results, operationTitle, "TID", "DID", undefined, undefined, showSuccessAlert);
445
414
  };
446
415
  const _downloadAttachmentsAsync = async (inputDcmts, confirmAttachments, showSuccessAlert = true) => {
447
416
  if (inputDcmts === undefined)
@@ -505,13 +474,12 @@ export const useDcmtOperations = () => {
505
474
  };
506
475
  const downloadDcmtsAsync = async (params) => {
507
476
  const { inputDcmts, downloadType = DownloadTypes.Attachment, downloadMode = "download", onFileDownloaded, confirmAttachments, skipConfirmation = false, retrieveOptions, useCache = true, showSuccessAlert = true, useInputFileName = false, // Default: usa il nome dal backend
508
- downloadConcurrency = 1, // Default: sequenziale
509
477
  } = params;
510
478
  switch (downloadType) {
511
479
  // Per il download di documenti, passa useInputFileName per controllare la priorità del nome
512
- case DownloadTypes.Dcmt: return await _downloadDcmtsAsync(inputDcmts, downloadMode, onFileDownloaded, skipConfirmation, retrieveOptions, useCache, showSuccessAlert, useInputFileName, downloadConcurrency);
480
+ case DownloadTypes.Dcmt: return await _downloadDcmtsAsync(inputDcmts, downloadMode, onFileDownloaded, skipConfirmation, retrieveOptions, useCache, showSuccessAlert, useInputFileName);
513
481
  case DownloadTypes.Attachment: return await _downloadAttachmentsAsync(inputDcmts, confirmAttachments, showSuccessAlert);
514
- default: return await _downloadDcmtsAsync(inputDcmts, undefined, undefined, skipConfirmation, retrieveOptions, useCache, showSuccessAlert, useInputFileName, downloadConcurrency);
482
+ default: return await _downloadDcmtsAsync(inputDcmts, undefined, undefined, skipConfirmation, retrieveOptions, useCache, showSuccessAlert, useInputFileName);
515
483
  }
516
484
  };
517
485
  const uploadDcmtsAsync = async (inputDcmts, operationTitle, operType, actionAfterOperationAsync) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.22.0-dev3.15",
3
+ "version": "6.22.0-dev3.17",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",