@sankhyalabs/core 3.1.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.docs/classes/Change.md +11 -11
  2. package/.docs/classes/DataUnit.md +385 -104
  3. package/.docs/classes/DataUnitStorage.md +116 -0
  4. package/.docs/classes/DateUtils.md +87 -10
  5. package/.docs/classes/MaskFormatter-1.md +25 -14
  6. package/.docs/enums/Action.md +11 -0
  7. package/.docs/enums/ChangeOperation.md +4 -4
  8. package/.docs/enums/DependencyType.md +3 -3
  9. package/.docs/enums/SortMode.md +2 -2
  10. package/.docs/enums/UserInterface.md +17 -17
  11. package/.docs/interfaces/ChildDescriptor.md +41 -0
  12. package/.docs/interfaces/ChildLink.md +30 -0
  13. package/.docs/interfaces/DUActionInterceptor.md +1 -1
  14. package/.docs/interfaces/FieldDescriptor.md +11 -11
  15. package/.docs/interfaces/Filter.md +3 -3
  16. package/.docs/interfaces/PageRequest.md +3 -3
  17. package/.docs/interfaces/QuickFilter.md +2 -2
  18. package/.docs/interfaces/Record.md +24 -2
  19. package/.docs/interfaces/SavedRecord.md +33 -3
  20. package/.docs/interfaces/Sort.md +3 -3
  21. package/.docs/interfaces/SortingProvider.md +1 -1
  22. package/.docs/interfaces/UnitMetadata.md +1 -1
  23. package/.docs/interfaces/WaitingChange.md +3 -3
  24. package/.docs/modules/MaskFormatter.md +4 -4
  25. package/.docs/modules.md +3 -0
  26. package/dist/dataunit/DataUnit.d.ts +83 -4
  27. package/dist/dataunit/DataUnit.js +216 -20
  28. package/dist/dataunit/DataUnit.js.map +1 -1
  29. package/dist/dataunit/DataUnitStorage.d.ts +24 -0
  30. package/dist/dataunit/DataUnitStorage.js +39 -0
  31. package/dist/dataunit/DataUnitStorage.js.map +1 -0
  32. package/dist/dataunit/formatting/PrettyFormatter.d.ts +2 -0
  33. package/dist/dataunit/formatting/PrettyFormatter.js +88 -0
  34. package/dist/dataunit/formatting/PrettyFormatter.js.map +1 -0
  35. package/dist/dataunit/metadata/UnitMetadata.d.ts +1 -0
  36. package/dist/dataunit/metadata/UnitMetadata.js.map +1 -1
  37. package/dist/dataunit/state/action/DataUnitAction.d.ts +2 -1
  38. package/dist/dataunit/state/action/DataUnitAction.js +1 -0
  39. package/dist/dataunit/state/action/DataUnitAction.js.map +1 -1
  40. package/dist/dataunit/state/slice/ChangesSlice.js +2 -1
  41. package/dist/dataunit/state/slice/ChangesSlice.js.map +1 -1
  42. package/dist/index.d.ts +3 -2
  43. package/dist/index.js +2 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/utils/DateUtils.d.ts +23 -0
  46. package/dist/utils/DateUtils.js +30 -0
  47. package/dist/utils/DateUtils.js.map +1 -1
  48. package/dist/utils/MaskFormatter.d.ts +1 -0
  49. package/dist/utils/MaskFormatter.js +6 -0
  50. package/dist/utils/MaskFormatter.js.map +1 -1
  51. package/package.json +4 -2
  52. package/src/dataunit/DataUnit.ts +244 -28
  53. package/src/dataunit/DataUnitStorage.ts +43 -0
  54. package/src/dataunit/formatting/PrettyFormatter.ts +106 -0
  55. package/src/dataunit/metadata/UnitMetadata.ts +1 -0
  56. package/src/dataunit/state/action/DataUnitAction.ts +3 -1
  57. package/src/dataunit/state/slice/ChangesSlice.ts +2 -1
  58. package/src/index.ts +6 -1
  59. package/src/utils/DateUtils.ts +34 -1
  60. package/src/utils/MaskFormatter.ts +8 -0
  61. package/test/util/DataUnitStorage.spec.ts +63 -0
  62. package/test/util/ElementIDUtils.spec.ts +127 -85
@@ -7,6 +7,7 @@ import StateManager from "./state/StateManager.js";
7
7
  import ErrorException from "../exceptions/ErrorException.js";
8
8
  import WaitingChangeException from "../exceptions/WaitingChangeException.js";
9
9
  import { StringUtils } from "../utils/StringUtils.js";
