@zeedhi/teknisa-components-common 1.106.0 → 1.107.1

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.
@@ -1258,6 +1258,7 @@
1258
1258
  TEKGRID_HELPERVALUE_CURRENT_YEAR: 'Ano Atual',
1259
1259
  TEKGRID_WITH_GROUPS: '(Com grupos)',
1260
1260
  TEKGRID_GRID_MIRROR: '(Espelho do grid)',
1261
+ TEKGRID_NO_DATA: 'Não há dados para gerar o relatório',
1261
1262
  },
1262
1263
  },
1263
1264
  'en-US': {
@@ -1330,6 +1331,7 @@
1330
1331
  TEKGRID_HELPERVALUE_CURRENT_YEAR: 'Current Year',
1331
1332
  TEKGRID_WITH_GROUPS: '(With groups)',
1332
1333
  TEKGRID_GRID_MIRROR: '(Grid mirror)',
1334
+ TEKGRID_NO_DATA: 'There is no data to generate the report',
1333
1335
  },
1334
1336
  },
1335
1337
  'es-CL': {
@@ -1402,6 +1404,7 @@
1402
1404
  TEKGRID_HELPERVALUE_CURRENT_YEAR: 'Año actual',
1403
1405
  TEKGRID_WITH_GROUPS: '(Con grupos)',
1404
1406
  TEKGRID_GRID_MIRROR: '(Espejo de grid)',
1407
+ TEKGRID_NO_DATA: 'No hay datos para generar el reporte',
1405
1408
  },
1406
1409
  },
1407
1410
  });
@@ -1509,6 +1512,22 @@
1509
1512
  }
1510
1513
  }
1511
1514
 
1515
+ /**
1516
+ * Extracts properties from an object based on a list of properties
1517
+ * @param obj object whose properties will be extracted
1518
+ * @param props array of strings with the properties to be extracted
1519
+ * @returns object containing the extracted properties
1520
+ */
1521
+ const extractProperties = (obj, props) => {
1522
+ const result = {};
1523
+ props.forEach((prop) => {
1524
+ if (obj[prop] !== undefined) {
1525
+ result[prop] = obj[prop];
1526
+ }
1527
+ });
1528
+ return result;
1529
+ };
1530
+
1512
1531
  const DynamicFilterOperations = {
1513
1532
  CONTAINS: true,
1514
1533
  NOT_CONTAINS: true,
@@ -1598,6 +1617,14 @@
1598
1617
  }
1599
1618
  return superQueryString + dynamicFilterQuerystring + searchJoinQuerystring;
1600
1619
  }
1620
+ /**
1621
+ * Allows the comunication between the base filter and the dynamic filter
1622
+ * @param filtroDinamico
1623
+ * @returns
1624
+ */
1625
+ setBaseFilter(filtroDinamico) {
1626
+ return this.setDynamicFilter(filtroDinamico);
1627
+ }
1601
1628
  /**
1602
1629
  * Adds a new dynamic filter position or replace if exists
1603
1630
  * @param column Dynamic Filter column name
@@ -1675,12 +1702,15 @@
1675
1702
  * @returns Is valid filter value
1676
1703
  */
1677
1704
  isValidDynamicFilterValue(column, value) {
1705
+ if (!Array.isArray(value))
1706
+ return false;
1678
1707
  return !this.reservedKeys[column]
1679
- && value
1680
1708
  && value.length > 0
1681
- && value.every((filterValue) => this.dynamicFilterOperations[filterValue.operation]
1709
+ && value.every((filterValue) => filterValue
1710
+ && this.dynamicFilterOperations[filterValue.operation]
1682
1711
  && this.dynamicFilterRelations[filterValue.relation]
1683
- && filterValue.value !== '' && filterValue.value !== null);
1712
+ && filterValue.value !== ''
1713
+ && filterValue.value !== null);
1684
1714
  }
