@topconsultnpm/sdkui-react-beta 6.17.39 → 6.17.41

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.
@@ -53,7 +53,6 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
53
53
  const [formData, setFormData] = useState([]);
54
54
  const [formDataOrig, setFormDataOrig] = useState([]);
55
55
  const [validationItems, setValidationItems] = useState([]);
56
- const [changedMetadata, setChangedMetadata] = useState([]);
57
56
  const [fromDTD, setFromDTD] = useState();
58
57
  const [showApprovePopup, setShowApprovePopup] = useState(false);
59
58
  const [showRejectPopup, setShowRejectPopup] = useState(false);
@@ -61,6 +60,13 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
61
60
  const [showMoreInfoPopup, setShowMoreInfoPopup] = useState(false);
62
61
  const [layout, setLayout] = useState();
63
62
  const appliedInputMidsRef = useRef(null);
63
+ // Refs per evitare stale closure nei callback
64
+ // I useCallback catturano i valori delle dipendenze al momento della creazione.
65
+ // Questi ref vengono sincronizzati tramite useEffect e permettono di accedere
66
+ // sempre ai valori correnti senza dover ricreare i callback ad ogni cambio di stato.
67
+ const formDataOrigRef = useRef([]);
68
+ const formDataRef = useRef([]);
69
+ const fromDTDRef = useRef();
64
70
  const [isOpenDetails, setIsOpenDetails] = useState(false);
65
71
  const [isOpenMaster, setIsOpenMaster] = useState(false);
66
72
  const [secondaryMasterDcmts, setSecondaryMasterDcmts] = useState([]);
@@ -129,6 +135,8 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
129
135
  }
130
136
  setFormDataOrig(structuredClone(metadataList));
131
137
  setFormData(structuredClone(metadataList));
138
+ // Sincronizza il ref con i dati caricati per evitare stale closure in handleSave
139
+ formDataOrigRef.current = structuredClone(metadataList);
132
140
  }
133
141
  catch (e) {
134
142
  TMExceptionBoxManager.show({ exception: e });
@@ -157,6 +165,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
157
165
  const metadataList = searchResultToMetadataValues(dtd?.id, undefined, [], [], renderedMetadata, layoutMode);
158
166
  setFormDataOrig(structuredClone(metadataList));
159
167
  setFormData(structuredClone(metadataList));
168
+ formDataOrigRef.current = structuredClone(metadataList);
160
169
  }
161
170
  let resLayout = await SDK_Globals.tmSession?.NewDcmtTypeEngine().LayoutRetrieveAsync(TID, layoutMode);
162
171
  setLayout(resLayout);
@@ -192,6 +201,16 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
192
201
  return changes;
193
202
  }, [createChange]);
194
203
  useEffect(() => { setID(genUniqueId()); }, []);
