@underpostnet/underpost 2.98.0 → 2.98.3

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.
@@ -52,13 +52,60 @@ const columnDefFormatter = (obj, columnDefs, customFormat) => {
52
52
 
53
53
  const DefaultManagement = {
54
54
  Tokens: {},
55
- loadTable: async function (id, options = { reload: true, force: true, createHistory: false, skipUrlUpdate: false }) {
55
+ // Helper functions for managing serviceOptions ID filter
56
+ setIdFilter: function (id, itemId) {
57
+ if (!this.Tokens[id]) {
58
+ this.Tokens[id] = {};
59
+ }
60
+ if (!this.Tokens[id].serviceOptions) {
61
+ this.Tokens[id].serviceOptions = {};
62
+ }
63
+ if (!this.Tokens[id].serviceOptions.get) {
64
+ this.Tokens[id].serviceOptions.get = {};
65
+ }
66
+ this.Tokens[id].serviceOptions.get.id = itemId;
67
+ },
68
+ clearIdFilter: function (id) {
69
+ if (this.Tokens[id]?.serviceOptions?.get?.id) {
70
+ delete this.Tokens[id].serviceOptions.get.id;
71
+ }
72
+ },
73
+ getIdFilter: function (id) {
74
+ return this.Tokens[id]?.serviceOptions?.get?.id ?? undefined;
75
+ },
76
+ waitGridReady: function (id) {
77
+ return new Promise((resolve) => {
78
+ if (this.Tokens[id]?.gridApi) {
79
+ return resolve(this.Tokens[id].gridApi);
80
+ }
81
+ if (!this.Tokens[id].readyGridEvent) this.Tokens[id].readyGridEvent = {};
82
+ this.Tokens[id].readyGridEvent['waitGridReady'] = (params) => {
83
+ delete this.Tokens[id].readyGridEvent['waitGridReady'];
84
+ resolve(params.api);
85
+ };
86
+ });
87
+ },
88
+ runIsolated: async function (id, callback) {
89
+ if (!this.Tokens[id]) return await callback();
90
+ this.Tokens[id].isProcessingQueryChange = true;
56
91
  try {
92
+ return await callback();
93
+ } finally {
94
+ this.Tokens[id].isProcessingQueryChange = false;
95
+ }
96
+ },
97
+ loadTable: async function (id, options = {}) {
98
+ options = { reload: true, force: true, createHistory: false, skipUrlUpdate: false, ...options };
99
+ try {
100
+ if (!this.Tokens[id]) {
101
+ logger.warn(`DefaultManagement loadTable - Token not found for id: ${id}`);
102
+ return;
103
+ }
57
104
  const { serviceId, columnDefs, customFormat, gridId } = this.Tokens[id];
58
105
 
59
106
  let _page = this.Tokens[id].page;
60
107
  let _limit = this.Tokens[id].limit;
61
- let _id = this.Tokens[id].serviceOptions?.get?.id ?? undefined;
108
+ let _id = this.getIdFilter(id);
62
109
 
63
110
  let filterModel = this.Tokens[id].filterModel || {};
64
111
  let sortModel = this.Tokens[id].sortModel || [];
@@ -84,15 +131,14 @@ const DefaultManagement = {
84
131
  // Use pushState (createHistory) for filter/sort changes to enable browser back/forward
85
132
  // Skip URL update when handling browser navigation to avoid interfering with history
86
133
  if (!options.skipUrlUpdate) {
87
- setQueryParams(
88
- {
89
- page: _page,
90
- limit: _limit,
91
- filterModel: filterModelStr,
92
- sortModel: sortModelStr,
93
- },
94
- { replace: !options.createHistory },
95
- );
134
+ const urlParams = {
135
+ page: _page,
136
+ limit: _limit,
137
+ filterModel: filterModelStr,
138
+ sortModel: sortModelStr,
139
+ id: _id ? _id : '',
140
+ };
141
+ setQueryParams(urlParams, { replace: !options.createHistory });
96
142
  }
97
143
 
98
144
  if (!options.force && this.Tokens[id].lastOptions) {
@@ -143,14 +189,34 @@ const DefaultManagement = {
143
189
  }
144
190
 
145
191
  logger.info(`Loading table ${serviceId}`, {
192
+ id,
193
+ idFilter: _id,
146
194
  page: _page,
147
195
  limit: _limit,
148
196
  hasFilters: Object.keys(filterModel).length > 0,
149
197
  });
150
198
 
199
+ if (!this.Tokens[id] || !this.Tokens[id].ServiceProvider) {
200
+ logger.warn(`DefaultManagement loadTable ${serviceId} - ServiceProvider not found for token ${id}`);
201
+ return;
202
+ }
203
+
151
204
  const result = await this.Tokens[id].ServiceProvider.get(queryOptions);
152
205
  if (result.status === 'success') {
153
- const { data, total, page, totalPages } = result.data;
206
+ let data, total, page, totalPages;
207
+
208
+ // Handle both single object (when querying by ID) and paginated array responses
209
+ if (queryOptions.id && result.data && !Array.isArray(result.data.data)) {
210
+ // Single object response when filtering by ID
211
+ data = [result.data];
212
+ total = 1;
213
+ page = 1;
214
+ totalPages = 1;
215
+ } else {
216
+ // Paginated array response
217
+ ({ data = [], total = 0, page = 1, totalPages = 1 } = result.data || {});
218
+ }
219
+
154
220
  this.Tokens[id].total = total;
155
221
  this.Tokens[id].page = page;
156
222
  this.Tokens[id].totalPages = totalPages;
@@ -176,6 +242,8 @@ const DefaultManagement = {
176
242
  for (const event of Object.keys(DefaultManagement.Tokens[id].readyRowDataEvent))
177
243
  await DefaultManagement.Tokens[id].readyRowDataEvent[event](rowDataScope);
178
244
  }, 1);
245
+ // Update clear filter button visibility
246
+ this.updateClearFilterButtonVisibility(id);
179
247
  } else {
180
248
  logger.error(`Failed to load table ${serviceId}:`, result);
181
249
  }
@@ -184,6 +252,27 @@ const DefaultManagement = {
184
252
  throw error;
185
253
  }
186
254
  },
255
+ hasActiveFilters: function (id) {
256
+ const gridId = this.Tokens[id]?.gridId;
257
+ if (!gridId) return false;
258
+
259
+ const gridApi = AgGrid.grids[gridId];
260
+ const filterModel = gridApi ? gridApi.getFilterModel() : {};
261
+ const idFilter = this.getIdFilter(id);
262
+ const sortModel = this.Tokens[id]?.sortModel || [];
263
+
264
+ return Object.keys(filterModel).length > 0 || !!idFilter || sortModel.length > 0;
265
+ },
266
+ updateClearFilterButtonVisibility: function (id) {
267
+ const clearFilterBtn = s(`.management-table-btn-clear-filter-${id}`);
268
+ if (!clearFilterBtn) return;
269
+
270
+ if (this.hasActiveFilters(id)) {
271
+ clearFilterBtn.classList.remove('hide');
272
+ } else {
273
+ clearFilterBtn.classList.add('hide');
274
+ }
275
+ },
187
276
  refreshTable: async function (id) {
188
277
  const gridApi = AgGrid.grids[this.Tokens[id].gridId];
189
278
  if (gridApi) {
@@ -207,6 +296,7 @@ const DefaultManagement = {
207
296
  const page = parseInt(queryParams.page) || 1;
208
297
  const defaultLimit = paginationOptions?.limitOptions?.[0] || 10;
209
298
  const limit = parseInt(queryParams.limit) || defaultLimit;
299
+ const urlId = queryParams.id || undefined;
210
300
 
211
301
  let filterModel = {};
212
302
  let sortModel = [];
@@ -275,10 +365,102 @@ const DefaultManagement = {
275
365
  return enhancedCol;
276
366
  });
277
367
 
368
+ class RemoveActionGridRenderer {
369
+ eGui;
370
+ tokens;
371
+
372
+ async init(params) {
373
+ this.eGui = document.createElement('div');
374
+ this.tokens = {};
375
+ const { rowIndex } = params;
376
+ const { createdAt, updatedAt } = params.data;
377
+
378
+ const cellRenderId = getId(this.tokens, `${serviceId}-`);
379
+ this.tokens[cellRenderId] = {};
380
+
381
+ this.eGui.innerHTML = html` ${await BtnIcon.Render({
382
+ label: html`<div class="abs center">
383
+ <i class="fas fa-times"></i>
384
+ </div> `,
385
+ class: `in fll section-mp management-table-btn-mini management-table-btn-remove-${id}-${cellRenderId} ${
386
+ !params.data._id ? 'hide' : ''
387
+ }`,
388
+ })}`;
389
+ setTimeout(() => {
390
+ EventsUI.onClick(
391
+ `.management-table-btn-remove-${id}-${cellRenderId}`,
392
+ async () => {
393
+ const confirmResult = await Modal.RenderConfirm({
394
+ html: async () => {
395
+ return html`
396
+ <div class="in section-mp" style="text-align: center">
397
+ ${Translate.Render('confirm-delete-item')}
398
+ ${Object.keys(params.data).length > 0
399
+ ? html`<br />
400
+ "${options.defaultColKeyFocus
401
+ ? getValueFromJoinString(params.data, options.defaultColKeyFocus)
402
+ : params.data[Object.keys(params.data)[0]]}"`
403
+ : ''}
404
+ </div>
405
+ `;
406
+ },
407
+ id: `delete-${params.data._id}`,
408
+ });
409
+ if (confirmResult.status !== 'confirm') return;
410
+ let result;
411
+ if (params.data._id) result = await ServiceProvider.delete({ id: params.data._id });
412
+ else result = { status: 'success' };
413
+
414
+ NotificationManager.Push({
415
+ html: result.status === 'error' ? result.message : Translate.Render('item-success-delete'),
416
+ status: result.status,
417
+ });
418
+ if (result.status === 'success') {
419
+ AgGrid.grids[gridId].applyTransaction({ remove: [params.data] });
420
+ const token = DefaultManagement.Tokens[id];
421
+ // if we are on the last page and we delete the last item, go to the previous page
422
+ const newTotal = token.total - 1;
423
+ const newTotalPages = Math.ceil(newTotal / token.limit);
424
+ if (token.page > newTotalPages && newTotalPages > 0) {
425
+ token.page = newTotalPages;
426
+ }
427
+
428
+ // reload the current page
429
+ await DefaultManagement.loadTable(id, { reload: false });
430
+ }
431
+ },
432
+ { context: 'modal' },
433
+ );
434
+ });
435
+ }
436
+
437
+ getGui() {
438
+ return this.eGui;
439
+ }
440
+
441
+ refresh(params) {
442
+ return true;
443
+ }
444
+ }
445
+
446
+ const finalColumnDefs = (enhancedColumnDefs || []).concat(
447
+ permissions.remove
448
+ ? [
449
+ {
450
+ field: 'remove-action',
451
+ headerName: '',
452
+ width: 100,
453
+ cellRenderer: RemoveActionGridRenderer,
454
+ editable: false,
455
+ },
456
+ ]
457
+ : [],
458
+ );
459
+
278
460
  this.Tokens[id] = {
279
461
  ...this.Tokens[id],
280
462
  ...options,
281
- columnDefs: enhancedColumnDefs, // Use enhanced definitions
463
+ columnDefs: finalColumnDefs, // Use enhanced definitions including actions
282
464
  gridId,
283
465
  page,
284
466
  limit,
@@ -290,107 +472,22 @@ const DefaultManagement = {
290
472
  isProcessingQueryChange: false, // Flag to prevent listener recursion
291
473
  };
292
474
 
475
+ // Initialize ID filter from query params if present
476
+ if (urlId) {
477
+ this.setIdFilter(id, urlId);
478
+ }
479
+
293
480
  setQueryParams({
294
481
  page,
295
482
  limit,
483
+ id: urlId ? urlId : '',
296
484
  filterModel: Object.keys(filterModel).length > 0 ? JSON.stringify(filterModel) : null,
297
485
  sortModel: sortModel.length > 0 ? JSON.stringify(sortModel) : null,
298
486
  });
487
+
299
488
  setTimeout(async () => {
300
489
  // https://www.ag-grid.com/javascript-data-grid/data-update-transactions/
301
490
 
302
- class RemoveActionGridRenderer {
303
- eGui;
304
- tokens;
305
-
306
- async init(params) {
307
- this.eGui = document.createElement('div');
308
- this.tokens = {};
309
- const { rowIndex } = params;
310
- const { createdAt, updatedAt } = params.data;
311
-
312
- const cellRenderId = getId(this.tokens, `${serviceId}-`);
313
- this.tokens[cellRenderId] = {};
314
-
315
- this.eGui.innerHTML = html` ${await BtnIcon.Render({
316
- label: html`<div class="abs center">
317
- <i class="fas fa-times"></i>
318
- </div> `,
319
- class: `in fll section-mp management-table-btn-mini management-table-btn-remove-${id}-${cellRenderId} ${!params.data._id ? 'hide' : ''}`,
320
- })}`;
321
- setTimeout(() => {
322
- EventsUI.onClick(
323
- `.management-table-btn-remove-${id}-${cellRenderId}`,
324
- async () => {
325
- const confirmResult = await Modal.RenderConfirm({
326
- html: async () => {
327
- return html`
328
- <div class="in section-mp" style="text-align: center">
329
- ${Translate.Render('confirm-delete-item')}
330
- ${Object.keys(params.data).length > 0
331
- ? html`<br />
332
- "${options.defaultColKeyFocus
333
- ? getValueFromJoinString(params.data, options.defaultColKeyFocus)
334
- : params.data[Object.keys(params.data)[0]]}"`
335
- : ''}
336
- </div>
337
- `;
338
- },
339
- id: `delete-${params.data._id}`,
340
- });
341
- if (confirmResult.status !== 'confirm') return;
342
- let result;
343
- if (params.data._id) result = await ServiceProvider.delete({ id: params.data._id });
344
- else result = { status: 'success' };
345
-
346
- NotificationManager.Push({
347
- html: result.status === 'error' ? result.message : Translate.Render('item-success-delete'),
348
- status: result.status,
349
- });
350
- if (result.status === 'success') {
351
- AgGrid.grids[gridId].applyTransaction({ remove: [params.data] });
352
- const token = DefaultManagement.Tokens[id];
353
- // if we are on the last page and we delete the last item, go to the previous page
354
- const newTotal = token.total - 1;
355
- const newTotalPages = Math.ceil(newTotal / token.limit);
356
- if (token.page > newTotalPages && newTotalPages > 0) {
357
- token.page = newTotalPages;
358
- }
359
-
360
- // reload the current page
361
- await DefaultManagement.loadTable(id, { reload: false });
362
- }
363
- },
364
- { context: 'modal' },
365
- );
366
- });
367
- }
368
-
369
- getGui() {
370
- return this.eGui;
371
- }
372
-
373
- refresh(params) {
374
- return true;
375
- }
376
- }
377
-
378
- AgGrid.grids[gridId].setGridOption(
379
- 'columnDefs',
380
- enhancedColumnDefs.concat(
381
- permissions.remove
382
- ? [
383
- {
384
- field: 'remove-action',
385
- headerName: '',
386
- width: 100,
387
- cellRenderer: RemoveActionGridRenderer,
388
- editable: false,
389
- },
390
- ]
391
- : [],
392
- ),
393
- );
394
491
  // Initial loadTable is now called in onGridReady after grid is fully initialized
395
492
  // {
396
493
  // const result = await ServiceProvider.get();
@@ -536,9 +633,21 @@ const DefaultManagement = {
536
633
  const newLimit = parseInt(queryParams.limit, 10) || this.Tokens[id].limit || 10;
537
634
  const newFilterModel = queryParams.filterModel;
538
635
  const newSortModel = queryParams.sortModel;
636
+ const newId = queryParams.id || undefined;
539
637
 
540
638
  let shouldReload = false;
541
639
 
640
+ // Check if id parameter changed
641
+ const currentId = this.getIdFilter(id);
642
+ if (newId !== currentId) {
643
+ if (newId) {
644
+ this.setIdFilter(id, newId);
645
+ } else {
646
+ this.clearIdFilter(id);
647
+ }
648
+ shouldReload = true;
649
+ }
650
+
542
651
  // Check if page or limit changed
543
652
  if (newPage !== this.Tokens[id].page || newLimit !== this.Tokens[id].limit) {
544
653
  this.Tokens[id].page = newPage;
@@ -626,6 +735,47 @@ const DefaultManagement = {
626
735
  },
627
736
  });
628
737
 
738
+ EventsUI.onClick(`.management-table-btn-clear-filter-${id}`, async () => {
739
+ try {
740
+ const gridApi = AgGrid.grids[gridId];
741
+
742
+ // Clear all filters
743
+ DefaultManagement.clearIdFilter(id);
744
+ if (gridApi) {
745
+ gridApi.setFilterModel({});
746
+ gridApi.applyColumnState({ defaultState: { sort: null } });
747
+ }
748
+
749
+ // Clear token state
750
+ if (DefaultManagement.Tokens[id]) {
751
+ DefaultManagement.Tokens[id].filterModel = {};
752
+ DefaultManagement.Tokens[id].sortModel = [];
753
+ }
754
+
755
+ // Update URL - keep only page and limit
756
+ const queryParams = getQueryParams();
757
+ setQueryParams({
758
+ page: queryParams.page || 1,
759
+ limit: queryParams.limit || DefaultManagement.Tokens[id]?.limit || 10,
760
+ filterModel: null,
761
+ sortModel: null,
762
+ id: null,
763
+ });
764
+
765
+ // Reload table
766
+ await DefaultManagement.loadTable(id, { force: true, reload: true });
767
+
768
+ NotificationManager.Push({
769
+ html: Translate.Render('success-clear-filter') || 'Filters cleared',
770
+ status: 'success',
771
+ });
772
+ } catch (error) {
773
+ NotificationManager.Push({
774
+ html: error.message || 'Error clearing filters',
775
+ status: 'error',
776
+ });
777
+ }
778
+ });
629
779
  EventsUI.onClick(`.management-table-btn-reload-${id}`, async () => {
630
780
  try {
631
781
  // Reload data from server
@@ -663,6 +813,7 @@ const DefaultManagement = {
663
813
  const queryParams = getQueryParams();
664
814
  const page = parseInt(queryParams.page) || 1;
665
815
  const limit = parseInt(queryParams.limit) || 10;
816
+ const urlId = queryParams.id || undefined;
666
817
  let filterModel = {};
667
818
  let sortModel = [];
668
819
  try {
@@ -671,17 +822,27 @@ const DefaultManagement = {
671
822
  } catch (e) {}
672
823
 
673
824
  const token = DefaultManagement.Tokens[id];
825
+
674
826
  if (!token) {
675
827
  // Token doesn't exist yet, table hasn't been initialized
676
828
  return;
677
829
  }
678
830
 
679
831
  // Check if state in URL is different from current state
832
+ const currentId = DefaultManagement.getIdFilter(id);
833
+ const isIdChanged = currentId !== urlId;
680
834
  const isPaginationChanged = token.page !== page || token.limit !== limit;
681
835
  const isFilterChanged = JSON.stringify(token.filterModel || {}) !== JSON.stringify(filterModel);
682
836
  const isSortChanged = JSON.stringify(token.sortModel || []) !== JSON.stringify(sortModel);
683
837
 
684
- if (isPaginationChanged || isFilterChanged || isSortChanged) {
838
+ if (isPaginationChanged || isFilterChanged || isSortChanged || isIdChanged) {
839
+ // Update ID filter from query params
840
+ if (urlId) {
841
+ DefaultManagement.setIdFilter(id, urlId);
842
+ } else {
843
+ DefaultManagement.clearIdFilter(id);
844
+ }
845
+
685
846
  token.page = page;
686
847
  token.limit = limit;
687
848
  token.filterModel = filterModel;
@@ -709,8 +870,8 @@ const DefaultManagement = {
709
870
  console.warn('Failed to apply column state:', e);
710
871
  }
711
872
  }
712
- // If only pagination changed, we must load manually because no grid event triggers
713
- if (isPaginationChanged && !isFilterChanged && !isSortChanged) {
873
+ // If pagination or ID changed, and no grid-level filter/sort changed, we must load manually
874
+ if ((isPaginationChanged || isIdChanged) && !isFilterChanged && !isSortChanged) {
714
875
  await DefaultManagement.loadTable(id);
715
876
  }
716
877
  } else if (!gridApi) {
@@ -746,6 +907,15 @@ const DefaultManagement = {
746
907
  label: html`<div class="abs center btn-clean-${id}-label"><i class="fas fa-broom"></i></div> `,
747
908
  type: 'button',
748
909
  })}
910
+ ${await BtnIcon.Render({
911
+ class: `in fll section-mp management-table-btn-mini management-table-btn-clear-filter-${id} ${
912
+ Object.keys(filterModel).length > 0 || sortModel.length > 0 || urlId ? '' : 'hide'
913
+ }`,
914
+ label: html`<div class="abs center btn-clear-filter-${id}-label">
915
+ <i class="fa-solid fa-filter-circle-xmark"></i>
916
+ </div> `,
917
+ type: 'button',
918
+ })}
749
919
  ${await BtnIcon.Render({
750
920
  class: `in fll section-mp management-table-btn-mini management-table-btn-reload-${id} ${
751
921
  permissions.reload ? '' : 'hide'
@@ -760,9 +930,21 @@ const DefaultManagement = {
760
930
  parentModal: options.idModal,
761
931
  usePagination: true,
762
932
  paginationOptions,
763
- customHeightOffset: !permissions.add && !permissions.remove && !permissions.reload ? 50 : 0,
764
- darkTheme,
933
+ customHeightOffset:
934
+ options.customHeightOffset !== undefined
935
+ ? options.customHeightOffset
936
+ : !permissions.add && !permissions.remove && !permissions.reload
937
+ ? 50
938
+ : 0,
939
+ darkTheme: typeof darkTheme !== 'undefined' ? darkTheme : false,
765
940
  gridOptions: {
941
+ columnDefs: finalColumnDefs,
942
+ getRowClass: (params) => {
943
+ const idFilter = DefaultManagement.getIdFilter(id);
944
+ if (idFilter && params.data && params.data._id === idFilter) {
945
+ return 'row-new-highlight';
946
+ }
947
+ },
766
948
  defaultColDef: {
767
949
  flex: 1,
768
950
  editable: true,
@@ -771,8 +953,16 @@ const DefaultManagement = {
771
953
  filter: true,
772
954
  autoHeight: true,
773
955
  },
774
- onGridReady: (params) => {
956
+ onGridReady: async (params) => {
775
957
  this.Tokens[id].gridApi = params.api;
958
+
959
+ if (this.Tokens[id].readyGridEvent) {
960
+ for (const key of Object.keys(this.Tokens[id].readyGridEvent)) {
961
+ await this.Tokens[id].readyGridEvent[key](params);
962
+ }
963
+ }
964
+
965
+ params.api.setGridOption('columnDefs', finalColumnDefs);
776
966
  // Apply initial state from URL
777
967
  const { filterModel, sortModel } = this.Tokens[id];
778
968
  if (filterModel && Object.keys(filterModel).length > 0) {
@@ -798,6 +988,10 @@ const DefaultManagement = {
798
988
  if (this.Tokens[id].isProcessingQueryChange) return;
799
989
  // Reset to page 1 on filter change
800
990
  this.Tokens[id].page = 1;
991
+
992
+ // Update clear filter button visibility
993
+ DefaultManagement.updateClearFilterButtonVisibility(id);
994
+
801
995
  // Create history entry for filter changes
802
996
  DefaultManagement.loadTable(id, { reload: true, force: true, createHistory: true });
803
997
  },
@@ -806,6 +1000,8 @@ const DefaultManagement = {
806
1000
  if (this.Tokens[id].isInitializing) return;
807
1001
  // Skip if we're processing a query change from browser navigation
808
1002
  if (this.Tokens[id].isProcessingQueryChange) return;
1003
+ // Update clear filter button visibility
1004
+ DefaultManagement.updateClearFilterButtonVisibility(id);
809
1005
  // Create history entry for sort changes
810
1006
  DefaultManagement.loadTable(id, { reload: true, force: true, createHistory: true });
811
1007
  },
@@ -881,33 +1077,25 @@ const DefaultManagement = {
881
1077
  status: result.status,
882
1078
  });
883
1079
  if (result.status === 'success') {
1080
+ // Filter by the newly created row's ID
1081
+ const newItemId = result.data._id;
1082
+
1083
+ // Update UI buttons first
1084
+ s(`.management-table-btn-save-${id}`).classList.add('hide');
1085
+ if (permissions.add) s(`.management-table-btn-add-${id}`).classList.remove('hide');
1086
+ if (permissions.remove) s(`.management-table-btn-clean-${id}`).classList.remove('hide');
1087
+ if (permissions.reload) s(`.management-table-btn-reload-${id}`).classList.remove('hide');
1088
+
1089
+ // Stop editing to avoid triggering other events
1090
+ AgGrid.grids[gridId].stopEditing();
1091
+
1092
+ // Set ID filter and reload
884
1093
  this.Tokens[id].page = 1;
885
- await DefaultManagement.loadTable(id);
886
- // const newItemId = result.data?.[entity]?._id || result.data?._id;
887
- // The `event.node.id` is the temporary ID assigned by AG Grid.
888
- // const rowNode = AgGrid.grids[gridId].getRowNode(event.node.id);
889
- setQueryParams({ page: 1, limit: this.Tokens[id].limit });
890
-
891
- let rowNode;
892
- AgGrid.grids[gridId].forEachLeafNode((_rowNode) => {
893
- if (_rowNode.data._id === result.data._id) {
894
- rowNode = _rowNode;
895
- }
1094
+ this.setIdFilter(id, newItemId);
1095
+
1096
+ setTimeout(async () => {
1097
+ await DefaultManagement.loadTable(id, { force: true, createHistory: true });
896
1098
  });
897
- if (rowNode) {
898
- const newRow = columnDefFormatter(result.data, columnDefs, options.customFormat);
899
- // Add a temporary flag to the new row data.
900
- newRow._new = true;
901
- // Update the row data with the data from the server, which includes the new permanent `_id`.
902
- rowNode.setData(newRow);
903
- // The `rowClassRules` will automatically apply the 'row-new-highlight' class.
904
- // Now, remove the flag after a delay to remove the highlight.
905
- // setTimeout(() => {
906
- // delete newRow._new;
907
- // rowNode.setData(newRow);
908
- // }, 2000);
909
- }
910
- s(`.management-table-btn-save-${id}`).click();
911
1099
  }
912
1100
  } else {
913
1101
  // Skip update here - onCellValueChanged already handles auto-save for existing rows