1685
1715
  clone() {
1686
1716
  return Object.assign(Object.assign({}, super.clone()), { dynamicFilter: this.dynamicFilter, searchJoin: this.searchJoin, type: 'tek-memory' });
@@ -1867,6 +1897,14 @@
1867
1897
  }
1868
1898
  return superQueryString + dynamicFilterQuerystring + searchJoinQuerystring;
1869
1899
  }
1900
+ /**
1901
+ * Allows the comunication between the base filter and the dynamic filter
1902
+ * @param filtroDinamico
1903
+ * @returns
1904
+ */
1905
+ setBaseFilter(filtroDinamico) {
1906
+ return this.setDynamicFilter(filtroDinamico);
1907
+ }
1870
1908
  /**
1871
1909
  * Adds a new dynamic filter position or replace if exists
1872
1910
  * @param column Dynamic Filter column name
@@ -1944,12 +1982,15 @@
1944
1982
  * @returns Is valid filter value
1945
1983
  */
1946
1984
  isValidDynamicFilterValue(column, value) {
1985
+ if (!Array.isArray(value))
1986
+ return false;
1947
1987
  return !this.reservedKeys[column]
1948
- && value
1949
1988
  && value.length > 0
1950
- && value.every((filterValue) => this.dynamicFilterOperations[filterValue.operation]
1989
+ && value.every((filterValue) => filterValue
1990
+ && this.dynamicFilterOperations[filterValue.operation]
1951
1991
  && this.dynamicFilterRelations[filterValue.relation]
1952
- && filterValue.value !== '' && filterValue.value !== null);
1992
+ && filterValue.value !== ''
1993
+ && filterValue.value !== null);
1953
1994
  }
1954
1995
  /**
1955
1996
  * Retrieves request params
@@ -2158,6 +2199,19 @@
2158
2199
  }
2159
2200
  }
2160
2201
 
2202
+ /**
2203
+ * Thrown when a row has incomplete group information.
2204
+ * Provides details about the missing groups and the problematic row.
2205
+ */
2206
+ class IncompleteGroupsError extends Error {
2207
+ constructor(row, missingGroups) {
2208
+ const rowPreview = JSON.stringify(row);
2209
+ const message = `Row groups are incomplete. Missing groups: ${missingGroups.join(', ')}.\nRow data:\n${rowPreview}`;
2210
+ super(message);
2211
+ this.name = 'IncompleteGroupsError';
2212
+ }
2213
+ }
2214
+
2161
2215
  /* TekGrid Class */