204
+ // Sincronizza i ref con gli stati correnti per evitare stale closure nei callback.
205
+ // I useCallback catturano le variabili delle dipendenze al momento della loro creazione,
206
+ // quindi se uno stato cambia frequentemente, il callback avrebbe sempre valori vecchi.
207
+ // Usando i ref, possiamo accedere sempre ai valori più recenti senza dover ricreare i callback.
208
+ useEffect(() => {
209
+ formDataRef.current = formData;
210
+ }, [formData]);
211
+ useEffect(() => {
212
+ fromDTDRef.current = fromDTD;
213
+ }, [fromDTD]);
195
214
  useEffect(() => {
196
215
  if (!inputFile || inputFile === null)
197
216
  return;
@@ -227,8 +246,6 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
227
246
  useEffect(() => {
228
247
  if (formData.length > 0) {
229
248
  setValidationItems(validateMetadataList(formData));
230
- let changes = getSpecificChangedKeysWithValues(formDataOrig, formData);
231
- setChangedMetadata(changes);
232
249
  const newDcmt = {
233
250
  tid: formData.find(o => o.mid == SystemMIDsAsNumber.TID)?.value,
234
251
  did: formData.find(o => o.mid == SystemMIDsAsNumber.DID)?.value,
@@ -426,7 +443,12 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
426
443
  ue.Metadata_ClearAll();
427
444
  try {
428
445
  TMSpinner.show({ description: 'Aggiornamento in corso...' });
429
- for (let metadata of changedMetadata) {
446
+ // Usa i ref invece degli stati per evitare stale closure.
447
+ // handleSave è un useCallback che verrebbe ricreato ogni volta che formData/formDataOrig cambiano,
448
+ // causando problemi di performance. Usando i ref, possiamo mantenere lo stesso callback
449
+ // ma accedere sempre ai valori correnti tramite formDataRef.current e formDataOrigRef.current.
450
+ const changes = getSpecificChangedKeysWithValues(formDataOrigRef.current, formDataRef.current);
451
+ for (let metadata of changes) {
430
452
  if (!metadata.modifiedValue)
431
453
  ue.Metadata_AddNull(metadata.mid);
432
454
  else {
@@ -447,7 +469,9 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
447
469
  await ue.UpdateAsync();
448
470
  // Aggiorna lo stato locale immediatamente dopo il salvataggio riuscito
449
471
  // Questo garantisce che isModified diventi false anche se il reload fallisce
450
- setFormDataOrig(structuredClone(formData));
472
+ const savedFormData = structuredClone(formDataRef.current);
473
+ setFormDataOrig(savedFormData);
474
+ formDataOrigRef.current = savedFormData;
451
475
  // Tenta di ottenere i metadati aggiornati
452
476
  let metadataResult = undefined;
453
477
  let hasGetMetadataError = false;
@@ -487,8 +511,12 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
487
511
  await onSavedAsyncCallback?.(TID, DID, metadataResult === null ? null : metadataResult);
488
512
  // Mostra messaggio di successo solo se non ci sono stati errori critici
489
513
  if (!hasGetMetadataError) {
490
- if (metadataResult && metadataResult !== null)
491
- await setMetadataList(fromDTD?.metadata ?? [], metadataResult);
514
+ if (metadataResult && metadataResult !== null) {
515
+ // Usa fromDTDRef.current invece di fromDTD per evitare stale closure.
516
+ // fromDTD potrebbe essere undefined/vuoto nel callback se lo stato è cambiato,
517
+ // mentre fromDTDRef.current contiene sempre il valore corrente sincronizzato tramite useEffect.
518
+ await setMetadataList(fromDTDRef.current?.metadata ?? [], metadataResult);
519
+ }
492
520
  ShowAlert({ mode: 'success', title: 'Form di documento', message: 'Le modifiche sono state salvate con successo', duration: 3000 });
493
521
  }
494
522
  else
@@ -500,7 +528,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
500
528
  finally {
501
529
  TMSpinner.hide();
502
530
  }
503
- }, [DID, TID, changedMetadata, formData, onSavedAsyncCallback, onClose, fromDTD?.metadata, setMetadataList]);
531
+ }, [DID, TID, getSpecificChangedKeysWithValues, onSavedAsyncCallback, onClose, setMetadataList]);
504
532
  const handleArchiveCompleted = useCallback(async () => {
505
533
  let firstBlock = true;
506
534
  let maxFileSize = 0;
@@ -630,7 +658,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
630
658
  if (e == ButtonNames.CANCEL)
631
659
  return;
632
660
  if (e == ButtonNames.YES)
633
- await handleSave?.();
661
+ await handleSave();
634
662
  onClose?.();
635
663
  }
636
664
  catch (ex) {
@@ -668,27 +696,55 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
668
696
  setShowAll(true);
669
697
  }
670
698
  }, [shouldShowAll, showAll]);
671
- const tmDcmtForm = useMemo(() => _jsx(_Fragment, { children: metadataValuesSource.length > 0 &&
672
- _jsxs(StyledToolbarCardContainer, { children: [_jsx(TMMetadataValues, { TID: TID, metadataValues: metadataValuesSource, metadataValuesOrig: metadataValuesSourceOrig, isExpertMode: isExpertMode, isOpenDistinctValues: isOpenDistinctValues, openChooserBySingleClick: !isOpenDistinctValues, selectedMID: focusedMetadataValue?.mid, isReadOnly: formMode === FormModes.ReadOnly, layoutMode: layoutMode, deviceType: deviceType, validationItems: validationItems, inputMids: inputMids, layout: layout, onFocusedItemChanged: (item) => { (item?.mid !== focusedMetadataValue?.mid) && setFocusedMetadataValue(item); }, onValueChanged: (newItems) => {
673
- setFormData((prevItems) => prevItems.map((item) => {
674
- const newItem = newItems.find((newItem) => newItem.tid === item.tid && newItem.mid === item.mid);
675
- return newItem ? { ...item, ...newItem } : item;
676
- }));
677
- }, onAdvancedMenuClick: (e) => {
678
- switch (e.button) {
679
- case AdvancedMenuButtons.DistinctValues:
680
- setIsOpenDistinctValues(!isOpenDistinctValues);
681
- break;
682
- case AdvancedMenuButtons.FormulaEditor:
683
- setIsOpenFormulaEditor(!isOpenFormulaEditor);
684
- break;
685
- }
686
- } }), _jsxs(StyledFormButtonsContainer, { children: [_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 10 }, children: _jsx("div", { style: { display: 'flex', justifyContent: 'center', alignItems: 'center', gap: '8px' }, children: layoutMode === LayoutModes.Update ? _jsxs(_Fragment, { children: [_jsx(TMSaveFormButtonSave, { showTooltip: false, btnStyle: 'advanced', advancedColor: '#f09c0a', isModified: isModified, formMode: formMode, errorsCount: validationItems.filter(o => o.ResultType == ResultTypes.ERROR).length, onSaveAsync: handleConfirmAction }), _jsx(TMSaveFormButtonUndo, { btnStyle: 'toolbar', showTooltip: true, color: 'primary', isModified: isModified, formMode: formMode, onUndo: handleUndo })] }) :
687
- _jsxs(_Fragment, { children: [_jsx(TMButton, { disabled: archiveBtnDisabled, btnStyle: 'advanced', icon: _jsx(IconBoxArchiveIn, {}), width: 'auto', showTooltip: false, caption: SDKUI_Localizator.Archive, advancedColor: TMColors.success, onClick: handleConfirmAction }), _jsx(TMButton, { disabled: !clearFormBtnDisabled, btnStyle: 'advanced', icon: _jsx(IconClear, {}), width: 'auto', showTooltip: false, caption: SDKUI_Localizator.Clear, advancedColor: TMColors.tertiary, onClick: handleClearForm }), DID && _jsx(TMButton, { disabled: undoBtnDisabled, btnStyle: 'advanced', icon: _jsx(IconUndo, {}), width: '150px', showTooltip: false, caption: SDKUI_Localizator.Undo, advancedColor: TMColors.tertiary, onClick: handleUndo })] }) }) }), totalItems > listMaxItems &&
688
- !isApprView &&
689
- TID !== SystemTIDs.Drafts &&
690
- !shouldShowAll &&
691
- _jsx(TMShowAllOrMaxItemsButton, { showAll: showAll, dataSourceLength: totalItems, onClick: () => { setShowAll(!showAll); } })] }), _jsx(ConfirmAttachmentsDialog, {})] }) }), [TID, DID, formData, formDataOrig, dcmtFile, focusedMetadataValue, isOpenDistinctValues, isOpenFormulaEditor, validationItems, showAll, inputFile, inputMids, shouldShowAll]);
699
+ const tmDcmtForm = useMemo(() => {
700
+ return _jsx(_Fragment, { children: metadataValuesSource.length > 0 &&
701
+ _jsxs(StyledToolbarCardContainer, { children: [_jsx(TMMetadataValues, { TID: TID, metadataValues: metadataValuesSource, metadataValuesOrig: metadataValuesSourceOrig, isExpertMode: isExpertMode, isOpenDistinctValues: isOpenDistinctValues, openChooserBySingleClick: !isOpenDistinctValues, selectedMID: focusedMetadataValue?.mid, isReadOnly: formMode === FormModes.ReadOnly, layoutMode: layoutMode, deviceType: deviceType, validationItems: validationItems, inputMids: inputMids, layout: layout, onFocusedItemChanged: (item) => { (item?.mid !== focusedMetadataValue?.mid) && setFocusedMetadataValue(item); }, onValueChanged: (newItems) => {
702
+ setFormData((prevItems) => prevItems.map((item) => {
703
+ const newItem = newItems.find((newItem) => newItem.tid === item.tid && newItem.mid === item.mid);
704
+ return newItem ? { ...item, ...newItem } : item;
705
+ }));
706
+ }, onAdvancedMenuClick: (e) => {
707
+ switch (e.button) {
708
+ case AdvancedMenuButtons.DistinctValues:
709
+ setIsOpenDistinctValues(!isOpenDistinctValues);
710
+ break;
711
+ case AdvancedMenuButtons.FormulaEditor:
712
+ setIsOpenFormulaEditor(!isOpenFormulaEditor);
713
+ break;
714
+ }
715
+ } }), _jsxs(StyledFormButtonsContainer, { children: [_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 10 }, children: _jsx("div", { style: { display: 'flex', justifyContent: 'center', alignItems: 'center', gap: '8px' }, children: layoutMode === LayoutModes.Update ? _jsxs(_Fragment, { children: [_jsx(TMSaveFormButtonSave, { showTooltip: false, btnStyle: 'advanced', advancedColor: '#f09c0a', isModified: isModified, formMode: formMode, errorsCount: validationItems.filter(o => o.ResultType == ResultTypes.ERROR).length, onSaveAsync: handleConfirmAction }), _jsx(TMSaveFormButtonUndo, { btnStyle: 'toolbar', showTooltip: true, color: 'primary', isModified: isModified, formMode: formMode, onUndo: handleUndo })] }) :
716
+ _jsxs(_Fragment, { children: [_jsx(TMButton, { disabled: archiveBtnDisabled, btnStyle: 'advanced', icon: _jsx(IconBoxArchiveIn, {}), width: 'auto', showTooltip: false, caption: SDKUI_Localizator.Archive, advancedColor: TMColors.success, onClick: handleConfirmAction }), _jsx(TMButton, { disabled: !clearFormBtnDisabled, btnStyle: 'advanced', icon: _jsx(IconClear, {}), width: 'auto', showTooltip: false, caption: SDKUI_Localizator.Clear, advancedColor: TMColors.tertiary, onClick: handleClearForm }), DID && _jsx(TMButton, { disabled: undoBtnDisabled, btnStyle: 'advanced', icon: _jsx(IconUndo, {}), width: '150px', showTooltip: false, caption: SDKUI_Localizator.Undo, advancedColor: TMColors.tertiary, onClick: handleUndo })] }) }) }), totalItems > listMaxItems &&
717
+ !isApprView &&
718
+ TID !== SystemTIDs.Drafts &&
719
+ !shouldShowAll &&
720
+ _jsx(TMShowAllOrMaxItemsButton, { showAll: showAll, dataSourceLength: totalItems, onClick: () => { setShowAll(!showAll); } })] }), _jsx(ConfirmAttachmentsDialog, {})] }) });
721
+ }, [
722
+ TID,
723
+ DID,
724
+ metadataValuesSource,
725
+ metadataValuesSourceOrig,
726
+ isExpertMode,
727
+ isOpenDistinctValues,
728
+ focusedMetadataValue?.mid,
729
+ formMode,
730
+ layoutMode,
731
+ deviceType,
732
+ validationItems,
733
+ inputMids,
734
+ layout,
735
+ isModified,
736
+ archiveBtnDisabled,
737
+ clearFormBtnDisabled,
738
+ undoBtnDisabled,
739
+ totalItems,
740
+ listMaxItems,
741
+ isApprView,
742
+ shouldShowAll,
743
+ showAll,
744
+ handleConfirmAction,
745
+ handleUndo,
746
+ handleClearForm
747
+ ]);
692
748
  const tmBlog = useMemo(() => _jsx(TMDcmtBlog, { tid: TID, did: DID }), [TID, DID]);
