@sd-angular/core 19.0.8 → 19.0.10

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 (112) hide show
  1. package/README.md +1 -1
  2. package/assets/scss/core/utilities/_grid.scss +149 -143
  3. package/components/form-generic/src/components/form-builder/components/attribute-table/attribute-table.component.d.ts +2 -4
  4. package/components/form-generic/src/components/form-builder/components/attribute-template/attribute-template.component.d.ts +2 -4
  5. package/components/form-generic/src/components/form-builder/components/configure-validation/configure-validation.component.d.ts +5 -6
  6. package/components/form-generic/src/components/form-render/form-render.component.d.ts +4 -6
  7. package/components/form-generic/src/services/form-generic.service.d.ts +1 -3
  8. package/components/modal/src/modal.component.d.ts +1 -1
  9. package/components/tab-router/src/models/tab-router.model.d.ts +2 -1
  10. package/components/tab-router/src/services/tab-router.service.d.ts +0 -1
  11. package/components/table/src/components/command/desktop-command.component.d.ts +7 -7
  12. package/components/table/src/components/command/pipes/command.pipe.d.ts +1 -12
  13. package/components/table/src/components/command/pipes/filter.pipe.d.ts +1 -8
  14. package/components/table/src/components/filter/column-filter/column-filter.component.d.ts +1 -1
  15. package/components/table/src/models/table-column.model.d.ts +3 -2
  16. package/components/table/src/pipes/selection-disabled.pipe.d.ts +1 -1
  17. package/components/table/src/services/config.service.d.ts +4 -5
  18. package/components/table/src/services/table-filter/table-filter.service.d.ts +1 -0
  19. package/components/table/src/services/table-local/table-local.util.d.ts +19 -0
  20. package/components/table/src/services/table-reorder/table-reorder.util.d.ts +29 -0
  21. package/components/table/src/services/table-selection/table-selection.util.d.ts +8 -0
  22. package/components/table/src/services/tree/tree.util.d.ts +10 -1
  23. package/components/table/src/table.component.d.ts +6 -10
  24. package/fesm2022/sd-angular-core-components-anchor.mjs +14 -14
  25. package/fesm2022/sd-angular-core-components-anchor.mjs.map +1 -1
  26. package/fesm2022/sd-angular-core-components-autoid-inspector.mjs.map +1 -1
  27. package/fesm2022/sd-angular-core-components-avatar.mjs.map +1 -1
  28. package/fesm2022/sd-angular-core-components-badge.mjs.map +1 -1
  29. package/fesm2022/sd-angular-core-components-button.mjs.map +1 -1
  30. package/fesm2022/sd-angular-core-components-ckeditor-styles.mjs.map +1 -1
  31. package/fesm2022/sd-angular-core-components-code-editor.mjs.map +1 -1
  32. package/fesm2022/sd-angular-core-components-document-builder.mjs +0 -1
  33. package/fesm2022/sd-angular-core-components-document-builder.mjs.map +1 -1
  34. package/fesm2022/sd-angular-core-components-form-generic.mjs +37 -66
  35. package/fesm2022/sd-angular-core-components-form-generic.mjs.map +1 -1
  36. package/fesm2022/sd-angular-core-components-import-excel.mjs.map +1 -1
  37. package/fesm2022/sd-angular-core-components-inform.mjs.map +1 -1
  38. package/fesm2022/sd-angular-core-components-mini-editor.mjs.map +1 -1
  39. package/fesm2022/sd-angular-core-components-modal-resizable.mjs.map +1 -1
  40. package/fesm2022/sd-angular-core-components-modal.mjs.map +1 -1
  41. package/fesm2022/sd-angular-core-components-operator.mjs.map +1 -1
  42. package/fesm2022/sd-angular-core-components-org-chart.mjs.map +1 -1
  43. package/fesm2022/sd-angular-core-components-query-builder.mjs.map +1 -1
  44. package/fesm2022/sd-angular-core-components-quick-action.mjs.map +1 -1
  45. package/fesm2022/sd-angular-core-components-section.mjs.map +1 -1
  46. package/fesm2022/sd-angular-core-components-side-drawer.mjs.map +1 -1
  47. package/fesm2022/sd-angular-core-components-splitter.mjs.map +1 -1
  48. package/fesm2022/sd-angular-core-components-stepper.mjs.map +1 -1
  49. package/fesm2022/sd-angular-core-components-tab-router.mjs +31 -26
  50. package/fesm2022/sd-angular-core-components-tab-router.mjs.map +1 -1
  51. package/fesm2022/sd-angular-core-components-tab.mjs.map +1 -1
  52. package/fesm2022/sd-angular-core-components-table.mjs +785 -833
  53. package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
  54. package/fesm2022/sd-angular-core-components-tree.mjs.map +1 -1
  55. package/fesm2022/sd-angular-core-components-upload-file.mjs.map +1 -1
  56. package/fesm2022/sd-angular-core-directives.mjs +0 -1
  57. package/fesm2022/sd-angular-core-directives.mjs.map +1 -1
  58. package/fesm2022/sd-angular-core-forms-autocomplete.mjs.map +1 -1
  59. package/fesm2022/sd-angular-core-forms-chip-calendar.mjs.map +1 -1
  60. package/fesm2022/sd-angular-core-forms-chip.mjs.map +1 -1
  61. package/fesm2022/sd-angular-core-forms-date-range.mjs +10 -2
  62. package/fesm2022/sd-angular-core-forms-date-range.mjs.map +1 -1
  63. package/fesm2022/sd-angular-core-forms-date.mjs.map +1 -1
  64. package/fesm2022/sd-angular-core-forms-datetime.mjs.map +1 -1
  65. package/fesm2022/sd-angular-core-forms-input-color.mjs.map +1 -1
  66. package/fesm2022/sd-angular-core-forms-input-number.mjs.map +1 -1
  67. package/fesm2022/sd-angular-core-forms-input.mjs.map +1 -1
  68. package/fesm2022/sd-angular-core-forms-radio.mjs.map +1 -1
  69. package/fesm2022/sd-angular-core-forms-select.mjs +45 -12
  70. package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
  71. package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
  72. package/fesm2022/sd-angular-core-interceptors.mjs +0 -2
  73. package/fesm2022/sd-angular-core-interceptors.mjs.map +1 -1
  74. package/fesm2022/sd-angular-core-modules-auth.mjs +14 -38
  75. package/fesm2022/sd-angular-core-modules-auth.mjs.map +1 -1
  76. package/fesm2022/sd-angular-core-modules-layout.mjs +5 -12
  77. package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
  78. package/fesm2022/sd-angular-core-modules-permission.mjs +5 -12
  79. package/fesm2022/sd-angular-core-modules-permission.mjs.map +1 -1
  80. package/fesm2022/sd-angular-core-pipes.mjs +4 -12
  81. package/fesm2022/sd-angular-core-pipes.mjs.map +1 -1
  82. package/fesm2022/sd-angular-core-services-confirm.mjs +16 -21
  83. package/fesm2022/sd-angular-core-services-confirm.mjs.map +1 -1
  84. package/fesm2022/sd-angular-core-services-docx.mjs +1 -8
  85. package/fesm2022/sd-angular-core-services-docx.mjs.map +1 -1
  86. package/fesm2022/sd-angular-core-services-license.mjs +5 -12
  87. package/fesm2022/sd-angular-core-services-license.mjs.map +1 -1
  88. package/fesm2022/sd-angular-core-services-notify.mjs +70 -87
  89. package/fesm2022/sd-angular-core-services-notify.mjs.map +1 -1
  90. package/fesm2022/sd-angular-core-services-storage.mjs +4 -12
  91. package/fesm2022/sd-angular-core-services-storage.mjs.map +1 -1
  92. package/fesm2022/sd-angular-core-utilities-extensions.mjs +43 -1
  93. package/fesm2022/sd-angular-core-utilities-extensions.mjs.map +1 -1
  94. package/forms/date-range/src/date-range.component.d.ts +2 -0
  95. package/forms/select/src/select-footer-action.directive.d.ts +5 -1
  96. package/forms/select/src/select.component.d.ts +4 -0
  97. package/modules/auth/guards/auth.guard.d.ts +2 -4
  98. package/modules/auth/guards/portal.guard.d.ts +2 -4
  99. package/modules/auth/services/auth.service.d.ts +3 -4
  100. package/modules/layout/modules/home/guards/home-redirect.guard.d.ts +3 -5
  101. package/modules/permission/src/guards/permission.guard.d.ts +2 -5
  102. package/package.json +31 -31
  103. package/pipes/src/format-number.pipe.d.ts +1 -3
  104. package/sd-angular-core-19.0.10.tgz +0 -0
  105. package/services/confirm/src/lib/components/dialog-confirm/dialog-confirm.component.d.ts +3 -3
  106. package/services/license/src/license.service.d.ts +2 -3
  107. package/services/notify/src/components/toast/toast.component.d.ts +9 -13
  108. package/services/notify/src/notify.service.d.ts +4 -5
  109. package/services/storage/src/storage.service.d.ts +3 -4
  110. package/utilities/extensions/index.d.ts +1 -0
  111. package/utilities/extensions/src/object.extension.d.ts +8 -0
  112. package/sd-angular-core-19.0.8.tgz +0 -0
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, inject, TemplateRef, Directive, booleanAttribute, computed, ChangeDetectionStrategy, Component, model, output, isSignal, effect, Pipe, ChangeDetectorRef, signal, untracked, viewChild, InjectionToken, Inject, Optional, Injectable, EventEmitter, Input, Output, ViewChild, ElementRef, DestroyRef, afterNextRender, Renderer2, NgZone, contentChild, contentChildren } from '@angular/core';
2
+ import { input, inject, TemplateRef, Directive, booleanAttribute, computed, ChangeDetectionStrategy, Component, model, output, isSignal, effect, Pipe, ChangeDetectorRef, signal, untracked, viewChild, InjectionToken, Injectable, EventEmitter, Input, Output, ViewChild, ElementRef, DestroyRef, afterNextRender, Renderer2, NgZone, contentChild, contentChildren } from '@angular/core';
3
3
  import { trigger, state, style, transition, animate } from '@angular/animations';
4
4
  import * as i4$1 from '@angular/material/paginator';
5
5
  import { MatPaginatorIntl, MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
@@ -8,9 +8,10 @@ import { MatSort, MatSortModule } from '@angular/material/sort';
8
8
  import * as i6 from '@angular/material/table';
9
9
  import { MatTable, MatTableModule } from '@angular/material/table';
10
10
  import * as i1$1 from '@sd-angular/core/services';
11
- import { SdExcelService, SdNotifyService } from '@sd-angular/core/services';
11
+ import { SdStorageService, SdExcelService, SdNotifyService } from '@sd-angular/core/services';
12
12
  import { Subject, fromEvent, Subscription, isObservable, firstValueFrom } from 'rxjs';
13
13
  import { debounceTime, startWith, map, switchMap } from 'rxjs/operators';
14
+ import { DateUtilities, StringUtilities } from '@sd-angular/core/utilities';
14
15
  import { CdkTableModule, CdkColumnDef } from '@angular/cdk/table';
15
16
  import * as i11 from '@angular/cdk/drag-drop';
16
17
  import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
@@ -32,8 +33,8 @@ import { SdButton } from '@sd-angular/core/components/button';
32
33
  import { SdQuickAction } from '@sd-angular/core/components/quick-action';
33
34
  import { SdTooltipDirective, SdScrollDirective, SdDesktopDirective, SdMobileDirective, SdHoverCopyDirective } from '@sd-angular/core/directives';
34
35
  import { SdSafeHtmlPipe, SdFormatNumberPipe } from '@sd-angular/core/pipes';
35
- import { BrowserUtilities, Utilities } from '@sdcorejs/utils/fns';
36
- import { DateUtilities, ArrayUtilities, NumberUtilities } from '@sd-angular/core/utilities/extensions';
36
+ import { Utilities, BrowserUtilities } from '@sdcorejs/utils/fns';
37
+ import { DateUtilities as DateUtilities$1, ArrayUtilities, NumberUtilities } from '@sd-angular/core/utilities/extensions';
37
38
  import { SdBadge } from '@sd-angular/core/components/badge';
38
39
  import { SdOperator } from '@sd-angular/core/components/operator';
39
40
  import { SdInput, SdInputNumber, SdSelect, SdDate, SdDatetime, SdDateRange as SdDateRange$1, SdCheckbox, SdSwitch } from '@sd-angular/core/forms';
@@ -50,7 +51,6 @@ import * as i4 from '@angular/material/button';
50
51
  import { MatButtonModule } from '@angular/material/button';
51
52
  import * as i2$1 from '@angular/material/tooltip';
52
53
  import { MatTooltipModule } from '@angular/material/tooltip';
53
- import { StringUtilities, DateUtilities as DateUtilities$1 } from '@sd-angular/core/utilities';
54
54
  import * as i5$1 from '@angular/material/chips';
55
55
  import { MatChipsModule } from '@angular/material/chips';
56
56
 
@@ -162,6 +162,242 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
162
162
  }]
163
163
  }] });
164
164
 
165
+ const SdConvertToPagingReq = (filterRequest, args) => {
166
+ const { externalFilters, columns, fieldMapping } = args;
167
+ const req = {
168
+ filters: [],
169
+ orders: args?.orders || [],
170
+ pageNumber: filterRequest.pageNumber,
171
+ pageSize: filterRequest.pageSize,
172
+ };
173
+ const { filters } = req;
174
+ const { rawExternalFilter, rawColumnFilter, columnOperator, orderBy, orderDirection } = filterRequest;
175
+ // Xử lý external filter
176
+ for (const externalFilter of externalFilters || []) {
177
+ const value = rawExternalFilter?.[externalFilter.field];
178
+ const field = fieldMapping?.[externalFilter.field] || externalFilter.field;
179
+ // Nếu có giá trị thì mới xử lý filter
180
+ if (value !== undefined && value !== null && value !== '') {
181
+ if (externalFilter.type === 'string') {
182
+ if (externalFilter.defaultOperator === 'EQUAL' && typeof value === 'string' && value.includes(',')) {
183
+ filters.push({
184
+ field,
185
+ operator: 'IN',
186
+ data: value.split(',').map(val => val.trim()),
187
+ });
188
+ }
189
+ else {
190
+ filters.push({
191
+ field,
192
+ operator: externalFilter.defaultOperator || 'CONTAIN',
193
+ data: value,
194
+ });
195
+ }
196
+ }
197
+ else if (externalFilter.type === 'boolean') {
198
+ filters.push({
199
+ field,
200
+ operator: 'EQUAL',
201
+ data: value === true || value === 1 || value === 'true' || value === '1',
202
+ });
203
+ }
204
+ else if (externalFilter.type === 'daterange') {
205
+ if (typeof value === 'object' && 'from' in value && 'to' in value) {
206
+ if (value?.from) {
207
+ filters.push({
208
+ field,
209
+ operator: 'GREATER_OR_EQUAL',
210
+ data: DateUtilities.begin(value?.from).toISOString(),
211
+ });
212
+ }
213
+ if (value?.to) {
214
+ filters.push({
215
+ field,
216
+ operator: 'LESS_THAN',
217
+ data: DateUtilities.begin(DateUtilities.addDays(value?.to, 1)).toISOString(),
218
+ });
219
+ }
220
+ }
221
+ }
222
+ else if (externalFilter.type === 'date' || externalFilter.type === 'datetime') {
223
+ if (typeof value === 'object' && 'from' in value && 'to' in value) {
224
+ if (value?.from) {
225
+ filters.push({
226
+ field,
227
+ operator: 'GREATER_OR_EQUAL',
228
+ data: DateUtilities.begin(value?.from).toISOString(),
229
+ });
230
+ }
231
+ if (value?.to) {
232
+ filters.push({
233
+ field,
234
+ operator: 'LESS_THAN',
235
+ data: DateUtilities.begin(DateUtilities.addDays(value?.to, 1)).toISOString(),
236
+ });
237
+ }
238
+ }
239
+ else if (DateUtilities.isDate(value)) {
240
+ if (externalFilter.type === 'date') {
241
+ if (externalFilter.defaultOperator === 'GREATER_OR_EQUAL') {
242
+ filters.push({
243
+ field,
244
+ operator: 'GREATER_OR_EQUAL',
245
+ data: DateUtilities.begin(value).toISOString(),
246
+ });
247
+ }
248
+ if (externalFilter.defaultOperator === 'LESS_OR_EQUAL') {
249
+ filters.push({
250
+ field,
251
+ operator: 'LESS_THAN',
252
+ data: DateUtilities.begin(DateUtilities.addDays(value, 1)).toISOString(),
253
+ });
254
+ }
255
+ }
256
+ else {
257
+ if (externalFilter.defaultOperator === 'GREATER_OR_EQUAL') {
258
+ filters.push({
259
+ field,
260
+ operator: 'GREATER_OR_EQUAL',
261
+ data: new Date(value).toISOString(),
262
+ });
263
+ }
264
+ if (externalFilter.defaultOperator === 'LESS_OR_EQUAL') {
265
+ filters.push({
266
+ field,
267
+ operator: 'LESS_OR_EQUAL',
268
+ data: new Date(value).toISOString(),
269
+ });
270
+ }
271
+ }
272
+ }
273
+ }
274
+ else {
275
+ if (Array.isArray(value)) {
276
+ if (value.length) {
277
+ filters.push({
278
+ field,
279
+ operator: 'IN',
280
+ data: value,
281
+ });
282
+ }
283
+ }
284
+ else if (typeof value === 'object' && 'from' in value && 'to' in value) {
285
+ if (value?.from) {
286
+ filters.push({
287
+ field,
288
+ operator: 'GREATER_OR_EQUAL',
289
+ data: DateUtilities.begin(value?.from).toISOString(),
290
+ });
291
+ }
292
+ if (value?.to) {
293
+ filters.push({
294
+ field,
295
+ operator: 'LESS_THAN',
296
+ data: DateUtilities.begin(DateUtilities.addDays(value?.to, 1)).toISOString(),
297
+ });
298
+ }
299
+ }
300
+ else {
301
+ filters.push({
302
+ field,
303
+ operator: externalFilter.defaultOperator || 'EQUAL',
304
+ data: value,
305
+ });
306
+ }
307
+ }
308
+ }
309
+ }
310
+ // Xử lý column filter
311
+ for (const column of columns || []) {
312
+ const value = rawColumnFilter?.[column.field];
313
+ const field = fieldMapping?.[column.field] || column.field;
314
+ const operator = columnOperator?.[column.field] || column.filter?.operator?.default;
315
+ // Nếu có giá trị thì mới xử lý filter
316
+ if (value !== undefined && value !== null && value !== '') {
317
+ if (column.type === 'string') {
318
+ filters.push({
319
+ field,
320
+ operator: operator || 'CONTAIN',
321
+ data: value,
322
+ });
323
+ }
324
+ else if (column.type === 'boolean') {
325
+ filters.push({
326
+ field,
327
+ operator: 'EQUAL',
328
+ data: value === true || value === 1 || value === 'true' || value === '1',
329
+ });
330
+ }
331
+ else if (column.type === 'date' || column.type === 'datetime') {
332
+ if (typeof value === 'object' && 'from' in value && 'to' in value) {
333
+ if (value?.from && value?.to) {
334
+ filters.push({
335
+ field,
336
+ operator: 'BETWEEN',
337
+ data: {
338
+ from: DateUtilities.begin(value?.from).toISOString(),
339
+ to: DateUtilities.end(value?.to).toISOString(),
340
+ },
341
+ });
342
+ }
343
+ else if (value?.from) {
344
+ filters.push({
345
+ field,
346
+ operator: 'GREATER_OR_EQUAL',
347
+ data: DateUtilities.begin(value?.from).toISOString(),
348
+ });
349
+ }
350
+ else if (value?.to) {
351
+ filters.push({
352
+ field,
353
+ operator: 'LESS_THAN',
354
+ data: DateUtilities.begin(DateUtilities.addDays(value?.to, 1)).toISOString(),
355
+ });
356
+ }
357
+ }
358
+ else {
359
+ if (DateUtilities.isDate(value)) {
360
+ filters.push({
361
+ field,
362
+ operator: 'BETWEEN',
363
+ data: {
364
+ from: DateUtilities.begin(value).toISOString(),
365
+ to: DateUtilities.end(value).toISOString(),
366
+ },
367
+ });
368
+ }
369
+ }
370
+ }
371
+ else {
372
+ if (Array.isArray(value)) {
373
+ if (value.length) {
374
+ filters.push({
375
+ field,
376
+ operator: 'IN',
377
+ data: value,
378
+ });
379
+ }
380
+ }
381
+ else {
382
+ filters.push({
383
+ field,
384
+ operator: operator || 'EQUAL',
385
+ data: value,
386
+ });
387
+ }
388
+ }
389
+ }
390
+ }
391
+ // Xử lý orders
392
+ if (orderBy && orderDirection) {
393
+ req.orders.push({
394
+ field: orderBy,
395
+ direction: orderDirection,
396
+ });
397
+ }
398
+ return req;
399
+ };
400
+
165
401
  /* eslint-disable @typescript-eslint/no-explicit-any */