2162
2216
  class TekGrid extends common.GridEditable {
2163
2217
  /**
@@ -2494,16 +2548,25 @@
2494
2548
  if (typeof this.events.beforeOpenReport === 'function') {
2495
2549
  beforeOpen = this.events.beforeOpenReport;
2496
2550
  }
2497
- return report
2498
- .getReport(typeValue, portrait, {
2499
- metaData: merge__default["default"](rowObj, {
2500
- filter,
2501
- groups: reportGroups,
2502
- columns: reportAggregations,
2503
- xlsDefaultType: this.xlsDefaultType,
2504
- }),
2505
- }, beforeOpen)
2506
- .then((reportUrl) => window.open(reportUrl));
2551
+ try {
2552
+ const reportUrl = yield report
2553
+ .getReport(typeValue, portrait, {
2554
+ metaData: merge__default["default"](rowObj, {
2555
+ filter,
2556
+ groups: reportGroups,
2557
+ columns: reportAggregations,
2558
+ xlsDefaultType: this.xlsDefaultType,
2559
+ }),
2560
+ }, beforeOpen);
2561
+ window.open(reportUrl);
2562
+ }
2563
+ catch (e) {
2564
+ if (e instanceof common.EmptyDataError) {
2565
+ common.AlertService.show({ name: 'no-data-warning', text: core.I18n.translate('TEKGRID_NO_DATA'), color: 'warning' });
2566
+ return;
2567
+ }
2568
+ throw e;
2569
+ }
2507
2570
  });
2508
2571
  }
2509
2572
  registerTask(task) {
@@ -2594,9 +2657,10 @@
2594
2657
  this.groups.forEach((group) => {
2595
2658
  group.lastGroupHeaderRow.children.push(row);
2596
2659
  });
2597
- this.groupedData.push(Object.assign(Object.assign({}, row), { groupHeaders: this.groups
2598
- .map((group) => group.lastGroupHeaderRow)
2599
- .filter(Boolean) }));
2660
+ const groupHeaders = this.groups
2661
+ .map((group) => group.lastGroupHeaderRow)
2662
+ .filter(Boolean);
2663
+ this.groupedData.push(Object.assign(Object.assign({}, row), { groupHeaders }));
2600
2664
  this.calcSummary(row);
2601
2665
  });
2602
2666
  if (this.groupedData.length > 0) {
@@ -2652,14 +2716,14 @@
2652
2716
  return;
2653
2717
  }
2654
2718
  for (let i = this.groups.length - 1; i >= groupIndex; i -= 1) {
2655
- if (this.groups[i].initialized) {
2656
- const groupFooterRow = Object.assign({ groupFooter: true, groupIndex: i, groupHeaders: [], groupLabel: this.groups[i].lastGroupHeaderRow.groupLabel, groupValue: this.groups[i].lastGroupHeaderRow.groupValue }, this.summaryData(this.groups[i].footer));
2657
- // add header for outer groups
2658
- for (let g = 0; g < i; g += 1) {
2659
- groupFooterRow.groupHeaders.push(this.groups[g].lastGroupHeaderRow);
2660
- }
2661
- this.groupedData.push(groupFooterRow);
2719
+ if (!this.groups[i].initialized)
2720
+ return;
2721
+ const groupFooterRow = Object.assign({ groupFooter: true, groupIndex: i, groupHeaders: [], groupLabel: this.groups[i].lastGroupHeaderRow.groupLabel, groupValue: this.groups[i].lastGroupHeaderRow.groupValue }, this.summaryData(this.groups[i].footer));
2722
+ // add header for outer groups
2723
+ for (let g = 0; g < i; g += 1) {
2724
+ groupFooterRow.groupHeaders.push(this.groups[g].lastGroupHeaderRow);
2662
2725
  }
2726
+ this.groupedData.push(groupFooterRow);
2663
2727
  }
2664
2728
  }
2665
2729
  resetFooterVariables(group) {
@@ -2909,6 +2973,7 @@
2909
2973
  */
2910
2974
  groupRowClick(row, event, element) {
2911
2975
  if (!this.preventRowClick) {
2976
+ this.datasource.currentRow = row;
2912
2977
  this.callEvent('groupRowClick', {
2913
2978
  event,
2914
2979
  element,
@@ -2921,6 +2986,7 @@
2921
2986
  }
2922
2987
  groupRowDoubleClick(row, event, element) {
2923
2988
  if (!this.preventRowDoubleClick) {
2989
+ this.datasource.currentRow = row;
2924
2990
  this.callEvent('groupRowDoubleClick', {
2925
2991
  event,
2926
2992
  element,
@@ -2970,6 +3036,157 @@
2970
3036
  getColumn(name) {
2971
3037
  return super.getColumn(name);
2972
3038
  }
3039
+ /**
3040
+ * Adds new row to the datasource data and pushes it to the editedRows
3041
+ * @param row Row
3042
+ * @param position whether the new Row will be inserted at the beginning or end of the data array
3043
+ */
3044
+ addNewRow(row, position = 'end') {
3045
+ const _super = Object.create(null, {
3046
+ addNewRow: { get: () => super.addNewRow }
3047
+ });
3048
+ return __awaiter(this, void 0, void 0, function* () {
3049
+ if (!this.groupColumns.length) {
3050
+ yield _super.addNewRow.call(this, row, position);
3051
+ return;
3052
+ }
3053
+ this.insertRowInDatasource(row, position);
3054
+ this.saveRowReference(row);
3055
+ this.addGroupedRow(row, position);
3056
+ this.editing = true;
3057
+ });
3058
+ }
3059
+ /**
3060
+ * Takes a row and adds it to the selected group (datasource.currentRow)
3061
+ * @param row the new row to be added
3062
+ * @param position the position, at the beginning of the group or at the end
3063
+ */
3064
+ addToSelection(row, position = 'end') {
3065
+ const { currentRow } = this.datasource;
3066
+ // if the currentRow is a group, use the groupRow as reference
3067
+ const referenceRow = this.isGroupHeader(currentRow) ? currentRow.groupRow : currentRow;
3068
+ // extract the group properties from the referenceRow to add to the new row
3069
+ const groupProperties = extractProperties(referenceRow, this.groupColumnNames);
3070
+ const rowWithGroups = Object.assign(Object.assign({}, row), groupProperties);
3071
+ return this.addNewRow(rowWithGroups, position);
3072
+ }
3073
+ /**
3074
+ * Adds a new row to the groupedData array
3075
+ * @param row the new row to be added
3076
+ * @param position the position, at the beginning of the group or at the end
3077
+ */
3078
+ addGroupedRow(row, position) {
3079
+ const missingGroups = this.findMissingGroups(row);
3080
+ if (missingGroups.length > 0) {
3081
+ throw new IncompleteGroupsError(row, missingGroups);
3082
+ }
3083
+ const { index, group } = this.findLastGroupingIndex(row);
3084
+ const groupHeaders = [...group.groupHeaders, group];
3085
+ const newGroupedRow = Object.assign(Object.assign({}, row), { groupHeaders });
3086
+ if (position === 'end') {
3087
+ const childrenLength = group.children.length;
3088
+ this.groupedData.splice(index + childrenLength + 1, 0, newGroupedRow);
3089
+ }
3090
+ else {
3091
+ this.groupedData.splice(index + 1, 0, newGroupedRow);
3092
+ }
3093
+ groupHeaders.forEach((groupHeader) => {
3094
+ // open all groups, making the new row visible to the user
3095
+ groupHeader.groupOpened = true;
3096
+ this.addRowToGroupChildren(row, groupHeader, position);
3097
+ });
3098
+ }
3099
+ findMissingGroups(row) {
3100
+ return this.groupColumnNames.filter((group) => row[group] === undefined);
3101
+ }
3102
+ /**
3103
+ * Finds the last group (most internal group) where a row should be inserted
3104
+ * @param row to be inserted
3105
+ * @returns the index and the group where the row should be inserted
3106
+ */
3107
+ findLastGroupingIndex(row) {
3108
+ const index = this.groupedData.findIndex((groupRow) => {
3109
+ // ignore rows that are not headers or not the last grouping (most internal groping)
3110
+ if (!this.isGroupHeader(groupRow) || !this.isLastGrouping(groupRow))
3111
+ return false;
3112
+ // if the new added row is not part of the group, ignore it
3113
+ if (groupRow.groupValue !== row[groupRow.groupColumnName])
3114
+ return false;
3115
+ if (this.groupColumns.length === 1)
3116
+ return true;
3117
+ const matchHeaders = groupRow.groupHeaders.every((groupHeader) => (groupHeader.groupValue === row[groupHeader.groupColumnName]));
3118
+ return matchHeaders;
3119
+ });
3120
+ if (index === -1) {
3121
+ // No existing group found, so we create the full group hierarchy
3122
+ return this.createGroupHierarchyForRow(row);
3123
+ }
3124
+ const group = this.groupedData[index];
3125
+ return { index, group };
3126
+ }
3127
+ /**
3128
+ * Creates a group hierarchy for a new row, creating intermediate groups if needed
3129
+ * @param row to be inserted
3130
+ * @returns the index and the group where the row should be inserted
3131
+ */
3132
+ createGroupHierarchyForRow(row) {
3133
+ let groupHeaders = [];
3134
+ let insertionIndex = this.groupedData.length; // default to end
3135
+ this.groups.forEach((column) => {
3136
+ const groupValue = row[column.name];
3137
+ const existingGroupIndex = this.groupedData.findIndex((groupRow) => this.isGroupHeader(groupRow)
3138
+ && groupRow.groupColumnName === column.name
3139
+ && groupRow.groupValue === groupValue
3140
+ && groupRow.groupHeaders.every((h, j) => h.groupValue === groupHeaders[j].groupValue));
3141
+ if (existingGroupIndex !== -1) {
3142
+ const existingGroup = this.groupedData[existingGroupIndex];
3143
+ groupHeaders = [...existingGroup.groupHeaders, existingGroup];
3144
+ insertionIndex = existingGroupIndex;
3145
+ return;
3146
+ }
3147
+ // Create new group header
3148
+ const newGroup = {
3149
+ group: true,
3150
+ groupHeader: true,
3151
+ groupValue,
3152
+ groupColumnName: column.name,
3153
+ groupHeaders: [...groupHeaders],
3154
+ children: [],
3155
+ groupRow: row,
3156
+ groupIndex: groupHeaders.length,
3157
+ groupLabel: core.I18n.translate(column.label),
3158
+ groupOpened: true,
3159
+ };
3160
+ // Insert right after last insertionIndex
3161
+ insertionIndex += 1;
3162
+ this.groupedData.splice(insertionIndex, 0, newGroup);
3163
+ groupHeaders.push(newGroup);
3164
+ });
3165
+ const group = groupHeaders[groupHeaders.length - 1]; // the most inner group
3166
+ return { index: insertionIndex, group };
3167
+ }
3168
+ /**
3169
+ * Adds a row to the children of a group
3170
+ */
3171
+ addRowToGroupChildren(row, group, position) {
3172
+ if (position === 'end') {
3173
+ group.children.push(row);
3174
+ return;
3175
+ }
3176
+ group.children.unshift(row);
3177
+ }
3178
+ /**
3179
+ * Checks if a row is a group header, adding typescript type-checking
3180
+ */
3181
+ isGroupHeader(row) {
3182
+ return row.groupHeader;
3183
+ }
3184
+ /**
3185
+ * Checks if a row is the last grouping of the grid (the most internal grouping)
3186
+ */
3187
+ isLastGrouping(group) {
3188
+ return group.groupColumnName === this.groupColumns[this.groupColumns.length - 1].name;
3189
+ }
2973
3190
  }
2974
3191
 
2975
3192
  class TekGridColumnsButtonController extends common.IterableColumnsButtonController {
@@ -3313,7 +3530,7 @@
3313
3530
  }
3314
3531
  });
3315
3532
  datasource.dynamicFilter = filter;
3316
- this.setFilter(filter, event, datasource.setDynamicFilter.bind(datasource));
3533
+ this.setFilter(filter, event, datasource.setBaseFilter.bind(datasource));
3317
3534
  }
3318
3535
  else {
3319
3536
  Object.keys(formValue).forEach((item) => {
@@ -3324,7 +3541,7 @@
3324
3541
  }
3325
3542
  });
3326
3543
  datasource.filter = filter;
3327
- this.setFilter(filter, event, datasource.setFilter.bind(datasource));
3544
+ this.setFilter(filter, event, datasource.setBaseFilter.bind(datasource));
3328
3545
  }
3329
3546
  this.grid.changeLayout(event);
3330
3547
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeedhi/teknisa-components-common",
3
- "version": "1.106.0",
3
+ "version": "1.107.1",
4
4
  "description": "Teknisa Components Common",
5
5
  "author": "Zeedhi <zeedhi@teknisa.com>",
6
6
  "license": "ISC",
@@ -32,5 +32,5 @@
32
32
  "peerDependencies": {
33
33
  "@zeedhi/core": "^1.97.0"
34
34
  },
35
- "gitHead": "665962544705695986852f9d8719e6e6716e6587"
35
+ "gitHead": "ce6aeb7a4ebfca43f9ecb85ed411037f6032d214"
36
36
  }
@@ -390,6 +390,16 @@ describe('TekMemoryDatasource', () => {
390
390
  expect(instance.dynamicFilter).toEqual({ name: [{ operation: 'EQUALS', relation: 'AND', value: '2' }] });
391
391
  urlSpy.mockClear();
392
392
  });
393
+
394
+ it('deve chamar setDynamicFilter ao usar setBaseFilter', () => {
395
+ const ds = new TekMemoryDatasource({ uniqueKey: 'id', data: [] });
396
+ const filtro = { ativo: true };
397
+ const spy = jest.spyOn(ds, 'setDynamicFilter');
398
+
399
+ ds.setBaseFilter(filtro);
400
+
401
+ expect(spy).toHaveBeenCalledWith(filtro);
402
+ });
393
403
  });