693
749
  const tmSysMetadata = useMemo(() => _jsx(TMMetadataValues, { layoutMode: layoutMode, openChooserBySingleClick: !isOpenDistinctValues, TID: TID, isReadOnly: true, deviceType: deviceType, metadataValues: formData.filter(o => (o.mid != undefined && o.mid <= 100)), metadataValuesOrig: formData.filter(o => (o.mid != undefined && o.mid <= 100)), validationItems: [], inputMids: inputMids }), [TID, layoutMode, formData, deviceType, inputMids]);
694
750
  const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { currentDcmt: currentDcmt, dcmtFile: dcmtFile ?? inputFile, deviceType: deviceType, fromDTD: fromDTD, layoutMode: layoutMode, onFileUpload: (setFile) => {
@@ -78,6 +78,7 @@ const TMSearchResult = ({ context = SearchResultContext.METADATA_SEARCH, isVisib
78
78
  const [isOpenArchiveRelationForm, setIsOpenArchiveRelationForm] = useState(false);
79
79
  const [archiveRelatedDcmtFormTID, setArchiveRelatedDcmtFormTID] = useState(undefined);
80
80
  const [archiveRelatedDcmtFormMids, setArchiveRelatedDcmtFormMids] = useState([]);
81
+ const [relatedDcmtsChooserDataSource, setRelatedDcmtsChooserDataSource] = useState(undefined);
81
82
  const [secondaryMasterDcmts, setSecondaryMasterDcmts] = useState([]);
82
83
  const [isOpenDcmtForm, setIsOpenDcmtForm] = useState(false);
83
84
  const [currentTIDHasDetailRelations, setCurrentTIDHasDetailRelations] = useState();
@@ -453,6 +454,9 @@ const TMSearchResult = ({ context = SearchResultContext.METADATA_SEARCH, isVisib
453
454
  const filterRelationsWithAssociations = (relations) => {
454
455
  return relations.filter(rel => rel.associations && rel.associations.length > 0);
455
456
  };
457
+ const getRelatedDcmt = async (relation, type) => {
458
+ return await DcmtTypeListCacheService.GetAsync(type === 'detail' ? relation.detailTID : relation.masterTID);
459
+ };
456
460
  const showNoRelationsAlert = (type) => {
457
461
  ShowAlert({
458
462
  message: type === 'detail'
@@ -507,6 +511,12 @@ const TMSearchResult = ({ context = SearchResultContext.METADATA_SEARCH, isVisib
507
511
  }
508
512
  setRelatedDcmts(withAssociations);
509
513
  if (withAssociations.length > 1) {
514
+ const dataSourcePromises = withAssociations.map(async (rel) => {
515
+ const relatedDcmt = await getRelatedDcmt(rel, type);
516
+ return { id: rel?.id, name: relatedDcmt?.name };
517
+ });
518
+ const dataSource = await Promise.all(dataSourcePromises);
519
+ setRelatedDcmtsChooserDataSource(dataSource);
510
520
  setArchiveType(type);
511
521
  setShowRelatedDcmtsChooser(true);
512
522
  }
@@ -554,7 +564,7 @@ const TMSearchResult = ({ context = SearchResultContext.METADATA_SEARCH, isVisib
554
564
  await refreshSelectionDataRowsAsync();
555
565
  }, onStatusChanged: (isModified) => { setIsModifiedBatchUpdate(isModified); } }), (showToppyForApprove && !showApprovePopup && !showRejectPopup && !showReAssignPopup && !showMoreInfoPopup && !openS4TViewer && !showTodoDcmtForm) &&
556
566
  _jsx(ToppyHelpCenter, { deviceType: deviceType, content: _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px' }, children: _jsx(WorkFlowOperationButtons, { deviceType: deviceType, onApprove: () => setShowApprovePopup(true), onSignApprove: handleSignApprove, onReject: () => setShowRejectPopup(true), onReAssign: () => setShowReAssignPopup(true), onMoreInfo: () => setShowMoreInfoPopup(true), approveDisable: disable, signApproveDisable: disableSignApproveDisable, rejectDisable: disable, reassignDisable: disable, infoDisable: getSelectedDcmtsOrFocused(selectedItems, focusedItem).length !== 1 }) }) })] }), _jsx(ConfirmFormatDialog, {}), _jsx(ConfirmAttachmentsDialog, {}), showRelatedDcmtsChooser &&
557
- _jsx(TMChooserForm, { dataSource: relatedDcmts, onChoose: async (selectedRelation) => {
567
+ _jsx(TMChooserForm, { dataSource: relatedDcmtsChooserDataSource, onChoose: async (selectedRelation) => {
558
568
  try {
559
569
  setShowRelatedDcmtsChooser(false);
560
570
  TMSpinner.show({ description: SDKUI_Localizator.Loading });
@@ -594,7 +604,8 @@ const TMSearchResult = ({ context = SearchResultContext.METADATA_SEARCH, isVisib
594
604
  openS4TViewer,
595
605
  showRelatedDcmtsChooser,
596
606
  relatedDcmts,
597
- setShowRelatedDcmtsChooser
607
+ setShowRelatedDcmtsChooser,
608
+ relatedDcmtsChooserDataSource
598
609
  ]);
599
610
  const tmBlog = useMemo(() => _jsx(TMDcmtBlog, { tid: focusedItem?.TID, did: focusedItem?.DID }), [focusedItem]);
600
611
  const tmSysMetadata = useMemo(() => _jsx(TMMetadataValues, { layoutMode: LayoutModes.Update, openChooserBySingleClick: true, TID: focusedItem?.TID, isReadOnly: true, deviceType: deviceType, metadataValues: currentMetadataValues.filter(o => (o.mid != undefined && o.mid <= 100)), metadataValuesOrig: currentMetadataValues.filter(o => (o.mid != undefined && o.mid <= 100)), validationItems: [] }), [focusedItem, currentMetadataValues, deviceType]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react-beta",
3
- "version": "6.17.39",
3
+ "version": "6.17.41",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",