166
402
  class ColumnTitleComponent {
167
403
  column = input.required();
@@ -183,6 +419,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
183
419
 
184
420
  /* eslint-disable @typescript-eslint/no-explicit-any */
185
421
  class ColumnFilterComponent {
422
+ #lastOnChangeValueHash;
186
423
  // ==========================================
187
424
  // 1. SIGNAL INPUTS
188
425
  // ==========================================
@@ -234,8 +471,6 @@ class ColumnFilterComponent {
234
471
  });
235
472
  // Chỉ các Operator value cho phép — truyền vào <sd-operator [operators]>.
236
473
  operatorValues = computed(() => this.operators().map(o => o.value));
237
- // Margin wrapper operator: số + đang chọn operator → thêm mb-4 (canh baseline với sd-input-number).
238
- operatorWrapperClass = computed(() => (this.column()?.type === 'number' && this.operator() ? 'mb-4 mr-2' : 'mr-2'));
239
474
  // Items cho sd-select khi column type = values / lazy-values
240
475
  items = computed(() => {
241
476
  const col = this.column();
@@ -269,20 +504,40 @@ class ColumnFilterComponent {
269
504
  if (col.type === 'number' && col.filter?.type === 'split-number') {
270
505
  filter[col.field] = filter[col.field] || { from: null, to: null };
271
506
  }
507
+ this.#lastOnChangeValueHash = this.#filterValueHash(filter[col.field]);
272
508
  });
273
509
  }
274
510
  // ==========================================
275
511
  // 4. HANDLERS
276
512
  // ==========================================
277
- onFilterChange = () => this.filterChange.emit();
513
+ onFilterChange = () => {
514
+ this.#emitFilterOnChange();
515
+ this.filterChange.emit();
516
+ };
278
517
  // Blur input: commit giá trị KHÔNG trigger reload.
279
- onFilterCommit = () => this.filterCommit.emit();
518
+ onFilterCommit = () => {
519
+ this.#emitFilterOnChange();
520
+ this.filterCommit.emit();
521
+ };
522
+ #emitFilterOnChange = () => {
523
+ const col = this.column();
524
+ const filter = this.columnFilter();
525
+ if (!col?.filter?.onChange || !filter)
526
+ return;
527
+ const value = filter[col.field];
528
+ const hash = this.#filterValueHash(value);
529
+ if (hash === this.#lastOnChangeValueHash)
530
+ return;
531
+ this.#lastOnChangeValueHash = hash;
532
+ col.filter.onChange(value, col, filter);
533
+ };
534
+ #filterValueHash = (value) => Utilities.hash({ value });
280
535
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ColumnFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
281
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ColumnFilterComponent, isStandalone: true, selector: "column-filter", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, columnFilter: { classPropertyName: "columnFilter", publicName: "columnFilter", isSignal: true, isRequired: false, transformFunction: null }, cacheValues: { classPropertyName: "cacheValues", publicName: "cacheValues", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, operator: { classPropertyName: "operator", publicName: "operator", isSignal: true, isRequired: false, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { operator: "operatorChange", filterChange: "filterChange", filterCommit: "filterCommit" }, ngImport: i0, template: "@let _column = column();\r\n@let _columnFilter = columnFilter();\r\n@let _autoId = autoId();\r\n@let _templateRef = templateRef();\r\n@let _operators = operators();\r\n@let _items = items();\r\n@let _label = label();\r\n@let _context = { columnFilter: _columnFilter, autoId: _autoId };\r\n\r\n<div class=\"d-flex c-inline-column align-items-end\" style=\"width: 100%; max-width: 100%\">\r\n @if (\r\n _column.type === 'string' ||\r\n _column.type === 'number' ||\r\n _column.type === 'boolean' ||\r\n _column.type === 'values' ||\r\n _column.type === 'lazy-values' ||\r\n _column.type === 'date' ||\r\n _column.type === 'datetime' ||\r\n _column.type === 'time'\r\n ) {\r\n <!-- Operator filter -->\r\n @if (_operators.length) {\r\n <div class=\"d-flex align-items-center\" [ngClass]=\"operatorWrapperClass()\">\r\n <sd-operator [(model)]=\"operator\" [operators]=\"operatorValues()\" />\r\n </div>\r\n }\r\n\r\n <!-- N\u1EBFu filter nh\u1EADn v\u00E0o template -->\r\n @if (_templateRef) {\r\n <ng-container *ngTemplateOutlet=\"_templateRef; context: _context\"></ng-container>\r\n }\r\n <!-- M\u1EB7c \u0111\u1ECBnh theo type v\u00E0 gi\u00E1 tr\u1ECB values defined c\u1EE7a column -->\r\n @else {\r\n @if (_column.type === 'string') {\r\n <sd-input\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n type=\"text\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (keyupEnter)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-input>\r\n } @else if (_column.type === 'number') {\r\n @if (!_column.filter?.type) {\r\n <sd-input-number\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (keyupEnter)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-input-number>\r\n } @else if (_column.filter?.type === 'split-number') {\r\n <ng-container\r\n *ngTemplateOutlet=\"splitNumberTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\r\n </ng-container>\r\n }\r\n } @else if (_column.type === 'boolean') {\r\n <sd-select\r\n minWidthPanel=\"200px\"\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n [style.width]=\"'100%'\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [items]=\"[\r\n { value: '1', display: _column.option?.displayOnTrue || 'True' },\r\n { value: '0', display: _column.option?.displayOnFalse || 'False' },\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n <ng-template sdItemDef let-item=\"item\">\r\n @if (item.value === '1') {\r\n <sd-badge color=\"success\" [title]=\"_column.option?.displayOnTrue || 'True'\"> </sd-badge>\r\n } @else {\r\n <sd-badge color=\"error\" [title]=\"_column.option?.displayOnFalse || 'False'\"> </sd-badge>\r\n }\r\n </ng-template>\r\n </sd-select>\r\n } @else if (_column.type === 'values') {\r\n <sd-select\r\n minWidthPanel=\"200px\"\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n [style.width]=\"'100%'\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [items]=\"_items\"\r\n [valueField]=\"_column.option.valueField\"\r\n [displayField]=\"_column.option.displayField\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\r\n hideInlineError>\r\n </sd-select>\r\n } @else if (_column.type === 'lazy-values') {\r\n <sd-select\r\n minWidthPanel=\"200px\"\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n [style.width]=\"'100%'\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [items]=\"_items\"\r\n [valueField]=\"_column.option.valueField\"\r\n [displayField]=\"_column.option.displayField\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\r\n hideInlineError>\r\n </sd-select>\r\n } @else if (_column.type === 'date' || _column.type === 'datetime' || _column.type === 'time') {\r\n @if (!_column.filter?.type || _column.filter?.type === 'daterange') {\r\n <sd-date-range\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-date-range>\r\n } @else if (_column.filter?.type === 'date') {\r\n <sd-date\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n type=\"date\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-date>\r\n } @else if (_column.filter?.type === 'split-date') {\r\n <ng-container\r\n *ngTemplateOutlet=\"splitDateTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\r\n </ng-container>\r\n }\r\n }\r\n }\r\n } @else {\r\n <sd-input [autoId]=\"_autoId\" style=\"flex: 1 1 0%; min-width: 0\" type=\"text\" size=\"sm\" disabled></sd-input>\r\n }\r\n</div>\r\n\r\n<!-- ================================================ -->\r\n<!-- Split from/to templates (\u0111\u01B0a xu\u1ED1ng cu\u1ED1i file) -->\r\n<!-- ================================================ -->\r\n<ng-template #splitNumberTpl let-field=\"field\" let-disabled=\"disabled\">\r\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\r\n <sd-input-number\r\n [autoId]=\"_autoId + '-from'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[field].from\"\r\n (sdChange)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-input-number>\r\n <div class=\"mx-4\">-</div>\r\n <sd-input-number\r\n [autoId]=\"_autoId + '-to'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [(model)]=\"_columnFilter[field].to\"\r\n (sdChange)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-input-number>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #splitDateTpl let-field=\"field\" let-disabled=\"disabled\">\r\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\r\n <sd-date\r\n [autoId]=\"_autoId + '-from'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n type=\"date\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[field].from\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-date>\r\n <div class=\"mx-4\">-</div>\r\n <sd-date\r\n [autoId]=\"_autoId + '-to'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n type=\"date\"\r\n size=\"sm\"\r\n [(model)]=\"_columnFilter[field].to\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-date>\r\n </div>\r\n</ng-template>\r\n", styles: [":host ::ng-deep .c-inline-column .mat-mdc-text-field-wrapper{background-color:#fff}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: SdOperator, selector: "sd-operator", inputs: ["model", "operators", "disabled", "autoId"], outputs: ["modelChange"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDateRange, selector: "sd-date-range", inputs: ["autoId", "name", "size", "form", "label", "helperText", "hideInlineError", "required", "disabled", "viewed", "clearable", "appearance", "floatLabel", "min", "max", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: SdBadge, selector: "sd-badge", inputs: ["type", "color", "primary", "secondary", "success", "info", "warning", "error", "fontSet", "title", "description", "tooltip", "icon", "size"], outputs: ["click"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
536
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ColumnFilterComponent, isStandalone: true, selector: "column-filter", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, columnFilter: { classPropertyName: "columnFilter", publicName: "columnFilter", isSignal: true, isRequired: false, transformFunction: null }, cacheValues: { classPropertyName: "cacheValues", publicName: "cacheValues", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, operator: { classPropertyName: "operator", publicName: "operator", isSignal: true, isRequired: false, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { operator: "operatorChange", filterChange: "filterChange", filterCommit: "filterCommit" }, ngImport: i0, template: "@let _column = column();\r\n@let _columnFilter = columnFilter();\r\n@let _autoId = autoId();\r\n@let _templateRef = templateRef();\r\n@let _operators = operators();\r\n@let _items = items();\r\n@let _label = label();\r\n@let _context = { columnFilter: _columnFilter, autoId: _autoId };\r\n\r\n<div class=\"d-flex c-inline-column align-items-center\" style=\"width: 100%; max-width: 100%\">\r\n @if (\r\n _column.type === 'string' ||\r\n _column.type === 'number' ||\r\n _column.type === 'boolean' ||\r\n _column.type === 'values' ||\r\n _column.type === 'lazy-values' ||\r\n _column.type === 'date' ||\r\n _column.type === 'datetime' ||\r\n _column.type === 'time'\r\n ) {\r\n <!-- Operator filter -->\r\n @if (_operators.length) {\r\n <sd-operator class=\"mr-4\" [(model)]=\"operator\" [operators]=\"operatorValues()\" />\r\n }\r\n\r\n <!-- N\u1EBFu filter nh\u1EADn v\u00E0o template -->\r\n @if (_templateRef) {\r\n <ng-container *ngTemplateOutlet=\"_templateRef; context: _context\"></ng-container>\r\n }\r\n <!-- M\u1EB7c \u0111\u1ECBnh theo type v\u00E0 gi\u00E1 tr\u1ECB values defined c\u1EE7a column -->\r\n @else {\r\n @if (_column.type === 'string') {\r\n <sd-input\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n type=\"text\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (keyupEnter)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-input>\r\n } @else if (_column.type === 'number') {\r\n @if (!_column.filter?.type) {\r\n <sd-input-number\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (keyupEnter)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-input-number>\r\n } @else if (_column.filter?.type === 'split-number') {\r\n <ng-container *ngTemplateOutlet=\"splitNumberTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\r\n </ng-container>\r\n }\r\n } @else if (_column.type === 'boolean') {\r\n <sd-select\r\n minWidthPanel=\"200px\"\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n [style.width]=\"'100%'\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [items]=\"[\r\n { value: '1', display: _column.option?.displayOnTrue || 'True' },\r\n { value: '0', display: _column.option?.displayOnFalse || 'False' },\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n <ng-template sdItemDef let-item=\"item\">\r\n @if (item.value === '1') {\r\n <sd-badge color=\"success\" [title]=\"_column.option?.displayOnTrue || 'True'\"> </sd-badge>\r\n } @else {\r\n <sd-badge color=\"error\" [title]=\"_column.option?.displayOnFalse || 'False'\"> </sd-badge>\r\n }\r\n </ng-template>\r\n </sd-select>\r\n } @else if (_column.type === 'values') {\r\n <sd-select\r\n minWidthPanel=\"200px\"\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n [style.width]=\"'100%'\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [items]=\"_items\"\r\n [valueField]=\"_column.option.valueField\"\r\n [displayField]=\"_column.option.displayField\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\r\n hideInlineError>\r\n </sd-select>\r\n } @else if (_column.type === 'lazy-values') {\r\n <sd-select\r\n minWidthPanel=\"200px\"\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n [style.width]=\"'100%'\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [items]=\"_items\"\r\n [valueField]=\"_column.option.valueField\"\r\n [displayField]=\"_column.option.displayField\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\r\n hideInlineError>\r\n </sd-select>\r\n } @else if (_column.type === 'date' || _column.type === 'datetime' || _column.type === 'time') {\r\n @if (!_column.filter?.type || _column.filter?.type === 'daterange') {\r\n <sd-date-range\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-date-range>\r\n } @else if (_column.filter?.type === 'date') {\r\n <sd-date\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n type=\"date\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-date>\r\n } @else if (_column.filter?.type === 'split-date') {\r\n <ng-container *ngTemplateOutlet=\"splitDateTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\r\n </ng-container>\r\n }\r\n }\r\n }\r\n } @else {\r\n <sd-input [autoId]=\"_autoId\" style=\"flex: 1 1 0%; min-width: 0\" type=\"text\" size=\"sm\" disabled></sd-input>\r\n }\r\n</div>\r\n\r\n<!-- ================================================ -->\r\n<!-- Split from/to templates (\u0111\u01B0a xu\u1ED1ng cu\u1ED1i file) -->\r\n<!-- ================================================ -->\r\n<ng-template #splitNumberTpl let-field=\"field\" let-disabled=\"disabled\">\r\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\r\n <sd-input-number\r\n [autoId]=\"_autoId + '-from'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[field].from\"\r\n (sdChange)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-input-number>\r\n <div class=\"mx-4\">-</div>\r\n <sd-input-number\r\n [autoId]=\"_autoId + '-to'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [(model)]=\"_columnFilter[field].to\"\r\n (sdChange)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-input-number>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #splitDateTpl let-field=\"field\" let-disabled=\"disabled\">\r\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\r\n <sd-date\r\n [autoId]=\"_autoId + '-from'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n type=\"date\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[field].from\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-date>\r\n <div class=\"mx-4\">-</div>\r\n <sd-date\r\n [autoId]=\"_autoId + '-to'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n type=\"date\"\r\n size=\"sm\"\r\n [(model)]=\"_columnFilter[field].to\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-date>\r\n </div>\r\n</ng-template>\r\n", styles: [":host ::ng-deep .c-inline-column .mat-mdc-text-field-wrapper{background-color:#fff}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: SdOperator, selector: "sd-operator", inputs: ["model", "operators", "disabled", "autoId"], outputs: ["modelChange"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDateRange, selector: "sd-date-range", inputs: ["autoId", "name", "size", "form", "label", "helperText", "hideInlineError", "required", "disabled", "viewed", "clearable", "appearance", "floatLabel", "min", "max", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: SdBadge, selector: "sd-badge", inputs: ["type", "color", "primary", "secondary", "success", "info", "warning", "error", "fontSet", "title", "description", "tooltip", "icon", "size"], outputs: ["click"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
282
537
  }
283
538
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ColumnFilterComponent, decorators: [{
284
539
  type: Component,
285
- args: [{ selector: 'column-filter', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, SdOperator, SdInput, SdInputNumber, SdSelect, SdDate, SdDateRange, SdBadge], template: "@let _column = column();\r\n@let _columnFilter = columnFilter();\r\n@let _autoId = autoId();\r\n@let _templateRef = templateRef();\r\n@let _operators = operators();\r\n@let _items = items();\r\n@let _label = label();\r\n@let _context = { columnFilter: _columnFilter, autoId: _autoId };\r\n\r\n<div class=\"d-flex c-inline-column align-items-end\" style=\"width: 100%; max-width: 100%\">\r\n @if (\r\n _column.type === 'string' ||\r\n _column.type === 'number' ||\r\n _column.type === 'boolean' ||\r\n _column.type === 'values' ||\r\n _column.type === 'lazy-values' ||\r\n _column.type === 'date' ||\r\n _column.type === 'datetime' ||\r\n _column.type === 'time'\r\n ) {\r\n <!-- Operator filter -->\r\n @if (_operators.length) {\r\n <div class=\"d-flex align-items-center\" [ngClass]=\"operatorWrapperClass()\">\r\n <sd-operator [(model)]=\"operator\" [operators]=\"operatorValues()\" />\r\n </div>\r\n }\r\n\r\n <!-- N\u1EBFu filter nh\u1EADn v\u00E0o template -->\r\n @if (_templateRef) {\r\n <ng-container *ngTemplateOutlet=\"_templateRef; context: _context\"></ng-container>\r\n }\r\n <!-- M\u1EB7c \u0111\u1ECBnh theo type v\u00E0 gi\u00E1 tr\u1ECB values defined c\u1EE7a column -->\r\n @else {\r\n @if (_column.type === 'string') {\r\n <sd-input\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n type=\"text\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (keyupEnter)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-input>\r\n } @else if (_column.type === 'number') {\r\n @if (!_column.filter?.type) {\r\n <sd-input-number\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (keyupEnter)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-input-number>\r\n } @else if (_column.filter?.type === 'split-number') {\r\n <ng-container\r\n *ngTemplateOutlet=\"splitNumberTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\r\n </ng-container>\r\n }\r\n } @else if (_column.type === 'boolean') {\r\n <sd-select\r\n minWidthPanel=\"200px\"\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n [style.width]=\"'100%'\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [items]=\"[\r\n { value: '1', display: _column.option?.displayOnTrue || 'True' },\r\n { value: '0', display: _column.option?.displayOnFalse || 'False' },\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n <ng-template sdItemDef let-item=\"item\">\r\n @if (item.value === '1') {\r\n <sd-badge color=\"success\" [title]=\"_column.option?.displayOnTrue || 'True'\"> </sd-badge>\r\n } @else {\r\n <sd-badge color=\"error\" [title]=\"_column.option?.displayOnFalse || 'False'\"> </sd-badge>\r\n }\r\n </ng-template>\r\n </sd-select>\r\n } @else if (_column.type === 'values') {\r\n <sd-select\r\n minWidthPanel=\"200px\"\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n [style.width]=\"'100%'\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [items]=\"_items\"\r\n [valueField]=\"_column.option.valueField\"\r\n [displayField]=\"_column.option.displayField\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\r\n hideInlineError>\r\n </sd-select>\r\n } @else if (_column.type === 'lazy-values') {\r\n <sd-select\r\n minWidthPanel=\"200px\"\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n [style.width]=\"'100%'\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [items]=\"_items\"\r\n [valueField]=\"_column.option.valueField\"\r\n [displayField]=\"_column.option.displayField\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\r\n hideInlineError>\r\n </sd-select>\r\n } @else if (_column.type === 'date' || _column.type === 'datetime' || _column.type === 'time') {\r\n @if (!_column.filter?.type || _column.filter?.type === 'daterange') {\r\n <sd-date-range\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-date-range>\r\n } @else if (_column.filter?.type === 'date') {\r\n <sd-date\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n type=\"date\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-date>\r\n } @else if (_column.filter?.type === 'split-date') {\r\n <ng-container\r\n *ngTemplateOutlet=\"splitDateTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\r\n </ng-container>\r\n }\r\n }\r\n }\r\n } @else {\r\n <sd-input [autoId]=\"_autoId\" style=\"flex: 1 1 0%; min-width: 0\" type=\"text\" size=\"sm\" disabled></sd-input>\r\n }\r\n</div>\r\n\r\n<!-- ================================================ -->\r\n<!-- Split from/to templates (\u0111\u01B0a xu\u1ED1ng cu\u1ED1i file) -->\r\n<!-- ================================================ -->\r\n<ng-template #splitNumberTpl let-field=\"field\" let-disabled=\"disabled\">\r\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\r\n <sd-input-number\r\n [autoId]=\"_autoId + '-from'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[field].from\"\r\n (sdChange)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-input-number>\r\n <div class=\"mx-4\">-</div>\r\n <sd-input-number\r\n [autoId]=\"_autoId + '-to'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [(model)]=\"_columnFilter[field].to\"\r\n (sdChange)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-input-number>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #splitDateTpl let-field=\"field\" let-disabled=\"disabled\">\r\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\r\n <sd-date\r\n [autoId]=\"_autoId + '-from'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n type=\"date\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[field].from\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-date>\r\n <div class=\"mx-4\">-</div>\r\n <sd-date\r\n [autoId]=\"_autoId + '-to'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n type=\"date\"\r\n size=\"sm\"\r\n [(model)]=\"_columnFilter[field].to\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-date>\r\n </div>\r\n</ng-template>\r\n", styles: [":host ::ng-deep .c-inline-column .mat-mdc-text-field-wrapper{background-color:#fff}\n"] }]
540
+ args: [{ selector: 'column-filter', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, SdOperator, SdInput, SdInputNumber, SdSelect, SdDate, SdDateRange, SdBadge], template: "@let _column = column();\r\n@let _columnFilter = columnFilter();\r\n@let _autoId = autoId();\r\n@let _templateRef = templateRef();\r\n@let _operators = operators();\r\n@let _items = items();\r\n@let _label = label();\r\n@let _context = { columnFilter: _columnFilter, autoId: _autoId };\r\n\r\n<div class=\"d-flex c-inline-column align-items-center\" style=\"width: 100%; max-width: 100%\">\r\n @if (\r\n _column.type === 'string' ||\r\n _column.type === 'number' ||\r\n _column.type === 'boolean' ||\r\n _column.type === 'values' ||\r\n _column.type === 'lazy-values' ||\r\n _column.type === 'date' ||\r\n _column.type === 'datetime' ||\r\n _column.type === 'time'\r\n ) {\r\n <!-- Operator filter -->\r\n @if (_operators.length) {\r\n <sd-operator class=\"mr-4\" [(model)]=\"operator\" [operators]=\"operatorValues()\" />\r\n }\r\n\r\n <!-- N\u1EBFu filter nh\u1EADn v\u00E0o template -->\r\n @if (_templateRef) {\r\n <ng-container *ngTemplateOutlet=\"_templateRef; context: _context\"></ng-container>\r\n }\r\n <!-- M\u1EB7c \u0111\u1ECBnh theo type v\u00E0 gi\u00E1 tr\u1ECB values defined c\u1EE7a column -->\r\n @else {\r\n @if (_column.type === 'string') {\r\n <sd-input\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n type=\"text\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (keyupEnter)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-input>\r\n } @else if (_column.type === 'number') {\r\n @if (!_column.filter?.type) {\r\n <sd-input-number\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (keyupEnter)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-input-number>\r\n } @else if (_column.filter?.type === 'split-number') {\r\n <ng-container *ngTemplateOutlet=\"splitNumberTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\r\n </ng-container>\r\n }\r\n } @else if (_column.type === 'boolean') {\r\n <sd-select\r\n minWidthPanel=\"200px\"\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n [style.width]=\"'100%'\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [items]=\"[\r\n { value: '1', display: _column.option?.displayOnTrue || 'True' },\r\n { value: '0', display: _column.option?.displayOnFalse || 'False' },\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n <ng-template sdItemDef let-item=\"item\">\r\n @if (item.value === '1') {\r\n <sd-badge color=\"success\" [title]=\"_column.option?.displayOnTrue || 'True'\"> </sd-badge>\r\n } @else {\r\n <sd-badge color=\"error\" [title]=\"_column.option?.displayOnFalse || 'False'\"> </sd-badge>\r\n }\r\n </ng-template>\r\n </sd-select>\r\n } @else if (_column.type === 'values') {\r\n <sd-select\r\n minWidthPanel=\"200px\"\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n [style.width]=\"'100%'\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [items]=\"_items\"\r\n [valueField]=\"_column.option.valueField\"\r\n [displayField]=\"_column.option.displayField\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\r\n hideInlineError>\r\n </sd-select>\r\n } @else if (_column.type === 'lazy-values') {\r\n <sd-select\r\n minWidthPanel=\"200px\"\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n [style.width]=\"'100%'\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [items]=\"_items\"\r\n [valueField]=\"_column.option.valueField\"\r\n [displayField]=\"_column.option.displayField\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\r\n hideInlineError>\r\n </sd-select>\r\n } @else if (_column.type === 'date' || _column.type === 'datetime' || _column.type === 'time') {\r\n @if (!_column.filter?.type || _column.filter?.type === 'daterange') {\r\n <sd-date-range\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-date-range>\r\n } @else if (_column.filter?.type === 'date') {\r\n <sd-date\r\n [autoId]=\"_autoId\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n type=\"date\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[_column.field]\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"_column.filter?.disabled\"\r\n hideInlineError>\r\n </sd-date>\r\n } @else if (_column.filter?.type === 'split-date') {\r\n <ng-container *ngTemplateOutlet=\"splitDateTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\r\n </ng-container>\r\n }\r\n }\r\n }\r\n } @else {\r\n <sd-input [autoId]=\"_autoId\" style=\"flex: 1 1 0%; min-width: 0\" type=\"text\" size=\"sm\" disabled></sd-input>\r\n }\r\n</div>\r\n\r\n<!-- ================================================ -->\r\n<!-- Split from/to templates (\u0111\u01B0a xu\u1ED1ng cu\u1ED1i file) -->\r\n<!-- ================================================ -->\r\n<ng-template #splitNumberTpl let-field=\"field\" let-disabled=\"disabled\">\r\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\r\n <sd-input-number\r\n [autoId]=\"_autoId + '-from'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[field].from\"\r\n (sdChange)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-input-number>\r\n <div class=\"mx-4\">-</div>\r\n <sd-input-number\r\n [autoId]=\"_autoId + '-to'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n size=\"sm\"\r\n [(model)]=\"_columnFilter[field].to\"\r\n (sdChange)=\"onFilterChange()\"\r\n (sdBlur)=\"onFilterCommit()\"\r\n (cleared)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-input-number>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #splitDateTpl let-field=\"field\" let-disabled=\"disabled\">\r\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\r\n <sd-date\r\n [autoId]=\"_autoId + '-from'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n type=\"date\"\r\n size=\"sm\"\r\n [label]=\"_label\"\r\n [(model)]=\"_columnFilter[field].from\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-date>\r\n <div class=\"mx-4\">-</div>\r\n <sd-date\r\n [autoId]=\"_autoId + '-to'\"\r\n style=\"flex: 1 1 0%; min-width: 0\"\r\n type=\"date\"\r\n size=\"sm\"\r\n [(model)]=\"_columnFilter[field].to\"\r\n (sdChange)=\"onFilterChange()\"\r\n [disabled]=\"disabled\"\r\n hideInlineError>\r\n </sd-date>\r\n </div>\r\n</ng-template>\r\n", styles: [":host ::ng-deep .c-inline-column .mat-mdc-text-field-wrapper{background-color:#fff}\n"] }]
286
541
  }], ctorParameters: () => [] });
287
542
 
288
543
  // Đối với filter loại values, items có thể là 1 mảng, có thể là 1 hàm, do đó cần xử lý nếu là hàm có thể xử lý gọi API
@@ -335,7 +590,7 @@ const getChildrenFromData = (data, option) => {
335
590
  * mặc định nhánh chưa nạp).
336
591
  */
337
592
  const resolveDefaultExpanded = (level, option) => {
338
- const def = option?.loadType === 'static' ? option.defaultExpanded ?? false : false;
593
+ const def = option?.loadType === 'static' ? (option.defaultExpanded ?? false) : false;
339
594
  if (def === true)
340
595
  return true;
341
596
  if (def === false)
@@ -391,6 +646,48 @@ const subtreeMatches = (data, predicate, option, visited = new Set()) => {
391
646
  * bị mất khi từ khoá search đổi. Đây là bước "prune" của tính năng search-con.
392
647
  */
393
648
  const filterMatchingChildren = (children, predicate, option) => children.filter(child => subtreeMatches(child, predicate, option));
649
+ const getVisibleChildrenData = (data, option, predicate) => {
650
+ const children = getChildrenFromData(data, option);
651
+ return predicate ? filterMatchingChildren(children, predicate, option) : children;
652
+ };
653
+ const saveTreeExpandState = (rows, expandState) => {
654
+ for (const row of rows) {
655
+ if (row.meta.tree?.isExpanded) {
656
+ expandState.set(row.meta.id, true);
657
+ }
658
+ const children = row.meta.tree?.childItems;
659
+ if (children?.length) {
660
+ saveTreeExpandState(children, expandState);
661
+ }
662
+ }
663
+ };
664
+ const clearTreeChildCache = (rows) => {
665
+ for (const row of rows) {
666
+ const children = row.meta.tree?.childItems;
667
+ if (children?.length) {
668
+ clearTreeChildCache(children);
669
+ row.meta.tree.childItems = undefined;
670
+ }
671
+ }
672
+ };
673
+ const initTreeMeta = (rows, option, args = {}) => {
674
+ const { expandState, treeSearchPredicate, level = 0, parentId } = args;
675
+ const searchActive = !!treeSearchPredicate;
676
+ for (const row of rows) {
677
+ const saved = expandState?.get(row.meta.id);
678
+ const hasChildren = searchActive
679
+ ? getVisibleChildrenData(row.data, option, treeSearchPredicate).length > 0
680
+ : resolveHasChildren(row, option);
681
+ row.meta.tree = {
682
+ ...row.meta.tree,
683
+ level,
684
+ parentId,
685
+ hasChildren,
686
+ isExpanded: searchActive ? hasChildren : (saved ?? resolveDefaultExpanded(level, option)),
687
+ isExpanding: false,
688
+ };
689
+ }
690
+ };
394
691
  const flattenTree = (roots, option, visited = new Set()) => {
395
692
  if (!option)
396
693
  return roots;
@@ -857,8 +1154,6 @@ const DEFAULT_TABLE_CONFIG = {
857
1154
  const SD_TABLE_CONFIGURATION = new InjectionToken('sd-table.configuration');
858
1155
 
859
1156
  class ConfigService {
860
- storageService;
861
- tableConfiguration;
862
1157
  #COLUMNS = {
863
1158
  SUBINFORMATION: 'sdSubInformationAction',
864
1159
  COMMAND: 'sdCommand',
@@ -872,10 +1167,8 @@ class ConfigService {
872
1167
  #storage;
873
1168
  #widthChange = new Subject();
874
1169
  widthChange$ = this.#widthChange.asObservable();
875
- constructor(storageService, tableConfiguration) {
876
- this.storageService = storageService;
877
- this.tableConfiguration = tableConfiguration;
878
- }
1170
+ storageService = inject(SdStorageService);
1171
+ tableConfiguration = inject(SD_TABLE_CONFIGURATION, { optional: true });
879
1172
  #loadConfiguredTable = (option) => {
880
1173
  // Nếu không có key thì không lấy được setting
881
1174
  if (!option?.key) {
@@ -1064,17 +1357,12 @@ class ConfigService {
1064
1357
  columns,
1065
1358
  };
1066
1359
  };
1067
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ConfigService, deps: [{ token: i1$1.SdStorageService }, { token: SD_TABLE_CONFIGURATION, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
1360
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1068
1361
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ConfigService });
1069
1362
  }
1070
1363
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ConfigService, decorators: [{
1071
1364
  type: Injectable
1072
- }], ctorParameters: () => [{ type: i1$1.SdStorageService }, { type: undefined, decorators: [{
1073
- type: Inject,
1074
- args: [SD_TABLE_CONFIGURATION]
1075
- }, {
1076
- type: Optional
1077
- }] }] });
1365
+ }] });
1078
1366
 
1079
1367
  /* eslint-disable @typescript-eslint/no-explicit-any */
1080
1368
  // eslint-disable-next-line @angular-eslint/component-class-suffix
@@ -1304,18 +1592,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
1304
1592
  }], ctorParameters: () => [] });