10
+ import ObjectUtils from "../utils/ObjectUtils.js";
10
11
  import { LoadDataRequest } from "./loading/LoadDataRequest.js";
11
12
  import { LoadDataResponse } from "./loading/LoadDataResponse.js";
12
13
  import { PaginationInfo } from "./loading/PaginationInfo.js";
@@ -14,18 +15,23 @@ import { AddedRecordsReducer, getAddedRecords, prepareAddedRecordId, prepareCopi
14
15
  import { ChangesReducer, getChangesToSave, isDirty, hasDirtyRecords } from "./state/slice/ChangesSlice.js";
15
16
  import { CurrentRecordsReducer, getCurrentRecords, getFieldValue, getModifiedRecords } from "./state/slice/CurrentRecordsSlice.js";
16
17
  import { getCurrentPage, getLastPage, getPaginationInfo, getCurrentRequest, LoadingControlReducer, hasMorePages, hasPreviousPages } from "./state/slice/LoadingControlSlice.js";
17
- import { RecordsReducer } from "./state/slice/RecordsSlice.js";
18
+ import { getRecords, RecordsReducer } from "./state/slice/RecordsSlice.js";
18
19
  import { RemovedRecordsReducer } from "./state/slice/RemovedRecordsSlice.js";
19
20
  import { getSelection, hasNext, hasPrevious, SelectionReducer } from "./state/slice/SelectionSlice.js";
20
21
  import { getField, getMetadata, UnitMetadataReducer } from "./state/slice/UnitMetadataSlice.js";
21
22
  import { getBlockingWaitingChanges, getWaitingChangePromisses, isWaiting, WaitingChangesReducer } from "./state/slice/WaitingChangesSlice.js";
22
23
  import { canRedo, canUndo, HistReducer } from "./state/HistReducer.js";
24
+ import { getFormattedValue } from "./formatting/PrettyFormatter.js";
25
+ import { v4 as uuid } from "uuid"
26
+ import { DataUnitStorage } from "./DataUnitStorage.js";
27
+
28
+ const DEFAULT_DATAUNIT_NAME = "dataunit";
23
29
 
24
30
  /***
25
31
  * `DataUnit`: Atua como uma camada de abstração entre o back-end e a interface do usuário.
26
32
  */
27
33
  export default class DataUnit {
28
-
34
+ private _uuid: string;
29
35
  private _name: string;
30
36
  private _observers: Array<(action: DataUnitAction) => void>;
31
37
  private _sortingProvider?: SortingProvider;
@@ -35,16 +41,16 @@ export default class DataUnit {
35
41
  private _pageSize: number;
36
42
  private _childByName = new Map<string, DataUnit>();
37
43
  private _parentDataUnit: DataUnit | undefined;
38
- private _parentSelectedRecordID: string | undefined;
39
-
44
+
40
45
  public metadataLoader?: (dataUnit: DataUnit) => Promise<UnitMetadata>;
41
46
  public dataLoader?: (dataUnit: DataUnit, request: LoadDataRequest) => Promise<LoadDataResponse>;
42
47
  public saveLoader?: (dataUnit: DataUnit, changes: Array<Change>) => Promise<Array<SavedRecord>>;
43
48
  public removeLoader?: (dataUnit: DataUnit, recordIds: Array<string>) => Promise<Array<string>>;
44
49
  public recordLoader?: (dataUnit: DataUnit, recordIds: Array<string>) => Promise<Array<Record>>;
45
50
 
46
- constructor(name: string, parentDataUnit?: DataUnit) {
47
- this._name = name;
51
+ constructor(name: string = DEFAULT_DATAUNIT_NAME, parentDataUnit?: DataUnit) {
52
+ this._uuid = uuid()
53
+ this._name = `${name}?uuid=${this._uuid}`;
48
54
  this._pageSize = 0;
49
55
  this._stateManager = new StateManager(
50
56
  [
@@ -66,7 +72,22 @@ export default class DataUnit {
66
72
  this._interceptors = [];
67
73
  this._parentDataUnit = parentDataUnit;
68
74
  this._parentDataUnit?.subscribe(this.onDataUnitParentEvent);
75
+ DataUnitStorage.put(this);
76
+ }
69
77
 
78
+ /**
79
+ * Desfaz vinculos do DataUnit. Chamado quando o DU não é mais necessário.
80
+ */
81
+ public release(){
82
+ DataUnitStorage.remove(this._name);
83
+ if(this._parentDataUnit){
84
+ this._parentDataUnit.unsubscribe(this.onDataUnitParentEvent);
85
+ this._parentDataUnit.removeChildDataunit(this._name);
86
+ }
87
+ }
88
+
89
+ public get dataUnitId(): string{
90
+ return this._uuid;
70
91
  }
71
92
 
72
93
  /**
@@ -104,14 +125,14 @@ export default class DataUnit {
104
125
  private onDataUnitParentEvent = (action: DataUnitAction) => {
105
126
  switch(action.type){
106
127
  case Action.SELECTION_CHANGED:
107
- this._parentSelectedRecordID = this._parentDataUnit?.getSelectedRecord()?.__record__id__;
128
+ if(this._parentDataUnit?.hasCopiedRecord() || this._parentDataUnit?.hasNewRecord()) return;
108
129
  this.loadData();
109
130
  break;
110
131
  case Action.DATA_LOADED:
111
132
  if(this._parentDataUnit?.getSelectedRecord() == undefined){
112
133
  this.clearDataUnit();
113
134
  }
114
- break;
135
+ break;
115
136
  }
116
137
  }
117
138
 
@@ -137,11 +158,24 @@ export default class DataUnit {
137
158
  *
138
159
  * @param request - Dados da requisição para carregamento dos registros.
139
160
  * @param executionCtx - Contexto de execução do carregamento dos registros do DataUnit.
161
+ * @param checkLastFilter - Habilita a verificação da última requisição, evitando carga desnecessária.
140
162
  *
141
163
  * @returns - Registros do DataUnit.
142
164
  *
143
165
  */
144
- private executeLoadData(request: LoadDataRequest, executionCtx?: ExecutionContext): Promise<LoadDataResponse> {
166
+ private executeLoadData(request: LoadDataRequest, executionCtx?: ExecutionContext, checkLastFilter?: boolean): Promise<LoadDataResponse> {
167
+
168
+ if(checkLastFilter && this.isSameRequest(request)){
169
+ const paginationInfo = getPaginationInfo(this._stateManager);
170
+ if(paginationInfo){
171
+ const response: LoadDataResponse = {
172
+ records: getRecords(this._stateManager),
173
+ paginationInfo
174
+ };
175
+ return Promise.resolve(response);
176
+ }
177
+ }
178
+
145
179
  return new Promise(async (resolve, fail) => {
146
180
  if (await this.dispatchAction(Action.LOADING_DATA, request, executionCtx)) {
147
181
  if (this.dataLoader) {
@@ -159,6 +193,11 @@ export default class DataUnit {
159
193
  });
160
194
  }
161
195
 
196
+ private isSameRequest(request: LoadDataRequest): boolean{
197
+ const lastRequest = getCurrentRequest(this._stateManager);
198
+ return ObjectUtils.objectToString(request) === ObjectUtils.objectToString(lastRequest || {});
199
+ }
200
+
162
201
  // Loaders
163
202
  /**
164
203
  *
@@ -193,11 +232,12 @@ export default class DataUnit {
193
232
  *
194
233
  * @param quickFilter - Filtros a serem aplicados.
195
234
  * @param executionCtx - Contexto de execução do carregamento dos registros.
235
+ * @param checkLastFilter - Habilita a verificação da última requisição, evitando carga desnecessária.
196
236
  *
197
237
  * @returns - Registros requisitados.
198
238
  *
199
239
  */
200
- public async loadData(quickFilter?: QuickFilter, executionCtx?: ExecutionContext): Promise<LoadDataResponse> {
240
+ public async loadData(quickFilter?: QuickFilter, executionCtx?: ExecutionContext, checkLastFilter?: boolean): Promise<LoadDataResponse> {
201
241
 
202
242
  if(this._parentDataUnit && !this._parentDataUnit.getSelectedRecord()){
203
243
  if(this.records){
@@ -207,12 +247,11 @@ export default class DataUnit {
207
247
  records: []
208
248
  });
209
249
  }
210
-
211
250
  const loadDataRequest: LoadDataRequest = {
212
251
  quickFilter,
213
252
  filters: this.getFilters(),
214
253
  sort: this.getSort(),
215
- parentRecordId: this._parentSelectedRecordID
254
+ parentRecordId: this.getParentRecordId()
216
255
  };
217
256
 
218
257
  if (this._pageSize > 0) {
@@ -220,7 +259,7 @@ export default class DataUnit {
220
259
  loadDataRequest.offset = 0;
221
260
  }
222
261
 
223
- return this.executeLoadData(loadDataRequest, executionCtx);
262
+ return this.executeLoadData(loadDataRequest, executionCtx, checkLastFilter);
224
263
  }
225
264
 
226
265
  /**
@@ -279,6 +318,22 @@ export default class DataUnit {
279
318
  return this.gotoPage(getCurrentPage(this._stateManager) - 1, executionCtx);
280
319
  }
281
320
 
321
+
322
+ private async notifySavingData(executionCtx?: ExecutionContext): Promise<boolean>{
323
+ const notifyPromises: Array<Promise<boolean>> = [];
324
+ Array.from(this._childByName.values()).forEach(
325
+ child => notifyPromises.push(child.notifySavingData(executionCtx))
326
+ );
327
+
328
+ if(isDirty(this._stateManager)){
329
+ notifyPromises.push(this.dispatchAction(Action.SAVING_DATA, undefined, executionCtx));
330
+ }
331
+
332
+ return new Promise(accept => Promise.all(notifyPromises).then(responses => {
333
+ accept(responses.reduce((previous, current) => previous && current, true));
334
+ }));
335
+ }
336
+
282
337
  /**
283
338
  *
284
339
  * Salva o estado do registro do DataUnit.
@@ -292,23 +347,32 @@ export default class DataUnit {
292
347
  const blockingWaitingChanges = getBlockingWaitingChanges(this._stateManager);
293
348
 
294
349
  if (blockingWaitingChanges && blockingWaitingChanges.size > 0) {
295
- const [field, waitingChange] = blockingWaitingChanges.entries().next().value;
350
+ const [_, waitingChange] = blockingWaitingChanges.entries().next().value;
296
351
  return Promise.reject(new WaitingChangeException("Aguardando alteração de campo", (waitingChange as WaitingChange).waitmessage));
297
352
  } else {
298
- if (isDirty(this._stateManager)) {
353
+ if (this.isDirty()) {
299
354
 
300
- if (await this.dispatchAction(Action.SAVING_DATA, undefined, executionCtx)) {
355
+ if (await this.notifySavingData(executionCtx)) {
301
356
  const promisses = getWaitingChangePromisses(this._stateManager);
302
357
  return new Promise((resolve, fail) => {
303
358
  Promise.all(promisses || []).then(() => {
304
359
  if (this.saveLoader) {
305
- const changes: Array<Change> = getChangesToSave(this._name, this._stateManager);
306
- this.saveLoader(this, changes).then(
307
- records => {
308
- this.dispatchAction(Action.DATA_SAVED, { changes, records }, executionCtx);
309
- resolve();
360
+ const changes: Array<Change> = this.getAllChangesToSave();
361
+
362
+ this.saveLoader(this, changes).then((records) => {
363
+ const recordsByDataUnit = this.getRecordsByDataUnit(records);
364
+
365
+ const dispatchPromisses = [];
366
+ for (const [ownerDataUnitName, splittedRecords] of recordsByDataUnit) {
367
+ const ownerDataUnit = DataUnitStorage.get(ownerDataUnitName);
368
+ const changesByDataUnit = changes.filter(change => change.dataUnit === ownerDataUnitName);
369
+
370
+ if(ownerDataUnit){
371
+ dispatchPromisses.push(ownerDataUnit.dispatchAction(Action.DATA_SAVED, { changes: changesByDataUnit, records: splittedRecords }, executionCtx));
372
+ }
310
373
  }
311
- ).catch(cause => {
374
+ Promise.all(dispatchPromisses).then(() => resolve())
375
+ }).catch(cause => {
312
376
  const {errorCode} = cause;
313
377
  fail(new ErrorException("Erro salvando alterações", cause, errorCode));
314
378
  });
@@ -324,6 +388,44 @@ export default class DataUnit {
324
388
  return Promise.resolve();
325
389
  }
326
390
 
391
+ /**
392
+ *
393
+ * Retorna as alterações a serem salvas no DataUnit atual.
394
+ *
395
+ *
396
+ *
397
+ * @returns - Mudanças realizadas no DataUnit atual
398
+ *
399
+ */
400
+ public buildChangesToSave(): Array<Change>{
401
+ return getChangesToSave(this._name, this._stateManager);
402
+ }
403
+
404
+ public buildChangesToSaveFromChild(allChanges: Array<Change>, dataUnit: DataUnit): void {
405
+ dataUnit._childByName?.forEach((dataUnitChild: DataUnit) => {
406
+ if (dataUnitChild.isDirty()) {
407
+ allChanges.push(...getChangesToSave(dataUnitChild.name, dataUnitChild._stateManager));
408
+ this.buildChangesToSaveFromChild(allChanges, dataUnitChild);
409
+ }
410
+ });
411
+ }
412
+
413
+ /**
414
+ *
415
+ * Retorna todas as alterações do DataUnit a serem salvas, incluindo no nível Master e Detail.
416
+ *
417
+ *
418
+ *
419
+ * @returns - Todas as mudanças realizadas no DataUnit, tanto Master quanto Detail;
420
+ *
421
+ */
422
+ public getAllChangesToSave(): Array<Change> {
423
+ const allChanges: Array<Change> = [];
424
+ allChanges.push(...this.buildChangesToSave());
425
+ this.buildChangesToSaveFromChild(allChanges, this);
426
+ return allChanges;
427
+ }
428
+
327
429
  /**
328
430
  *
329
431
  * Remove o registro selecionado.
@@ -423,6 +525,29 @@ export default class DataUnit {
423
525
  return toString(descriptor?.dataType, value);
424
526
  }
425
527
 
528
+ /**
529
+ *
530
+ * Formata o valor do campo considerando as informações do descriptor.
531
+ * Diferente do método "valueToString" que retorna o dado em valor textual,
532
+ * getFormattedValue retorna uma informação amigável ao usuário, geralmente
533
+ * usada na interface.
534
+ *
535
+ * @param fieldName - Nome do campo utilizado do qual se quer obter valor.
536
+ *
537
+ * @returns - Valor formatado.
538
+ *
539
+ */
540
+ public getFormattedValue(fieldName: string): string {
541
+
542
+ const descriptor = this.getField(fieldName);
543
+ const value = this.getFieldValue(fieldName);
544
+ if(value == undefined){
545
+ return "";
546
+ }
547
+
548
+ return getFormattedValue(value, descriptor);
549
+ }
550
+
426
551
  /**
427
552
  *
428
553
  * Adiciona um interceptor correspondente a uma ação do DataUnit para fazer um processamento customizado.
@@ -588,7 +713,7 @@ export default class DataUnit {
588
713
  *
589
714
  */
590
715
  public addRecord(executionCtx?: ExecutionContext): void {
591
- this.dispatchAction(Action.RECORDS_ADDED, prepareAddedRecordId(this._stateManager, [{}], this._parentSelectedRecordID), executionCtx);
716
+ this.dispatchAction(Action.RECORDS_ADDED, prepareAddedRecordId(this._stateManager, [{}], this.getParentRecordId()), executionCtx);
592
717
  }
593
718
 
594
719
  /**
@@ -601,9 +726,35 @@ export default class DataUnit {
601
726
  public copySelected(executionCtx?: ExecutionContext): void {
602
727
  const selectedRecords = this.getSelectedRecords();
603
728
  if (selectedRecords) {
604
- this.dispatchAction(Action.RECORDS_COPIED, prepareCopiedRecord(this._stateManager, selectedRecords, this._parentSelectedRecordID), executionCtx);
729
+ this.dispatchAction(Action.RECORDS_COPIED, prepareCopiedRecord(this._stateManager, selectedRecords, this.getParentRecordId()), executionCtx);
605
730
  }
606
731
  }
732
+
733
+ private getRecordsByDataUnit(records: Array<Record>): Map<string, Array<Record>> {
734
+ const recordsMap = new Map<string, Array<Record>>();
735
+
736
+ records.forEach(record => {
737
+ const { __owner__dataunit__name__: ownerDataUnitName } = record;
738
+
739
+ if(ownerDataUnitName){
740
+ let changes = recordsMap.get(ownerDataUnitName);
741
+
742
+ if(!changes){
743
+ changes = [];
744
+ recordsMap.set(ownerDataUnitName, changes);
745
+ }
746
+
747
+ changes.push(record);
748
+ }
749
+ });
750
+
751
+ return recordsMap;
752
+ }
753
+
754
+ private getParentRecordId(){
755
+ const parentRecord = this._parentDataUnit?.getSelectedRecord();
756
+ return parentRecord?.__record__id__;
757
+ }
607
758
 
608
759
  /**
609
760
  *
@@ -836,8 +987,13 @@ export default class DataUnit {
836
987
  * @param executionCtx - Contexto de execução do cancelamento da seleção dos registros.
837
988
  *
838
989
  */
839
- public cancelEdition(executionCtx?: ExecutionContext): void {
840
- this.dispatchAction(Action.EDITION_CANCELED, undefined, executionCtx);
990
+ public async cancelEdition(executionCtx?: ExecutionContext, fromParent?: boolean): Promise<void> {
991
+ await this.dispatchAction(Action.EDITION_CANCELED, {fromParent}, executionCtx);
992
+ for (const [key, dataUnit] of this._childByName) {
993
+ if(dataUnit.isDirty()){
994
+ dataUnit.cancelEdition(undefined, true);
995
+ }
996
+ }
841
997
  }
842
998
 
843
999
  /**
@@ -846,9 +1002,23 @@ export default class DataUnit {
846
1002
  *
847
1003
  * @returns Verdadeiro se existir alterações pendentes.
848
1004
  *
849
- */
1005
+ */
850
1006
  public isDirty(): boolean {
851
- return isDirty(this._stateManager);
1007
+ return isDirty(this._stateManager) || this.childrenIsDirty();
1008
+ }
1009
+
1010
+ /**
1011
+ *
1012
+ * Retorna se existe algum DataUnit detail com alterações pendentes.
1013
+ *
1014
+ * @returns Verdadeiro se existir alterações pendentes em algum DataUnit detail.
1015
+ *
1016
+ */
1017
+ private childrenIsDirty():boolean {
1018
+ for (let [key, dataUnitChild] of this._childByName) {
1019
+ if(dataUnitChild.isDirty()) return true;
1020
+ }
1021
+ return false;
852
1022
  }
853
1023
 
854
1024
  /**
@@ -909,6 +1079,38 @@ export default class DataUnit {
909
1079
  return false;
910
1080
  }
911
1081
 
1082
+ /**
1083
+ *
1084
+ * Retorna se existe pelo menos um registro novo.
1085
+ *
1086
+ * @returns Verdadeiro se algum registro foi adicionado.
1087
+ *
1088
+ */
1089
+ public hasNewRecord(): boolean {
1090
+ const newRecords = getAddedRecords(this._stateManager);
1091
+ return newRecords && newRecords.length > 0;
1092
+ }
1093
+
1094
+ /**
1095
+ *
1096
+ * Retorna se existe pelo menos um registro novo.
1097
+ *
1098
+ * @returns Verdadeiro se algum registro foi adicionado.
1099
+ *
1100
+ */
1101
+ public hasCopiedRecord(): boolean {
1102
+ const newRecords = getAddedRecords(this._stateManager);
1103
+ if(newRecords == undefined){
1104
+ return false;
1105
+ }
1106
+ for(let record of newRecords){
1107
+ if (record['__record__source__id__'] != undefined) {
1108
+ return true
1109
+ }
1110
+ }
1111
+ return false;
1112
+ }
1113
+
912
1114
  /**
913
1115
  *
914
1116
  * Retorna se a informação do estado anterior está salva, permitindo desfazer a ação.
@@ -1027,6 +1229,7 @@ export default class DataUnit {
1027
1229
  */
1028
1230
  private doDispatchAction(action: DataUnitAction): void {
1029
1231
  this._stateManager.process(action);
1232
+ this?._parentDataUnit?.dispatchAction(Action.CHILD_CHANGED, { srcAction: action, srcDataUnit: this });
1030
1233
  this._observers.forEach(f => {
1031
1234
  /*
1032
1235
  if some observer throws exceptions,
@@ -1052,6 +1255,17 @@ export default class DataUnit {
1052
1255
  this._childByName.set(name, detail);
1053
1256
  return detail;
1054
1257
  }
1258
+
1259
+ /**
1260
+ *
1261
+ * Remove um dataunit filho.
1262
+ *
1263
+ * @param name - Nome do dataunit filho.
1264
+ *
1265
+ */
1266
+ public removeChildDataunit(name:string){
1267
+ this._childByName.delete(name);
1268
+ }
1055
1269
 
1056
1270
  /**
1057
1271
  *
@@ -1196,7 +1410,9 @@ export interface DUActionInterceptor {
1196
1410
 
1197
1411
  export interface Record {
1198
1412
  __record__id__: string;
1413
+ __record__label__?: string;
1199
1414
  __parent__record__id__?: string;
1415
+ __owner__dataunit__name__?: string;
1200
1416
  [key: string]: any;
1201
1417
  }
1202
1418
 
@@ -0,0 +1,43 @@
1
+ import DataUnit from "./DataUnit.js";
2
+
3
+ /**
4
+ * Classe responsável por armazenar instâncias de DataUnit.
5
+ */
6
+ export class DataUnitStorage {
7
+ private static dataUnitMap: WeakMap<{ key: string }, DataUnit> = new WeakMap();
8
+ private static keyMap: Map<string, { key: string }> = new Map();
9
+
10
+ /**
11
+ * Retorna uma instância de DataUnit armazenada no DataUnitStorage.
12
+ * @param {string} dataUnitName - O nome do DataUnit.
13
+ * @returns {DataUnit | undefined} - A instância do DataUnit ou undefined caso não seja encontrada.
14
+ */
15
+ static get(dataUnitName: string): DataUnit | undefined {
16
+ const key = this.keyMap.get(dataUnitName);
17
+ if (key) return this.dataUnitMap.get(key);
18
+ }
19
+
20
+ /**
21
+ * Armazena uma instância de DataUnit no DataUnitStorage.
22
+ * @param {DataUnit} dataUnit - A instância de DataUnit a ser armazenada.
23
+ */
24
+ static put(dataUnit: DataUnit): void {
25
+ const key = { key: dataUnit.name };
26
+ this.dataUnitMap.set(key, dataUnit);
27
+ this.keyMap.set(dataUnit.name, key);
28
+ }
29
+
30
+ /**
31
+ * Remove uma instância de DataUnit do DataUnitStorage.
32
+ * @param {DataUnit | string} dataUnit - A instância de DataUnit ou o nome do DataUnit a ser removido.
33
+ */
34
+ static remove(dataUnit: DataUnit | string): void {
35
+ const key = typeof dataUnit === 'string' ? this.keyMap.get(dataUnit) : this.keyMap.get(dataUnit.name);
36
+ const dataUnitKey = typeof dataUnit === 'string' ? dataUnit : dataUnit.name;
37
+
38
+ if (key) {
39
+ this.dataUnitMap.delete(key);
40
+ this.keyMap.delete(dataUnitKey);
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,106 @@
1
+ import DateUtils from "../../utils/DateUtils.js";
2
+ import { MaskFormatter } from "../../utils/MaskFormatter.js";
3
+ import { NumberUtils } from "../../utils/NumberUtils.js";
4
+ import { DataType, toString } from "../metadata/DataType.js";
5
+ import { FieldDescriptor, UserInterface } from "../metadata/UnitMetadata.js";
6
+
7
+ export const getFormattedValue = (value: any, descriptor?: FieldDescriptor) => {
8
+ if(descriptor?.dataType === DataType.OBJECT && Object.hasOwn(value, "value")){
9
+ return getSearchFormat(value, descriptor);
10
+ }
11
+
12
+ if(descriptor?.userInterface === UserInterface.OPTIONSELECTOR){
13
+ return getOptionFormat(value, descriptor);
14
+ }
15
+
16
+ if(descriptor?.dataType === DataType.BOOLEAN){
17
+ return value ? "Sim" : "Não";
18
+ }
19
+
20
+ if(descriptor?.dataType === DataType.NUMBER){
21
+ return getNumberFormat(value, descriptor);
22
+ }
23
+
24
+ if(descriptor?.userInterface === UserInterface.DATE){
25
+ return DateUtils.formatDate(value);
26
+ }
27
+
28
+ if(descriptor?.userInterface === UserInterface.TIME){
29
+ return DateUtils.formatTime(value);
30
+ }
31
+
32
+ if(descriptor?.userInterface === UserInterface.DATETIME){
33
+ return DateUtils.formatDateTime(value);
34
+ }
35
+
36
+ const mask = getMask(value, descriptor);
37
+ if(mask != undefined){
38
+ try{
39
+ return new MaskFormatter(mask).format(value);
40
+ } catch(error){
41
+ console.warn(`Erro ao formatar valor: ${value}. Mascara: ${mask}. Erro: ${error}`);
42
+ }
43
+ }
44
+
45
+ return toString(descriptor?.dataType, value);
46
+ }
47
+
48
+ const getNumberFormat = (value: any, descriptor: FieldDescriptor) => {
49
+ if(descriptor.userInterface === UserInterface.INTEGERNUMBER){
50
+ return value;
51
+ }
52
+ const precision = Number(descriptor.properties?.precision || 2);
53
+ const prettyPrecision = Number(descriptor.properties?.prettyPrecision || precision);
54
+
55
+ return NumberUtils.format(value, precision, prettyPrecision);
56
+ }
57
+
58
+ const getSearchFormat = (value: any, descriptor: FieldDescriptor) => {
59
+ const {value: codeValue, label} = value;
60
+ if(descriptor.userInterface === UserInterface.SEARCH){
61
+ return label ? `${codeValue} - ${label}` : codeValue;
62
+ }
63
+
64
+ return label ? label : codeValue;
65
+ }
66
+
67
+ const getOptionFormat = (value: any, descriptor: FieldDescriptor) => {
68
+ const prop: string | Array<{label: string, value: string}> = descriptor.properties?.options;
69
+ let options: any;
70
+
71
+ if (typeof prop === "string") {
72
+ options = JSON.parse(prop);
73
+ } else {
74
+ options = {};
75
+ prop.forEach(opt => options[opt.label] = opt.value);
76
+ }
77
+
78
+ if(options && Object.hasOwn(options, value)){
79
+ return options[value];
80
+ }
81
+
82
+ return value;
83
+ }
84
+
85
+
86
+ const getMask = (value: string, descriptor: FieldDescriptor | undefined): string | undefined => {
87
+
88
+ if(descriptor == undefined){
89
+ return;
90
+ }
91
+
92
+ const mask = descriptor.properties?.mask;
93
+ if(mask == undefined){
94
+ return;
95
+ }
96
+
97
+ if(mask === "cgc_cpf"){
98
+ return value.length === 11 ? MaskFormatter.DEFAULT_MASKS.cpf : MaskFormatter.DEFAULT_MASKS.cnpj;
99
+ }
100
+
101
+ if(Object.hasOwn(MaskFormatter.DEFAULT_MASKS, mask)){
102
+ return MaskFormatter.DEFAULT_MASKS[mask];
103
+ }
104
+
105
+ return mask;
106
+ }
@@ -8,6 +8,7 @@ export interface UnitMetadata{
8
8
  }
9
9
 
10
10
  export interface ChildDescriptor{
11
+ label: string;
11
12
  name: string;
12
13
  links: Array<ChildLink>;
13
14
  }
@@ -56,6 +56,8 @@ export enum Action{
56
56
  STATE_CHANGED = "stateChanged",
57
57
 
58
58
  LOADING_RECORD = "loadingRecord",
59
- RECORD_LOADED = "recordLoaded"
59
+ RECORD_LOADED = "recordLoaded",
60
+
61
+ CHILD_CHANGED = "childChanged"
60
62
 
61
63
  }
@@ -25,6 +25,7 @@ class ChangesReducerImpl implements ActionReducer{
25
25
  delete rChanges.__record__id__;
26
26
  delete rChanges.__parent__record__id__;
27
27
  delete rChanges.__record__source__id__;
28
+ delete rChanges.__record__label__;
28
29
  delete rChanges.__copy__;
29
30
  changes.set(r.__record__id__, rChanges);
30
31
  }
@@ -89,7 +90,7 @@ export const getChangesToSave = (dataUnit: string, stateManager: StateManager):
89
90
  if(changes){
90
91
  const c = changes.get(r.__record__id__);
91
92
  if (c) {
92
- result.push(new Change(dataUnit, r, c, ChangeOperation.UPDATE));
93
+ result.push(new Change(dataUnit, r, c, ChangeOperation.UPDATE, r.__record__source__id__));
93
94
  }
94
95
  }
95
96
  });
package/src/index.ts CHANGED
@@ -11,7 +11,7 @@ import { RequestMetadata } from "./http/RequestMetadata.js";
11
11
  import { AuthorizedServiceCaller } from "./http/AuthorizedServiceCaller.js";
12
12
  import DataUnit, {SavedRecord, Record, Change, ChangeOperation, DUActionInterceptor, WaitingChange, PageRequest, QuickFilter} from "./dataunit/DataUnit.js";
13
13
  import { DataType } from "./dataunit/metadata/DataType.js";
14
- import { UnitMetadata, FieldDescriptor, UserInterface, Sort, SortMode, SortingProvider, Filter, DependencyType } from "./dataunit/metadata/UnitMetadata.js";
14
+ import { UnitMetadata, FieldDescriptor, UserInterface, Sort, SortMode, SortingProvider, Filter, DependencyType, ChildDescriptor, ChildLink } from "./dataunit/metadata/UnitMetadata.js";
15
15
  import { DataUnitAction, Action, ExecutionContext } from "./dataunit/state/action/DataUnitAction.js";
16
16
  import ApplicationContext from "./utils/ApplicationContext.js";
17
17
  import ReadyUtil from "./utils/ReadyUtil.js";
@@ -26,6 +26,8 @@ import { LoadDataResponse } from "./dataunit/loading/LoadDataResponse.js";
26
26
  import { ElementIDUtils, IElementIDInfo } from "./utils/ElementIDUtils.js";
27
27
  import { UserAgentUtils } from "./utils/UserAgentUtils/index.js";
28
28
  import { JSUtils } from "./utils/JSUtils.js";
29
+ import { DataUnitStorage } from "./dataunit/DataUnitStorage.js";
30
+
29
31
 
30
32
  /*Classes públicas no pacote*/
31
33
  export {
@@ -41,6 +43,7 @@ export {
41
43
  RequestMetadata,
42
44
  AuthorizedServiceCaller,
43
45
  DataUnit,
46
+ DataUnitStorage,
44
47
  Record,
45
48
  SavedRecord,
46
49
  DataType,
@@ -48,6 +51,8 @@ export {
48
51
  FieldDescriptor,
49
52
  UserInterface,
50
53
  DependencyType,
54
+ ChildDescriptor,
55
+ ChildLink,
51
56
  DataUnitAction,
52
57
  Action,
53
58
  Change,