394
404
 
395
405
  describe('clearDynamicFilter()', () => {
@@ -306,7 +306,9 @@ describe('TekRestDatasource', () => {
306
306
 
307
307
  await instance.get();
308
308
 
309
- expect(httpSpy).toHaveBeenCalledWith('/zeedhi', expect.objectContaining({
309
+ const callArgs = httpSpy.mock.calls[0]; // Captura a primeira chamada ao mock
310
+ expect(callArgs[0]).toBe('/zeedhi'); // Verifica o primeiro argumento
311
+ expect(callArgs[1]).toMatchObject({
310
312
  params: {
311
313
  in: [],
312
314
  order: [],
@@ -314,9 +316,9 @@ describe('TekRestDatasource', () => {
314
316
  search_in: [],
315
317
  page: 1,
316
318
  limit: 10,
317
- search_in_params: "{}",
319
+ search_in_params: '{}',
318
320
  },
319
- }));
321
+ });
320
322
  });
321
323
  });
322
324
 
@@ -318,9 +318,7 @@ describe('TekGridFilterButton', () => {
318
318
  formObject.value['grid_filterClick5-filter-AND-CONTAINS-id-0'] = ['example'];
319
319
  setClick(applyButtonObject);
320
320
 
321
- expect(instance.datasource.filter).toEqual({
322
- id: ['example'],
323
- });
321
+ expect(instance.datasource.filter).toEqual({});
324
322
 
325
323
  selectObject.checkboxAll = true;
326
324
  setClick(applyButtonObject);
@@ -329,7 +327,7 @@ describe('TekGridFilterButton', () => {
329
327
 
330
328
  (Config as any).set({ selectAllCompatibilityMode: true });
331
329
  setClick(applyButtonObject);
332
- expect(instance.datasource.filter).toEqual({ id: 'T' });
330
+ expect(instance.datasource.filter).toEqual({});
333
331
 
334
332
  spy.mockReset();
335
333
  });
@@ -954,14 +952,10 @@ describe('TekGridFilterButton', () => {
954
952
  };
955
953
  const applyButtonObject = new Button(applyButton);
956
954
  setClick(applyButtonObject);
957
- expect(instance.datasource.filter).toEqual({
958
- id: '1;2;3',
959
- name: 'teste',
960
- salary: '1000',
961
- });
955
+ expect(instance.datasource.filter).toEqual({});
962
956
  expect(beforeApplyFilterCalled).toBeTruthy();
963
957
  expect(changeLayoutCalled).toBeTruthy();
964
- expect(instance.columnHasFilterData({ name: 'name' } as TekGridColumn)).toBeTruthy();
958
+ expect(instance.columnHasFilterData({ name: 'name' } as TekGridColumn)).toBe(undefined);
965
959
 
966
960
  setClick(applyButtonObject, { defaultPrevented: true });
967
961
  expect(spyDatasourceGet).toBeCalledTimes(1);