1305
1593
 
1306
1594
  /**
1307
- * Pipe gộp resolve metadata của 1 command theo row hiện tại return 1 object
1308
- * chứa `disabled`, `title`, `icon`, và `htmlTemplate` (khi command là child có
1309
- * htmlTemplate). Một lần gọi cho cả 4 truy vấn, template dùng `@let` để alias.
1310
- *
1311
- * Trước đây tách thành 3 pipe `commandDisable` / `commandTitle` / `commandIcon`.
1312
- *
1313
- * @example
1314
- * @let meta = item | command:cmd;
1315
- * <button [disabled]="meta.disabled" [matTooltip]="meta.title">
1316
- * <mat-icon>{{ meta.icon }}</mat-icon>
1317
- * <div [innerHTML]="meta.htmlTemplate"></div>
1318
- * </button>
1595
+ * Resolves command metadata for the current row once, then lets the template reuse it with `@let`.
1319
1596
  */
1320
1597
  class CommandPipe {
1321
1598
  transform(item, command) {
@@ -1342,8 +1619,7 @@ class CommandPipe {
1342
1619
  return '';
1343
1620
  return typeof command.icon === 'string' ? command.icon : command.icon(item.data);
1344
1621
  };
1345
- // Chỉ child command (SdTableCommandNormal) mới htmlTemplate.
1346
- // Parent (SdTableCommandChildren) không khai báo — trả về undefined.
1622
+ // Only child commands can provide htmlTemplate.
1347
1623
  #htmlTemplate = (item, command) => {
1348
1624
  const tpl = command?.htmlTemplate;
1349
1625
  return typeof tpl === 'function' ? tpl(item.data) : undefined;
@@ -1353,18 +1629,11 @@ class CommandPipe {
1353
1629
  }
1354
1630
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: CommandPipe, decorators: [{
1355
1631
  type: Pipe,
1356
- args: [{ name: 'command' }]
1632
+ args: [{ name: 'command', standalone: true }]
1357
1633
  }] });
1358
1634
 
1359
1635
  /**
1360
- * Lọc danh sách commands theo `hidden` (sync / async) + pre-resolve `icon`, `title`, `key`
1361
- * cho từng row. Output dùng trực tiếp trong template không cần thêm pipe phụ.
1362
- *
1363
- * Đã rename từ `commandFilter` → `filter` sau khi gom toàn bộ pipe command vào
1364
- * `components/command/pipes/`.
1365
- *
1366
- * @example
1367
- * @let filteredCommands = item | filter:commands | async;
1636
+ * Filters visible commands and pre-resolves icon, title, and stable key for the current row.
1368
1637
  */
1369
1638
  class CommandFilterPipe {
1370
1639
  async transform(item, commands) {
@@ -1431,7 +1700,7 @@ class CommandFilterPipe {
1431
1700
  return '';
1432
1701
  return typeof command.title === 'string' ? command.title : command.title(item.data);
1433
1702
  };
1434
- // Key dùng để gán cho autoId cần stable theo (icon, title).
1703
+ // Stable autoId key generated from resolved icon and title.
1435
1704
  #key = (item, command) => {
1436
1705
  const icon = this.#icon(item, command);
1437
1706
  const title = this.#title(item, command);
@@ -1442,42 +1711,34 @@ class CommandFilterPipe {
1442
1711
  }
1443
1712
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: CommandFilterPipe, decorators: [{
1444
1713
  type: Pipe,
1445
- args: [{ name: 'filter' }]
1714
+ args: [{ name: 'filter', standalone: true }]
1446
1715
  }] });
1447
1716
 
1448
1717
  // eslint-disable-next-line @angular-eslint/component-class-suffix
1449
1718
  class DesktopCommand {
1450
- // ==========================================
1451
- // 1. SIGNAL INPUTS
1452
- // ==========================================
1453
1719
  autoIdInput = input(undefined, { alias: 'autoId' });
1454
1720
  item = input.required();
1455
1721
  itemIndex = input.required();
1456
1722
  commands = input([]);
1457
- // ==========================================
1458
- // 2. COMPUTED
1459
- // ==========================================
1460
- // Key định danh row dùng để build autoId: ưu tiên id → code → value → fallback itemIndex.
1723
+ // Row key for autoId: prefer id, then code, then value, then itemIndex.
1461
1724
  itemKey = computed(() => {
1462
1725
  const data = this.item()?.data;
1463
1726
  return data?.['id']?.toString() || data?.['code']?.toString() || data?.['value']?.toString() || this.itemIndex().toString();
1464
1727
  });
1465
- // autoId của host: `<base>-command-<itemKey>` đã unique theo row.
1466
- // Template ghép tiếp `-<commandKey>` (và `-<childKey>` nếu child) cho mỗi button.
1728
+ // Host autoId: `<base>-command-<itemKey>`. Template appends command and child keys.
1467
1729
  autoId = computed(() => {
1468
1730
  const base = this.autoIdInput();
1469
1731
  return base ? `${base}-command-${this.itemKey()}` : '';
1470
1732
  });
1471
- // Commands signal (mặc định mảng rỗng).
1472
1733
  _commands = computed(() => this.commands() || []);
1473
1734
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: DesktopCommand, deps: [], target: i0.ɵɵFactoryTarget.Component });
1474
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: DesktopCommand, isStandalone: true, selector: "desktop-command", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, itemIndex: { classPropertyName: "itemIndex", publicName: "itemIndex", isSignal: true, isRequired: true, transformFunction: null }, commands: { classPropertyName: "commands", publicName: "commands", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-autoid": "autoId()" } }, ngImport: i0, template: "@let _autoId = autoId();\n@let _item = item();\n@let filteredCommands = _item | filter: _commands() | async;\n\n@if (filteredCommands?.length) {\n @for (command of filteredCommands; track command.key) {\n @let _meta = _item | command: command;\n @if (command.type === 'normal') {\n <button\n [attr.data-autoid]=\"_autoId + '-' + command.key\"\n (click)=\"command.click(_item.data)\"\n [matTooltip]=\"_meta.title\"\n aria-hidden=\"true\"\n [disabled]=\"_meta.disabled\"\n mat-icon-button>\n <mat-icon class=\"c-icon\" [color]=\"command.color || 'secondary'\" [fontSet]=\"command.fontSet!\">\n {{ _meta.icon }}\n </mat-icon>\n </button>\n } @else {\n <button\n [attr.data-autoid]=\"_autoId + '-' + command.key\"\n [matMenuTriggerFor]=\"menu\"\n aria-hidden=\"true\"\n mat-icon-button>\n @if (_meta.icon) {\n <mat-icon class=\"c-icon\" [fontSet]=\"command.fontSet!\">{{ _meta.icon }}</mat-icon>\n } @else {\n <mat-icon class=\"c-icon\">more_vert</mat-icon>\n }\n </button>\n <mat-menu #menu=\"matMenu\" [attr.data-autoid]=\"_autoId + '-' + command.key + '-menu'\">\n <span>\n @for (childCommand of command.children; track childCommand.key) {\n @let _childMeta = _item | command: childCommand;\n <button\n [attr.data-autoid]=\"_autoId + '-' + command.key + '-' + childCommand.key\"\n mat-menu-item\n (click)=\"childCommand.click(_item.data)\"\n [disabled]=\"_childMeta.disabled\">\n @if (_childMeta.htmlTemplate) {\n <div [innerHTML]=\"_childMeta.htmlTemplate\"></div>\n } @else {\n <div>\n <mat-icon [color]=\"childCommand.color || 'secondary'\" [fontSet]=\"command.fontSet!\">\n {{ _childMeta.icon }}\n </mat-icon>\n <span> {{ _childMeta.title }}</span>\n </div>\n }\n </button>\n }\n </span>\n </mat-menu>\n }\n }\n}\n", styles: [".text-primary{color:var(--sd-primary)!important}.bg-primary{background:var(--sd-primary)!important}.border-primary{border-color:var(--sd-primary)!important}.text-primary-light{color:var(--sd-primary-light)!important}.bg-primary-light{background:var(--sd-primary-light)!important}.border-primary-light{border-color:var(--sd-primary-light)!important}.text-primary-dark{color:var(--sd-primary-dark)!important}.bg-primary-dark{background:var(--sd-primary-dark)!important}.border-primary-dark{border-color:var(--sd-primary-dark)!important}.text-info{color:var(--sd-info)!important}.bg-info{background:var(--sd-info)!important}.border-info{border-color:var(--sd-info)!important}.text-info-light{color:var(--sd-info-light)!important}.bg-info-light{background:var(--sd-info-light)!important}.border-info-light{border-color:var(--sd-info-light)!important}.text-info-dark{color:var(--sd-info-dark)!important}.bg-info-dark{background:var(--sd-info-dark)!important}.border-info-dark{border-color:var(--sd-info-dark)!important}.text-success{color:var(--sd-success)!important}.bg-success{background:var(--sd-success)!important}.border-success{border-color:var(--sd-success)!important}.text-success-light{color:var(--sd-success-light)!important}.bg-success-light{background:var(--sd-success-light)!important}.border-success-light{border-color:var(--sd-success-light)!important}.text-success-dark{color:var(--sd-success-dark)!important}.bg-success-dark{background:var(--sd-success-dark)!important}.border-success-dark{border-color:var(--sd-success-dark)!important}.text-warning{color:var(--sd-warning)!important}.bg-warning{background:var(--sd-warning)!important}.border-warning{border-color:var(--sd-warning)!important}.text-warning-light{color:var(--sd-warning-light)!important}.bg-warning-light{background:var(--sd-warning-light)!important}.border-warning-light{border-color:var(--sd-warning-light)!important}.text-warning-dark{color:var(--sd-warning-dark)!important}.bg-warning-dark{background:var(--sd-warning-dark)!important}.border-warning-dark{border-color:var(--sd-warning-dark)!important}.text-error{color:var(--sd-error)!important}.bg-error{background:var(--sd-error)!important}.border-error{border-color:var(--sd-error)!important}.text-error-light{color:var(--sd-error-light)!important}.bg-error-light{background:var(--sd-error-light)!important}.border-error-light{border-color:var(--sd-error-light)!important}.text-error-dark{color:var(--sd-error-dark)!important}.bg-error-dark{background:var(--sd-error-dark)!important}.border-error-dark{border-color:var(--sd-error-dark)!important}.text-secondary{color:var(--sd-secondary)!important}.bg-secondary{background:var(--sd-secondary)!important}.border-secondary{border-color:var(--sd-secondary)!important}.text-secondary-light{color:var(--sd-secondary-light)!important}.bg-secondary-light{background:var(--sd-secondary-light)!important}.border-secondary-light{border-color:var(--sd-secondary-light)!important}.text-secondary-dark{color:var(--sd-secondary-dark)!important}.bg-secondary-dark{background:var(--sd-secondary-dark)!important}.border-secondary-dark{border-color:var(--sd-secondary-dark)!important}.text-light{color:var(--sd-light)!important}.bg-light{background:var(--sd-light)!important}.border-light{border-color:var(--sd-light)!important}.text-dark{color:var(--sd-dark)!important}.bg-dark{background:var(--sd-dark)!important}.border-dark{border-color:var(--sd-dark)!important}.text-black500{color:var(--sd-black500)!important}.bg-black500{background:var(--sd-black500)!important}.border-black500{border-color:var(--sd-black500)!important}.text-black400{color:var(--sd-black400)!important}.bg-black400{background:var(--sd-black400)!important}.border-black400{border-color:var(--sd-black400)!important}.text-black300{color:var(--sd-black300)!important}.bg-black300{background:var(--sd-black300)!important}.border-black300{border-color:var(--sd-black300)!important}.text-black200{color:var(--sd-black200)!important}.bg-black200{background:var(--sd-black200)!important}.border-black200{border-color:var(--sd-black200)!important}.text-black100{color:var(--sd-black100)!important}.bg-black100{background:var(--sd-black100)!important}.border-black100{border-color:var(--sd-black100)!important}.text-white{color:#fff!important}.bg-white{background:#fff!important}.border-white{border-color:#fff!important}.text-black{color:#000!important}.bg-black{background:#000!important}.border-black{border-color:#000!important}:host{display:flex;align-items:center}button[mat-icon-button]{width:24px;height:24px;line-height:24px;padding:0}mat-icon.c-icon{color:var(--sd-black400)!important;width:16px;height:16px;font-size:16px;line-height:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2$1.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i2.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: CommandPipe, name: "command" }, { kind: "pipe", type: CommandFilterPipe, name: "filter" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1735
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: DesktopCommand, isStandalone: true, selector: "desktop-command", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, itemIndex: { classPropertyName: "itemIndex", publicName: "itemIndex", isSignal: true, isRequired: true, transformFunction: null }, commands: { classPropertyName: "commands", publicName: "commands", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-autoid": "autoId()" } }, ngImport: i0, template: "@let _autoId = autoId();\n@let _item = item();\n@let filteredCommands = _item | filter: _commands() | async;\n\n@if (filteredCommands?.length) {\n @for (command of filteredCommands; track command.key) {\n @let _meta = _item | command: command;\n @if (command.type === 'normal') {\n <button\n [attr.data-autoid]=\"_autoId + '-' + command.key\"\n (click)=\"command.click(_item.data)\"\n [matTooltip]=\"_meta.title\"\n aria-hidden=\"true\"\n [disabled]=\"_meta.disabled\"\n mat-icon-button>\n <mat-icon class=\"c-icon\" [color]=\"command.color || 'secondary'\" [fontSet]=\"command.fontSet!\">\n {{ _meta.icon }}\n </mat-icon>\n </button>\n } @else {\n <button\n [attr.data-autoid]=\"_autoId + '-' + command.key\"\n [matMenuTriggerFor]=\"menu\"\n aria-hidden=\"true\"\n mat-icon-button>\n @if (_meta.icon) {\n <mat-icon class=\"c-icon\" [fontSet]=\"command.fontSet!\">{{ _meta.icon }}</mat-icon>\n } @else {\n <mat-icon class=\"c-icon\">more_vert</mat-icon>\n }\n </button>\n <mat-menu #menu=\"matMenu\" [attr.data-autoid]=\"_autoId + '-' + command.key + '-menu'\">\n <span>\n @for (childCommand of command.children; track childCommand.key) {\n @let _childMeta = _item | command: childCommand;\n <button\n [attr.data-autoid]=\"_autoId + '-' + command.key + '-' + childCommand.key\"\n mat-menu-item\n (click)=\"childCommand.click(_item.data)\"\n [disabled]=\"_childMeta.disabled\">\n @if (_childMeta.htmlTemplate) {\n <div [innerHTML]=\"_childMeta.htmlTemplate\"></div>\n } @else {\n <span class=\"sd-command-menu-item__content\">\n <mat-icon class=\"sd-command-menu-item__icon\" [color]=\"childCommand.color || 'secondary'\" [fontSet]=\"command.fontSet!\">\n {{ _childMeta.icon }}\n </mat-icon>\n <span class=\"sd-command-menu-item__title\">{{ _childMeta.title }}</span>\n </span>\n }\n </button>\n }\n </span>\n </mat-menu>\n }\n }\n}\n", styles: [".text-primary{color:var(--sd-primary)!important}.bg-primary{background:var(--sd-primary)!important}.border-primary{border-color:var(--sd-primary)!important}.text-primary-light{color:var(--sd-primary-light)!important}.bg-primary-light{background:var(--sd-primary-light)!important}.border-primary-light{border-color:var(--sd-primary-light)!important}.text-primary-dark{color:var(--sd-primary-dark)!important}.bg-primary-dark{background:var(--sd-primary-dark)!important}.border-primary-dark{border-color:var(--sd-primary-dark)!important}.text-info{color:var(--sd-info)!important}.bg-info{background:var(--sd-info)!important}.border-info{border-color:var(--sd-info)!important}.text-info-light{color:var(--sd-info-light)!important}.bg-info-light{background:var(--sd-info-light)!important}.border-info-light{border-color:var(--sd-info-light)!important}.text-info-dark{color:var(--sd-info-dark)!important}.bg-info-dark{background:var(--sd-info-dark)!important}.border-info-dark{border-color:var(--sd-info-dark)!important}.text-success{color:var(--sd-success)!important}.bg-success{background:var(--sd-success)!important}.border-success{border-color:var(--sd-success)!important}.text-success-light{color:var(--sd-success-light)!important}.bg-success-light{background:var(--sd-success-light)!important}.border-success-light{border-color:var(--sd-success-light)!important}.text-success-dark{color:var(--sd-success-dark)!important}.bg-success-dark{background:var(--sd-success-dark)!important}.border-success-dark{border-color:var(--sd-success-dark)!important}.text-warning{color:var(--sd-warning)!important}.bg-warning{background:var(--sd-warning)!important}.border-warning{border-color:var(--sd-warning)!important}.text-warning-light{color:var(--sd-warning-light)!important}.bg-warning-light{background:var(--sd-warning-light)!important}.border-warning-light{border-color:var(--sd-warning-light)!important}.text-warning-dark{color:var(--sd-warning-dark)!important}.bg-warning-dark{background:var(--sd-warning-dark)!important}.border-warning-dark{border-color:var(--sd-warning-dark)!important}.text-error{color:var(--sd-error)!important}.bg-error{background:var(--sd-error)!important}.border-error{border-color:var(--sd-error)!important}.text-error-light{color:var(--sd-error-light)!important}.bg-error-light{background:var(--sd-error-light)!important}.border-error-light{border-color:var(--sd-error-light)!important}.text-error-dark{color:var(--sd-error-dark)!important}.bg-error-dark{background:var(--sd-error-dark)!important}.border-error-dark{border-color:var(--sd-error-dark)!important}.text-secondary{color:var(--sd-secondary)!important}.bg-secondary{background:var(--sd-secondary)!important}.border-secondary{border-color:var(--sd-secondary)!important}.text-secondary-light{color:var(--sd-secondary-light)!important}.bg-secondary-light{background:var(--sd-secondary-light)!important}.border-secondary-light{border-color:var(--sd-secondary-light)!important}.text-secondary-dark{color:var(--sd-secondary-dark)!important}.bg-secondary-dark{background:var(--sd-secondary-dark)!important}.border-secondary-dark{border-color:var(--sd-secondary-dark)!important}.text-light{color:var(--sd-light)!important}.bg-light{background:var(--sd-light)!important}.border-light{border-color:var(--sd-light)!important}.text-dark{color:var(--sd-dark)!important}.bg-dark{background:var(--sd-dark)!important}.border-dark{border-color:var(--sd-dark)!important}.text-black500{color:var(--sd-black500)!important}.bg-black500{background:var(--sd-black500)!important}.border-black500{border-color:var(--sd-black500)!important}.text-black400{color:var(--sd-black400)!important}.bg-black400{background:var(--sd-black400)!important}.border-black400{border-color:var(--sd-black400)!important}.text-black300{color:var(--sd-black300)!important}.bg-black300{background:var(--sd-black300)!important}.border-black300{border-color:var(--sd-black300)!important}.text-black200{color:var(--sd-black200)!important}.bg-black200{background:var(--sd-black200)!important}.border-black200{border-color:var(--sd-black200)!important}.text-black100{color:var(--sd-black100)!important}.bg-black100{background:var(--sd-black100)!important}.border-black100{border-color:var(--sd-black100)!important}.text-white{color:#fff!important}.bg-white{background:#fff!important}.border-white{border-color:#fff!important}.text-black{color:#000!important}.bg-black{background:#000!important}.border-black{border-color:#000!important}:host{display:flex;align-items:center}button[mat-icon-button]{width:24px;height:24px;line-height:24px;padding:0}mat-icon.c-icon{color:var(--sd-black400)!important;width:16px;height:16px;font-size:16px;line-height:16px}.sd-command-menu-item__content{display:inline-flex;align-items:center;gap:8px;min-width:0;width:100%}.sd-command-menu-item__icon{color:var(--sd-black400)!important;width:24px;height:24px;font-size:18px;line-height:24px!important;margin-right:0!important;flex:0 0 24px}.sd-command-menu-item__title{min-width:0;line-height:20px;white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2$1.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i2.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: CommandPipe, name: "command" }, { kind: "pipe", type: CommandFilterPipe, name: "filter" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1475
1736
  }
1476
1737
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: DesktopCommand, decorators: [{
1477
1738
  type: Component,
1478
1739
  args: [{ selector: 'desktop-command', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, host: {
1479
1740
  '[attr.data-autoid]': 'autoId()',
1480
- }, imports: [CommonModule, MatTooltipModule, MatMenuModule, MatButtonModule, MatIconModule, CommandPipe, CommandFilterPipe], template: "@let _autoId = autoId();\n@let _item = item();\n@let filteredCommands = _item | filter: _commands() | async;\n\n@if (filteredCommands?.length) {\n @for (command of filteredCommands; track command.key) {\n @let _meta = _item | command: command;\n @if (command.type === 'normal') {\n <button\n [attr.data-autoid]=\"_autoId + '-' + command.key\"\n (click)=\"command.click(_item.data)\"\n [matTooltip]=\"_meta.title\"\n aria-hidden=\"true\"\n [disabled]=\"_meta.disabled\"\n mat-icon-button>\n <mat-icon class=\"c-icon\" [color]=\"command.color || 'secondary'\" [fontSet]=\"command.fontSet!\">\n {{ _meta.icon }}\n </mat-icon>\n </button>\n } @else {\n <button\n [attr.data-autoid]=\"_autoId + '-' + command.key\"\n [matMenuTriggerFor]=\"menu\"\n aria-hidden=\"true\"\n mat-icon-button>\n @if (_meta.icon) {\n <mat-icon class=\"c-icon\" [fontSet]=\"command.fontSet!\">{{ _meta.icon }}</mat-icon>\n } @else {\n <mat-icon class=\"c-icon\">more_vert</mat-icon>\n }\n </button>\n <mat-menu #menu=\"matMenu\" [attr.data-autoid]=\"_autoId + '-' + command.key + '-menu'\">\n <span>\n @for (childCommand of command.children; track childCommand.key) {\n @let _childMeta = _item | command: childCommand;\n <button\n [attr.data-autoid]=\"_autoId + '-' + command.key + '-' + childCommand.key\"\n mat-menu-item\n (click)=\"childCommand.click(_item.data)\"\n [disabled]=\"_childMeta.disabled\">\n @if (_childMeta.htmlTemplate) {\n <div [innerHTML]=\"_childMeta.htmlTemplate\"></div>\n } @else {\n <div>\n <mat-icon [color]=\"childCommand.color || 'secondary'\" [fontSet]=\"command.fontSet!\">\n {{ _childMeta.icon }}\n </mat-icon>\n <span> {{ _childMeta.title }}</span>\n </div>\n }\n </button>\n }\n </span>\n </mat-menu>\n }\n }\n}\n", styles: [".text-primary{color:var(--sd-primary)!important}.bg-primary{background:var(--sd-primary)!important}.border-primary{border-color:var(--sd-primary)!important}.text-primary-light{color:var(--sd-primary-light)!important}.bg-primary-light{background:var(--sd-primary-light)!important}.border-primary-light{border-color:var(--sd-primary-light)!important}.text-primary-dark{color:var(--sd-primary-dark)!important}.bg-primary-dark{background:var(--sd-primary-dark)!important}.border-primary-dark{border-color:var(--sd-primary-dark)!important}.text-info{color:var(--sd-info)!important}.bg-info{background:var(--sd-info)!important}.border-info{border-color:var(--sd-info)!important}.text-info-light{color:var(--sd-info-light)!important}.bg-info-light{background:var(--sd-info-light)!important}.border-info-light{border-color:var(--sd-info-light)!important}.text-info-dark{color:var(--sd-info-dark)!important}.bg-info-dark{background:var(--sd-info-dark)!important}.border-info-dark{border-color:var(--sd-info-dark)!important}.text-success{color:var(--sd-success)!important}.bg-success{background:var(--sd-success)!important}.border-success{border-color:var(--sd-success)!important}.text-success-light{color:var(--sd-success-light)!important}.bg-success-light{background:var(--sd-success-light)!important}.border-success-light{border-color:var(--sd-success-light)!important}.text-success-dark{color:var(--sd-success-dark)!important}.bg-success-dark{background:var(--sd-success-dark)!important}.border-success-dark{border-color:var(--sd-success-dark)!important}.text-warning{color:var(--sd-warning)!important}.bg-warning{background:var(--sd-warning)!important}.border-warning{border-color:var(--sd-warning)!important}.text-warning-light{color:var(--sd-warning-light)!important}.bg-warning-light{background:var(--sd-warning-light)!important}.border-warning-light{border-color:var(--sd-warning-light)!important}.text-warning-dark{color:var(--sd-warning-dark)!important}.bg-warning-dark{background:var(--sd-warning-dark)!important}.border-warning-dark{border-color:var(--sd-warning-dark)!important}.text-error{color:var(--sd-error)!important}.bg-error{background:var(--sd-error)!important}.border-error{border-color:var(--sd-error)!important}.text-error-light{color:var(--sd-error-light)!important}.bg-error-light{background:var(--sd-error-light)!important}.border-error-light{border-color:var(--sd-error-light)!important}.text-error-dark{color:var(--sd-error-dark)!important}.bg-error-dark{background:var(--sd-error-dark)!important}.border-error-dark{border-color:var(--sd-error-dark)!important}.text-secondary{color:var(--sd-secondary)!important}.bg-secondary{background:var(--sd-secondary)!important}.border-secondary{border-color:var(--sd-secondary)!important}.text-secondary-light{color:var(--sd-secondary-light)!important}.bg-secondary-light{background:var(--sd-secondary-light)!important}.border-secondary-light{border-color:var(--sd-secondary-light)!important}.text-secondary-dark{color:var(--sd-secondary-dark)!important}.bg-secondary-dark{background:var(--sd-secondary-dark)!important}.border-secondary-dark{border-color:var(--sd-secondary-dark)!important}.text-light{color:var(--sd-light)!important}.bg-light{background:var(--sd-light)!important}.border-light{border-color:var(--sd-light)!important}.text-dark{color:var(--sd-dark)!important}.bg-dark{background:var(--sd-dark)!important}.border-dark{border-color:var(--sd-dark)!important}.text-black500{color:var(--sd-black500)!important}.bg-black500{background:var(--sd-black500)!important}.border-black500{border-color:var(--sd-black500)!important}.text-black400{color:var(--sd-black400)!important}.bg-black400{background:var(--sd-black400)!important}.border-black400{border-color:var(--sd-black400)!important}.text-black300{color:var(--sd-black300)!important}.bg-black300{background:var(--sd-black300)!important}.border-black300{border-color:var(--sd-black300)!important}.text-black200{color:var(--sd-black200)!important}.bg-black200{background:var(--sd-black200)!important}.border-black200{border-color:var(--sd-black200)!important}.text-black100{color:var(--sd-black100)!important}.bg-black100{background:var(--sd-black100)!important}.border-black100{border-color:var(--sd-black100)!important}.text-white{color:#fff!important}.bg-white{background:#fff!important}.border-white{border-color:#fff!important}.text-black{color:#000!important}.bg-black{background:#000!important}.border-black{border-color:#000!important}:host{display:flex;align-items:center}button[mat-icon-button]{width:24px;height:24px;line-height:24px;padding:0}mat-icon.c-icon{color:var(--sd-black400)!important;width:16px;height:16px;font-size:16px;line-height:16px}\n"] }]
1741
+ }, imports: [CommonModule, MatTooltipModule, MatMenuModule, MatButtonModule, MatIconModule, CommandPipe, CommandFilterPipe], template: "@let _autoId = autoId();\n@let _item = item();\n@let filteredCommands = _item | filter: _commands() | async;\n\n@if (filteredCommands?.length) {\n @for (command of filteredCommands; track command.key) {\n @let _meta = _item | command: command;\n @if (command.type === 'normal') {\n <button\n [attr.data-autoid]=\"_autoId + '-' + command.key\"\n (click)=\"command.click(_item.data)\"\n [matTooltip]=\"_meta.title\"\n aria-hidden=\"true\"\n [disabled]=\"_meta.disabled\"\n mat-icon-button>\n <mat-icon class=\"c-icon\" [color]=\"command.color || 'secondary'\" [fontSet]=\"command.fontSet!\">\n {{ _meta.icon }}\n </mat-icon>\n </button>\n } @else {\n <button\n [attr.data-autoid]=\"_autoId + '-' + command.key\"\n [matMenuTriggerFor]=\"menu\"\n aria-hidden=\"true\"\n mat-icon-button>\n @if (_meta.icon) {\n <mat-icon class=\"c-icon\" [fontSet]=\"command.fontSet!\">{{ _meta.icon }}</mat-icon>\n } @else {\n <mat-icon class=\"c-icon\">more_vert</mat-icon>\n }\n </button>\n <mat-menu #menu=\"matMenu\" [attr.data-autoid]=\"_autoId + '-' + command.key + '-menu'\">\n <span>\n @for (childCommand of command.children; track childCommand.key) {\n @let _childMeta = _item | command: childCommand;\n <button\n [attr.data-autoid]=\"_autoId + '-' + command.key + '-' + childCommand.key\"\n mat-menu-item\n (click)=\"childCommand.click(_item.data)\"\n [disabled]=\"_childMeta.disabled\">\n @if (_childMeta.htmlTemplate) {\n <div [innerHTML]=\"_childMeta.htmlTemplate\"></div>\n } @else {\n <span class=\"sd-command-menu-item__content\">\n <mat-icon class=\"sd-command-menu-item__icon\" [color]=\"childCommand.color || 'secondary'\" [fontSet]=\"command.fontSet!\">\n {{ _childMeta.icon }}\n </mat-icon>\n <span class=\"sd-command-menu-item__title\">{{ _childMeta.title }}</span>\n </span>\n }\n </button>\n }\n </span>\n </mat-menu>\n }\n }\n}\n", styles: [".text-primary{color:var(--sd-primary)!important}.bg-primary{background:var(--sd-primary)!important}.border-primary{border-color:var(--sd-primary)!important}.text-primary-light{color:var(--sd-primary-light)!important}.bg-primary-light{background:var(--sd-primary-light)!important}.border-primary-light{border-color:var(--sd-primary-light)!important}.text-primary-dark{color:var(--sd-primary-dark)!important}.bg-primary-dark{background:var(--sd-primary-dark)!important}.border-primary-dark{border-color:var(--sd-primary-dark)!important}.text-info{color:var(--sd-info)!important}.bg-info{background:var(--sd-info)!important}.border-info{border-color:var(--sd-info)!important}.text-info-light{color:var(--sd-info-light)!important}.bg-info-light{background:var(--sd-info-light)!important}.border-info-light{border-color:var(--sd-info-light)!important}.text-info-dark{color:var(--sd-info-dark)!important}.bg-info-dark{background:var(--sd-info-dark)!important}.border-info-dark{border-color:var(--sd-info-dark)!important}.text-success{color:var(--sd-success)!important}.bg-success{background:var(--sd-success)!important}.border-success{border-color:var(--sd-success)!important}.text-success-light{color:var(--sd-success-light)!important}.bg-success-light{background:var(--sd-success-light)!important}.border-success-light{border-color:var(--sd-success-light)!important}.text-success-dark{color:var(--sd-success-dark)!important}.bg-success-dark{background:var(--sd-success-dark)!important}.border-success-dark{border-color:var(--sd-success-dark)!important}.text-warning{color:var(--sd-warning)!important}.bg-warning{background:var(--sd-warning)!important}.border-warning{border-color:var(--sd-warning)!important}.text-warning-light{color:var(--sd-warning-light)!important}.bg-warning-light{background:var(--sd-warning-light)!important}.border-warning-light{border-color:var(--sd-warning-light)!important}.text-warning-dark{color:var(--sd-warning-dark)!important}.bg-warning-dark{background:var(--sd-warning-dark)!important}.border-warning-dark{border-color:var(--sd-warning-dark)!important}.text-error{color:var(--sd-error)!important}.bg-error{background:var(--sd-error)!important}.border-error{border-color:var(--sd-error)!important}.text-error-light{color:var(--sd-error-light)!important}.bg-error-light{background:var(--sd-error-light)!important}.border-error-light{border-color:var(--sd-error-light)!important}.text-error-dark{color:var(--sd-error-dark)!important}.bg-error-dark{background:var(--sd-error-dark)!important}.border-error-dark{border-color:var(--sd-error-dark)!important}.text-secondary{color:var(--sd-secondary)!important}.bg-secondary{background:var(--sd-secondary)!important}.border-secondary{border-color:var(--sd-secondary)!important}.text-secondary-light{color:var(--sd-secondary-light)!important}.bg-secondary-light{background:var(--sd-secondary-light)!important}.border-secondary-light{border-color:var(--sd-secondary-light)!important}.text-secondary-dark{color:var(--sd-secondary-dark)!important}.bg-secondary-dark{background:var(--sd-secondary-dark)!important}.border-secondary-dark{border-color:var(--sd-secondary-dark)!important}.text-light{color:var(--sd-light)!important}.bg-light{background:var(--sd-light)!important}.border-light{border-color:var(--sd-light)!important}.text-dark{color:var(--sd-dark)!important}.bg-dark{background:var(--sd-dark)!important}.border-dark{border-color:var(--sd-dark)!important}.text-black500{color:var(--sd-black500)!important}.bg-black500{background:var(--sd-black500)!important}.border-black500{border-color:var(--sd-black500)!important}.text-black400{color:var(--sd-black400)!important}.bg-black400{background:var(--sd-black400)!important}.border-black400{border-color:var(--sd-black400)!important}.text-black300{color:var(--sd-black300)!important}.bg-black300{background:var(--sd-black300)!important}.border-black300{border-color:var(--sd-black300)!important}.text-black200{color:var(--sd-black200)!important}.bg-black200{background:var(--sd-black200)!important}.border-black200{border-color:var(--sd-black200)!important}.text-black100{color:var(--sd-black100)!important}.bg-black100{background:var(--sd-black100)!important}.border-black100{border-color:var(--sd-black100)!important}.text-white{color:#fff!important}.bg-white{background:#fff!important}.border-white{border-color:#fff!important}.text-black{color:#000!important}.bg-black{background:#000!important}.border-black{border-color:#000!important}:host{display:flex;align-items:center}button[mat-icon-button]{width:24px;height:24px;line-height:24px;padding:0}mat-icon.c-icon{color:var(--sd-black400)!important;width:16px;height:16px;font-size:16px;line-height:16px}.sd-command-menu-item__content{display:inline-flex;align-items:center;gap:8px;min-width:0;width:100%}.sd-command-menu-item__icon{color:var(--sd-black400)!important;width:24px;height:24px;font-size:18px;line-height:24px!important;margin-right:0!important;flex:0 0 24px}.sd-command-menu-item__title{min-width:0;line-height:20px;white-space:nowrap}\n"] }]
1481
1742
  }] });
1482
1743
 
1483
1744
  /* eslint-disable @angular-eslint/no-input-rename */
@@ -2118,19 +2379,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
2118
2379
  class SdSelectionDisabledPipe {
2119
2380
  transform = (selectedItems, rowData, selection) => {
2120
2381
  const { disabled, actions } = selection;
2382
+ if (rowData.meta.selector.isSelected) {
2383
+ rowData.meta.selector.selectable = true;
2384
+ return false;
2385
+ }
2121
2386
  if (!actions?.length) {
2122
2387
  if (!disabled) {
2123
2388
  rowData.meta.selector.selectable = true;
2124
2389
  return false;
2125
2390
  }
2126
- rowData.meta.selector.selectable = !disabled(rowData, selectedItems);
2391
+ rowData.meta.selector.selectable = !disabled(rowData.data, selectedItems.map(e => e.data));
2127
2392
  return !rowData.meta.selector.selectable;
2128
2393
  }
2129
2394
  // Kiểm tra có bị disabled theo function không
2130
2395
  // Dữ liệu chưa được check thì kiểm tra hàm disable nếu có
2131
- if (disabled && !rowData.meta.selector.isSelected) {
2396
+ if (disabled) {
2132
2397
  // Nếu disabled và dữ liệu chưa được check
2133
- if (disabled(rowData, selectedItems)) {
2398
+ if (disabled(rowData.data, selectedItems.map(e => e.data))) {
2134
2399
  return true;
2135
2400
  }
2136
2401
  }
@@ -2410,13 +2675,13 @@ class TableExportService {
2410
2675
  }
2411
2676
  }
2412
2677
  else if (column.type === 'date') {
2413
- obj[fieldStr] = DateUtilities.toFormat(itemValue, 'yyyy/MM/dd');
2678
+ obj[fieldStr] = DateUtilities$1.toFormat(itemValue, 'yyyy/MM/dd');
2414
2679
  }
2415
2680
  else if (column.type === 'datetime') {
2416
- obj[fieldStr] = DateUtilities.toFormat(itemValue, 'yyyy/MM/dd HH:mm:ss');
2681
+ obj[fieldStr] = DateUtilities$1.toFormat(itemValue, 'yyyy/MM/dd HH:mm:ss');
2417
2682
  }
2418
2683
  else if (column.type === 'time') {
2419
- obj[fieldStr] = DateUtilities.toFormat(itemValue, 'HH:mm:ss');
2684
+ obj[fieldStr] = DateUtilities$1.toFormat(itemValue, 'HH:mm:ss');
2420
2685
  }
2421
2686
  else if (column.type === 'values' || column.type === 'lazy-values') {
2422
2687
  const vals = (Array.isArray(itemValue) ? itemValue : [itemValue]).filter(e => !!e?.toString());
@@ -2711,8 +2976,8 @@ class TableFormatService {
2711
2976
  // PRIVATE HELPERS
2712
2977
  // ==========================================
2713
2978
  #formatDateDisplay(value, type) {
2714
- const date = DateUtilities.toFormat(value, 'dd/MM/yyyy');
2715
- const time = DateUtilities.toFormat(value, 'HH:mm:ss');
2979
+ const date = DateUtilities$1.toFormat(value, 'dd/MM/yyyy');
2980
+ const time = DateUtilities$1.toFormat(value, 'HH:mm:ss');
2716
2981
  if (type === 'datetime') {
2717
2982
  return time && date ? `<div class="T14R">${date}<span class="T14R text-black400 ml-4">${time}</span></div>` : '';
2718
2983
  }
@@ -2773,25 +3038,286 @@ const buildColumnWidthMap = (column) => {
2773
3038
  return result;
2774
3039
  };
2775
3040
 
2776
- class SdTableFilterService {
2777
- storageService;
2778
- #filterConfiguration = 'GRID-FILTER-CONFIGURATION';
2779
- #filterValue = 'GRID-FILTER-VALUE';
2780
- #cache = {};
2781
- constructor(storageService) {
2782
- this.storageService = storageService;
3041
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3042
+ const isTableItem = (item) => !!item && typeof item === 'object' && 'data' in item && 'meta' in item;
3043
+ const resolveItemData = (item) => (isTableItem(item) ? item.data : item);
3044
+ const hasActiveColumnFilter = (rawColumnFilter = {}, columns = []) => columns.some(col => {
3045
+ const v = rawColumnFilter[col.field];
3046
+ if (Array.isArray(v))
3047
+ return v.length > 0;
3048
+ if (v && typeof v === 'object')
3049
+ return !!v.from || !!v.to;
3050
+ return v !== undefined && v !== null && v !== '';
3051
+ });
3052
+ const matchesColumnFilter = (data, columns = [], rawColumnFilter = {}) => {
3053
+ for (const column of columns) {
3054
+ const { field, type } = column;
3055
+ const filterValue = (rawColumnFilter[field] || '').toString().trim().toLowerCase();
3056
+ const rawColVal = Utilities.getNestedValue(data, field);
3057
+ const columnValue = (rawColVal || '').toString().trim().toLowerCase();
3058
+ if (filterValue) {
3059
+ if (!columnValue && type !== 'datetime' && type !== 'date' && type !== 'time') {
3060
+ return false;
3061
+ }
3062
+ if (type === 'string') {
3063
+ if (columnValue.indexOf(filterValue) === -1) {
3064
+ return false;
3065
+ }
3066
+ }
3067
+ else if (type === 'values' || type === 'lazy-values') {
3068
+ const columnType = column;
3069
+ const isMultiple = columnType.option.selection === 'MULTIPLE';
3070
+ if (isMultiple && Array.isArray(rawColVal)) {
3071
+ const columnValues = rawColVal.map((i) => (Utilities.getNestedValue(i, columnType.option.valueField) ?? '').toString().trim().toLowerCase()) ??
3072
+ [];
3073
+ const filterValues = rawColumnFilter[field]?.map((v) => (v ?? '').toString().trim().toLowerCase());
3074
+ if (filterValues?.length && filterValues.every(fv => !columnValues.includes(fv))) {
3075
+ return false;
3076
+ }
3077
+ }
3078
+ else {
3079
+ if (columnValue !== filterValue) {
3080
+ return false;
3081
+ }
3082
+ }
3083
+ }
3084
+ else if (type === 'number') {
3085
+ const fValue = +filterValue.replace('>=', '').replace('<=', '').replace('>', '').replace('<', '');
3086
+ const cValue = +columnValue;
3087
+ if (fValue || fValue === 0) {
3088
+ if (!cValue && cValue !== 0) {
3089
+ return false;
3090
+ }
3091
+ if (filterValue.indexOf('>=') > -1 && cValue < fValue) {
3092
+ return false;
3093
+ }
3094
+ else if (filterValue.indexOf('<=') > -1 && cValue > fValue) {
3095
+ return false;
3096
+ }
3097
+ else if (filterValue.indexOf('<') > -1 && cValue >= fValue) {
3098
+ return false;
3099
+ }
3100
+ else if (filterValue.indexOf('>') > -1 && cValue <= fValue) {
3101
+ return false;
3102
+ }
3103
+ else if (cValue !== fValue) {
3104
+ return false;
3105
+ }
3106
+ }
3107
+ }
3108
+ else if (type === 'boolean') {
3109
+ if ((filterValue === '1' || filterValue === 'true') && columnValue !== '1' && columnValue !== 'true') {
3110
+ return false;
3111
+ }
3112
+ else if ((filterValue === '0' || filterValue === 'false') && columnValue !== '0' && columnValue !== 'false') {
3113
+ return false;
3114
+ }
3115
+ }
3116
+ else if (type === 'datetime' || type === 'date' || type === 'time') {
3117
+ const from = rawColumnFilter[field]?.from ?? rawColumnFilter[field];
3118
+ const to = rawColumnFilter[field]?.to ?? rawColumnFilter[field];
3119
+ const fromDate = DateUtilities$1.begin(from);
3120
+ const toDate = DateUtilities$1.end(to);
3121
+ if (fromDate || toDate) {
3122
+ if (!columnValue) {
3123
+ return false;
3124
+ }
3125
+ const columnTime = new Date(columnValue).getTime();
3126
+ const fromDateTime = fromDate?.getTime() || null;
3127
+ const toDateTime = toDate?.getTime() || null;
3128
+ if (fromDateTime && fromDateTime > columnTime) {
3129
+ return false;
3130
+ }
3131
+ if (toDateTime && columnTime > toDateTime) {
3132
+ return false;
3133
+ }
3134
+ }
3135
+ }
3136
+ }
2783
3137
  }
2784
- register = (filter, args) => {
2785
- let cacheSession = false;
3138
+ return true;
3139
+ };
3140
+ const filterLocalItems = (localItems, option, filterInfo) => {
3141
+ const { columns } = option;
3142
+ const { rawColumnFilter, orderBy, orderDirection, pageSize, pageNumber } = filterInfo;
3143
+ const matchesData = (data) => matchesColumnFilter(data, columns, rawColumnFilter);
3144
+ const treeOpt = option.tree;
3145
+ const treeSearch = option.type === 'local' && treeOpt?.loadType === 'static' && hasActiveColumnFilter(rawColumnFilter, columns);
3146
+ const treeSearchPredicate = treeSearch ? matchesData : undefined;
3147
+ const items = treeSearch
3148
+ ? localItems.filter(item => subtreeMatches(resolveItemData(item), matchesData, treeOpt))
3149
+ : localItems.filter(item => matchesData(resolveItemData(item)));
3150
+ if (orderBy && orderDirection) {
3151
+ const column = columns.find(e => e.field === orderBy);
3152
+ if (column) {
3153
+ const { type, field } = column;
3154
+ items.sort((tableItemCurrent, tableItemNext) => {
3155
+ const dataVal = Utilities.getNestedValue(resolveItemData(tableItemCurrent), field);
3156
+ const nextVal = Utilities.getNestedValue(resolveItemData(tableItemNext), field);
3157
+ if (type === 'number') {
3158
+ return (dataVal || 0) - (nextVal || 0);
3159
+ }
3160
+ if (type === 'date' || type === 'datetime' || type === 'time') {
3161
+ const d1 = new Date(dataVal || '').getTime();
3162
+ const d2 = new Date(nextVal || '').getTime();
3163
+ return d1 - d2;
3164
+ }
3165
+ const s1 = (dataVal || '').toString();
3166
+ const s2 = (nextVal || '').toString();
3167
+ if (s1 > s2) {
3168
+ return 1;
3169
+ }
3170
+ if (s1 < s2) {
3171
+ return -1;
3172
+ }
3173
+ return 0;
3174
+ });
3175
+ if (orderDirection === 'DESC') {
3176
+ items.reverse();
3177
+ }
3178
+ }
3179
+ }
3180
+ return {
3181
+ items: items.filter((item, index) => index >= pageNumber * pageSize && index < (pageNumber + 1) * pageSize),
3182
+ total: items.length,
3183
+ treeSearchPredicate,
3184
+ };
3185
+ };
3186
+
3187
+ const isGroupHeader = (item) => !!item?.meta?.group?.items?.length;
3188
+ const isChildRow = (item) => (item?.meta.tree?.level ?? 0) > 0;
3189
+ const isRootReorderRow = (item) => !!item && !isGroupHeader(item) && !isChildRow(item);
3190
+ const moveItem = (items, fromIndex, toIndex) => {
3191
+ const next = [...items];
3192
+ const [moved] = next.splice(fromIndex, 1);
3193
+ if (!moved)
3194
+ return next;
3195
+ next.splice(toIndex, 0, moved);
3196
+ return next;
3197
+ };
3198
+ const isReorderDisabled = (item, option, index = -1) => {
3199
+ if (isChildRow(item))
3200
+ return true;
3201
+ if (!option?.disabled || isGroupHeader(item))
3202
+ return false;
3203
+ return option.disabled(item.data, index);
3204
+ };
3205
+ const sameReorderGroup = (a, b, allItems) => {
3206
+ const groupOf = (item) => {
3207
+ let lastGroupIdx = -1;
3208
+ for (let i = 0; i < allItems.length; i++) {
3209
+ if (isGroupHeader(allItems[i]))
3210
+ lastGroupIdx = i;
3211
+ if (allItems[i] === item)
3212
+ return lastGroupIdx;
3213
+ }
3214
+ return -1;
3215
+ };
3216
+ return groupOf(a) === groupOf(b);
3217
+ };
3218
+ const canSortReorder = ({ enabled, index, dragItem, allItems = [], hasGroup }) => {
3219
+ if (!enabled)
3220
+ return false;
3221
+ const targetItem = allItems[index];
3222
+ if (!isRootReorderRow(targetItem))
3223
+ return false;
3224
+ if (isChildRow(dragItem))
3225
+ return false;
3226
+ if (hasGroup) {
3227
+ return sameReorderGroup(dragItem, targetItem, allItems);
3228
+ }
3229
+ return true;
3230
+ };
3231
+ const toReorderItemsIndex = (renderedItems, renderedIndex) => {
3232
+ let count = 0;
3233
+ for (let i = 0; i < renderedIndex; i++) {
3234
+ if (isRootReorderRow(renderedItems[i]))
3235
+ count++;
3236
+ }
3237
+ return count;
3238
+ };
3239
+ const reorderTableItems = ({ items, renderedItems, previousRenderedIndex, currentRenderedIndex, localItems, }) => {
3240
+ const fromIndex = toReorderItemsIndex(renderedItems, previousRenderedIndex);
3241
+ const toIndex = toReorderItemsIndex(renderedItems, currentRenderedIndex);
3242
+ const nextItems = moveItem(items, fromIndex, toIndex);
3243
+ let nextLocalItems = localItems;
3244
+ if (localItems) {
3245
+ const localPositions = items.map(item => localItems.indexOf(item));
3246
+ if (localPositions.every(p => p >= 0)) {
3247
+ nextLocalItems = [...localItems];
3248
+ nextItems.forEach((item, i) => {
3249
+ nextLocalItems[localPositions[i]] = item;
3250
+ });
3251
+ }
3252
+ }
3253
+ return {
3254
+ items: nextItems,
3255
+ localItems: nextLocalItems,
3256
+ fromIndex,
3257
+ toIndex,
3258
+ };
3259
+ };
3260
+
3261
+ const getSelectionRows = (roots, treeOpt) => treeOpt ? flattenTree(roots, treeOpt) : roots;
3262
+ const getSelectedRowData = (rows) => rows.filter(row => row.meta.selector.isSelected).map(row => row.data);
3263
+ const applyDefaultSelected = (roots, treeOpt, defaultSelected) => {
3264
+ if (!defaultSelected)
3265
+ return;
3266
+ const rows = treeOpt ? collectFormattedTreeRows(roots) : roots;
3267
+ rows.forEach(item => {
3268
+ item.meta.selector.isSelected = defaultSelected(item.data);
3269
+ });
3270
+ };
3271
+ const resolveSelectAllState = (visibleRows) => visibleRows.length > 0 && visibleRows.every(row => row.meta.selector?.isSelected);
3272
+ const restorePreservedSelection = (visibleRows, preservedSelectedMap) => {
3273
+ visibleRows.forEach(item => {
3274
+ if (preservedSelectedMap.has(item.meta.id)) {
3275
+ item.meta.selector.isSelected = true;
3276
+ preservedSelectedMap.set(item.meta.id, item);
3277
+ }
3278
+ });
3279
+ };
3280
+ const syncPreservedSelection = (visibleRows, preservedSelectedMap) => {
3281
+ visibleRows.forEach(item => {
3282
+ const id = item.meta.id;
3283
+ if (item.meta.selector.isSelected) {
3284
+ preservedSelectedMap.set(id, item);
3285
+ }
3286
+ else {
3287
+ preservedSelectedMap.delete(id);
3288
+ }
3289
+ });
3290
+ return Array.from(preservedSelectedMap.values());
3291
+ };
3292
+
3293
+ class SdTableFilterService {
3294
+ storageService;
3295
+ #filterConfiguration = 'GRID-FILTER-CONFIGURATION';
3296
+ #filterValue = 'GRID-FILTER-VALUE';
3297
+ #cache = {};
3298
+ constructor(storageService) {
3299
+ this.storageService = storageService;
3300
+ }
3301
+ #hasOwnValue = (values, field) => {
3302
+ return !!values && Object.prototype.hasOwnProperty.call(values, field);
3303
+ };
3304
+ #resolveKey = (filter, args) => {
2786
3305
  const { id, columns, externalFilters } = args;
2787
3306
  const tempKey = Utilities.hash({
2788
3307
  id,
2789
3308
  columns: columns?.map(e => e.field).filter(field => !!field) || [],
2790
3309
  externalFilters: externalFilters?.map(e => e.field).filter(field => !!field) || [],
2791
3310
  });
2792
- const key = filter?.key || tempKey;
2793
- if (!filter?.key) {
2794
- cacheSession = true; // Nếu không có key thì chỉ lưu theo session
3311
+ return {
3312
+ tempKey,
3313
+ key: filter?.key || tempKey,
3314
+ cacheSession: !filter?.key,
3315
+ };
3316
+ };
3317
+ register = (filter, args) => {
3318
+ const { key, tempKey, cacheSession } = this.#resolveKey(filter, args);
3319
+ if (args.force) {
3320
+ delete this.#cache[key];
2795
3321
  }
2796
3322
  if (!this.#cache[key]) {
2797
3323
  // Setting của filter configuration
@@ -2958,21 +3484,25 @@ class SdTableFilterService {
2958
3484
  const { columns, externalFilters } = args;
2959
3485
  // Filter column
2960
3486
  for (const item of columns || []) {
2961
- columnFilter[item.field] = value?.columnFilter?.[item.field] ?? item?.filter?.default;
3487
+ // why: null is a deliberate cached clear from SD controls; only a missing key should fall back to default.
3488
+ columnFilter[item.field] = this.#hasOwnValue(value?.columnFilter, item.field)
3489
+ ? value?.columnFilter?.[item.field]
3490
+ : item?.filter?.default;
2962
3491
  if (item?.filter?.operator?.enable && item?.filter?.operator?.default) {
2963
3492
  columnOperator[item.field] = item.filter.operator.default;
2964
3493
  }
2965
3494
  }
2966
3495
  // Filter external
2967
3496
  for (const item of externalFilters || []) {
3497
+ const hasExternalValue = this.#hasOwnValue(value?.externalFilter, item.field);
2968
3498
  if (item.type === 'daterange') {
2969
3499
  externalFilter[item.field] = {
2970
- from: value?.externalFilter?.[item.field]?.from ?? item.default?.from,
2971
- to: value?.externalFilter?.[item.field]?.to ?? item.default?.to,
3500
+ from: hasExternalValue ? value?.externalFilter?.[item.field]?.from : item.default?.from,
3501
+ to: hasExternalValue ? value?.externalFilter?.[item.field]?.to : item.default?.to,
2972
3502
  };
2973
3503
  }
2974
3504
  else {
2975
- externalFilter[item.field] = value?.externalFilter?.[item.field] ?? item?.default;
3505
+ externalFilter[item.field] = hasExternalValue ? value?.externalFilter?.[item.field] : item?.default;
2976
3506
  }
2977
3507
  }
2978
3508
  return {
@@ -3089,6 +3619,10 @@ class SdTable {
3089
3619
  columnFilter = {};
3090
3620
  #localItems = [];
3091
3621
  #subscription = new Subscription();
3622
+ #configurationSubscription;
3623
+ #filterRegisterSubscription;
3624
+ #optionInstance;
3625
+ #optionRevision = 0;
3092
3626
  #reload = new Subject();
3093
3627
  #loadCompleted = false;
3094
3628
  cacheValues = {};
@@ -3098,7 +3632,7 @@ class SdTable {
3098
3632
  treeRevision = signal(0);
3099
3633
  // Search ở cấp con (static tree + type 'local'): predicate khớp 1 dòng theo
3100
3634
  // column filter hiện hành. Set ở #filterLocal khi có filter active, đọc lại ở
3101
- // #render/#initTreeMeta/#ensureChildItemsFormatted để prune + auto-expand các
3635
+ // #render/#ensureChildItemsFormatted dùng predicate này để prune + auto-expand các
3102
3636
  // nhánh có node con khớp. undefined = không search → cây render bình thường.
3103
3637
  #treeSearchPredicate;
3104
3638
  // Lần render trước có đang ở chế độ search hay không (để xử lý chuyển trạng thái).
@@ -3121,27 +3655,40 @@ class SdTable {
3121
3655
  const option = this.option();
3122
3656
  if (option) {
3123
3657
  untracked(() => {
3658
+ if (this.#optionInstance !== option) {
3659
+ this.#optionInstance = option;
3660
+ this.#resetStateForNewOption();
3661
+ }
3662
+ const optionRevision = this.#optionRevision;
3124
3663
  const initOpt = this.#initConfiguration({ ...option });
3125
3664
  this.tableOption.set(initOpt);
3126
3665
  this.#loadCompleted = false;
3127
3666
  const storage = this.#configService.init(initOpt);
3128
- this.#subscription.add(storage.observer.pipe(startWith(storage.subject.getValue())).subscribe(() => {
3667
+ this.#configurationSubscription?.unsubscribe();
3668
+ this.#configurationSubscription = storage.observer.pipe(startWith(storage.subject.getValue())).subscribe(() => {
3669
+ if (optionRevision !== this.#optionRevision)
3670
+ return;
3129
3671
  const configurationResult = this.#configService.loadConfigurationResult(initOpt, storage.get());
3130
3672
  const displayColumns = configurationResult.displayedColumns || [];
3131
3673
  this.#ref.detectChanges();
3132
3674
  this.#tableFormatService
3133
3675
  .loadValues(initOpt.columns.filter(column => displayColumns.includes(column.field)), this.cacheValues, this.#cacheObjValues)
3134
3676
  .then(() => {
3677
+ if (optionRevision !== this.#optionRevision)
3678
+ return;
3135
3679
  this.configuration.set(configurationResult);
3136
3680
  this.#loadFilterRegister();
3137
3681
  if (this.filterRegister) {
3138
- this.#reload.next({ force: true });
3682
+ this.#requestReload(true);
3139
3683
  }
3140
3684
  })
3141
3685
  .finally(() => {
3686
+ if (optionRevision !== this.#optionRevision)
3687
+ return;
3142
3688
  this.#ref.detectChanges();
3143
3689
  });
3144
- }));
3690
+ });
3691
+ this.#subscription.add(this.#configurationSubscription);
3145
3692
  });
3146
3693
  }
3147
3694
  });
@@ -3149,7 +3696,7 @@ class SdTable {
3149
3696
  const paginator = this.paginator();
3150
3697
  if (paginator) {
3151
3698
  untracked(() => {
3152
- this.#subscription.add(paginator.page.subscribe(() => this.#reload.next({ force: false })));
3699
+ this.#subscription.add(paginator.page.subscribe(() => this.#requestReload(false)));
3153
3700
  });
3154
3701
  }
3155
3702
  });
@@ -3157,7 +3704,7 @@ class SdTable {
3157
3704
  const sort = this.sort();
3158
3705
  if (sort) {
3159
3706
  untracked(() => {
3160
- this.#subscription.add(sort.sortChange.subscribe(() => this.#reload.next({ force: false })));
3707
+ this.#subscription.add(sort.sortChange.subscribe(() => this.#requestReload(false)));
3161
3708
  });
3162
3709
  }
3163
3710
  });
@@ -3171,7 +3718,7 @@ class SdTable {
3171
3718
  const conf = this.configuration();
3172
3719
  if (!conf)
3173
3720
  return;
3174
- const firstColumns = conf.firstColumns.map(c => c.field === field ? { ...c, width } : c);
3721
+ const firstColumns = conf.firstColumns.map(c => (c.field === field ? { ...c, width } : c));
3175
3722
  const column = { ...conf.column };
3176
3723
  if (column[field]) {
3177
3724
  column[field] = { ...column[field], width };
@@ -3183,16 +3730,26 @@ class SdTable {
3183
3730
  this.configuration.set({ ...conf, firstColumns, column, fixedColumn });
3184
3731
  }));
3185
3732
  }
3186
- ngOnInit() { }
3187
3733
  ngAfterViewInit() {
3188
3734
  this.#subscription.add(this.#reload
3189
3735
  .pipe(debounceTime(200), switchMap(async (data) => {
3736
+ if (data.revision !== this.#optionRevision || !this.filterRegister)
3737
+ return undefined;
3190
3738
  const filterInfo = this.getFilterRequest();
3191
3739
  const result = await this.#load(filterInfo, !this.#loadCompleted || data.force);
3740
+ if (data.revision !== this.#optionRevision)
3741
+ return undefined;
3192
3742
  this.#loadCompleted = true;
3193
- return result;
3743
+ return {
3744
+ result,
3745
+ revision: data.revision,
3746
+ };
3194
3747
  }))
3195
- .subscribe(this.#render));
3748
+ .subscribe(loadResult => {
3749
+ if (!loadResult || loadResult.revision !== this.#optionRevision)
3750
+ return;
3751
+ this.#render(loadResult.result);
3752
+ }));
3196
3753
  }
3197
3754
  ngOnDestroy() {
3198
3755
  this.#subscription.unsubscribe();
@@ -3211,6 +3768,49 @@ class SdTable {
3211
3768
  isExported: true,
3212
3769
  };
3213
3770
  };
3771
+ #requestReload = (force) => {
3772
+ this.#reload.next({ force, revision: this.#optionRevision });
3773
+ };
3774
+ #resetStateForNewOption = () => {
3775
+ this.#optionRevision += 1;
3776
+ this.#configurationSubscription?.unsubscribe();
3777
+ this.#configurationSubscription = undefined;
3778
+ this.#filterRegisterSubscription?.unsubscribe();
3779
+ this.#filterRegisterSubscription = undefined;
3780
+ this.filterRegister = undefined;
3781
+ this.#tableId = Utilities.generateUuid();
3782
+ this.key = Utilities.generateUuid();
3783
+ this.#loadCompleted = false;
3784
+ this.tableOption.set(undefined);
3785
+ this.configuration.set(undefined);
3786
+ this.items.set([]);
3787
+ this.selectedTableItems.set([]);
3788
+ this.total.set(undefined);
3789
+ this.loading.set(false);
3790
+ this.isSelectAll.set(false);
3791
+ this.isFiltered.set(false);
3792
+ this.requireFiltered.set(false);
3793
+ this.columnOperator = {};
3794
+ this.#syncColumnFilterInPlace({});
3795
+ this.#localItems = [];
3796
+ this.cacheValues = {};
3797
+ this.#cacheObjValues = {};
3798
+ this.#itemIndexMap = new WeakMap();
3799
+ this.#treeExpandState.clear();
3800
+ this.#treeSearchPredicate = undefined;
3801
+ this.#treeSearchActive = false;
3802
+ this.groupExpandState.clear();
3803
+ this.#preservedSelectedMap.clear();
3804
+ this.treeRevision.update(n => n + 1);
3805
+ if (this.paginator()) {
3806
+ this.paginator().pageIndex = 0;
3807
+ }
3808
+ const sort = this.sort();
3809
+ if (sort) {
3810
+ sort.active = '';
3811
+ sort.direction = '';
3812
+ }
3813
+ };
3214
3814
  #initConfiguration = (option) => {
3215
3815
  option.paginate = {
3216
3816
  hidden: option?.paginate?.hidden,
@@ -3230,7 +3830,10 @@ class SdTable {
3230
3830
  if (!column.filter?.operator?.list?.length) {
3231
3831
  column.filter.operator.list = this.tableConfiguration?.filter?.operator?.list?.[column.type] || [];
3232
3832
  }
3233
- this.columnOperator[column?.field] = this.tableConfiguration?.filter?.operator?.default?.[column.type];
3833
+ const defaultOperator = this.tableConfiguration?.filter?.operator?.default?.[column.type];
3834
+ if (defaultOperator) {
3835
+ this.columnOperator[column.field] = defaultOperator;
3836
+ }
3234
3837
  if (column.filter.operator.default && column.filter.operator.list?.some(el => el === column.filter?.operator?.default)) {
3235
3838
  this.columnOperator[column.field] = column.filter.operator.default;
3236
3839
  }
@@ -3240,33 +3843,39 @@ class SdTable {
3240
3843
  };
3241
3844
  #loadFilterRegister = () => {
3242
3845
  const opt = this.tableOption();
3243
- if (opt && !this.filterRegister) {
3244
- this.filterRegister = this.#gridFilterService.register(opt?.filter, {
3245
- id: this.#tableId,
3246
- columns: opt?.columns,
3247
- externalFilters: opt?.filter?.externalFilters,
3248
- filterDefs: [...this.sdFilterDefs()],
3249
- columnOperator: this.columnOperator,
3250
- });
3251
- this.#subscription.add(this.filterRegister.value.observer
3252
- .pipe(debounceTime(500), map(filterValue => {
3253
- const { columnOperator, columnFilter, notReload } = filterValue;
3254
- this.columnOperator = columnOperator || {};
3255
- // Sync IN PLACE — không gán object clone mới. column-filter chia sẻ
3256
- // reference this.columnFilter qua [columnFilter] input; nếu gán clone
3257
- // mới, cf giữ ref cũ (do OnPush + reload async lag) → ghi clear vào
3258
- // object orphan → giá trị cũ persist. Giữ ref ổn định để cf + table
3259
- // luôn trỏ cùng 1 object.
3260
- this.#syncColumnFilterInPlace(columnFilter || {});
3261
- if (!notReload) {
3262
- if (this.paginator()) {
3263
- this.paginator().pageIndex = 0;
3264
- }
3265
- this.#reload.next({ force: false });
3846
+ if (!opt || this.filterRegister)
3847
+ return;
3848
+ this.filterRegister = this.#gridFilterService.register(opt?.filter, {
3849
+ id: this.#tableId,
3850
+ columns: opt?.columns,
3851
+ externalFilters: opt?.filter?.externalFilters,
3852
+ filterDefs: [...this.sdFilterDefs()],
3853
+ columnOperator: this.columnOperator,
3854
+ force: true,
3855
+ });
3856
+ const { columnOperator, columnFilter } = this.filterRegister.value.get();
3857
+ this.columnOperator = columnOperator || {};
3858
+ this.#syncColumnFilterInPlace(columnFilter || {});
3859
+ this.#filterRegisterSubscription?.unsubscribe();
3860
+ this.#filterRegisterSubscription = this.filterRegister.value.observer
3861
+ .pipe(debounceTime(500), map(filterValue => {
3862
+ const { columnOperator, columnFilter, notReload } = filterValue;
3863
+ this.columnOperator = columnOperator || {};
3864
+ // Sync IN PLACE — không gán object clone mới. column-filter chia sẻ
3865
+ // reference this.columnFilter qua [columnFilter] input; nếu gán clone
3866
+ // mới, cf giữ ref cũ (do OnPush + reload async lag) ghi clear vào
3867
+ // object orphan → giá trị cũ persist. Giữ ref ổn định để cf + table
3868
+ // luôn trỏ cùng 1 object.
3869
+ this.#syncColumnFilterInPlace(columnFilter || {});
3870
+ if (!notReload) {
3871
+ if (this.paginator()) {
3872
+ this.paginator().pageIndex = 0;
3266
3873
  }
3267
- }))
3268
- .subscribe());
3269
- }
3874
+ this.#requestReload(false);
3875
+ }
3876
+ }))
3877
+ .subscribe();
3878
+ this.#subscription.add(this.#filterRegisterSubscription);
3270
3879
  };
3271
3880
  // Đồng bộ this.columnFilter với `next` mà GIỮ NGUYÊN reference object.
3272
3881
  // column-filter chia sẻ ref này qua [columnFilter] — reassign clone mới sẽ
@@ -3281,158 +3890,12 @@ class SdTable {
3281
3890
  }
3282
3891
  Object.assign(cur, next);
3283
3892
  };
3284
- // True nếu column filter hiện có ít nhất một giá trị đang lọc (để bật search-con).
3285
- #hasActiveColumnFilter = (rawColumnFilter, columns) => columns.some(col => {
3286
- const v = rawColumnFilter[col.field];
3287
- if (Array.isArray(v))
3288
- return v.length > 0;
3289
- if (v && typeof v === 'object')
3290
- return !!v.from || !!v.to;
3291
- return v !== undefined && v !== null && v !== '';
3292
- });
3293
- // Predicate khớp MỘT dòng (raw data) theo column filter. Tách riêng để dùng lại
3294
- // cho cả lọc root lẫn search-con (subtreeMatches/filterMatchingChildren).
3295
- #matchesColumnFilter = (data, columns, rawColumnFilter) => {
3296
- for (const column of columns) {
3297
- const { field, type } = column;
3298
- const filterValue = (rawColumnFilter[field] || '').toString().trim().toLowerCase();
3299
- // SỬA: Dùng getNestedValue để hỗ trợ nested field trong filterLocal
3300
- const rawColVal = Utilities.getNestedValue(data, field);
3301
- const columnValue = (rawColVal || '').toString().trim().toLowerCase();
3302
- if (filterValue) {
3303
- if (!columnValue && type !== 'datetime' && type !== 'date' && type !== 'time') {
3304
- return false;
3305
- }
3306
- if (type === 'string') {
3307
- if (columnValue.indexOf(filterValue) === -1) {
3308
- return false;
3309
- }
3310
- }
3311
- else if (type === 'values' || type === 'lazy-values') {
3312
- const columnType = column;
3313
- const isMultiple = columnType.option.selection === 'MULTIPLE';
3314
- if (isMultiple && Array.isArray(rawColVal)) {
3315
- const columnValues = rawColVal.map((i) => (Utilities.getNestedValue(i, columnType.option.valueField) ?? '').toString().trim().toLowerCase()) ?? [];
3316
- const filterValues = rawColumnFilter[field]?.map((v) => (v ?? '').toString().trim().toLowerCase());
3317
- if (filterValues?.length && filterValues.every(fv => !columnValues.includes(fv))) {
3318
- return false;
3319
- }
3320
- }
3321
- else {
3322
- if (columnValue !== filterValue) {
3323
- return false;
3324
- }
3325
- }
3326
- }
3327
- else if (type === 'number') {
3328
- const fValue = +filterValue.replace('>=', '').replace('<=', '').replace('>', '').replace('<', '');
3329
- const cValue = +columnValue;
3330
- if (fValue || fValue === 0) {
3331
- if (!cValue && cValue !== 0) {
3332
- return false;
3333
- }
3334
- if (filterValue.indexOf('>=') > -1 && cValue < fValue) {
3335
- return false;
3336
- }
3337
- else if (filterValue.indexOf('<=') > -1 && cValue > fValue) {
3338
- return false;
3339
- }
3340
- else if (filterValue.indexOf('<') > -1 && cValue >= fValue) {
3341
- return false;
3342
- }
3343
- else if (filterValue.indexOf('>') > -1 && cValue <= fValue) {
3344
- return false;
3345
- }
3346
- else if (cValue !== fValue) {
3347
- return false;
3348
- }
3349
- }
3350
- }
3351
- else if (type === 'boolean') {
3352
- if ((filterValue === '1' || filterValue === 'true') && columnValue !== '1' && columnValue !== 'true') {
3353
- return false;
3354
- }
3355
- else if ((filterValue === '0' || filterValue === 'false') && columnValue !== '0' && columnValue !== 'false') {
3356
- return false;
3357
- }
3358
- }
3359
- else if (type === 'datetime' || type === 'date' || type === 'time') {
3360
- const from = rawColumnFilter[field]?.from ?? rawColumnFilter[field];
3361
- const to = rawColumnFilter[field]?.to ?? rawColumnFilter[field];
3362
- const fromDate = DateUtilities.begin(from);
3363
- const toDate = DateUtilities.end(to);
3364
- if (fromDate || toDate) {
3365
- if (!columnValue) {
3366
- return false;
3367
- }
3368
- const columnTime = new Date(columnValue).getTime();
3369
- const fromDateTime = fromDate?.getTime() || null;
3370
- const toDateTime = toDate?.getTime() || null;
3371
- if (fromDateTime && fromDateTime > columnTime) {
3372
- return false;
3373
- }
3374
- if (toDateTime && columnTime > toDateTime) {
3375
- return false;
3376
- }
3377
- }
3378
- }
3379
- }
3380
- }
3381
- return true;
3382
- };
3383
3893
  #filterLocal = (localItems, filterInfo) => {
3384
- const opt = this.tableOption();
3385
- const { columns } = opt;
3386
- const { rawColumnFilter, orderBy, orderDirection, pageSize, pageNumber } = filterInfo;
3387
- const matchesData = (data) => this.#matchesColumnFilter(data, columns, rawColumnFilter);
3388
- // Search ở cấp con: chỉ bật cho table local + static tree + đang có filter.
3389
- // Khi bật, một root được giữ nếu CHÍNH NÓ hoặc bất kỳ hậu duệ nào khớp; và
3390
- // lưu predicate để #render prune + auto-expand nhánh khớp.
3391
- const treeOpt = opt.tree;
3392
- const treeSearch = opt.type === 'local' && treeOpt?.loadType === 'static' && this.#hasActiveColumnFilter(rawColumnFilter, columns);
3393
- this.#treeSearchPredicate = treeSearch ? matchesData : undefined;
3394
- const items = treeSearch
3395
- ? localItems.filter(tableItem => subtreeMatches(tableItem.data, matchesData, treeOpt))
3396
- : localItems.filter(tableItem => matchesData(tableItem.data));
3397
- // Sort
3398
- if (orderBy && orderDirection) {
3399
- const column = columns.find(e => e.field === orderBy);
3400
- if (column) {
3401
- const { type, field } = column;
3402
- items.sort((tableItemCurrent, tableItemNext) => {
3403
- const data = tableItemCurrent.data;
3404
- const next = tableItemNext.data;
3405
- // SỬA: Dùng getNestedValue cho sorting
3406
- const dataVal = Utilities.getNestedValue(data, field);
3407
- const nextVal = Utilities.getNestedValue(next, field);
3408
- if (type === 'number') {
3409
- return (dataVal || 0) - (nextVal || 0);
3410
- }
3411
- if (type === 'date' || type === 'datetime' || type === 'time') {
3412
- const d1 = new Date(dataVal || '').getTime();
3413
- const d2 = new Date(nextVal || '').getTime();
3414
- return d1 - d2;
3415
- }
3416
- const s1 = (dataVal || '').toString();
3417
- const s2 = (nextVal || '').toString();
3418
- if (s1 > s2) {
3419
- return 1;
3420
- }
3421
- if (s1 < s2) {
3422
- return -1;
3423
- }
3424
- return 0;
3425
- });
3426
- if (orderDirection === 'DESC') {
3427
- items.reverse();
3428
- }
3429
- }
3430
- }
3894
+ const result = filterLocalItems(localItems, this.tableOption(), filterInfo);
3895
+ this.#treeSearchPredicate = result.treeSearchPredicate;
3431
3896
  return {
3432
- items: items.filter((item, index) => {
3433
- return index >= pageNumber * pageSize && index < (pageNumber + 1) * pageSize;
3434
- }),
3435
- total: items.length,
3897
+ items: result.items,
3898
+ total: result.total,
3436
3899
  };
3437
3900
  };
3438
3901
  getFilterRequest = () => {
@@ -3516,7 +3979,10 @@ class SdTable {
3516
3979
  total: 0,
3517
3980
  };
3518
3981
  }
3519
- const pagingReq = this.#convertPagingReq(filterReq);
3982
+ const pagingReq = SdConvertToPagingReq(filterReq, {
3983
+ columns: opt.columns,
3984
+ externalFilters: opt.filter?.externalFilters,
3985
+ });
3520
3986
  const data = await items(filterReq, pagingReq).catch(err => {
3521
3987
  console.error(err);
3522
3988
  return {
@@ -3566,18 +4032,21 @@ class SdTable {
3566
4032
  // why: KHÔNG persist trạng thái bung bị ÉP trong lúc search (tránh nhánh
3567
4033
  // tự bung do search bị ghi nhầm thành lựa chọn của user sau khi clear).
3568
4034
  if (!this.#treeSearchActive) {
3569
- this.#saveTreeExpandState(this.items());
4035
+ saveTreeExpandState(this.items(), this.#treeExpandState);
3570
4036
  }
3571
4037
  // why: search vừa tắt → childItems đang là tập đã prune theo từ khoá cũ;
3572
4038
  // xoá cache để dựng lại đầy đủ children ở chế độ thường.
3573
4039
  if (this.#treeSearchActive && !searchNow) {
3574
- this.#clearTreeChildCache(this.#localItems);
4040
+ clearTreeChildCache(this.#localItems);
3575
4041
  }
3576
4042
  }
3577
4043
  this.items.set(args?.items || []);
3578
4044
  this.total.set(args?.total || 0);
3579
4045
  if (treeOpt) {
3580
- this.#initTreeMeta(this.items(), treeOpt);
4046
+ initTreeMeta(this.items(), treeOpt, {
4047
+ expandState: this.#treeExpandState,
4048
+ treeSearchPredicate: this.#treeSearchPredicate,
4049
+ });
3581
4050
  await this.#expandDefaultBranches(this.items(), treeOpt);
3582
4051
  this.treeRevision.update(n => n + 1);
3583
4052
  }
@@ -3624,7 +4093,10 @@ class SdTable {
3624
4093
  else {
3625
4094
  const filterReq = this.#filterExportInfo(pageNumber, pageSize);
3626
4095
  if (opt.type === 'server') {
3627
- const pagingReq = this.#convertPagingReq(filterReq);
4096
+ const pagingReq = SdConvertToPagingReq(filterReq, {
4097
+ columns: opt.columns,
4098
+ externalFilters: opt.filter?.externalFilters,
4099
+ });
3628
4100
  const result = opt.items(filterReq, pagingReq);
3629
4101
  return await result;
3630
4102
  }
@@ -3669,51 +4141,6 @@ class SdTable {
3669
4141
  exportCustom = () => {
3670
4142
  this.#tableExportService.exportCustom(this.#createExportContext());
3671
4143
  };
3672
- #saveTreeExpandState = (rows) => {
3673
- for (const row of rows) {
3674
- if (row.meta.tree?.isExpanded) {
3675
- this.#treeExpandState.set(row.meta.id, true);
3676
- }
3677
- for (const child of row.meta.tree?.childItems ?? []) {
3678
- this.#saveTreeExpandState([child]);
3679
- }
3680
- }
3681
- };
3682
- // Children "nhìn thấy được" của một row: bình thường = toàn bộ embedded;
3683
- // khi search-con = chỉ giữ child có subtree khớp từ khoá (prune).
3684
- #visibleChildrenData = (data, treeOpt) => {
3685
- const all = getChildrenFromData(data, treeOpt);
3686
- const predicate = this.#treeSearchPredicate;
3687
- return predicate ? filterMatchingChildren(all, predicate, treeOpt) : all;
3688
- };
3689
- // Xoá cache childItems trên toàn cây — dùng khi tắt search để dựng lại đầy đủ.
3690
- #clearTreeChildCache = (rows) => {
3691
- for (const row of rows) {
3692
- const children = row.meta.tree?.childItems;
3693
- if (children?.length) {
3694
- this.#clearTreeChildCache(children);
3695
- row.meta.tree.childItems = undefined;
3696
- }
3697
- }
3698
- };
3699
- #initTreeMeta = (rows, option, level = 0, parentId) => {
3700
- const searchActive = !!this.#treeSearchPredicate;
3701
- for (const row of rows) {
3702
- const saved = this.#treeExpandState.get(row.meta.id);
3703
- // Khi search: hasChildren tính theo tập con đã prune; auto-expand nhánh còn con.
3704
- const hasChildren = searchActive
3705
- ? this.#visibleChildrenData(row.data, option).length > 0
3706
- : resolveHasChildren(row, option);
3707
- row.meta.tree = {
3708
- ...row.meta.tree,
3709
- level,
3710
- parentId,
3711
- hasChildren,
3712
- isExpanded: searchActive ? hasChildren : (saved ?? resolveDefaultExpanded(level, option)),
3713
- isExpanding: false,
3714
- };
3715
- }
3716
- };
3717
4144
  #ensureChildItemsFormatted = async (row) => {
3718
4145
  const opt = this.tableOption();
3719
4146
  const treeOpt = opt.tree;
@@ -3722,7 +4149,7 @@ class SdTable {
3722
4149
  // vì tập này phụ thuộc từ khoá (đổi mỗi lần gõ) nên không thể cache.
3723
4150
  if (!searchActive && row.meta.tree?.childItems?.length)
3724
4151
  return;
3725
- const raw = this.#visibleChildrenData(row.data, treeOpt);
4152
+ const raw = getVisibleChildrenData(row.data, treeOpt, this.#treeSearchPredicate);
3726
4153
  if (!raw.length) {
3727
4154
  if (searchActive && row.meta.tree)
3728
4155
  row.meta.tree.childItems = [];
@@ -3730,20 +4157,12 @@ class SdTable {
3730
4157
  }
3731
4158
  const formatted = await this.#tableFormatService.format(raw, opt.columns, this.cacheValues, this.#cacheObjValues);
3732
4159
  row.meta.tree.childItems = formatted;
3733
- const childLevel = (row.meta.tree.level ?? 0) + 1;
3734
- for (const child of formatted) {
3735
- const saved = this.#treeExpandState.get(child.meta.id);
3736
- const childHasChildren = searchActive
3737
- ? this.#visibleChildrenData(child.data, treeOpt).length > 0
3738
- : resolveHasChildren(child, treeOpt);
3739
- child.meta.tree = {
3740
- level: childLevel,
3741
- parentId: row.meta.id,
3742
- hasChildren: childHasChildren,
3743
- isExpanded: searchActive ? childHasChildren : (saved ?? resolveDefaultExpanded(childLevel, treeOpt)),
3744
- isExpanding: false,
3745
- };
3746
- }
4160
+ initTreeMeta(formatted, treeOpt, {
4161
+ level: (row.meta.tree.level ?? 0) + 1,
4162
+ parentId: row.meta.id,
4163
+ expandState: this.#treeExpandState,
4164
+ treeSearchPredicate: this.#treeSearchPredicate,
4165
+ });
3747
4166
  };
3748
4167
  #expandDefaultBranches = async (rows, option) => {
3749
4168
  const maxDepth = option.maxDepth;
@@ -3902,28 +4321,14 @@ class SdTable {
3902
4321
  this.#updateSelectedItems();
3903
4322
  };
3904
4323
  #getSelectionRows = () => {
3905
- const roots = this.items();
3906
- const treeOpt = this.tableOption()?.tree;
3907
- if (!treeOpt)
3908
- return roots;
3909
- return flattenTree(roots, treeOpt);
4324
+ return getSelectionRows(this.items(), this.tableOption()?.tree);
3910
4325
  };
3911
- #getSelectedRowData = () => this.#getSelectionRows()
3912
- .filter(e => e.meta.selector.isSelected)
3913
- .map(e => e.data);
4326
+ #getSelectedRowData = () => getSelectedRowData(this.#getSelectionRows());
3914
4327
  #applyDefaultSelected = () => {
3915
- const defaultSelected = this.tableOption()?.selector?.defaultSelected;
3916
- if (!defaultSelected)
3917
- return;
3918
- const treeOpt = this.tableOption()?.tree;
3919
- const rows = treeOpt ? collectFormattedTreeRows(this.items()) : this.items();
3920
- rows.forEach(item => {
3921
- item.meta.selector.isSelected = defaultSelected(item.data);
3922
- });
4328
+ applyDefaultSelected(this.items(), this.tableOption()?.tree, this.tableOption()?.selector?.defaultSelected);
3923
4329
  };
3924
4330
  #syncSelectAllState = () => {
3925
- const visible = this.#getSelectionRows();
3926
- this.isSelectAll.set(visible.length > 0 && visible.every(e => e.meta.selector?.isSelected));
4331
+ this.isSelectAll.set(resolveSelectAllState(this.#getSelectionRows()));
3927
4332
  };
3928
4333
  // ==========================================
3929
4334
  // GROUP HELPERS — sync selection + expand state cho group header rows
@@ -3966,9 +4371,7 @@ class SdTable {
3966
4371
  return;
3967
4372
  const key = header.meta.group.key;
3968
4373
  const defaultExpanded = !this.tableOption()?.group?.defaultCollapsed;
3969
- const current = this.groupExpandState.has(key)
3970
- ? !!this.groupExpandState.get(key)
3971
- : defaultExpanded;
4374
+ const current = this.groupExpandState.has(key) ? !!this.groupExpandState.get(key) : defaultExpanded;
3972
4375
  this.groupExpandState.set(key, !current);
3973
4376
  header.meta.group.isExpanded = !current;
3974
4377
  // why: trigger pipe re-eval — Map mutation không thay đổi reference items() nên cần update.
@@ -3985,7 +4388,7 @@ class SdTable {
3985
4388
  * Colspan cho cell sdGroupHeader trên group row = displayedColumns.length.
3986
4389
  * Span TOÀN BỘ width data row vì group row chỉ có 1 cell.
3987
4390
  */
3988
- sdGroupColspan = computed(() => (this.configuration()?.displayedColumns?.length || 1));
4391
+ sdGroupColspan = computed(() => this.configuration()?.displayedColumns?.length || 1);
3989
4392
  /** Build context object truyền vào SdTableGroupDefDirective template. */
3990
4393
  groupContext = (header) => {
3991
4394
  const g = header.meta.group;
@@ -4007,9 +4410,7 @@ class SdTable {
4007
4410
  if (!fn)
4008
4411
  return null;
4009
4412
  const tree = row.meta?.tree;
4010
- const ctx = this.tableOption()?.tree && tree
4011
- ? { level: tree.level, hasChildren: tree.hasChildren, isExpanded: tree.isExpanded }
4012
- : undefined;
4413
+ const ctx = this.tableOption()?.tree && tree ? { level: tree.level, hasChildren: tree.hasChildren, isExpanded: tree.isExpanded } : undefined;
4013
4414
  return fn(row.data, index, ctx);
4014
4415
  };
4015
4416
  // why: khi preserveSelection bật, giữ map nội bộ id → SdTableItem để selection
@@ -4023,29 +4424,14 @@ class SdTable {
4023
4424
  #restorePreservedSelection = () => {
4024
4425
  if (!this.#preserveEnabled())
4025
4426
  return;
4026
- const rows = this.#getSelectionRows();
4027
- rows.forEach(item => {
4028
- if (this.#preservedSelectedMap.has(item.meta.id)) {
4029
- item.meta.selector.isSelected = true;
4030
- this.#preservedSelectedMap.set(item.meta.id, item);
4031
- }
4032
- });
4427
+ restorePreservedSelection(this.#getSelectionRows(), this.#preservedSelectedMap);
4033
4428
  };
4034
4429
  #updateSelectedItems = () => {
4035
4430
  const rows = this.#getSelectionRows();
4036
4431
  if (this.#preserveEnabled()) {
4037
4432
  // Sync map theo state visible: add nếu selected, remove nếu deselected.
4038
4433
  // Off-page items giữ nguyên trong map (không bị visit ở đây).
4039
- rows.forEach(item => {
4040
- const id = item.meta.id;
4041
- if (item.meta.selector.isSelected) {
4042
- this.#preservedSelectedMap.set(id, item);
4043
- }
4044
- else {
4045
- this.#preservedSelectedMap.delete(id);
4046
- }
4047
- });
4048
- this.selectedTableItems.set(Array.from(this.#preservedSelectedMap.values()));
4434
+ this.selectedTableItems.set(syncPreservedSelection(rows, this.#preservedSelectedMap));
4049
4435
  }
4050
4436
  else {
4051
4437
  this.selectedTableItems.set(rows.filter(item => item.meta.selector.isSelected));
@@ -4097,295 +4483,38 @@ class SdTable {
4097
4483
  return (p?.pageIndex ?? 0) * (p?.pageSize ?? 0);
4098
4484
  });
4099
4485
  isReorderDisabled(item) {
4100
- if ((item.meta.tree?.level ?? 0) > 0)
4101
- return true;
4102
4486
  const opt = this.tableOption()?.rowReorder;
4103
- if (!opt?.disabled || item.meta?.group?.items?.length)
4104
- return false;
4105
4487
  const idx = this.#itemIndexMap.get(item) ?? -1;
4106
- return opt.disabled(item.data, idx);
4107
- }
4108
- #sameGroup(a, b, allItems) {
4109
- const groupOf = (item) => {
4110
- let lastGroupIdx = -1;
4111
- for (let i = 0; i < allItems.length; i++) {
4112
- if (allItems[i].meta?.group?.items?.length)
4113
- lastGroupIdx = i;
4114
- if (allItems[i] === item)
4115
- return lastGroupIdx;
4116
- }
4117
- return -1;
4118
- };
4119
- return groupOf(a) === groupOf(b);
4488
+ return isReorderDisabled(item, opt, idx);
4120
4489
  }
4121
4490
  reorderSortPredicate = (index, drag, drop) => {
4122
4491
  const opt = this.tableOption()?.rowReorder;
4123
- if (!opt?.enabled)
4124
- return false;
4125
- const allItems = drop.data;
4126
- const targetItem = allItems?.[index];
4127
- if (!targetItem)
4128
- return false;
4129
- if ((targetItem.meta.tree?.level ?? 0) > 0)
4130
- return false;
4131
- if ((drag.data.meta.tree?.level ?? 0) > 0)
4132
- return false;
4133
- if (targetItem.meta?.group?.items?.length)
4134
- return false;
4135
- if (this.tableOption()?.group) {
4136
- return this.#sameGroup(drag.data, targetItem, allItems);
4137
- }
4138
- return true;
4492
+ return canSortReorder({
4493
+ enabled: opt?.enabled,
4494
+ index,
4495
+ dragItem: drag.data,
4496
+ allItems: drop.data,
4497
+ hasGroup: !!this.tableOption()?.group,
4498
+ });
4139
4499
  };
4140
4500
  onReorderDrop(event) {
4141
4501
  const { previousIndex, currentIndex } = event;
4142
4502
  if (previousIndex === currentIndex)
4143
4503
  return;
4144
- const groupedItems = event.container.data;
4145
- const toItemsIndex = (domIdx) => {
4146
- let count = 0;
4147
- for (let i = 0; i < domIdx; i++) {
4148
- if (!groupedItems[i]?.meta?.group?.items?.length && (groupedItems[i]?.meta?.tree?.level ?? 0) === 0)
4149
- count++;
4150
- }
4151
- return count;
4152
- };
4153
- const fromIdx = toItemsIndex(previousIndex);
4154
- const toIdx = toItemsIndex(currentIndex);
4155
- const current = [...this.items()];
4156
- const localPositions = current.map(item => this.#localItems.indexOf(item));
4157
- moveItemInArray(current, fromIdx, toIdx);
4158
- this.items.set(current);
4159
- if (this.tableOption()?.type === 'local' && localPositions.every(p => p >= 0)) {
4160
- const newLocal = [...this.#localItems];
4161
- current.forEach((item, i) => {
4162
- newLocal[localPositions[i]] = item;
4163
- });
4164
- this.#localItems = newLocal;
4504
+ const result = reorderTableItems({
4505
+ items: this.items(),
4506
+ renderedItems: event.container.data,
4507
+ previousRenderedIndex: previousIndex,
4508
+ currentRenderedIndex: currentIndex,
4509
+ localItems: this.tableOption()?.type === 'local' ? this.#localItems : undefined,
4510
+ });
4511
+ this.items.set(result.items);
4512
+ if (this.tableOption()?.type === 'local' && result.localItems) {
4513
+ this.#localItems = result.localItems;
4165
4514
  }
4166
4515
  this.table()?.renderRows();
4167
- this.tableOption()?.rowReorder?.onChange?.(current.map(i => i.data), event.item.data.data, fromIdx, toIdx);
4516
+ this.tableOption()?.rowReorder?.onChange?.(result.items.map(i => i.data), event.item.data.data, result.fromIndex, result.toIndex);
4168
4517
  }
4169
- #convertPagingReq = (filterReq) => {
4170
- const opt = this.tableOption();
4171
- const { columns, filter } = opt;
4172
- const externalFilters = filter?.externalFilters || [];
4173
- const req = {
4174
- filters: [],
4175
- orders: [],
4176
- pageNumber: filterReq.pageNumber,
4177
- pageSize: filterReq.pageSize,
4178
- };
4179
- const { filters, orders } = req;
4180
- const { rawColumnFilter, columnOperator, rawExternalFilter, orderBy, orderDirection } = filterReq;
4181
- for (const externalFilter of externalFilters || []) {
4182
- const { field } = externalFilter;
4183
- const value = rawExternalFilter?.[field];
4184
- if (value !== undefined && value !== null && value !== '') {
4185
- if (externalFilter.type === 'string') {
4186
- if (externalFilter.defaultOperator === 'EQUAL' && value?.includes(',')) {
4187
- filters.push({
4188
- field,
4189
- operator: 'IN',
4190
- data: value.split(',').map(val => val.trim()),
4191
- });
4192
- }
4193
- else {
4194
- filters.push({
4195
- field,
4196
- operator: externalFilter.defaultOperator || 'CONTAIN',
4197
- data: value,
4198
- });
4199
- }
4200
- }
4201
- else if (externalFilter.type === 'boolean') {
4202
- filters.push({
4203
- field,
4204
- operator: 'EQUAL',
4205
- data: value === true || value === 1 || value === 'true' || value === '1',
4206
- });
4207
- }
4208
- else if (externalFilter.type === 'daterange') {
4209
- if (typeof value === 'object' && 'from' in value && 'to' in value) {
4210
- if (value?.from) {
4211
- filters.push({
4212
- field,
4213
- operator: 'GREATER_OR_EQUAL',
4214
- data: DateUtilities.begin(value?.from).toISOString(),
4215
- });
4216
- }
4217
- if (value?.to) {
4218
- filters.push({
4219
- field,
4220
- operator: 'LESS_THAN',
4221
- data: DateUtilities.begin(DateUtilities.addDays(value?.to, 1)).toISOString(),
4222
- });
4223
- }
4224
- }
4225
- }
4226
- else if (externalFilter.type === 'date' || externalFilter.type === 'datetime') {
4227
- if (DateUtilities.isDate(value)) {
4228
- if (externalFilter.type === 'date') {
4229
- if (externalFilter.defaultOperator === 'GREATER_OR_EQUAL') {
4230
- filters.push({
4231
- field,
4232
- operator: 'GREATER_OR_EQUAL',
4233
- data: DateUtilities.begin(value).toISOString(),
4234
- });
4235
- }
4236
- if (externalFilter.defaultOperator === 'LESS_OR_EQUAL') {
4237
- filters.push({
4238
- field,
4239
- operator: 'LESS_THAN',
4240
- data: DateUtilities.begin(DateUtilities.addDays(value, 1)).toISOString(),
4241
- });
4242
- }
4243
- }
4244
- else {
4245
- if (externalFilter.defaultOperator === 'GREATER_OR_EQUAL') {
4246
- filters.push({
4247
- field,
4248
- operator: 'GREATER_OR_EQUAL',
4249
- data: new Date(value).toISOString(),
4250
- });
4251
- }
4252
- if (externalFilter.defaultOperator === 'LESS_OR_EQUAL') {
4253
- filters.push({
4254
- field,
4255
- operator: 'LESS_OR_EQUAL',
4256
- data: new Date(value).toISOString(),
4257
- });
4258
- }
4259
- }
4260
- }
4261
- }
4262
- else {
4263
- if (Array.isArray(value)) {
4264
- if (value.length) {
4265
- filters.push({
4266
- field,
4267
- operator: 'IN',
4268
- data: value,
4269
- });
4270
- }
4271
- }
4272
- else if (typeof value === 'object' && 'from' in value && 'to' in value) {
4273
- if (value?.from) {
4274
- filters.push({
4275
- field,
4276
- operator: 'GREATER_OR_EQUAL',
4277
- data: DateUtilities.begin(value?.from).toISOString(),
4278
- });
4279
- }
4280
- if (value?.to) {
4281
- filters.push({
4282
- field,
4283
- operator: 'LESS_THAN',
4284
- data: DateUtilities.begin(DateUtilities.addDays(value?.to, 1)).toISOString(),
4285
- });
4286
- }
4287
- }
4288
- else {
4289
- filters.push({
4290
- field,
4291
- operator: externalFilter.defaultOperator || 'EQUAL',
4292
- data: value,
4293
- });
4294
- }
4295
- }
4296
- }
4297
- }
4298
- for (const column of columns || []) {
4299
- const { field } = column;
4300
- const value = rawColumnFilter?.[field];
4301
- const operator = columnOperator?.[field] || column.filter?.operator?.default;
4302
- if (value !== undefined && value !== null && value !== '') {
4303
- if (column.type === 'string') {
4304
- // `operator` is the wide `Operator` union; in this data-bearing path it is a
4305
- // single-value comparison operator, hence the cast to the `Filter` contract.
4306
- filters.push({
4307
- field: field,
4308
- operator: operator || 'CONTAIN',
4309
- data: value,
4310
- });
4311
- }
4312
- else if (column.type === 'boolean') {
4313
- filters.push({
4314
- field: field,
4315
- operator: 'EQUAL',
4316
- data: value === true || value === 1 || value === 'true' || value === '1',
4317
- });
4318
- }
4319
- else if (column.type === 'date' || column.type === 'datetime') {
4320
- if (value && typeof value === 'object' && 'from' in value && 'to' in value) {
4321
- if (value?.from && value?.to) {
4322
- filters.push({
4323
- field: field,
4324
- operator: 'BETWEEN',
4325
- data: {
4326
- from: DateUtilities.begin(value?.from).toISOString(),
4327
- to: DateUtilities.end(value?.to).toISOString(),
4328
- },
4329
- });
4330
- }
4331
- else if (value?.from) {
4332
- filters.push({
4333
- field: field,
4334
- operator: 'GREATER_OR_EQUAL',
4335
- data: DateUtilities.begin(value?.from).toISOString(),
4336
- });
4337
- }
4338
- else if (value?.to) {
4339
- filters.push({
4340
- field: field,
4341
- operator: 'LESS_THAN',
4342
- data: DateUtilities.begin(DateUtilities.addDays(value?.to, 1)).toISOString(),
4343
- });
4344
- }
4345
- }
4346
- else {
4347
- if (DateUtilities.isDate(value)) {
4348
- filters.push({
4349
- field: field,
4350
- operator: 'BETWEEN',
4351
- data: {
4352
- from: DateUtilities.begin(value).toISOString(),
4353
- to: DateUtilities.end(value).toISOString(),
4354
- },
4355
- });
4356
- }
4357
- }
4358
- }
4359
- else {
4360
- if (Array.isArray(value)) {
4361
- if (value.length) {
4362
- filters.push({
4363
- field: field,
4364
- operator: 'IN',
4365
- data: value,
4366
- });
4367
- }
4368
- }
4369
- else {
4370
- // `operator` is the wide `Operator` union; this data-bearing path uses a
4371
- // single-value comparison operator, hence the cast to the `Filter` contract.
4372
- filters.push({
4373
- field: field,
4374
- operator: operator || 'EQUAL',
4375
- data: value,
4376
- });
4377
- }
4378
- }
4379
- }
4380
- }
4381
- if (orderBy && orderDirection) {
4382
- orders.push({
4383
- field: orderBy,
4384
- direction: orderDirection,
4385
- });
4386
- }
4387
- return req;
4388
- };
4389
4518
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
4390
4519
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdTable, isStandalone: true, selector: "sd-table", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, option: { classPropertyName: "option", publicName: "option", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-autoid": "autoId()", "attr.data-loading": "loading() ? \"true\" : \"false\"" } }, providers: [
4391
4520
  DatePipe,
@@ -4467,183 +4596,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
4467
4596
  ], template: "@let _configuration = configuration();\r\n@let _tableOption = tableOption()!;\r\n@let _loading = loading();\r\n@let _items = items();\r\n@let _total = total();\r\n@let _isFiltered = isFiltered();\r\n@let _requireFiltered = requireFiltered();\r\n@let _export = _tableOption?.export;\r\n@let _exporting = exporting();\r\n@let _titleDef = titleDef();\r\n@let _autoId = autoId();\r\n@let _exportTitle = exportTitle();\r\n@let _cellDef = cellDef();\r\n@let _footerDef = footerDef();\r\n@let _sdSubInformation = sdSubInformation();\r\n@let _configComponent = configComponent();\r\n@let _mobileFilter = mobileFilter();\r\n@let _selectedTableItems = selectedTableItems();\r\n<!-- why: tree kh\u00F4ng c\u00F2n c\u1ED9t ri\u00EAng \u2014 khi KH\u00D4NG c\u00F3 c\u1ED9t Index, icon expand nh\u00FAng v\u00E0o\r\n c\u1ED9t data \u0111\u1EA7u ti\u00EAn. \u0110\u00E2y l\u00E0 field c\u1EE7a c\u1ED9t \u0111\u00F3 (null n\u1EBFu c\u00F3 Index ho\u1EB7c kh\u00F4ng tree). -->\r\n@let _treeFirstField = _tableOption?.tree && !_tableOption.index?.enabled ? _configuration?.firstColumns?.[0]?.field : null;\r\n\r\n@if (_configuration) {\r\n @if (!_tableOption.filter?.disabled && !!_tableOption.filter?.externalFilters?.length && filterRegister) {\r\n <external-filter\r\n class=\"mb-16\"\r\n [autoId]=\"_autoId\"\r\n [filterRegister]=\"filterRegister\"\r\n [filter]=\"_tableOption.filter!\"\r\n [externalFilters]=\"_tableOption.filter?.externalFilters!\">\r\n </external-filter>\r\n }\r\n @let groupedItems = _items | sdTree: _tableOption.tree : treeRevision() | sdGroup: _tableOption : groupExpandState;\r\n\r\n <!-- Tree toggle d\u00F9ng chung: indent (theo c\u1EA5p) + slot chevron/spinner. Nh\u00FAng v\u00E0o\r\n c\u1ED9t Index ho\u1EB7c c\u1ED9t data \u0111\u1EA7u qua *ngTemplateOutlet (context: { row }). -->\r\n <ng-template #sdTreeToggle let-row=\"row\">\r\n @let _tree = row?.meta?.tree;\r\n @if (_tree) {\r\n <span class=\"sd-tree-indent\" [style.width.px]=\"(_tree.level ?? 0) * (_tableOption.tree?.indentSize ?? 20)\"></span>\r\n <span class=\"sd-tree-toggle-slot\">\r\n @if (_tree.hasChildren) {\r\n @if (_tree.isExpanding) {\r\n <span class=\"lds-ring sd-tree-spinner\">\r\n <div></div>\r\n <div></div>\r\n <div></div>\r\n <div></div>\r\n </span>\r\n } @else {\r\n @let _aid = _autoId;\r\n <button\r\n type=\"button\"\r\n class=\"sd-tree-toggle-btn\"\r\n [attr.data-autoid]=\"_aid ? _aid + '-tree-toggle-' + row.meta.id : null\"\r\n (click)=\"onTreeToggle(row); $event.stopPropagation()\">\r\n <mat-icon>{{ _tree.isExpanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\r\n </button>\r\n }\r\n }\r\n </span>\r\n }\r\n </ng-template>\r\n\r\n <ng-content select=\"[sdTableTop]\"></ng-content>\r\n <div class=\"c-container\">\r\n @if (_loading) {\r\n <div class=\"c-loading\">\r\n <mat-spinner></mat-spinner>\r\n </div>\r\n }\r\n <div\r\n class=\"c-table\"\r\n sdScroll\r\n stickyShadow\r\n [style.max-height]=\"_tableOption.style?.maxHeight\"\r\n [style.min-height]=\"_tableOption.style?.minHeight\">\r\n <table\r\n mat-table\r\n [dataSource]=\"groupedItems\"\r\n [trackBy]=\"trackBy\"\r\n matSort\r\n [matSortDisabled]=\"!_tableOption.sort?.enable\"\r\n multiTemplateDataRows\r\n cdkDropList\r\n [cdkDropListData]=\"groupedItems\"\r\n [cdkDropListDisabled]=\"!_tableOption.rowReorder?.enabled\"\r\n [cdkDropListSortPredicate]=\"reorderSortPredicate\"\r\n (cdkDropListDropped)=\"onReorderDrop($event)\">\r\n @if (_tableOption.rowReorder?.enabled) {\r\n <ng-container matColumnDef=\"sdReorder\">\r\n <th mat-header-cell *matHeaderCellDef class=\"sd-reorder-header\" [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\"></th>\r\n <td mat-cell *matCellDef=\"let row\" class=\"sd-reorder-cell\">\r\n @if (!row.meta?.group?.items?.length) {\r\n <mat-icon cdkDragHandle [class.sd-reorder-disabled]=\"isReorderDisabled(row)\">\r\n {{ _tableOption.rowReorder?.icon || 'drag_indicator' }}\r\n </mat-icon>\r\n }\r\n </td>\r\n <td mat-footer-cell *matFooterCellDef></td>\r\n </ng-container>\r\n }\r\n\r\n <ng-container matColumnDef=\"sdSubInformation\" sticky>\r\n <td class=\"p-0\" mat-cell *matCellDef=\"let item\" [attr.colspan]=\"_configuration.displayedColumns.length\">\r\n @if (_sdSubInformation?.templateRef) {\r\n @if (_tableOption.expand?.always) {\r\n <ng-container *ngTemplateOutlet=\"_sdSubInformation?.templateRef!; context: { item: item }\"> </ng-container>\r\n } @else {\r\n <div [@detailExpand]=\"item.isExpanded ? 'expanded' : 'collapsed'\">\r\n @if (item.isExpanded) {\r\n <ng-container *ngTemplateOutlet=\"_sdSubInformation?.templateRef!; context: { item: item }\"> </ng-container>\r\n }\r\n </div>\r\n }\r\n }\r\n </td>\r\n <td mat-footer-cell *matFooterCellDef></td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"sdSubInformationAction\" stickyEnd>\r\n <th class=\"p-0\" mat-header-cell *matHeaderCellDef style=\"width: 1px\" [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\"></th>\r\n <td mat-cell *matCellDef=\"let element\">\r\n @if (!element.isExpanding && !_tableOption.expand?.always) {\r\n <sd-button\r\n [autoId]=\"_autoId ? _autoId + '-expand-' + element.meta.id : null\"\r\n type=\"link\"\r\n [prefixIcon]=\"element.isExpanded ? 'expand_less' : 'expand_more'\"\r\n (click)=\"onExpand(element)\"\r\n width=\"35px\">\r\n </sd-button>\r\n }\r\n @if (element.isExpanding) {\r\n <div class=\"lds-ring\">\r\n <div></div>\r\n <div></div>\r\n <div></div>\r\n <div></div>\r\n </div>\r\n }\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"sdSelection\" sticky>\r\n <th class=\"sd-selection-cell p-0\" mat-header-cell *matHeaderCellDef [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\">\r\n @let visibleSelectAll = _items | selectionVisibleSelectAll: _tableOption.selector | async;\r\n @if (visibleSelectAll) {\r\n <mat-checkbox\r\n class=\"c-selection px-8\"\r\n color=\"primary\"\r\n [ngModel]=\"isSelectAll()\"\r\n (ngModelChange)=\"isSelectAll.set($event)\"\r\n (change)=\"onSelectAll()\">\r\n </mat-checkbox>\r\n }\r\n </th>\r\n\r\n <td class=\"sd-selection-cell p-0\" mat-cell *matCellDef=\"let item\">\r\n @let visible = item | selectionVisible: _tableOption.selector;\r\n @if (visible) {\r\n @if (_tableOption.selector?.single) {\r\n <mat-radio-button\r\n class=\"c-selection px-8\"\r\n color=\"primary\"\r\n [checked]=\"item.meta.selector.isSelected\"\r\n (change)=\"item.meta.selector.isSelected = true; onSelect(item)\"\r\n [disabled]=\"_selectedTableItems | selectionDisabled: item : _tableOption.selector!\">\r\n </mat-radio-button>\r\n } @else {\r\n <mat-checkbox\r\n class=\"c-selection px-8\"\r\n color=\"primary\"\r\n [(ngModel)]=\"item.meta.selector.isSelected\"\r\n (change)=\"onSelect(item)\"\r\n [disabled]=\"_selectedTableItems | selectionDisabled: item : _tableOption.selector!\">\r\n </mat-checkbox>\r\n }\r\n }\r\n </td>\r\n <td mat-footer-cell *matFooterCellDef></td>\r\n </ng-container>\r\n @if (_tableOption.index?.enabled) {\r\n @let _pageOffset = pageOffset();\r\n @let _indexWidth = _tableOption.index?.width || '50px';\r\n <!-- why: khi tree, c\u1ED9t Index \"\u00F4m\" th\u00EAm chevron + indent. D\u00F9ng width:1px +\r\n nowrap (.sd-tree-index-cell) \u0111\u1EC3 c\u1ED9t co S\u00C1T n\u1ED9i dung, kh\u00F4ng chi\u1EBFm ch\u1ED7\r\n th\u1EEBa; header \"#\" canh tr\u00E1i l\u00F9i v\u00E0o (.sd-tree-index-header) cho th\u1EB3ng s\u1ED1. -->\r\n @let _treeIndex = !!_tableOption.tree;\r\n <ng-container matColumnDef=\"sdIndex\" sticky>\r\n <th\r\n [class.p-0]=\"!_treeIndex\"\r\n [class.text-center]=\"!_treeIndex\"\r\n [class.sd-tree-index-cell]=\"_treeIndex\"\r\n [class.sd-tree-index-header]=\"_treeIndex\"\r\n mat-header-cell\r\n *matHeaderCellDef\r\n [style.width]=\"_treeIndex ? '1px' : _indexWidth\"\r\n [style.min-width]=\"_treeIndex ? null : _indexWidth\"\r\n [style.max-width]=\"_treeIndex ? null : _indexWidth\"\r\n [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\">\r\n {{ _tableOption.index?.title || '#' }}\r\n </th>\r\n <td\r\n [class.px-4]=\"!_treeIndex\"\r\n [class.text-center]=\"!_treeIndex\"\r\n [class.sd-tree-index-cell]=\"_treeIndex\"\r\n mat-cell\r\n *matCellDef=\"let item; let i = dataIndex\"\r\n [style.width]=\"_treeIndex ? '1px' : null\"\r\n [style.min-width]=\"_treeIndex ? null : _indexWidth\"\r\n [style.max-width]=\"_treeIndex ? null : _indexWidth\">\r\n @if (!item?.meta?.group?.isGroupHeader) {\r\n @if (_treeIndex) {\r\n <!-- why: tree \u2192 chevron + indent + STT ph\u00E2n c\u1EA5p (1, 1.2, 1.2.1). T\u00F4 \u0111\u1EADm root (level 0). -->\r\n <span class=\"sd-tree-line\">\r\n <ng-container *ngTemplateOutlet=\"sdTreeToggle; context: { row: item }\"></ng-container>\r\n @if (item?.meta?.tree?.indexPath?.length) {\r\n <span class=\"sd-tree-stt\" [class.sd-stt-root]=\"(item.meta.tree.level ?? 0) === 0\">\r\n {{ item.meta.tree.indexPath.join('.') }}\r\n </span>\r\n }\r\n </span>\r\n } @else {\r\n {{ _pageOffset + i + 1 }}\r\n }\r\n }\r\n </td>\r\n <td mat-footer-cell *matFooterCellDef [style.width]=\"_treeIndex ? '1px' : null\" [style.min-width]=\"_treeIndex ? null : _indexWidth\" [style.max-width]=\"_treeIndex ? null : _indexWidth\"></td>\r\n </ng-container>\r\n }\r\n <ng-container\r\n matColumnDef=\"sdCommand\"\r\n [sticky]=\"_tableOption.command?.align !== 'right'\"\r\n [stickyEnd]=\"_tableOption.command?.align === 'right'\">\r\n <th class=\"p-0\" mat-header-cell *matHeaderCellDef style=\"width: 50px\" [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\"></th>\r\n <td class=\"px-8\" mat-cell *matCellDef=\"let item\">\r\n <desktop-command\r\n [autoId]=\"_autoId\"\r\n [commands]=\"_tableOption.command?.commands || _tableOption.commands || []\"\r\n [item]=\"item\"\r\n [itemIndex]=\"groupedItems.indexOf(item)\"></desktop-command>\r\n </td>\r\n <td mat-footer-cell *matFooterCellDef></td>\r\n </ng-container>\r\n <!-- why: group header row d\u00F9ng matRowDef RI\u00CANG v\u1EDBi column list ['sdGroupHeader']\r\n qua predicate when:isGroupHeaderRow. Cell colspan = displayedColumns.length \u0111\u1EC3\r\n span TO\u00C0N B\u1ED8 width data row. Tr\u00E1nh colspan trick overflow row khi c\u00E1c TD kh\u00E1c render. -->\r\n <ng-container matColumnDef=\"sdGroupHeader\">\r\n <td\r\n class=\"p-0 sd-group-header-cell\"\r\n mat-cell\r\n *matCellDef=\"let item\"\r\n [attr.colspan]=\"sdGroupColspan()\">\r\n <div class=\"sd-group-row d-flex align-items-center py-8\">\r\n <!-- why: slot 42px kh\u1EDBp width v\u1EDBi sd-selection-cell data row \u2192 checkbox group c\u0103n TR\u1EE4C d\u1ECDc \u0111\u00FAng v\u1EDBi checkbox data. -->\r\n @if (_tableOption.selector?.visible && !_tableOption.selector?.single) {\r\n <div class=\"sd-group-selection-slot d-flex align-items-center justify-content-center\">\r\n <mat-checkbox\r\n class=\"c-selection\"\r\n color=\"primary\"\r\n [checked]=\"isGroupAllSelected(item)\"\r\n [indeterminate]=\"isGroupIndeterminate(item)\"\r\n (change)=\"onSelectGroup(item, $event.checked)\">\r\n </mat-checkbox>\r\n </div>\r\n } @else {\r\n <div class=\"sd-group-selection-slot\"></div>\r\n }\r\n @if (_tableOption.group?.collapsible) {\r\n <sd-button\r\n [autoId]=\"_autoId ? _autoId + '-group-toggle-' + (item.meta.group?.key || item.meta.id) : null\"\r\n type=\"link\"\r\n [prefixIcon]=\"item.meta.group.isExpanded ? 'expand_more' : 'chevron_right'\"\r\n width=\"32px\"\r\n (click)=\"toggleGroupExpand(item)\">\r\n </sd-button>\r\n }\r\n @if (sdGroupDef()?.templateRef; as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl; context: groupContext(item)\"></ng-container>\r\n } @else {\r\n <span class=\"T14R\">{{ item.meta.group.values | json }}</span>\r\n }\r\n </div>\r\n </td>\r\n </ng-container>\r\n <!-- why: filler column \u1EDF cu\u1ED1i h\u1EA5p th\u1EE5 leftover space khi t\u1ED5ng width c\u00E1c c\u1ED9t data < container \u2014 gi\u1EEF cho selection/STT/command kh\u00F4ng b\u1ECB d\u00E3n. -->\r\n <ng-container matColumnDef=\"sdFiller\">\r\n <th class=\"sd-filler-cell p-0\" mat-header-cell *matHeaderCellDef [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\"></th>\r\n <td class=\"sd-filler-cell p-0\" mat-cell *matCellDef=\"let item\"></td>\r\n <td class=\"sd-filler-cell p-0\" mat-footer-cell *matFooterCellDef></td>\r\n </ng-container>\r\n @for (column of _configuration.firstColumns; track column.field) {\r\n <ng-container [matColumnDef]=\"column.field\" [sticky]=\"_configuration.fixedColumn[column.field]\">\r\n <th\r\n mat-header-cell\r\n *matHeaderCellDef\r\n class=\"px-8 py-8 c-th\"\r\n [sdColumnResize]=\"!!_tableOption.config?.resizable && column.type !== 'children'\"\r\n [minWidth]=\"column.minWidth\"\r\n [maxWidth]=\"column.maxWidth\"\r\n (resizeEnd)=\"onColumnResize(column.field, $event)\"\r\n [style.width]=\"column.width\"\r\n [style.min-width]=\"column.minWidth || column.width\"\r\n [style.max-width]=\"column.maxWidth\"\r\n [attr.rowspan]=\"_configuration.multipleHeader && column.type !== 'children' ? 2 : 1\"\r\n [attr.colspan]=\"column.type === 'children' ? column.children.length : 1\">\r\n <div>\r\n @if (column.type === 'children') {\r\n <div\r\n aria-hidden=\"true\"\r\n class=\"c-header-title\"\r\n [class.justify-content-end]=\"column.align === 'right'\"\r\n [class.text-right]=\"column.align === 'right'\">\r\n <column-title [column]=\"column\" [titleDef]=\"_titleDef[column.field]\"></column-title>\r\n </div>\r\n } @else {\r\n <div\r\n aria-hidden=\"true\"\r\n mat-sort-header\r\n class=\"c-header-title\"\r\n [class.justify-content-end]=\"column.align === 'right'\"\r\n [class.text-right]=\"column.align === 'right'\"\r\n [disabled]=\"!column.sortable\">\r\n <column-title [column]=\"column\" [titleDef]=\"_titleDef[column.field]\"></column-title>\r\n </div>\r\n @let hideInlineFilter =\r\n _tableOption.filter?.disabled ||\r\n _tableOption.filter?.hideInlineFilter === true ||\r\n (_tableOption.filter?.hideInlineFilter === 'auto' && !_isFiltered && _total! <= 10);\r\n @if (!hideInlineFilter) {\r\n <ng-container *sdDesktop>\r\n <column-filter\r\n [autoId]=\"_autoId\"\r\n [value]=\"columnFilter?.[column.field!]!\"\r\n [operator]=\"columnOperator[column.field]\"\r\n (operatorChange)=\"columnOperator[column.field] = $event!; onOperatorChange(column, $event)\"\r\n [columnFilter]=\"columnFilter!\"\r\n [cacheValues]=\"cacheValues\"\r\n [column]=\"column\"\r\n (filterChange)=\"onFilterChange()\"\r\n (filterCommit)=\"onFilterCommit()\">\r\n </column-filter>\r\n </ng-container>\r\n }\r\n }\r\n </div>\r\n </th>\r\n <td\r\n class=\"c-td px-0\"\r\n [class.d-none]=\"column.type === 'children'\"\r\n mat-cell\r\n *matCellDef=\"let item\"\r\n [sdHoverCopy]=\"(item.data && item.data[column.field]) || ''\"\r\n [sdHoverCopyDisabled]=\"!column.cell?.copiable\">\r\n @if (column.type !== 'children' && !item?.meta?.group?.isGroupHeader) {\r\n @if (_treeFirstField && column.field === _treeFirstField) {\r\n <!-- why: kh\u00F4ng c\u00F3 c\u1ED9t Index \u2192 chevron + indent nh\u00FAng v\u00E0o c\u1ED9t data \u0111\u1EA7u (nh\u01B0 file explorer). -->\r\n <span class=\"sd-tree-line px-8\">\r\n <ng-container *ngTemplateOutlet=\"sdTreeToggle; context: { row: item }\"></ng-container>\r\n <desktop-cell class=\"d-block\" [column]=\"column\" [item]=\"item\" [cellDef]=\"_cellDef\"></desktop-cell>\r\n </span>\r\n } @else {\r\n <desktop-cell class=\"d-block px-8\" [column]=\"column\" [item]=\"item\" [cellDef]=\"_cellDef\"> </desktop-cell>\r\n }\r\n }\r\n </td>\r\n <td mat-footer-cell *matFooterCellDef>\r\n <ng-container *ngIf=\"_footerDef[column.field]\">\r\n <ng-container *ngTemplateOutlet=\"_footerDef[column.field].templateRef; context: { items: _items, column: column }\">\r\n </ng-container>\r\n </ng-container>\r\n </td>\r\n </ng-container>\r\n }\r\n @for (column of _configuration.secondColumns; track column.field) {\r\n <ng-container [matColumnDef]=\"column.field\">\r\n <th\r\n mat-header-cell\r\n *matHeaderCellDef\r\n class=\"c-th px-8\"\r\n [style.width]=\"column.width\"\r\n [style.min-width]=\"column.minWidth || column.width\"\r\n [style.max-width]=\"column.maxWidth\">\r\n <div>\r\n <div\r\n aria-hidden=\"true\"\r\n mat-sort-header\r\n class=\"c-header-title\"\r\n [class.justify-content-end]=\"column.align === 'right'\"\r\n [class.text-right]=\"column.align === 'right'\"\r\n [disabled]=\"!column.sortable\">\r\n <column-title [column]=\"column\" [titleDef]=\"_titleDef[column.field]\"></column-title>\r\n </div>\r\n @if (!_tableOption.filter?.disabled && !_tableOption.filter?.hideInlineFilter && columnOperator) {\r\n <ng-container *sdDesktop>\r\n <column-filter\r\n [autoId]=\"_autoId\"\r\n [value]=\"columnFilter?.[column.field!]!\"\r\n [operator]=\"columnOperator[column.field]\"\r\n [columnFilter]=\"columnFilter!\"\r\n [cacheValues]=\"cacheValues\"\r\n [column]=\"column\"\r\n (operatorChange)=\"columnOperator[column.field] = $event!; onOperatorChange(column, $event)\"\r\n (filterChange)=\"onFilterChange()\"\r\n (filterCommit)=\"onFilterCommit()\">\r\n </column-filter>\r\n </ng-container>\r\n }\r\n </div>\r\n </th>\r\n <td\r\n class=\"c-td px-0\"\r\n mat-cell\r\n *matCellDef=\"let item\"\r\n [sdHoverCopy]=\"item[column.field]\"\r\n [sdHoverCopyDisabled]=\"!column.cell?.copiable\">\r\n @if (column.type !== 'children') {\r\n <desktop-cell class=\"d-block px-8\" [column]=\"column\" [item]=\"item\" [cellDef]=\"_cellDef\"> </desktop-cell>\r\n }\r\n </td>\r\n <td mat-footer-cell *matFooterCellDef>\r\n <ng-container *ngIf=\"_footerDef[column.field]\">\r\n <ng-container *ngTemplateOutlet=\"_footerDef[column.field].templateRef; context: { items: _items, column: column }\">\r\n </ng-container>\r\n </ng-container>\r\n </td>\r\n </ng-container>\r\n }\r\n <tr class=\"c-first-header\" mat-header-row *matHeaderRowDef=\"_configuration.firstHeaders; sticky: true\"></tr>\r\n @if (!!_configuration.secondHeaders.length) {\r\n <tr class=\"c-second-header\" mat-header-row *matHeaderRowDef=\"_configuration.secondHeaders; sticky: true\"></tr>\r\n }\r\n <!-- Group header row \u2014 ch\u1EC9 1 cell sdGroupHeader colspan to\u00E0n width. -->\r\n <tr\r\n mat-row\r\n *matRowDef=\"let row; columns: ['sdGroupHeader']; when: isGroupHeaderRow\"\r\n class=\"c-group-row-tr\"></tr>\r\n\r\n <!-- Data row \u2014 d\u00F9ng displayedColumns \u0111\u1EA7y \u0111\u1EE7; when isDataRow \u0111\u1EC3 lo\u1EA1i tr\u1EEB group header. -->\r\n <tr\r\n mat-row\r\n cdkDrag\r\n [cdkDragData]=\"row\"\r\n [cdkDragDisabled]=\"\r\n !_tableOption.rowReorder?.enabled ||\r\n !!row.meta?.group?.items?.length ||\r\n (row.meta?.tree?.level ?? 0) > 0 ||\r\n isReorderDisabled(row)\r\n \"\r\n *matRowDef=\"let row; columns: _configuration.displayedColumns; when: isDataRow\"\r\n matRipple\r\n class=\"c-row\"\r\n [class.sd-tree-row]=\"!!_tableOption.tree\"\r\n [ngClass]=\"_tableOption.tree ? 'sd-tree-level-' + (row.meta?.tree?.level ?? 0) : null\"\r\n [ngStyle]=\"rowStyle(row)\"\r\n [class.selected]=\"row.meta.selector.isSelected\"></tr>\r\n\r\n <!-- Sub-information row \u2014 also gated b\u1EDFi isDataRow \u0111\u1EC3 KH\u00D4NG render empty expand row d\u01B0\u1EDBi group header. -->\r\n <tr mat-row *matRowDef=\"let row; columns: ['sdSubInformation']; when: isDataRow\" class=\"c-detail-row\"></tr>\r\n @if (hasFooter() && !!_configuration.displayedFooters.length) {\r\n <tr mat-footer-row *matFooterRowDef=\"_configuration.displayedFooters; sticky: true\"></tr>\r\n }\r\n </table>\r\n @if (!_loading && !_total) {\r\n <div class=\"c-no-data-row\">\r\n @if (_isFiltered) {\r\n @if (tableConfiguration?.images?.filterEmpty) {\r\n <img class=\"c-image\" [src]=\"tableConfiguration!.images!.filterEmpty\" alt=\"filter-empty\" />\r\n } @else {\r\n <img class=\"c-image sd-image-filter-empty\" alt=\"filter-empty\" />\r\n }\r\n <div class=\"T16M\">{{ 'core.component.table.no-results' | translate }}</div>\r\n <div class=\"T16R text-secondary\">{{ 'core.component.table.no-results-hint' | translate }}</div>\r\n } @else {\r\n @if (_requireFiltered) {\r\n @if (tableConfiguration?.images?.filterRequired) {\r\n <img class=\"c-image\" [src]=\"tableConfiguration!.images!.filterRequired\" alt=\"filter-required\" />\r\n } @else {\r\n <img class=\"c-image sd-image-filter-required\" alt=\"filter-required\" />\r\n }\r\n <div class=\"T16R text-secondary\">{{ 'core.component.table.choose-filter-hint' | translate }}</div>\r\n } @else {\r\n @if (tableConfiguration?.images?.dataEmpty) {\r\n <img class=\"c-image\" [src]=\"tableConfiguration!.images!.dataEmpty\" alt=\"data-empty\" />\r\n } @else {\r\n <img class=\"c-image sd-image-data-empty\" alt=\"data-empty\" />\r\n }\r\n <div class=\"T16R text-secondary\">{{ 'core.component.table.no-data' | translate }}</div>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n <div class=\"c-paginator\">\r\n <div class=\"c-action\">\r\n <ng-container *sdDesktop>\r\n @if (_tableOption.reload?.visible) {\r\n <sd-button\r\n [autoId]=\"_autoId ? _autoId + '-reload' : null\"\r\n class=\"mr-8\"\r\n [title]=\"'core.component.table.reload' | translate\"\r\n prefixIcon=\"refresh\"\r\n (click)=\"reload()\"\r\n [disabled]=\"!_items.length\"\r\n type=\"link\">\r\n </sd-button>\r\n }\r\n </ng-container>\r\n @if (_export && _items.length) {\r\n @if (_export.type === 'custom') {\r\n <sd-button [autoId]=\"_autoId ? _autoId + '-export' : null\" class=\"mr-8\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" (click)=\"exportCustom()\" type=\"link\"> </sd-button>\r\n } @else {\r\n @if (_export.visible) {\r\n @if (_exporting) {\r\n <sd-button [autoId]=\"_autoId ? _autoId + '-export' : null\" class=\"mr-8\" [loading]=\"_exporting\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" type=\"link\"> </sd-button>\r\n } @else {\r\n @if (_export.visible === 'ALL' || !_export.visible) {\r\n <sd-button [autoId]=\"_autoId ? _autoId + '-export' : null\" class=\"mr-8\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" [matMenuTriggerFor]=\"menu\" type=\"link\"> </sd-button>\r\n <mat-menu #menu=\"matMenu\">\r\n <button mat-menu-item [attr.data-autoid]=\"_autoId ? _autoId + '-export-excel' : null\" (click)=\"exportExcel()\" type=\"button\">\r\n <mat-icon fontSet=\"material-icons-outlined\">file_download</mat-icon>\r\n <span> {{ 'core.component.table.export-excel' | translate }}</span>\r\n </button>\r\n <button mat-menu-item [attr.data-autoid]=\"_autoId ? _autoId + '-export-csv' : null\" (click)=\"exportCSV()\" type=\"button\">\r\n <mat-icon fontSet=\"material-icons-outlined\">file_download</mat-icon>\r\n <span> {{ 'core.component.table.export-csv' | translate }}</span>\r\n </button>\r\n </mat-menu>\r\n } @else if (_export.visible === 'EXCEL') {\r\n <sd-button [autoId]=\"_autoId ? _autoId + '-export-excel' : null\" class=\"mr-8\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" (click)=\"exportExcel()\" type=\"link\"> </sd-button>\r\n } @else if (_export.visible === 'CSV') {\r\n <sd-button [autoId]=\"_autoId ? _autoId + '-export-csv' : null\" class=\"mr-8\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" (click)=\"exportCSV()\" type=\"link\"> </sd-button>\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n <ng-container *sdDesktop>\r\n @if (_configComponent) {\r\n <sd-button\r\n [autoId]=\"_autoId ? _autoId + '-config' : null\"\r\n class=\"mr-8\"\r\n [title]=\"'core.component.table.setup-short' | translate\"\r\n prefixIcon=\"settings\"\r\n (click)=\"_configComponent.open()\"\r\n type=\"link\">\r\n </sd-button>\r\n }\r\n </ng-container>\r\n <!-- Mobile: n\u00FAt Filter m\u1EDF mobile-filter drawer (inline filter \u1EA9n tr\u00EAn mobile) -->\r\n <ng-container *sdMobile>\r\n @if (!_tableOption.filter?.disabled && _mobileFilter) {\r\n <sd-button\r\n [autoId]=\"_autoId ? _autoId + '-mobile-filter-open' : null\"\r\n class=\"mr-8\"\r\n prefixIcon=\"filter_alt\"\r\n (click)=\"_mobileFilter.open()\"\r\n type=\"link\">\r\n </sd-button>\r\n }\r\n </ng-container>\r\n </div>\r\n @let hidePaginator = !_tableOption.paginate?.pageSize || _total === undefined || _total <= _tableOption.paginate?.pageSize!;\r\n <!-- Desktop paginator: full config (pageSize dropdown, first/last buttons) -->\r\n <ng-container *sdDesktop>\r\n <mat-paginator\r\n [class.d-none]=\"_tableOption.paginate?.hidden || hidePaginator\"\r\n [length]=\"_total\"\r\n [pageSize]=\"_tableOption.paginate?.pageSize\"\r\n [pageSizeOptions]=\"_tableOption.paginate?.pages!\"\r\n [showFirstLastButtons]=\"_tableOption.paginate?.showFirstLastButtons\"\r\n [hidePageSize]=\"_tableOption.paginate?.hidePageSize\"></mat-paginator>\r\n </ng-container>\r\n <!-- Mobile paginator: \u1EA9n pageSize dropdown + first/last buttons \u0111\u1EC3 ti\u1EBFt ki\u1EC7m kh\u00F4ng gian -->\r\n <ng-container *sdMobile>\r\n <mat-paginator\r\n [class.d-none]=\"_tableOption.paginate?.hidden || hidePaginator\"\r\n [length]=\"_total\"\r\n [pageSize]=\"_tableOption.paginate?.pageSize\"\r\n [showFirstLastButtons]=\"false\"\r\n hidePageSize></mat-paginator>\r\n </ng-container>\r\n <!-- \"\u0110ang hi\u1EC3n th\u1ECB...\" ch\u1EC9 desktop -->\r\n <ng-container *sdDesktop>\r\n @if (!_tableOption.paginate?.hidden && hidePaginator && _total !== undefined && _total > 0) {\r\n <div class=\"T14R pr-16\">\r\n {{ 'core.component.table.showing' | translate }} <span class=\"T14M ml-2\">1-{{ _total }}/{{ _total }}</span>\r\n </div>\r\n }\r\n </ng-container>\r\n </div>\r\n </div>\r\n <selector-action [autoId]=\"_autoId\" [tableOption]=\"_tableOption\" [selectedTableItems]=\"_selectedTableItems\" (clear)=\"onClearSelection(groupedItems)\" />\r\n @if (_tableOption.config?.visible) {\r\n <config [autoId]=\"_autoId\" [tableOption]=\"_tableOption\" />\r\n }\r\n <!-- Mobile filter drawer \u2014 ch\u1EC9 instantiate khi c\u1EA7n render tr\u00EAn mobile -->\r\n @if (!_tableOption.filter?.disabled && filterRegister) {\r\n <mobile-filter\r\n [autoId]=\"_autoId\"\r\n [filter]=\"_tableOption.filter!\"\r\n [externalFilters]=\"_tableOption.filter?.externalFilters!\"\r\n [columns]=\"_configuration.firstColumns\"\r\n [filterDefs]=\"sdFilterDefs()\"\r\n [filterRegister]=\"filterRegister\"\r\n [cacheValues]=\"cacheValues\">\r\n </mobile-filter>\r\n }\r\n}\r\n", styles: [":host{display:flex;flex-direction:column;overflow:auto;width:100%;height:100%}:host .c-header-title{height:40px;display:flex;align-items:center}:host .c-container{position:relative;min-height:50px;display:flex;flex-direction:column;flex:1}:host .c-container .c-table{position:relative;flex:1;display:flex;flex-direction:column}:host .c-container .c-table table{border-collapse:separate;width:100%}:host .c-container .c-table table tr.c-first-header.mat-mdc-header-row{height:40px}:host .c-container .c-table table tr.c-second-header.mat-mdc-header-row{height:40px}:host .c-container .c-table table tr.c-detail-row{height:0}:host .c-container .c-table table tr.c-row.activated{background-color:#e5ecff}:host .c-container .c-table table tr.c-row.selected{background-color:#eef2ff}:host .c-container .c-table table tr.c-row:not(.selected):not(.activated):hover{background-color:#f5f5f5}:host .c-container .c-table table tr.c-row td{border-bottom-width:0}:host .c-container .c-table table tr.c-row.c-expandable{cursor:pointer}:host .c-container .c-table table tr.c-row.c-expandable:hover{background:#f5f5f5}:host .c-container .c-table table th.mat-mdc-header-cell{background-color:#f2f3f4;border-bottom:0!important}:host .c-container .c-table table td.mat-mdc-cell,:host .c-container .c-table table td.mat-mdc-footer-cell,:host .c-container .c-table table th.mat-mdc-header-cell{border-bottom-color:#f2f2f2!important}:host .c-container .c-table .c-th{vertical-align:middle;font-weight:500;font-size:14px;line-height:20px;color:#212121}:host .c-container .c-table .c-th.sd-col-resize-host{position:relative}:host .c-container .c-table .c-th.sd-resizing{-webkit-user-select:none;user-select:none}:host .c-container .c-table .c-th .sd-col-resize-handle{position:absolute;top:0;right:0;width:6px;height:100%;cursor:col-resize;-webkit-user-select:none;user-select:none;z-index:2}:host .c-container .c-table .c-th .sd-col-resize-handle:hover{background:#00000014}:host .c-container .c-table .c-td:first{padding-left:10px}:host .c-container .c-table .c-no-data-row{flex:1;width:100%;position:sticky;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center}:host .c-container .c-table .c-no-data-row .c-image{margin-bottom:16px;width:96px}:host .c-container .c-loading{position:absolute;inset:0 0 56px;background:#00000026;z-index:2;display:flex;align-items:center;justify-content:center}:host .c-container .c-paginator{display:flex;flex-direction:row;justify-content:space-between;align-items:center;background-color:#fff}:host .c-container .c-paginator .c-action{padding:5px}:host .c-container .c-empty{text-align:center;background-color:#fff;border:none!important}:host .c-container .c-empty mat-icon{font-size:150px;margin-top:30px;margin-bottom:30px;opacity:.2;width:auto;height:auto}:host button.c-btn-add{background-color:#fff;box-shadow:0 2px 4px #2f313629}:host .lds-ring{display:inline-block;position:relative;width:40px;height:40px}:host .lds-ring div{box-sizing:border-box;display:block;position:absolute;width:32px;height:32px;margin:4px;border:4px solid #cef;border-radius:50%;animation:lds-ring 1.2s cubic-bezier(.5,0,.5,1) infinite;border-color:#cef transparent transparent transparent}:host .lds-ring div:nth-child(1){animation-delay:-.45s}:host .lds-ring div:nth-child(2){animation-delay:-.3s}:host .lds-ring div:nth-child(3){animation-delay:-.15s}@keyframes lds-ring{0%{transform:rotate(0)}to{transform:rotate(360deg)}}:host .sd-reorder-header,:host .sd-reorder-cell{width:40px;min-width:40px;padding:0 4px;box-sizing:border-box}:host .sd-reorder-cell mat-icon{cursor:grab;color:#00000061;display:flex;align-items:center}:host .sd-reorder-cell mat-icon:active{cursor:grabbing}:host .sd-reorder-cell mat-icon.sd-reorder-disabled{opacity:.3;cursor:not-allowed;pointer-events:none}:host ::ng-deep .mat-sort-header-content{display:block;text-align:left;width:100%}:host ::ng-deep .mat-select-arrow{color:#a6a6a6}:host ::ng-deep .mat-sort-header-disabled{background-image:none!important;cursor:default!important;padding-right:0!important}:host ::ng-deep .mat-sort-header-container{align-items:start!important}:host ::ng-deep .mat-sort-header-arrow{margin-top:4px!important}:host ::ng-deep .mat-sort-header-arrow{display:none!important}:host ::ng-deep .mat-sort-header{cursor:pointer}:host ::ng-deep .mat-sort-header[aria-sort]{background-repeat:no-repeat;background-position:center right 0;background-size:16px 16px;cursor:pointer;padding-right:24px}:host ::ng-deep .mat-sort-header[aria-sort=none]{background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 0 24 24' width='24px' fill='%23000000'%3E%3Cpath d='M0 0h24v24H0V0z' fill='none'/%3E%3Cpath fill='%237A7A7A' d='M12 5.83L15.17 9l1.41-1.41L12 3 7.41 7.59 8.83 9 12 5.83zm0 12.34L8.83 15l-1.41 1.41L12 21l4.59-4.59L15.17 15 12 18.17z'/%3E%3C/svg%3E\")}:host ::ng-deep .mat-sort-header[aria-sort=ascending]{background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 0 24 24' width='24px' fill='%23000000'%3E%3Cpath d='M0 0h24v24H0V0z' fill='none'/%3E%3Cpath fill='%237A7A7A' d='M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z'/%3E%3C/svg%3E\")}:host ::ng-deep .mat-sort-header[aria-sort=descending]{background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 0 24 24' width='24px' fill='%23000000'%3E%3Cpath d='M0 0h24v24H0V0z' fill='none'/%3E%3Cpath fill='%237A7A7A' d='M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z'/%3E%3C/svg%3E\")}:host ::ng-deep .c-paginator .mat-mdc-paginator .mdc-text-field--outlined .mat-mdc-form-field-infix,:host ::ng-deep .c-paginator .mat-mdc-paginator .mdc-text-field--no-label .mat-mdc-form-field-infix{padding-top:2px;padding-bottom:2px;min-height:32px}:host ::ng-deep .c-paginator .mat-mdc-paginator-range-label{margin:0 4px}:host ::ng-deep .c-paginator .mat-mdc-paginator-page-size-select mat-select{margin-top:2px!important}:host ::ng-deep .cdk-drag-preview{box-shadow:0 4px 12px #00000026;background:#fff;display:table;width:100%}:host ::ng-deep .cdk-drag-preview td{padding:0 8px;border-bottom:1px solid rgba(0,0,0,.12)}:host ::ng-deep .cdk-drag-placeholder{opacity:0}:host ::ng-deep .cdk-drop-list-dragging tr.c-row:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}:host ::ng-deep .sd-selection-cell{width:42px;min-width:42px;max-width:42px}:host ::ng-deep .sd-filler-cell{width:auto;min-width:0;padding:0!important;border:0}:host ::ng-deep .sd-group-header-cell{background:#f5f7fc;border-top:1px solid #e6e9f2;border-bottom:1px solid #e6e9f2}:host ::ng-deep .sd-group-row{min-height:36px}:host ::ng-deep .sd-group-selection-slot{width:42px;min-width:42px;max-width:42px}:host ::ng-deep .c-selection{transform:scale(.85);transform-origin:center}:host ::ng-deep .sd-tree-line{display:inline-flex;align-items:center;min-height:32px}:host ::ng-deep .sd-tree-indent{flex:0 0 auto}:host ::ng-deep .sd-tree-toggle-slot{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px}:host ::ng-deep .sd-tree-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:0;background:transparent;border-radius:50%;cursor:pointer;color:var(--sd-text-secondary, #5b6472);transition:background-color .15s ease}:host ::ng-deep .sd-tree-toggle-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}:host ::ng-deep .sd-tree-toggle-btn:hover{background-color:#0f172a14}:host ::ng-deep .sd-tree-spinner{transform:scale(.45)}:host ::ng-deep .sd-tree-stt{margin-left:2px;white-space:nowrap}:host ::ng-deep .sd-tree-index-cell{text-align:left!important;white-space:nowrap;padding-left:8px;padding-right:8px}:host ::ng-deep .sd-tree-index-header{padding-left:32px}:host ::ng-deep tr.c-row.sd-tree-row.sd-tree-level-0{background:#fbfcfe}:host ::ng-deep tr.c-row.sd-tree-row.sd-tree-level-0>td{font-weight:500}:host ::ng-deep tr.c-row.sd-tree-row.sd-tree-level-1{background:#fff}:host ::ng-deep tr.c-row.sd-tree-row.sd-tree-level-2{background:#fafbfd}:host ::ng-deep tr.c-row.sd-tree-row.sd-tree-level-3{background:#f6f8fb}:host ::ng-deep .sd-stt-root{font-weight:700;color:#1f2330}\n", ":host ::ng-deep .sticky-shadow-right{overflow:visible!important}:host ::ng-deep .sticky-shadow-right:after{content:\"\";position:absolute;top:0;right:0;bottom:0;width:8px;transform:translate(100%);background:linear-gradient(to right,rgba(0,0,0,.12),transparent);pointer-events:none}:host ::ng-deep .sticky-shadow-left{overflow:visible!important}:host ::ng-deep .sticky-shadow-left:after{content:\"\";position:absolute;top:0;left:0;bottom:0;width:8px;transform:translate(-100%);background:linear-gradient(to left,rgba(0,0,0,.12),transparent);pointer-events:none}\n"] }]
4468
4597
  }], ctorParameters: () => [] });
4469
4598
 
4470
- const SdConvertToPagingReq = (filterRequest, args) => {
4471
- const { externalFilters, columns, fieldMapping } = args;
4472
- const req = {
4473
- filters: [],
4474
- orders: args?.orders || [],
4475
- pageNumber: filterRequest.pageNumber,
4476
- pageSize: filterRequest.pageSize,
4477
- };
4478
- const { filters } = req;
4479
- const { rawExternalFilter, rawColumnFilter, columnOperator, orderBy, orderDirection } = filterRequest;
4480
- // Xử lý external filter
4481
- for (const externalFilter of externalFilters || []) {
4482
- const value = rawExternalFilter?.[externalFilter.field];
4483
- const field = fieldMapping?.[externalFilter.field] || externalFilter.field;
4484
- // Nếu có giá trị thì mới xử lý filter
4485
- if (value !== undefined && value !== null && value !== '') {
4486
- if (externalFilter.type === 'string') {
4487
- filters.push({
4488
- field,
4489
- operator: externalFilter.defaultOperator || 'CONTAIN',
4490
- data: value,
4491
- });
4492
- }
4493
- else if (externalFilter.type === 'boolean') {
4494
- filters.push({
4495
- field,
4496
- operator: 'EQUAL',
4497
- data: value === true || value === 1 || value === 'true' || value === '1',
4498
- });
4499
- }
4500
- else if (externalFilter.type === 'date') {
4501
- if (typeof value === 'object' && 'from' in value && 'to' in value) {
4502
- if (value?.from) {
4503
- filters.push({
4504
- field,
4505
- operator: 'GREATER_OR_EQUAL',
4506
- data: DateUtilities$1.begin(value?.from).toISOString(),
4507
- });
4508
- }
4509
- if (value?.to) {
4510
- filters.push({
4511
- field,
4512
- operator: 'LESS_THAN',
4513
- data: DateUtilities$1.begin(DateUtilities$1.addDays(value?.to, 1)).toISOString(),
4514
- });
4515
- }
4516
- }
4517
- else {
4518
- if (DateUtilities$1.isDate(value)) {
4519
- if (externalFilter.defaultOperator === 'GREATER_OR_EQUAL') {
4520
- filters.push({
4521
- field,
4522
- operator: 'GREATER_OR_EQUAL',
4523
- data: DateUtilities$1.begin(value).toISOString(),
4524
- });
4525
- }
4526
- if (externalFilter.defaultOperator === 'LESS_OR_EQUAL') {
4527
- filters.push({
4528
- field,
4529
- operator: 'LESS_THAN',
4530
- data: DateUtilities$1.begin(DateUtilities$1.addDays(value, 1)).toISOString(),
4531
- });
4532
- }
4533
- }
4534
- }
4535
- }
4536
- else {
4537
- if (Array.isArray(value)) {
4538
- if (value.length) {
4539
- filters.push({
4540
- field,
4541
- operator: 'IN',
4542
- data: value,
4543
- });
4544
- }
4545
- }
4546
- else {
4547
- filters.push({
4548
- field,
4549
- operator: externalFilter.defaultOperator || 'EQUAL',
4550
- data: value,
4551
- });
4552
- }
4553
- }
4554
- }
4555
- }
4556
- // Xử lý column filter
4557
- for (const column of columns || []) {
4558
- const value = rawColumnFilter?.[column.field];
4559
- const field = fieldMapping?.[column.field] || column.field;
4560
- const operator = columnOperator?.[column.field] || column.filter?.operator?.default;
4561
- // Nếu có giá trị thì mới xử lý filter
4562
- if (value !== undefined && value !== null && value !== '') {
4563
- if (column.type === 'string') {
4564
- filters.push({
4565
- field,
4566
- operator: operator || 'CONTAIN',
4567
- data: value,
4568
- });
4569
- }
4570
- else if (column.type === 'boolean') {
4571
- filters.push({
4572
- field,
4573
- operator: 'EQUAL',
4574
- data: value === true || value === 1 || value === 'true' || value === '1',
4575
- });
4576
- }
4577
- else if (column.type === 'date' || column.type === 'datetime') {
4578
- if (typeof value === 'object' && 'from' in value && 'to' in value) {
4579
- if (value?.from && value?.to) {
4580
- filters.push({
4581
- field,
4582
- operator: 'BETWEEN',
4583
- data: {
4584
- from: DateUtilities$1.begin(value?.from).toISOString(),
4585
- to: DateUtilities$1.end(value?.to).toISOString(),
4586
- },
4587
- });
4588
- }
4589
- else if (value?.from) {
4590
- filters.push({
4591
- field,
4592
- operator: 'GREATER_OR_EQUAL',
4593
- data: DateUtilities$1.begin(value?.from).toISOString(),
4594
- });
4595
- }
4596
- else if (value?.to) {
4597
- filters.push({
4598
- field,
4599
- operator: 'LESS_THAN',
4600
- data: DateUtilities$1.begin(DateUtilities$1.addDays(value?.to, 1)).toISOString(),
4601
- });
4602
- }
4603
- }
4604
- else {
4605
- if (DateUtilities$1.isDate(value)) {
4606
- filters.push({
4607
- field,
4608
- operator: 'BETWEEN',
4609
- data: {
4610
- from: DateUtilities$1.begin(value).toISOString(),
4611
- to: DateUtilities$1.end(value).toISOString(),
4612
- },
4613
- });
4614
- }
4615
- }
4616
- }
4617
- else {
4618
- if (Array.isArray(value)) {
4619
- if (value.length) {
4620
- filters.push({
4621
- field,
4622
- operator: 'IN',
4623
- data: value,
4624
- });
4625
- }
4626
- }
4627
- else {
4628
- filters.push({
4629
- field,
4630
- operator: operator || 'EQUAL',
4631
- data: value,
4632
- });
4633
- }
4634
- }
4635
- }
4636
- }
4637
- // Xử lý orders
4638
- if (orderBy && orderDirection) {
4639
- req.orders.push({
4640
- field: orderBy,
4641
- direction: orderDirection,
4642
- });
4643
- }
4644
- return req;
4645
- };
4646
-
4647
4599
  /**
4648
4600
  * Generated bundle index. Do not edit.
4649
4601
  */