@sdcorejs/angular 20.0.8 → 20.0.9
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.
- package/components/table/index.d.ts +5 -9
- package/fesm2022/sdcorejs-angular-components-table.mjs +764 -801
- package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs +10 -2
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
- package/forms/date-range/index.d.ts +2 -0
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ import * as i1$1 from '@sdcorejs/angular/services';
|
|
|
11
11
|
import { SdExcelService, SdNotifyService } from '@sdcorejs/angular/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 '@sdcorejs/angular/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';
|
|
@@ -33,7 +34,7 @@ import { SdQuickAction } from '@sdcorejs/angular/components/quick-action';
|
|
|
33
34
|
import { SdTooltipDirective, SdScrollDirective, SdDesktopDirective, SdMobileDirective, SdHoverCopyDirective } from '@sdcorejs/angular/directives';
|
|
34
35
|
import { SdSafeHtmlPipe, SdFormatNumberPipe } from '@sdcorejs/angular/pipes';
|
|
35
36
|
import { BrowserUtilities, Utilities } from '@sdcorejs/utils/fns';
|
|
36
|
-
import { DateUtilities, ArrayUtilities, NumberUtilities } from '@sdcorejs/angular/utilities/extensions';
|
|
37
|
+
import { DateUtilities as DateUtilities$1, ArrayUtilities, NumberUtilities } from '@sdcorejs/angular/utilities/extensions';
|
|
37
38
|
import { SdBadge } from '@sdcorejs/angular/components/badge';
|
|
38
39
|
import { SdOperator } from '@sdcorejs/angular/components/operator';
|
|
39
40
|
import { SdInput, SdInputNumber, SdSelect, SdDate, SdDatetime, SdDateRange as SdDateRange$1, SdCheckbox, SdSwitch } from '@sdcorejs/angular/forms';
|
|
@@ -50,7 +51,6 @@ import * as i3 from '@angular/material/button';
|
|
|
50
51
|
import { MatButtonModule } from '@angular/material/button';
|
|
51
52
|
import * as i1$2 from '@angular/material/tooltip';
|
|
52
53
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
53
|
-
import { StringUtilities, DateUtilities as DateUtilities$1 } from '@sdcorejs/angular/utilities';
|
|
54
54
|
import * as i5 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: "20.3.25", ngImpo
|
|
|
162
162
|
}]
|
|
163
163
|
}], propDecorators: { sdTableTitleDef: [{ type: i0.Input, args: [{ isSignal: true, alias: "sdTableTitleDef", required: true }] }] } });
|
|
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(...(ngDevMode ? [{ debugName: "column" }] : []));
|
|
@@ -234,8 +470,6 @@ class ColumnFilterComponent {
|
|
|
234
470
|
}, ...(ngDevMode ? [{ debugName: "operators" }] : []));
|
|
235
471
|
// Chỉ các Operator value cho phép — truyền vào <sd-operator [operators]>.
|
|
236
472
|
operatorValues = computed(() => this.operators().map(o => o.value), ...(ngDevMode ? [{ debugName: "operatorValues" }] : []));
|
|
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'), ...(ngDevMode ? [{ debugName: "operatorWrapperClass" }] : []));
|
|
239
473
|
// Items cho sd-select khi column type = values / lazy-values
|
|
240
474
|
items = computed(() => {
|
|
241
475
|
const col = this.column();
|
|
@@ -278,11 +512,11 @@ class ColumnFilterComponent {
|
|
|
278
512
|
// Blur input: commit giá trị KHÔNG trigger reload.
|
|
279
513
|
onFilterCommit = () => this.filterCommit.emit();
|
|
280
514
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ColumnFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
281
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", 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();\n@let _columnFilter = columnFilter();\n@let _autoId = autoId();\n@let _templateRef = templateRef();\n@let _operators = operators();\n@let _items = items();\n@let _label = label();\n@let _context = { columnFilter: _columnFilter, autoId: _autoId };\n\n<div class=\"d-flex c-inline-column align-items-end\" style=\"width: 100%; max-width: 100%\">\n @if (\n _column.type === 'string' ||\n _column.type === 'number' ||\n _column.type === 'boolean' ||\n _column.type === 'values' ||\n _column.type === 'lazy-values' ||\n _column.type === 'date' ||\n _column.type === 'datetime' ||\n _column.type === 'time'\n ) {\n <!-- Operator filter -->\n @if (_operators.length) {\n <div class=\"d-flex align-items-center\" [ngClass]=\"operatorWrapperClass()\">\n <sd-operator [(model)]=\"operator\" [operators]=\"operatorValues()\" />\n </div>\n }\n\n <!-- N\u1EBFu filter nh\u1EADn v\u00E0o template -->\n @if (_templateRef) {\n <ng-container *ngTemplateOutlet=\"_templateRef; context: _context\"></ng-container>\n }\n <!-- M\u1EB7c \u0111\u1ECBnh theo type v\u00E0 gi\u00E1 tr\u1ECB values defined c\u1EE7a column -->\n @else {\n @if (_column.type === 'string') {\n <sd-input\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n type=\"text\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (keyupEnter)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-input>\n } @else if (_column.type === 'number') {\n @if (!_column.filter?.type) {\n <sd-input-number\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (keyupEnter)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-input-number>\n } @else if (_column.filter?.type === 'split-number') {\n <ng-container\n *ngTemplateOutlet=\"splitNumberTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\n </ng-container>\n }\n } @else if (_column.type === 'boolean') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"[\n { value: '1', display: _column.option?.displayOnTrue || 'True' },\n { value: '0', display: _column.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n <ng-template sdItemDef let-item=\"item\">\n @if (item.value === '1') {\n <sd-badge color=\"success\" [title]=\"_column.option?.displayOnTrue || 'True'\"> </sd-badge>\n } @else {\n <sd-badge color=\"error\" [title]=\"_column.option?.displayOnFalse || 'False'\"> </sd-badge>\n }\n </ng-template>\n </sd-select>\n } @else if (_column.type === 'values') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"_items\"\n [valueField]=\"_column.option.valueField\"\n [displayField]=\"_column.option.displayField\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\n hideInlineError>\n </sd-select>\n } @else if (_column.type === 'lazy-values') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"_items\"\n [valueField]=\"_column.option.valueField\"\n [displayField]=\"_column.option.displayField\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\n hideInlineError>\n </sd-select>\n } @else if (_column.type === 'date' || _column.type === 'datetime' || _column.type === 'time') {\n @if (!_column.filter?.type || _column.filter?.type === 'daterange') {\n <sd-date-range\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-date-range>\n } @else if (_column.filter?.type === 'date') {\n <sd-date\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-date>\n } @else if (_column.filter?.type === 'split-date') {\n <ng-container\n *ngTemplateOutlet=\"splitDateTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\n </ng-container>\n }\n }\n }\n } @else {\n <sd-input [autoId]=\"_autoId\" style=\"flex: 1 1 0%; min-width: 0\" type=\"text\" size=\"sm\" disabled></sd-input>\n }\n</div>\n\n<!-- ================================================ -->\n<!-- Split from/to templates (\u0111\u01B0a xu\u1ED1ng cu\u1ED1i file) -->\n<!-- ================================================ -->\n<ng-template #splitNumberTpl let-field=\"field\" let-disabled=\"disabled\">\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\n <sd-input-number\n [autoId]=\"_autoId + '-from'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[field].from\"\n (sdChange)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-input-number>\n <div class=\"mx-4\">-</div>\n <sd-input-number\n [autoId]=\"_autoId + '-to'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [(model)]=\"_columnFilter[field].to\"\n (sdChange)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-input-number>\n </div>\n</ng-template>\n\n<ng-template #splitDateTpl let-field=\"field\" let-disabled=\"disabled\">\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\n <sd-date\n [autoId]=\"_autoId + '-from'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[field].from\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-date>\n <div class=\"mx-4\">-</div>\n <sd-date\n [autoId]=\"_autoId + '-to'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [(model)]=\"_columnFilter[field].to\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-date>\n </div>\n</ng-template>\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 });
|
|
515
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", 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();\n@let _columnFilter = columnFilter();\n@let _autoId = autoId();\n@let _templateRef = templateRef();\n@let _operators = operators();\n@let _items = items();\n@let _label = label();\n@let _context = { columnFilter: _columnFilter, autoId: _autoId };\n\n<div class=\"d-flex c-inline-column align-items-center\" style=\"width: 100%; max-width: 100%\">\n @if (\n _column.type === 'string' ||\n _column.type === 'number' ||\n _column.type === 'boolean' ||\n _column.type === 'values' ||\n _column.type === 'lazy-values' ||\n _column.type === 'date' ||\n _column.type === 'datetime' ||\n _column.type === 'time'\n ) {\n <!-- Operator filter -->\n @if (_operators.length) {\n <sd-operator class=\"mr-4\" [(model)]=\"operator\" [operators]=\"operatorValues()\" />\n }\n\n <!-- N\u1EBFu filter nh\u1EADn v\u00E0o template -->\n @if (_templateRef) {\n <ng-container *ngTemplateOutlet=\"_templateRef; context: _context\"></ng-container>\n }\n <!-- M\u1EB7c \u0111\u1ECBnh theo type v\u00E0 gi\u00E1 tr\u1ECB values defined c\u1EE7a column -->\n @else {\n @if (_column.type === 'string') {\n <sd-input\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n type=\"text\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (keyupEnter)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-input>\n } @else if (_column.type === 'number') {\n @if (!_column.filter?.type) {\n <sd-input-number\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (keyupEnter)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-input-number>\n } @else if (_column.filter?.type === 'split-number') {\n <ng-container *ngTemplateOutlet=\"splitNumberTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\n </ng-container>\n }\n } @else if (_column.type === 'boolean') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"[\n { value: '1', display: _column.option?.displayOnTrue || 'True' },\n { value: '0', display: _column.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n <ng-template sdItemDef let-item=\"item\">\n @if (item.value === '1') {\n <sd-badge color=\"success\" [title]=\"_column.option?.displayOnTrue || 'True'\"> </sd-badge>\n } @else {\n <sd-badge color=\"error\" [title]=\"_column.option?.displayOnFalse || 'False'\"> </sd-badge>\n }\n </ng-template>\n </sd-select>\n } @else if (_column.type === 'values') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"_items\"\n [valueField]=\"_column.option.valueField\"\n [displayField]=\"_column.option.displayField\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\n hideInlineError>\n </sd-select>\n } @else if (_column.type === 'lazy-values') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"_items\"\n [valueField]=\"_column.option.valueField\"\n [displayField]=\"_column.option.displayField\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\n hideInlineError>\n </sd-select>\n } @else if (_column.type === 'date' || _column.type === 'datetime' || _column.type === 'time') {\n @if (!_column.filter?.type || _column.filter?.type === 'daterange') {\n <sd-date-range\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-date-range>\n } @else if (_column.filter?.type === 'date') {\n <sd-date\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-date>\n } @else if (_column.filter?.type === 'split-date') {\n <ng-container *ngTemplateOutlet=\"splitDateTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\n </ng-container>\n }\n }\n }\n } @else {\n <sd-input [autoId]=\"_autoId\" style=\"flex: 1 1 0%; min-width: 0\" type=\"text\" size=\"sm\" disabled></sd-input>\n }\n</div>\n\n<!-- ================================================ -->\n<!-- Split from/to templates (\u0111\u01B0a xu\u1ED1ng cu\u1ED1i file) -->\n<!-- ================================================ -->\n<ng-template #splitNumberTpl let-field=\"field\" let-disabled=\"disabled\">\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\n <sd-input-number\n [autoId]=\"_autoId + '-from'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[field].from\"\n (sdChange)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-input-number>\n <div class=\"mx-4\">-</div>\n <sd-input-number\n [autoId]=\"_autoId + '-to'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [(model)]=\"_columnFilter[field].to\"\n (sdChange)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-input-number>\n </div>\n</ng-template>\n\n<ng-template #splitDateTpl let-field=\"field\" let-disabled=\"disabled\">\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\n <sd-date\n [autoId]=\"_autoId + '-from'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[field].from\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-date>\n <div class=\"mx-4\">-</div>\n <sd-date\n [autoId]=\"_autoId + '-to'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [(model)]=\"_columnFilter[field].to\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-date>\n </div>\n</ng-template>\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
516
|
}
|
|
283
517
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ColumnFilterComponent, decorators: [{
|
|
284
518
|
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();\n@let _columnFilter = columnFilter();\n@let _autoId = autoId();\n@let _templateRef = templateRef();\n@let _operators = operators();\n@let _items = items();\n@let _label = label();\n@let _context = { columnFilter: _columnFilter, autoId: _autoId };\n\n<div class=\"d-flex c-inline-column align-items-
|
|
519
|
+
args: [{ selector: 'column-filter', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, SdOperator, SdInput, SdInputNumber, SdSelect, SdDate, SdDateRange, SdBadge], template: "@let _column = column();\n@let _columnFilter = columnFilter();\n@let _autoId = autoId();\n@let _templateRef = templateRef();\n@let _operators = operators();\n@let _items = items();\n@let _label = label();\n@let _context = { columnFilter: _columnFilter, autoId: _autoId };\n\n<div class=\"d-flex c-inline-column align-items-center\" style=\"width: 100%; max-width: 100%\">\n @if (\n _column.type === 'string' ||\n _column.type === 'number' ||\n _column.type === 'boolean' ||\n _column.type === 'values' ||\n _column.type === 'lazy-values' ||\n _column.type === 'date' ||\n _column.type === 'datetime' ||\n _column.type === 'time'\n ) {\n <!-- Operator filter -->\n @if (_operators.length) {\n <sd-operator class=\"mr-4\" [(model)]=\"operator\" [operators]=\"operatorValues()\" />\n }\n\n <!-- N\u1EBFu filter nh\u1EADn v\u00E0o template -->\n @if (_templateRef) {\n <ng-container *ngTemplateOutlet=\"_templateRef; context: _context\"></ng-container>\n }\n <!-- M\u1EB7c \u0111\u1ECBnh theo type v\u00E0 gi\u00E1 tr\u1ECB values defined c\u1EE7a column -->\n @else {\n @if (_column.type === 'string') {\n <sd-input\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n type=\"text\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (keyupEnter)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-input>\n } @else if (_column.type === 'number') {\n @if (!_column.filter?.type) {\n <sd-input-number\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (keyupEnter)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-input-number>\n } @else if (_column.filter?.type === 'split-number') {\n <ng-container *ngTemplateOutlet=\"splitNumberTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\n </ng-container>\n }\n } @else if (_column.type === 'boolean') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"[\n { value: '1', display: _column.option?.displayOnTrue || 'True' },\n { value: '0', display: _column.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n <ng-template sdItemDef let-item=\"item\">\n @if (item.value === '1') {\n <sd-badge color=\"success\" [title]=\"_column.option?.displayOnTrue || 'True'\"> </sd-badge>\n } @else {\n <sd-badge color=\"error\" [title]=\"_column.option?.displayOnFalse || 'False'\"> </sd-badge>\n }\n </ng-template>\n </sd-select>\n } @else if (_column.type === 'values') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"_items\"\n [valueField]=\"_column.option.valueField\"\n [displayField]=\"_column.option.displayField\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\n hideInlineError>\n </sd-select>\n } @else if (_column.type === 'lazy-values') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"_items\"\n [valueField]=\"_column.option.valueField\"\n [displayField]=\"_column.option.displayField\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\n hideInlineError>\n </sd-select>\n } @else if (_column.type === 'date' || _column.type === 'datetime' || _column.type === 'time') {\n @if (!_column.filter?.type || _column.filter?.type === 'daterange') {\n <sd-date-range\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-date-range>\n } @else if (_column.filter?.type === 'date') {\n <sd-date\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-date>\n } @else if (_column.filter?.type === 'split-date') {\n <ng-container *ngTemplateOutlet=\"splitDateTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\n </ng-container>\n }\n }\n }\n } @else {\n <sd-input [autoId]=\"_autoId\" style=\"flex: 1 1 0%; min-width: 0\" type=\"text\" size=\"sm\" disabled></sd-input>\n }\n</div>\n\n<!-- ================================================ -->\n<!-- Split from/to templates (\u0111\u01B0a xu\u1ED1ng cu\u1ED1i file) -->\n<!-- ================================================ -->\n<ng-template #splitNumberTpl let-field=\"field\" let-disabled=\"disabled\">\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\n <sd-input-number\n [autoId]=\"_autoId + '-from'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[field].from\"\n (sdChange)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-input-number>\n <div class=\"mx-4\">-</div>\n <sd-input-number\n [autoId]=\"_autoId + '-to'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [(model)]=\"_columnFilter[field].to\"\n (sdChange)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-input-number>\n </div>\n</ng-template>\n\n<ng-template #splitDateTpl let-field=\"field\" let-disabled=\"disabled\">\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\n <sd-date\n [autoId]=\"_autoId + '-from'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[field].from\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-date>\n <div class=\"mx-4\">-</div>\n <sd-date\n [autoId]=\"_autoId + '-to'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [(model)]=\"_columnFilter[field].to\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-date>\n </div>\n</ng-template>\n", styles: [":host ::ng-deep .c-inline-column .mat-mdc-text-field-wrapper{background-color:#fff}\n"] }]
|
|
286
520
|
}], ctorParameters: () => [], propDecorators: { autoIdInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoId", required: false }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], columnFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnFilter", required: false }] }], cacheValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "cacheValues", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], operator: [{ type: i0.Input, args: [{ isSignal: true, alias: "operator", required: false }] }, { type: i0.Output, args: ["operatorChange"] }], isMobile: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMobile", required: false }] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], filterCommit: [{ type: i0.Output, args: ["filterCommit"] }] } });
|
|
287
521
|
|
|
288
522
|
// Đố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 +569,7 @@ const getChildrenFromData = (data, option) => {
|
|
|
335
569
|
* mặc định nhánh chưa nạp).
|
|
336
570
|
*/
|
|
337
571
|
const resolveDefaultExpanded = (level, option) => {
|
|
338
|
-
const def = option?.loadType === 'static' ? option.defaultExpanded ?? false : false;
|
|
572
|
+
const def = option?.loadType === 'static' ? (option.defaultExpanded ?? false) : false;
|
|
339
573
|
if (def === true)
|
|
340
574
|
return true;
|
|
341
575
|
if (def === false)
|
|
@@ -391,6 +625,48 @@ const subtreeMatches = (data, predicate, option, visited = new Set()) => {
|
|
|
391
625
|
* bị mất khi từ khoá search đổi. Đây là bước "prune" của tính năng search-con.
|
|
392
626
|
*/
|
|
393
627
|
const filterMatchingChildren = (children, predicate, option) => children.filter(child => subtreeMatches(child, predicate, option));
|
|
628
|
+
const getVisibleChildrenData = (data, option, predicate) => {
|
|
629
|
+
const children = getChildrenFromData(data, option);
|
|
630
|
+
return predicate ? filterMatchingChildren(children, predicate, option) : children;
|
|
631
|
+
};
|
|
632
|
+
const saveTreeExpandState = (rows, expandState) => {
|
|
633
|
+
for (const row of rows) {
|
|
634
|
+
if (row.meta.tree?.isExpanded) {
|
|
635
|
+
expandState.set(row.meta.id, true);
|
|
636
|
+
}
|
|
637
|
+
const children = row.meta.tree?.childItems;
|
|
638
|
+
if (children?.length) {
|
|
639
|
+
saveTreeExpandState(children, expandState);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
const clearTreeChildCache = (rows) => {
|
|
644
|
+
for (const row of rows) {
|
|
645
|
+
const children = row.meta.tree?.childItems;
|
|
646
|
+
if (children?.length) {
|
|
647
|
+
clearTreeChildCache(children);
|
|
648
|
+
row.meta.tree.childItems = undefined;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
const initTreeMeta = (rows, option, args = {}) => {
|
|
653
|
+
const { expandState, treeSearchPredicate, level = 0, parentId } = args;
|
|
654
|
+
const searchActive = !!treeSearchPredicate;
|
|
655
|
+
for (const row of rows) {
|
|
656
|
+
const saved = expandState?.get(row.meta.id);
|
|
657
|
+
const hasChildren = searchActive
|
|
658
|
+
? getVisibleChildrenData(row.data, option, treeSearchPredicate).length > 0
|
|
659
|
+
: resolveHasChildren(row, option);
|
|
660
|
+
row.meta.tree = {
|
|
661
|
+
...row.meta.tree,
|
|
662
|
+
level,
|
|
663
|
+
parentId,
|
|
664
|
+
hasChildren,
|
|
665
|
+
isExpanded: searchActive ? hasChildren : (saved ?? resolveDefaultExpanded(level, option)),
|
|
666
|
+
isExpanding: false,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
};
|
|
394
670
|
const flattenTree = (roots, option, visited = new Set()) => {
|
|
395
671
|
if (!option)
|
|
396
672
|
return roots;
|
|
@@ -2410,13 +2686,13 @@ class TableExportService {
|
|
|
2410
2686
|
}
|
|
2411
2687
|
}
|
|
2412
2688
|
else if (column.type === 'date') {
|
|
2413
|
-
obj[fieldStr] = DateUtilities.toFormat(itemValue, 'yyyy/MM/dd');
|
|
2689
|
+
obj[fieldStr] = DateUtilities$1.toFormat(itemValue, 'yyyy/MM/dd');
|
|
2414
2690
|
}
|
|
2415
2691
|
else if (column.type === 'datetime') {
|
|
2416
|
-
obj[fieldStr] = DateUtilities.toFormat(itemValue, 'yyyy/MM/dd HH:mm:ss');
|
|
2692
|
+
obj[fieldStr] = DateUtilities$1.toFormat(itemValue, 'yyyy/MM/dd HH:mm:ss');
|
|
2417
2693
|
}
|
|
2418
2694
|
else if (column.type === 'time') {
|
|
2419
|
-
obj[fieldStr] = DateUtilities.toFormat(itemValue, 'HH:mm:ss');
|
|
2695
|
+
obj[fieldStr] = DateUtilities$1.toFormat(itemValue, 'HH:mm:ss');
|
|
2420
2696
|
}
|
|
2421
2697
|
else if (column.type === 'values' || column.type === 'lazy-values') {
|
|
2422
2698
|
const vals = (Array.isArray(itemValue) ? itemValue : [itemValue]).filter(e => !!e?.toString());
|
|
@@ -2711,8 +2987,8 @@ class TableFormatService {
|
|
|
2711
2987
|
// PRIVATE HELPERS
|
|
2712
2988
|
// ==========================================
|
|
2713
2989
|
#formatDateDisplay(value, type) {
|
|
2714
|
-
const date = DateUtilities.toFormat(value, 'dd/MM/yyyy');
|
|
2715
|
-
const time = DateUtilities.toFormat(value, 'HH:mm:ss');
|
|
2990
|
+
const date = DateUtilities$1.toFormat(value, 'dd/MM/yyyy');
|
|
2991
|
+
const time = DateUtilities$1.toFormat(value, 'HH:mm:ss');
|
|
2716
2992
|
if (type === 'datetime') {
|
|
2717
2993
|
return time && date ? `<div class="T14R">${date}<span class="T14R text-black400 ml-4">${time}</span></div>` : '';
|
|
2718
2994
|
}
|
|
@@ -2773,44 +3049,305 @@ const buildColumnWidthMap = (column) => {
|
|
|
2773
3049
|
return result;
|
|
2774
3050
|
};
|
|
2775
3051
|
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
const
|
|
2793
|
-
if (
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
}
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
3052
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3053
|
+
const isTableItem = (item) => !!item && typeof item === 'object' && 'data' in item && 'meta' in item;
|
|
3054
|
+
const resolveItemData = (item) => (isTableItem(item) ? item.data : item);
|
|
3055
|
+
const hasActiveColumnFilter = (rawColumnFilter = {}, columns = []) => columns.some(col => {
|
|
3056
|
+
const v = rawColumnFilter[col.field];
|
|
3057
|
+
if (Array.isArray(v))
|
|
3058
|
+
return v.length > 0;
|
|
3059
|
+
if (v && typeof v === 'object')
|
|
3060
|
+
return !!v.from || !!v.to;
|
|
3061
|
+
return v !== undefined && v !== null && v !== '';
|
|
3062
|
+
});
|
|
3063
|
+
const matchesColumnFilter = (data, columns = [], rawColumnFilter = {}) => {
|
|
3064
|
+
for (const column of columns) {
|
|
3065
|
+
const { field, type } = column;
|
|
3066
|
+
const filterValue = (rawColumnFilter[field] || '').toString().trim().toLowerCase();
|
|
3067
|
+
const rawColVal = Utilities.getNestedValue(data, field);
|
|
3068
|
+
const columnValue = (rawColVal || '').toString().trim().toLowerCase();
|
|
3069
|
+
if (filterValue) {
|
|
3070
|
+
if (!columnValue && type !== 'datetime' && type !== 'date' && type !== 'time') {
|
|
3071
|
+
return false;
|
|
3072
|
+
}
|
|
3073
|
+
if (type === 'string') {
|
|
3074
|
+
if (columnValue.indexOf(filterValue) === -1) {
|
|
3075
|
+
return false;
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
else if (type === 'values' || type === 'lazy-values') {
|
|
3079
|
+
const columnType = column;
|
|
3080
|
+
const isMultiple = columnType.option.selection === 'MULTIPLE';
|
|
3081
|
+
if (isMultiple && Array.isArray(rawColVal)) {
|
|
3082
|
+
const columnValues = rawColVal.map((i) => (Utilities.getNestedValue(i, columnType.option.valueField) ?? '').toString().trim().toLowerCase()) ??
|
|
3083
|
+
[];
|
|
3084
|
+
const filterValues = rawColumnFilter[field]?.map((v) => (v ?? '').toString().trim().toLowerCase());
|
|
3085
|
+
if (filterValues?.length && filterValues.every(fv => !columnValues.includes(fv))) {
|
|
3086
|
+
return false;
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
else {
|
|
3090
|
+
if (columnValue !== filterValue) {
|
|
3091
|
+
return false;
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
else if (type === 'number') {
|
|
3096
|
+
const fValue = +filterValue.replace('>=', '').replace('<=', '').replace('>', '').replace('<', '');
|
|
3097
|
+
const cValue = +columnValue;
|
|
3098
|
+
if (fValue || fValue === 0) {
|
|
3099
|
+
if (!cValue && cValue !== 0) {
|
|
3100
|
+
return false;
|
|
3101
|
+
}
|
|
3102
|
+
if (filterValue.indexOf('>=') > -1 && cValue < fValue) {
|
|
3103
|
+
return false;
|
|
3104
|
+
}
|
|
3105
|
+
else if (filterValue.indexOf('<=') > -1 && cValue > fValue) {
|
|
3106
|
+
return false;
|
|
3107
|
+
}
|
|
3108
|
+
else if (filterValue.indexOf('<') > -1 && cValue >= fValue) {
|
|
3109
|
+
return false;
|
|
3110
|
+
}
|
|
3111
|
+
else if (filterValue.indexOf('>') > -1 && cValue <= fValue) {
|
|
3112
|
+
return false;
|
|
3113
|
+
}
|
|
3114
|
+
else if (cValue !== fValue) {
|
|
3115
|
+
return false;
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
else if (type === 'boolean') {
|
|
3120
|
+
if ((filterValue === '1' || filterValue === 'true') && columnValue !== '1' && columnValue !== 'true') {
|
|
3121
|
+
return false;
|
|
3122
|
+
}
|
|
3123
|
+
else if ((filterValue === '0' || filterValue === 'false') && columnValue !== '0' && columnValue !== 'false') {
|
|
3124
|
+
return false;
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
else if (type === 'datetime' || type === 'date' || type === 'time') {
|
|
3128
|
+
const from = rawColumnFilter[field]?.from ?? rawColumnFilter[field];
|
|
3129
|
+
const to = rawColumnFilter[field]?.to ?? rawColumnFilter[field];
|
|
3130
|
+
const fromDate = DateUtilities$1.begin(from);
|
|
3131
|
+
const toDate = DateUtilities$1.end(to);
|
|
3132
|
+
if (fromDate || toDate) {
|
|
3133
|
+
if (!columnValue) {
|
|
3134
|
+
return false;
|
|
3135
|
+
}
|
|
3136
|
+
const columnTime = new Date(columnValue).getTime();
|
|
3137
|
+
const fromDateTime = fromDate?.getTime() || null;
|
|
3138
|
+
const toDateTime = toDate?.getTime() || null;
|
|
3139
|
+
if (fromDateTime && fromDateTime > columnTime) {
|
|
3140
|
+
return false;
|
|
3141
|
+
}
|
|
3142
|
+
if (toDateTime && columnTime > toDateTime) {
|
|
3143
|
+
return false;
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
return true;
|
|
3150
|
+
};
|
|
3151
|
+
const filterLocalItems = (localItems, option, filterInfo) => {
|
|
3152
|
+
const { columns } = option;
|
|
3153
|
+
const { rawColumnFilter, orderBy, orderDirection, pageSize, pageNumber } = filterInfo;
|
|
3154
|
+
const matchesData = (data) => matchesColumnFilter(data, columns, rawColumnFilter);
|
|
3155
|
+
const treeOpt = option.tree;
|
|
3156
|
+
const treeSearch = option.type === 'local' && treeOpt?.loadType === 'static' && hasActiveColumnFilter(rawColumnFilter, columns);
|
|
3157
|
+
const treeSearchPredicate = treeSearch ? matchesData : undefined;
|
|
3158
|
+
const items = treeSearch
|
|
3159
|
+
? localItems.filter(item => subtreeMatches(resolveItemData(item), matchesData, treeOpt))
|
|
3160
|
+
: localItems.filter(item => matchesData(resolveItemData(item)));
|
|
3161
|
+
if (orderBy && orderDirection) {
|
|
3162
|
+
const column = columns.find(e => e.field === orderBy);
|
|
3163
|
+
if (column) {
|
|
3164
|
+
const { type, field } = column;
|
|
3165
|
+
items.sort((tableItemCurrent, tableItemNext) => {
|
|
3166
|
+
const dataVal = Utilities.getNestedValue(resolveItemData(tableItemCurrent), field);
|
|
3167
|
+
const nextVal = Utilities.getNestedValue(resolveItemData(tableItemNext), field);
|
|
3168
|
+
if (type === 'number') {
|
|
3169
|
+
return (dataVal || 0) - (nextVal || 0);
|
|
3170
|
+
}
|
|
3171
|
+
if (type === 'date' || type === 'datetime' || type === 'time') {
|
|
3172
|
+
const d1 = new Date(dataVal || '').getTime();
|
|
3173
|
+
const d2 = new Date(nextVal || '').getTime();
|
|
3174
|
+
return d1 - d2;
|
|
3175
|
+
}
|
|
3176
|
+
const s1 = (dataVal || '').toString();
|
|
3177
|
+
const s2 = (nextVal || '').toString();
|
|
3178
|
+
if (s1 > s2) {
|
|
3179
|
+
return 1;
|
|
3180
|
+
}
|
|
3181
|
+
if (s1 < s2) {
|
|
3182
|
+
return -1;
|
|
3183
|
+
}
|
|
3184
|
+
return 0;
|
|
3185
|
+
});
|
|
3186
|
+
if (orderDirection === 'DESC') {
|
|
3187
|
+
items.reverse();
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
return {
|
|
3192
|
+
items: items.filter((item, index) => index >= pageNumber * pageSize && index < (pageNumber + 1) * pageSize),
|
|
3193
|
+
total: items.length,
|
|
3194
|
+
treeSearchPredicate,
|
|
3195
|
+
};
|
|
3196
|
+
};
|
|
3197
|
+
|
|
3198
|
+
const isGroupHeader = (item) => !!item?.meta?.group?.items?.length;
|
|
3199
|
+
const isChildRow = (item) => (item?.meta.tree?.level ?? 0) > 0;
|
|
3200
|
+
const isRootReorderRow = (item) => !!item && !isGroupHeader(item) && !isChildRow(item);
|
|
3201
|
+
const moveItem = (items, fromIndex, toIndex) => {
|
|
3202
|
+
const next = [...items];
|
|
3203
|
+
const [moved] = next.splice(fromIndex, 1);
|
|
3204
|
+
if (!moved)
|
|
3205
|
+
return next;
|
|
3206
|
+
next.splice(toIndex, 0, moved);
|
|
3207
|
+
return next;
|
|
3208
|
+
};
|
|
3209
|
+
const isReorderDisabled = (item, option, index = -1) => {
|
|
3210
|
+
if (isChildRow(item))
|
|
3211
|
+
return true;
|
|
3212
|
+
if (!option?.disabled || isGroupHeader(item))
|
|
3213
|
+
return false;
|
|
3214
|
+
return option.disabled(item.data, index);
|
|
3215
|
+
};
|
|
3216
|
+
const sameReorderGroup = (a, b, allItems) => {
|
|
3217
|
+
const groupOf = (item) => {
|
|
3218
|
+
let lastGroupIdx = -1;
|
|
3219
|
+
for (let i = 0; i < allItems.length; i++) {
|
|
3220
|
+
if (isGroupHeader(allItems[i]))
|
|
3221
|
+
lastGroupIdx = i;
|
|
3222
|
+
if (allItems[i] === item)
|
|
3223
|
+
return lastGroupIdx;
|
|
3224
|
+
}
|
|
3225
|
+
return -1;
|
|
3226
|
+
};
|
|
3227
|
+
return groupOf(a) === groupOf(b);
|
|
3228
|
+
};
|
|
3229
|
+
const canSortReorder = ({ enabled, index, dragItem, allItems = [], hasGroup }) => {
|
|
3230
|
+
if (!enabled)
|
|
3231
|
+
return false;
|
|
3232
|
+
const targetItem = allItems[index];
|
|
3233
|
+
if (!isRootReorderRow(targetItem))
|
|
3234
|
+
return false;
|
|
3235
|
+
if (isChildRow(dragItem))
|
|
3236
|
+
return false;
|
|
3237
|
+
if (hasGroup) {
|
|
3238
|
+
return sameReorderGroup(dragItem, targetItem, allItems);
|
|
3239
|
+
}
|
|
3240
|
+
return true;
|
|
3241
|
+
};
|
|
3242
|
+
const toReorderItemsIndex = (renderedItems, renderedIndex) => {
|
|
3243
|
+
let count = 0;
|
|
3244
|
+
for (let i = 0; i < renderedIndex; i++) {
|
|
3245
|
+
if (isRootReorderRow(renderedItems[i]))
|
|
3246
|
+
count++;
|
|
3247
|
+
}
|
|
3248
|
+
return count;
|
|
3249
|
+
};
|
|
3250
|
+
const reorderTableItems = ({ items, renderedItems, previousRenderedIndex, currentRenderedIndex, localItems, }) => {
|
|
3251
|
+
const fromIndex = toReorderItemsIndex(renderedItems, previousRenderedIndex);
|
|
3252
|
+
const toIndex = toReorderItemsIndex(renderedItems, currentRenderedIndex);
|
|
3253
|
+
const nextItems = moveItem(items, fromIndex, toIndex);
|
|
3254
|
+
let nextLocalItems = localItems;
|
|
3255
|
+
if (localItems) {
|
|
3256
|
+
const localPositions = items.map(item => localItems.indexOf(item));
|
|
3257
|
+
if (localPositions.every(p => p >= 0)) {
|
|
3258
|
+
nextLocalItems = [...localItems];
|
|
3259
|
+
nextItems.forEach((item, i) => {
|
|
3260
|
+
nextLocalItems[localPositions[i]] = item;
|
|
3261
|
+
});
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
return {
|
|
3265
|
+
items: nextItems,
|
|
3266
|
+
localItems: nextLocalItems,
|
|
3267
|
+
fromIndex,
|
|
3268
|
+
toIndex,
|
|
3269
|
+
};
|
|
3270
|
+
};
|
|
3271
|
+
|
|
3272
|
+
const getSelectionRows = (roots, treeOpt) => treeOpt ? flattenTree(roots, treeOpt) : roots;
|
|
3273
|
+
const getSelectedRowData = (rows) => rows.filter(row => row.meta.selector.isSelected).map(row => row.data);
|
|
3274
|
+
const applyDefaultSelected = (roots, treeOpt, defaultSelected) => {
|
|
3275
|
+
if (!defaultSelected)
|
|
3276
|
+
return;
|
|
3277
|
+
const rows = treeOpt ? collectFormattedTreeRows(roots) : roots;
|
|
3278
|
+
rows.forEach(item => {
|
|
3279
|
+
item.meta.selector.isSelected = defaultSelected(item.data);
|
|
3280
|
+
});
|
|
3281
|
+
};
|
|
3282
|
+
const resolveSelectAllState = (visibleRows) => visibleRows.length > 0 && visibleRows.every(row => row.meta.selector?.isSelected);
|
|
3283
|
+
const restorePreservedSelection = (visibleRows, preservedSelectedMap) => {
|
|
3284
|
+
visibleRows.forEach(item => {
|
|
3285
|
+
if (preservedSelectedMap.has(item.meta.id)) {
|
|
3286
|
+
item.meta.selector.isSelected = true;
|
|
3287
|
+
preservedSelectedMap.set(item.meta.id, item);
|
|
3288
|
+
}
|
|
3289
|
+
});
|
|
3290
|
+
};
|
|
3291
|
+
const syncPreservedSelection = (visibleRows, preservedSelectedMap) => {
|
|
3292
|
+
visibleRows.forEach(item => {
|
|
3293
|
+
const id = item.meta.id;
|
|
3294
|
+
if (item.meta.selector.isSelected) {
|
|
3295
|
+
preservedSelectedMap.set(id, item);
|
|
3296
|
+
}
|
|
3297
|
+
else {
|
|
3298
|
+
preservedSelectedMap.delete(id);
|
|
3299
|
+
}
|
|
3300
|
+
});
|
|
3301
|
+
return Array.from(preservedSelectedMap.values());
|
|
3302
|
+
};
|
|
3303
|
+
|
|
3304
|
+
class SdTableFilterService {
|
|
3305
|
+
storageService;
|
|
3306
|
+
#filterConfiguration = 'GRID-FILTER-CONFIGURATION';
|
|
3307
|
+
#filterValue = 'GRID-FILTER-VALUE';
|
|
3308
|
+
#cache = {};
|
|
3309
|
+
constructor(storageService) {
|
|
3310
|
+
this.storageService = storageService;
|
|
3311
|
+
}
|
|
3312
|
+
#hasOwnValue = (values, field) => {
|
|
3313
|
+
return !!values && Object.prototype.hasOwnProperty.call(values, field);
|
|
3314
|
+
};
|
|
3315
|
+
#resolveKey = (filter, args) => {
|
|
3316
|
+
const { id, columns, externalFilters } = args;
|
|
3317
|
+
const tempKey = Utilities.hash({
|
|
3318
|
+
id,
|
|
3319
|
+
columns: columns?.map(e => e.field).filter(field => !!field) || [],
|
|
3320
|
+
externalFilters: externalFilters?.map(e => e.field).filter(field => !!field) || [],
|
|
3321
|
+
});
|
|
3322
|
+
return {
|
|
3323
|
+
tempKey,
|
|
3324
|
+
key: filter?.key || tempKey,
|
|
3325
|
+
cacheSession: !filter?.key,
|
|
3326
|
+
};
|
|
3327
|
+
};
|
|
3328
|
+
register = (filter, args) => {
|
|
3329
|
+
const { key, tempKey, cacheSession } = this.#resolveKey(filter, args);
|
|
3330
|
+
if (args.force) {
|
|
3331
|
+
delete this.#cache[key];
|
|
3332
|
+
}
|
|
3333
|
+
if (!this.#cache[key]) {
|
|
3334
|
+
// Setting của filter configuration
|
|
3335
|
+
const filterConfiguration = this.storageService.create({
|
|
3336
|
+
prefix: this.#filterConfiguration,
|
|
3337
|
+
key,
|
|
3338
|
+
}, {
|
|
3339
|
+
default: this.#defaultConfiguration(args),
|
|
3340
|
+
type: cacheSession ? 'session' : undefined,
|
|
3341
|
+
});
|
|
3342
|
+
// Lấy giá trị configuration merge với giá trị defaultShowing của args nếu như args có thay đổi
|
|
3343
|
+
filterConfiguration.set(this.#initConfiguration(args, filterConfiguration.get()));
|
|
3344
|
+
// Setting của filter value
|
|
3345
|
+
const filterValue = this.storageService.create({
|
|
3346
|
+
prefix: this.#filterValue,
|
|
3347
|
+
key: !filter?.cacheable ? tempKey : key,
|
|
3348
|
+
}, {
|
|
3349
|
+
default: this.#defaultValue(args),
|
|
3350
|
+
type: cacheSession || !filter?.cacheable ? 'session' : undefined,
|
|
2814
3351
|
});
|
|
2815
3352
|
// Lấy giá trị value merge với giá trị default của args nếu như args có thay đổi
|
|
2816
3353
|
filterValue.set(this.#initValue(args, filterValue.get()));
|
|
@@ -2958,21 +3495,25 @@ class SdTableFilterService {
|
|
|
2958
3495
|
const { columns, externalFilters } = args;
|
|
2959
3496
|
// Filter column
|
|
2960
3497
|
for (const item of columns || []) {
|
|
2961
|
-
|
|
3498
|
+
// why: null is a deliberate cached clear from SD controls; only a missing key should fall back to default.
|
|
3499
|
+
columnFilter[item.field] = this.#hasOwnValue(value?.columnFilter, item.field)
|
|
3500
|
+
? value?.columnFilter?.[item.field]
|
|
3501
|
+
: item?.filter?.default;
|
|
2962
3502
|
if (item?.filter?.operator?.enable && item?.filter?.operator?.default) {
|
|
2963
3503
|
columnOperator[item.field] = item.filter.operator.default;
|
|
2964
3504
|
}
|
|
2965
3505
|
}
|
|
2966
3506
|
// Filter external
|
|
2967
3507
|
for (const item of externalFilters || []) {
|
|
3508
|
+
const hasExternalValue = this.#hasOwnValue(value?.externalFilter, item.field);
|
|
2968
3509
|
if (item.type === 'daterange') {
|
|
2969
3510
|
externalFilter[item.field] = {
|
|
2970
|
-
from: value?.externalFilter?.[item.field]?.from
|
|
2971
|
-
to: value?.externalFilter?.[item.field]?.to
|
|
3511
|
+
from: hasExternalValue ? value?.externalFilter?.[item.field]?.from : item.default?.from,
|
|
3512
|
+
to: hasExternalValue ? value?.externalFilter?.[item.field]?.to : item.default?.to,
|
|
2972
3513
|
};
|
|
2973
3514
|
}
|
|
2974
3515
|
else {
|
|
2975
|
-
externalFilter[item.field] = value?.externalFilter?.[item.field]
|
|
3516
|
+
externalFilter[item.field] = hasExternalValue ? value?.externalFilter?.[item.field] : item?.default;
|
|
2976
3517
|
}
|
|
2977
3518
|
}
|
|
2978
3519
|
return {
|
|
@@ -3089,6 +3630,10 @@ class SdTable {
|
|
|
3089
3630
|
columnFilter = {};
|
|
3090
3631
|
#localItems = [];
|
|
3091
3632
|
#subscription = new Subscription();
|
|
3633
|
+
#configurationSubscription;
|
|
3634
|
+
#filterRegisterSubscription;
|
|
3635
|
+
#optionInstance;
|
|
3636
|
+
#optionRevision = 0;
|
|
3092
3637
|
#reload = new Subject();
|
|
3093
3638
|
#loadCompleted = false;
|
|
3094
3639
|
cacheValues = {};
|
|
@@ -3098,7 +3643,7 @@ class SdTable {
|
|
|
3098
3643
|
treeRevision = signal(0, ...(ngDevMode ? [{ debugName: "treeRevision" }] : []));
|
|
3099
3644
|
// Search ở cấp con (static tree + type 'local'): predicate khớp 1 dòng theo
|
|
3100
3645
|
// column filter hiện hành. Set ở #filterLocal khi có filter active, đọc lại ở
|
|
3101
|
-
// #render/#
|
|
3646
|
+
// #render/#ensureChildItemsFormatted dùng predicate này để prune + auto-expand các
|
|
3102
3647
|
// nhánh có node con khớp. undefined = không search → cây render bình thường.
|
|
3103
3648
|
#treeSearchPredicate;
|
|
3104
3649
|
// Lần render trước có đang ở chế độ search hay không (để xử lý chuyển trạng thái).
|
|
@@ -3121,27 +3666,40 @@ class SdTable {
|
|
|
3121
3666
|
const option = this.option();
|
|
3122
3667
|
if (option) {
|
|
3123
3668
|
untracked(() => {
|
|
3669
|
+
if (this.#optionInstance !== option) {
|
|
3670
|
+
this.#optionInstance = option;
|
|
3671
|
+
this.#resetStateForNewOption();
|
|
3672
|
+
}
|
|
3673
|
+
const optionRevision = this.#optionRevision;
|
|
3124
3674
|
const initOpt = this.#initConfiguration({ ...option });
|
|
3125
3675
|
this.tableOption.set(initOpt);
|
|
3126
3676
|
this.#loadCompleted = false;
|
|
3127
3677
|
const storage = this.#configService.init(initOpt);
|
|
3128
|
-
this.#
|
|
3678
|
+
this.#configurationSubscription?.unsubscribe();
|
|
3679
|
+
this.#configurationSubscription = storage.observer.pipe(startWith(storage.subject.getValue())).subscribe(() => {
|
|
3680
|
+
if (optionRevision !== this.#optionRevision)
|
|
3681
|
+
return;
|
|
3129
3682
|
const configurationResult = this.#configService.loadConfigurationResult(initOpt, storage.get());
|
|
3130
3683
|
const displayColumns = configurationResult.displayedColumns || [];
|
|
3131
3684
|
this.#ref.detectChanges();
|
|
3132
3685
|
this.#tableFormatService
|
|
3133
3686
|
.loadValues(initOpt.columns.filter(column => displayColumns.includes(column.field)), this.cacheValues, this.#cacheObjValues)
|
|
3134
3687
|
.then(() => {
|
|
3688
|
+
if (optionRevision !== this.#optionRevision)
|
|
3689
|
+
return;
|
|
3135
3690
|
this.configuration.set(configurationResult);
|
|
3136
3691
|
this.#loadFilterRegister();
|
|
3137
3692
|
if (this.filterRegister) {
|
|
3138
|
-
this.#
|
|
3693
|
+
this.#requestReload(true);
|
|
3139
3694
|
}
|
|
3140
3695
|
})
|
|
3141
3696
|
.finally(() => {
|
|
3697
|
+
if (optionRevision !== this.#optionRevision)
|
|
3698
|
+
return;
|
|
3142
3699
|
this.#ref.detectChanges();
|
|
3143
3700
|
});
|
|
3144
|
-
})
|
|
3701
|
+
});
|
|
3702
|
+
this.#subscription.add(this.#configurationSubscription);
|
|
3145
3703
|
});
|
|
3146
3704
|
}
|
|
3147
3705
|
});
|
|
@@ -3149,7 +3707,7 @@ class SdTable {
|
|
|
3149
3707
|
const paginator = this.paginator();
|
|
3150
3708
|
if (paginator) {
|
|
3151
3709
|
untracked(() => {
|
|
3152
|
-
this.#subscription.add(paginator.page.subscribe(() => this.#
|
|
3710
|
+
this.#subscription.add(paginator.page.subscribe(() => this.#requestReload(false)));
|
|
3153
3711
|
});
|
|
3154
3712
|
}
|
|
3155
3713
|
});
|
|
@@ -3157,7 +3715,7 @@ class SdTable {
|
|
|
3157
3715
|
const sort = this.sort();
|
|
3158
3716
|
if (sort) {
|
|
3159
3717
|
untracked(() => {
|
|
3160
|
-
this.#subscription.add(sort.sortChange.subscribe(() => this.#
|
|
3718
|
+
this.#subscription.add(sort.sortChange.subscribe(() => this.#requestReload(false)));
|
|
3161
3719
|
});
|
|
3162
3720
|
}
|
|
3163
3721
|
});
|
|
@@ -3171,7 +3729,7 @@ class SdTable {
|
|
|
3171
3729
|
const conf = this.configuration();
|
|
3172
3730
|
if (!conf)
|
|
3173
3731
|
return;
|
|
3174
|
-
const firstColumns = conf.firstColumns.map(c => c.field === field ? { ...c, width } : c);
|
|
3732
|
+
const firstColumns = conf.firstColumns.map(c => (c.field === field ? { ...c, width } : c));
|
|
3175
3733
|
const column = { ...conf.column };
|
|
3176
3734
|
if (column[field]) {
|
|
3177
3735
|
column[field] = { ...column[field], width };
|
|
@@ -3183,16 +3741,26 @@ class SdTable {
|
|
|
3183
3741
|
this.configuration.set({ ...conf, firstColumns, column, fixedColumn });
|
|
3184
3742
|
}));
|
|
3185
3743
|
}
|
|
3186
|
-
ngOnInit() { }
|
|
3187
3744
|
ngAfterViewInit() {
|
|
3188
3745
|
this.#subscription.add(this.#reload
|
|
3189
3746
|
.pipe(debounceTime(200), switchMap(async (data) => {
|
|
3747
|
+
if (data.revision !== this.#optionRevision || !this.filterRegister)
|
|
3748
|
+
return undefined;
|
|
3190
3749
|
const filterInfo = this.getFilterRequest();
|
|
3191
3750
|
const result = await this.#load(filterInfo, !this.#loadCompleted || data.force);
|
|
3751
|
+
if (data.revision !== this.#optionRevision)
|
|
3752
|
+
return undefined;
|
|
3192
3753
|
this.#loadCompleted = true;
|
|
3193
|
-
return
|
|
3754
|
+
return {
|
|
3755
|
+
result,
|
|
3756
|
+
revision: data.revision,
|
|
3757
|
+
};
|
|
3194
3758
|
}))
|
|
3195
|
-
.subscribe(
|
|
3759
|
+
.subscribe(loadResult => {
|
|
3760
|
+
if (!loadResult || loadResult.revision !== this.#optionRevision)
|
|
3761
|
+
return;
|
|
3762
|
+
this.#render(loadResult.result);
|
|
3763
|
+
}));
|
|
3196
3764
|
}
|
|
3197
3765
|
ngOnDestroy() {
|
|
3198
3766
|
this.#subscription.unsubscribe();
|
|
@@ -3211,6 +3779,49 @@ class SdTable {
|
|
|
3211
3779
|
isExported: true,
|
|
3212
3780
|
};
|
|
3213
3781
|
};
|
|
3782
|
+
#requestReload = (force) => {
|
|
3783
|
+
this.#reload.next({ force, revision: this.#optionRevision });
|
|
3784
|
+
};
|
|
3785
|
+
#resetStateForNewOption = () => {
|
|
3786
|
+
this.#optionRevision += 1;
|
|
3787
|
+
this.#configurationSubscription?.unsubscribe();
|
|
3788
|
+
this.#configurationSubscription = undefined;
|
|
3789
|
+
this.#filterRegisterSubscription?.unsubscribe();
|
|
3790
|
+
this.#filterRegisterSubscription = undefined;
|
|
3791
|
+
this.filterRegister = undefined;
|
|
3792
|
+
this.#tableId = Utilities.generateUuid();
|
|
3793
|
+
this.key = Utilities.generateUuid();
|
|
3794
|
+
this.#loadCompleted = false;
|
|
3795
|
+
this.tableOption.set(undefined);
|
|
3796
|
+
this.configuration.set(undefined);
|
|
3797
|
+
this.items.set([]);
|
|
3798
|
+
this.selectedTableItems.set([]);
|
|
3799
|
+
this.total.set(undefined);
|
|
3800
|
+
this.loading.set(false);
|
|
3801
|
+
this.isSelectAll.set(false);
|
|
3802
|
+
this.isFiltered.set(false);
|
|
3803
|
+
this.requireFiltered.set(false);
|
|
3804
|
+
this.columnOperator = {};
|
|
3805
|
+
this.#syncColumnFilterInPlace({});
|
|
3806
|
+
this.#localItems = [];
|
|
3807
|
+
this.cacheValues = {};
|
|
3808
|
+
this.#cacheObjValues = {};
|
|
3809
|
+
this.#itemIndexMap = new WeakMap();
|
|
3810
|
+
this.#treeExpandState.clear();
|
|
3811
|
+
this.#treeSearchPredicate = undefined;
|
|
3812
|
+
this.#treeSearchActive = false;
|
|
3813
|
+
this.groupExpandState.clear();
|
|
3814
|
+
this.#preservedSelectedMap.clear();
|
|
3815
|
+
this.treeRevision.update(n => n + 1);
|
|
3816
|
+
if (this.paginator()) {
|
|
3817
|
+
this.paginator().pageIndex = 0;
|
|
3818
|
+
}
|
|
3819
|
+
const sort = this.sort();
|
|
3820
|
+
if (sort) {
|
|
3821
|
+
sort.active = '';
|
|
3822
|
+
sort.direction = '';
|
|
3823
|
+
}
|
|
3824
|
+
};
|
|
3214
3825
|
#initConfiguration = (option) => {
|
|
3215
3826
|
option.paginate = {
|
|
3216
3827
|
hidden: option?.paginate?.hidden,
|
|
@@ -3230,7 +3841,10 @@ class SdTable {
|
|
|
3230
3841
|
if (!column.filter?.operator?.list?.length) {
|
|
3231
3842
|
column.filter.operator.list = this.tableConfiguration?.filter?.operator?.list?.[column.type] || [];
|
|
3232
3843
|
}
|
|
3233
|
-
|
|
3844
|
+
const defaultOperator = this.tableConfiguration?.filter?.operator?.default?.[column.type];
|
|
3845
|
+
if (defaultOperator) {
|
|
3846
|
+
this.columnOperator[column.field] = defaultOperator;
|
|
3847
|
+
}
|
|
3234
3848
|
if (column.filter.operator.default && column.filter.operator.list?.some(el => el === column.filter?.operator?.default)) {
|
|
3235
3849
|
this.columnOperator[column.field] = column.filter.operator.default;
|
|
3236
3850
|
}
|
|
@@ -3240,33 +3854,39 @@ class SdTable {
|
|
|
3240
3854
|
};
|
|
3241
3855
|
#loadFilterRegister = () => {
|
|
3242
3856
|
const opt = this.tableOption();
|
|
3243
|
-
if (opt
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3857
|
+
if (!opt || this.filterRegister)
|
|
3858
|
+
return;
|
|
3859
|
+
this.filterRegister = this.#gridFilterService.register(opt?.filter, {
|
|
3860
|
+
id: this.#tableId,
|
|
3861
|
+
columns: opt?.columns,
|
|
3862
|
+
externalFilters: opt?.filter?.externalFilters,
|
|
3863
|
+
filterDefs: [...this.sdFilterDefs()],
|
|
3864
|
+
columnOperator: this.columnOperator,
|
|
3865
|
+
force: true,
|
|
3866
|
+
});
|
|
3867
|
+
const { columnOperator, columnFilter } = this.filterRegister.value.get();
|
|
3868
|
+
this.columnOperator = columnOperator || {};
|
|
3869
|
+
this.#syncColumnFilterInPlace(columnFilter || {});
|
|
3870
|
+
this.#filterRegisterSubscription?.unsubscribe();
|
|
3871
|
+
this.#filterRegisterSubscription = this.filterRegister.value.observer
|
|
3872
|
+
.pipe(debounceTime(500), map(filterValue => {
|
|
3873
|
+
const { columnOperator, columnFilter, notReload } = filterValue;
|
|
3874
|
+
this.columnOperator = columnOperator || {};
|
|
3875
|
+
// Sync IN PLACE — không gán object clone mới. column-filter chia sẻ
|
|
3876
|
+
// reference this.columnFilter qua [columnFilter] input; nếu gán clone
|
|
3877
|
+
// mới, cf giữ ref cũ (do OnPush + reload async lag) → ghi clear vào
|
|
3878
|
+
// object orphan → giá trị cũ persist. Giữ ref ổn định để cf + table
|
|
3879
|
+
// luôn trỏ cùng 1 object.
|
|
3880
|
+
this.#syncColumnFilterInPlace(columnFilter || {});
|
|
3881
|
+
if (!notReload) {
|
|
3882
|
+
if (this.paginator()) {
|
|
3883
|
+
this.paginator().pageIndex = 0;
|
|
3266
3884
|
}
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
}
|
|
3885
|
+
this.#requestReload(false);
|
|
3886
|
+
}
|
|
3887
|
+
}))
|
|
3888
|
+
.subscribe();
|
|
3889
|
+
this.#subscription.add(this.#filterRegisterSubscription);
|
|
3270
3890
|
};
|
|
3271
3891
|
// Đồng bộ this.columnFilter với `next` mà GIỮ NGUYÊN reference object.
|
|
3272
3892
|
// column-filter chia sẻ ref này qua [columnFilter] — reassign clone mới sẽ
|
|
@@ -3281,158 +3901,12 @@ class SdTable {
|
|
|
3281
3901
|
}
|
|
3282
3902
|
Object.assign(cur, next);
|
|
3283
3903
|
};
|
|
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
3904
|
#filterLocal = (localItems, filterInfo) => {
|
|
3384
|
-
const
|
|
3385
|
-
|
|
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
|
-
}
|
|
3905
|
+
const result = filterLocalItems(localItems, this.tableOption(), filterInfo);
|
|
3906
|
+
this.#treeSearchPredicate = result.treeSearchPredicate;
|
|
3431
3907
|
return {
|
|
3432
|
-
items: items
|
|
3433
|
-
|
|
3434
|
-
}),
|
|
3435
|
-
total: items.length,
|
|
3908
|
+
items: result.items,
|
|
3909
|
+
total: result.total,
|
|
3436
3910
|
};
|
|
3437
3911
|
};
|
|
3438
3912
|
getFilterRequest = () => {
|
|
@@ -3516,7 +3990,10 @@ class SdTable {
|
|
|
3516
3990
|
total: 0,
|
|
3517
3991
|
};
|
|
3518
3992
|
}
|
|
3519
|
-
const pagingReq =
|
|
3993
|
+
const pagingReq = SdConvertToPagingReq(filterReq, {
|
|
3994
|
+
columns: opt.columns,
|
|
3995
|
+
externalFilters: opt.filter?.externalFilters,
|
|
3996
|
+
});
|
|
3520
3997
|
const data = await items(filterReq, pagingReq).catch(err => {
|
|
3521
3998
|
console.error(err);
|
|
3522
3999
|
return {
|
|
@@ -3566,18 +4043,21 @@ class SdTable {
|
|
|
3566
4043
|
// why: KHÔNG persist trạng thái bung bị ÉP trong lúc search (tránh nhánh
|
|
3567
4044
|
// tự bung do search bị ghi nhầm thành lựa chọn của user sau khi clear).
|
|
3568
4045
|
if (!this.#treeSearchActive) {
|
|
3569
|
-
|
|
4046
|
+
saveTreeExpandState(this.items(), this.#treeExpandState);
|
|
3570
4047
|
}
|
|
3571
4048
|
// why: search vừa tắt → childItems đang là tập đã prune theo từ khoá cũ;
|
|
3572
4049
|
// xoá cache để dựng lại đầy đủ children ở chế độ thường.
|
|
3573
4050
|
if (this.#treeSearchActive && !searchNow) {
|
|
3574
|
-
|
|
4051
|
+
clearTreeChildCache(this.#localItems);
|
|
3575
4052
|
}
|
|
3576
4053
|
}
|
|
3577
4054
|
this.items.set(args?.items || []);
|
|
3578
4055
|
this.total.set(args?.total || 0);
|
|
3579
4056
|
if (treeOpt) {
|
|
3580
|
-
|
|
4057
|
+
initTreeMeta(this.items(), treeOpt, {
|
|
4058
|
+
expandState: this.#treeExpandState,
|
|
4059
|
+
treeSearchPredicate: this.#treeSearchPredicate,
|
|
4060
|
+
});
|
|
3581
4061
|
await this.#expandDefaultBranches(this.items(), treeOpt);
|
|
3582
4062
|
this.treeRevision.update(n => n + 1);
|
|
3583
4063
|
}
|
|
@@ -3624,7 +4104,10 @@ class SdTable {
|
|
|
3624
4104
|
else {
|
|
3625
4105
|
const filterReq = this.#filterExportInfo(pageNumber, pageSize);
|
|
3626
4106
|
if (opt.type === 'server') {
|
|
3627
|
-
const pagingReq =
|
|
4107
|
+
const pagingReq = SdConvertToPagingReq(filterReq, {
|
|
4108
|
+
columns: opt.columns,
|
|
4109
|
+
externalFilters: opt.filter?.externalFilters,
|
|
4110
|
+
});
|
|
3628
4111
|
const result = opt.items(filterReq, pagingReq);
|
|
3629
4112
|
return await result;
|
|
3630
4113
|
}
|
|
@@ -3669,51 +4152,6 @@ class SdTable {
|
|
|
3669
4152
|
exportCustom = () => {
|
|
3670
4153
|
this.#tableExportService.exportCustom(this.#createExportContext());
|
|
3671
4154
|
};
|
|
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
4155
|
#ensureChildItemsFormatted = async (row) => {
|
|
3718
4156
|
const opt = this.tableOption();
|
|
3719
4157
|
const treeOpt = opt.tree;
|
|
@@ -3722,7 +4160,7 @@ class SdTable {
|
|
|
3722
4160
|
// vì tập này phụ thuộc từ khoá (đổi mỗi lần gõ) nên không thể cache.
|
|
3723
4161
|
if (!searchActive && row.meta.tree?.childItems?.length)
|
|
3724
4162
|
return;
|
|
3725
|
-
const raw =
|
|
4163
|
+
const raw = getVisibleChildrenData(row.data, treeOpt, this.#treeSearchPredicate);
|
|
3726
4164
|
if (!raw.length) {
|
|
3727
4165
|
if (searchActive && row.meta.tree)
|
|
3728
4166
|
row.meta.tree.childItems = [];
|
|
@@ -3730,20 +4168,12 @@ class SdTable {
|
|
|
3730
4168
|
}
|
|
3731
4169
|
const formatted = await this.#tableFormatService.format(raw, opt.columns, this.cacheValues, this.#cacheObjValues);
|
|
3732
4170
|
row.meta.tree.childItems = formatted;
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
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
|
-
}
|
|
4171
|
+
initTreeMeta(formatted, treeOpt, {
|
|
4172
|
+
level: (row.meta.tree.level ?? 0) + 1,
|
|
4173
|
+
parentId: row.meta.id,
|
|
4174
|
+
expandState: this.#treeExpandState,
|
|
4175
|
+
treeSearchPredicate: this.#treeSearchPredicate,
|
|
4176
|
+
});
|
|
3747
4177
|
};
|
|
3748
4178
|
#expandDefaultBranches = async (rows, option) => {
|
|
3749
4179
|
const maxDepth = option.maxDepth;
|
|
@@ -3902,28 +4332,14 @@ class SdTable {
|
|
|
3902
4332
|
this.#updateSelectedItems();
|
|
3903
4333
|
};
|
|
3904
4334
|
#getSelectionRows = () => {
|
|
3905
|
-
|
|
3906
|
-
const treeOpt = this.tableOption()?.tree;
|
|
3907
|
-
if (!treeOpt)
|
|
3908
|
-
return roots;
|
|
3909
|
-
return flattenTree(roots, treeOpt);
|
|
4335
|
+
return getSelectionRows(this.items(), this.tableOption()?.tree);
|
|
3910
4336
|
};
|
|
3911
|
-
#getSelectedRowData = () => this.#getSelectionRows()
|
|
3912
|
-
.filter(e => e.meta.selector.isSelected)
|
|
3913
|
-
.map(e => e.data);
|
|
4337
|
+
#getSelectedRowData = () => getSelectedRowData(this.#getSelectionRows());
|
|
3914
4338
|
#applyDefaultSelected = () => {
|
|
3915
|
-
|
|
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
|
-
});
|
|
4339
|
+
applyDefaultSelected(this.items(), this.tableOption()?.tree, this.tableOption()?.selector?.defaultSelected);
|
|
3923
4340
|
};
|
|
3924
4341
|
#syncSelectAllState = () => {
|
|
3925
|
-
|
|
3926
|
-
this.isSelectAll.set(visible.length > 0 && visible.every(e => e.meta.selector?.isSelected));
|
|
4342
|
+
this.isSelectAll.set(resolveSelectAllState(this.#getSelectionRows()));
|
|
3927
4343
|
};
|
|
3928
4344
|
// ==========================================
|
|
3929
4345
|
// GROUP HELPERS — sync selection + expand state cho group header rows
|
|
@@ -3966,9 +4382,7 @@ class SdTable {
|
|
|
3966
4382
|
return;
|
|
3967
4383
|
const key = header.meta.group.key;
|
|
3968
4384
|
const defaultExpanded = !this.tableOption()?.group?.defaultCollapsed;
|
|
3969
|
-
const current = this.groupExpandState.has(key)
|
|
3970
|
-
? !!this.groupExpandState.get(key)
|
|
3971
|
-
: defaultExpanded;
|
|
4385
|
+
const current = this.groupExpandState.has(key) ? !!this.groupExpandState.get(key) : defaultExpanded;
|
|
3972
4386
|
this.groupExpandState.set(key, !current);
|
|
3973
4387
|
header.meta.group.isExpanded = !current;
|
|
3974
4388
|
// why: trigger pipe re-eval — Map mutation không thay đổi reference items() nên cần update.
|
|
@@ -3985,7 +4399,7 @@ class SdTable {
|
|
|
3985
4399
|
* Colspan cho cell sdGroupHeader trên group row = displayedColumns.length.
|
|
3986
4400
|
* Span TOÀN BỘ width data row vì group row chỉ có 1 cell.
|
|
3987
4401
|
*/
|
|
3988
|
-
sdGroupColspan = computed(() =>
|
|
4402
|
+
sdGroupColspan = computed(() => this.configuration()?.displayedColumns?.length || 1, ...(ngDevMode ? [{ debugName: "sdGroupColspan" }] : []));
|
|
3989
4403
|
/** Build context object truyền vào SdTableGroupDefDirective template. */
|
|
3990
4404
|
groupContext = (header) => {
|
|
3991
4405
|
const g = header.meta.group;
|
|
@@ -4007,9 +4421,7 @@ class SdTable {
|
|
|
4007
4421
|
if (!fn)
|
|
4008
4422
|
return null;
|
|
4009
4423
|
const tree = row.meta?.tree;
|
|
4010
|
-
const ctx = this.tableOption()?.tree && tree
|
|
4011
|
-
? { level: tree.level, hasChildren: tree.hasChildren, isExpanded: tree.isExpanded }
|
|
4012
|
-
: undefined;
|
|
4424
|
+
const ctx = this.tableOption()?.tree && tree ? { level: tree.level, hasChildren: tree.hasChildren, isExpanded: tree.isExpanded } : undefined;
|
|
4013
4425
|
return fn(row.data, index, ctx);
|
|
4014
4426
|
};
|
|
4015
4427
|
// why: khi preserveSelection bật, giữ map nội bộ id → SdTableItem để selection
|
|
@@ -4023,29 +4435,14 @@ class SdTable {
|
|
|
4023
4435
|
#restorePreservedSelection = () => {
|
|
4024
4436
|
if (!this.#preserveEnabled())
|
|
4025
4437
|
return;
|
|
4026
|
-
|
|
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
|
-
});
|
|
4438
|
+
restorePreservedSelection(this.#getSelectionRows(), this.#preservedSelectedMap);
|
|
4033
4439
|
};
|
|
4034
4440
|
#updateSelectedItems = () => {
|
|
4035
4441
|
const rows = this.#getSelectionRows();
|
|
4036
4442
|
if (this.#preserveEnabled()) {
|
|
4037
4443
|
// Sync map theo state visible: add nếu selected, remove nếu deselected.
|
|
4038
4444
|
// Off-page items giữ nguyên trong map (không bị visit ở đây).
|
|
4039
|
-
|
|
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()));
|
|
4445
|
+
this.selectedTableItems.set(syncPreservedSelection(rows, this.#preservedSelectedMap));
|
|
4049
4446
|
}
|
|
4050
4447
|
else {
|
|
4051
4448
|
this.selectedTableItems.set(rows.filter(item => item.meta.selector.isSelected));
|
|
@@ -4097,295 +4494,38 @@ class SdTable {
|
|
|
4097
4494
|
return (p?.pageIndex ?? 0) * (p?.pageSize ?? 0);
|
|
4098
4495
|
}, ...(ngDevMode ? [{ debugName: "pageOffset" }] : []));
|
|
4099
4496
|
isReorderDisabled(item) {
|
|
4100
|
-
if ((item.meta.tree?.level ?? 0) > 0)
|
|
4101
|
-
return true;
|
|
4102
4497
|
const opt = this.tableOption()?.rowReorder;
|
|
4103
|
-
if (!opt?.disabled || item.meta?.group?.items?.length)
|
|
4104
|
-
return false;
|
|
4105
4498
|
const idx = this.#itemIndexMap.get(item) ?? -1;
|
|
4106
|
-
return
|
|
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);
|
|
4499
|
+
return isReorderDisabled(item, opt, idx);
|
|
4120
4500
|
}
|
|
4121
4501
|
reorderSortPredicate = (index, drag, drop) => {
|
|
4122
4502
|
const opt = this.tableOption()?.rowReorder;
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
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;
|
|
4503
|
+
return canSortReorder({
|
|
4504
|
+
enabled: opt?.enabled,
|
|
4505
|
+
index,
|
|
4506
|
+
dragItem: drag.data,
|
|
4507
|
+
allItems: drop.data,
|
|
4508
|
+
hasGroup: !!this.tableOption()?.group,
|
|
4509
|
+
});
|
|
4139
4510
|
};
|
|
4140
4511
|
onReorderDrop(event) {
|
|
4141
4512
|
const { previousIndex, currentIndex } = event;
|
|
4142
4513
|
if (previousIndex === currentIndex)
|
|
4143
4514
|
return;
|
|
4144
|
-
const
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
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;
|
|
4515
|
+
const result = reorderTableItems({
|
|
4516
|
+
items: this.items(),
|
|
4517
|
+
renderedItems: event.container.data,
|
|
4518
|
+
previousRenderedIndex: previousIndex,
|
|
4519
|
+
currentRenderedIndex: currentIndex,
|
|
4520
|
+
localItems: this.tableOption()?.type === 'local' ? this.#localItems : undefined,
|
|
4521
|
+
});
|
|
4522
|
+
this.items.set(result.items);
|
|
4523
|
+
if (this.tableOption()?.type === 'local' && result.localItems) {
|
|
4524
|
+
this.#localItems = result.localItems;
|
|
4165
4525
|
}
|
|
4166
4526
|
this.table()?.renderRows();
|
|
4167
|
-
this.tableOption()?.rowReorder?.onChange?.(
|
|
4527
|
+
this.tableOption()?.rowReorder?.onChange?.(result.items.map(i => i.data), event.item.data.data, result.fromIndex, result.toIndex);
|
|
4168
4528
|
}
|
|
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
4529
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4390
4530
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", 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
4531
|
DatePipe,
|
|
@@ -4467,183 +4607,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
4467
4607
|
], template: "@let _configuration = configuration();\n@let _tableOption = tableOption()!;\n@let _loading = loading();\n@let _items = items();\n@let _total = total();\n@let _isFiltered = isFiltered();\n@let _requireFiltered = requireFiltered();\n@let _export = _tableOption?.export;\n@let _exporting = exporting();\n@let _titleDef = titleDef();\n@let _autoId = autoId();\n@let _exportTitle = exportTitle();\n@let _cellDef = cellDef();\n@let _footerDef = footerDef();\n@let _sdSubInformation = sdSubInformation();\n@let _configComponent = configComponent();\n@let _mobileFilter = mobileFilter();\n@let _selectedTableItems = selectedTableItems();\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\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). -->\n@let _treeFirstField = _tableOption?.tree && !_tableOption.index?.enabled ? _configuration?.firstColumns?.[0]?.field : null;\n\n@if (_configuration) {\n @if (!_tableOption.filter?.disabled && !!_tableOption.filter?.externalFilters?.length && filterRegister) {\n <external-filter\n class=\"mb-16\"\n [autoId]=\"_autoId\"\n [filterRegister]=\"filterRegister\"\n [filter]=\"_tableOption.filter!\"\n [externalFilters]=\"_tableOption.filter?.externalFilters!\">\n </external-filter>\n }\n @let groupedItems = _items | sdTree: _tableOption.tree : treeRevision() | sdGroup: _tableOption : groupExpandState;\n\n <!-- Tree toggle d\u00F9ng chung: indent (theo c\u1EA5p) + slot chevron/spinner. Nh\u00FAng v\u00E0o\n c\u1ED9t Index ho\u1EB7c c\u1ED9t data \u0111\u1EA7u qua *ngTemplateOutlet (context: { row }). -->\n <ng-template #sdTreeToggle let-row=\"row\">\n @let _tree = row?.meta?.tree;\n @if (_tree) {\n <span class=\"sd-tree-indent\" [style.width.px]=\"(_tree.level ?? 0) * (_tableOption.tree?.indentSize ?? 20)\"></span>\n <span class=\"sd-tree-toggle-slot\">\n @if (_tree.hasChildren) {\n @if (_tree.isExpanding) {\n <span class=\"lds-ring sd-tree-spinner\">\n <div></div>\n <div></div>\n <div></div>\n <div></div>\n </span>\n } @else {\n @let _aid = _autoId;\n <button\n type=\"button\"\n class=\"sd-tree-toggle-btn\"\n [attr.data-autoid]=\"_aid ? _aid + '-tree-toggle-' + row.meta.id : null\"\n (click)=\"onTreeToggle(row); $event.stopPropagation()\">\n <mat-icon>{{ _tree.isExpanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n }\n }\n </span>\n }\n </ng-template>\n\n <ng-content select=\"[sdTableTop]\"></ng-content>\n <div class=\"c-container\">\n @if (_loading) {\n <div class=\"c-loading\">\n <mat-spinner></mat-spinner>\n </div>\n }\n <div\n class=\"c-table\"\n sdScroll\n stickyShadow\n [style.max-height]=\"_tableOption.style?.maxHeight\"\n [style.min-height]=\"_tableOption.style?.minHeight\">\n <table\n mat-table\n [dataSource]=\"groupedItems\"\n [trackBy]=\"trackBy\"\n matSort\n [matSortDisabled]=\"!_tableOption.sort?.enable\"\n multiTemplateDataRows\n cdkDropList\n [cdkDropListData]=\"groupedItems\"\n [cdkDropListDisabled]=\"!_tableOption.rowReorder?.enabled\"\n [cdkDropListSortPredicate]=\"reorderSortPredicate\"\n (cdkDropListDropped)=\"onReorderDrop($event)\">\n @if (_tableOption.rowReorder?.enabled) {\n <ng-container matColumnDef=\"sdReorder\">\n <th mat-header-cell *matHeaderCellDef class=\"sd-reorder-header\" [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\"></th>\n <td mat-cell *matCellDef=\"let row\" class=\"sd-reorder-cell\">\n @if (!row.meta?.group?.items?.length) {\n <mat-icon cdkDragHandle [class.sd-reorder-disabled]=\"isReorderDisabled(row)\">\n {{ _tableOption.rowReorder?.icon || 'drag_indicator' }}\n </mat-icon>\n }\n </td>\n <td mat-footer-cell *matFooterCellDef></td>\n </ng-container>\n }\n\n <ng-container matColumnDef=\"sdSubInformation\" sticky>\n <td class=\"p-0\" mat-cell *matCellDef=\"let item\" [attr.colspan]=\"_configuration.displayedColumns.length\">\n @if (_sdSubInformation?.templateRef) {\n @if (_tableOption.expand?.always) {\n <ng-container *ngTemplateOutlet=\"_sdSubInformation?.templateRef!; context: { item: item }\"> </ng-container>\n } @else {\n <div [@detailExpand]=\"item.isExpanded ? 'expanded' : 'collapsed'\">\n @if (item.isExpanded) {\n <ng-container *ngTemplateOutlet=\"_sdSubInformation?.templateRef!; context: { item: item }\"> </ng-container>\n }\n </div>\n }\n }\n </td>\n <td mat-footer-cell *matFooterCellDef></td>\n </ng-container>\n\n <ng-container matColumnDef=\"sdSubInformationAction\" stickyEnd>\n <th class=\"p-0\" mat-header-cell *matHeaderCellDef style=\"width: 1px\" [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\"></th>\n <td mat-cell *matCellDef=\"let element\">\n @if (!element.isExpanding && !_tableOption.expand?.always) {\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-expand-' + element.meta.id : null\"\n type=\"link\"\n [prefixIcon]=\"element.isExpanded ? 'expand_less' : 'expand_more'\"\n (click)=\"onExpand(element)\"\n width=\"35px\">\n </sd-button>\n }\n @if (element.isExpanding) {\n <div class=\"lds-ring\">\n <div></div>\n <div></div>\n <div></div>\n <div></div>\n </div>\n }\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"sdSelection\" sticky>\n <th class=\"sd-selection-cell p-0\" mat-header-cell *matHeaderCellDef [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\">\n @let visibleSelectAll = _items | selectionVisibleSelectAll: _tableOption.selector | async;\n @if (visibleSelectAll) {\n <mat-checkbox\n class=\"c-selection px-8\"\n color=\"primary\"\n [ngModel]=\"isSelectAll()\"\n (ngModelChange)=\"isSelectAll.set($event)\"\n (change)=\"onSelectAll()\">\n </mat-checkbox>\n }\n </th>\n\n <td class=\"sd-selection-cell p-0\" mat-cell *matCellDef=\"let item\">\n @let visible = item | selectionVisible: _tableOption.selector;\n @if (visible) {\n @if (_tableOption.selector?.single) {\n <mat-radio-button\n class=\"c-selection px-8\"\n color=\"primary\"\n [checked]=\"item.meta.selector.isSelected\"\n (change)=\"item.meta.selector.isSelected = true; onSelect(item)\"\n [disabled]=\"_selectedTableItems | selectionDisabled: item : _tableOption.selector!\">\n </mat-radio-button>\n } @else {\n <mat-checkbox\n class=\"c-selection px-8\"\n color=\"primary\"\n [(ngModel)]=\"item.meta.selector.isSelected\"\n (change)=\"onSelect(item)\"\n [disabled]=\"_selectedTableItems | selectionDisabled: item : _tableOption.selector!\">\n </mat-checkbox>\n }\n }\n </td>\n <td mat-footer-cell *matFooterCellDef></td>\n </ng-container>\n @if (_tableOption.index?.enabled) {\n @let _pageOffset = pageOffset();\n @let _indexWidth = _tableOption.index?.width || '50px';\n <!-- why: khi tree, c\u1ED9t Index \"\u00F4m\" th\u00EAm chevron + indent. D\u00F9ng width:1px +\n nowrap (.sd-tree-index-cell) \u0111\u1EC3 c\u1ED9t co S\u00C1T n\u1ED9i dung, kh\u00F4ng chi\u1EBFm ch\u1ED7\n th\u1EEBa; header \"#\" canh tr\u00E1i l\u00F9i v\u00E0o (.sd-tree-index-header) cho th\u1EB3ng s\u1ED1. -->\n @let _treeIndex = !!_tableOption.tree;\n <ng-container matColumnDef=\"sdIndex\" sticky>\n <th\n [class.p-0]=\"!_treeIndex\"\n [class.text-center]=\"!_treeIndex\"\n [class.sd-tree-index-cell]=\"_treeIndex\"\n [class.sd-tree-index-header]=\"_treeIndex\"\n mat-header-cell\n *matHeaderCellDef\n [style.width]=\"_treeIndex ? '1px' : _indexWidth\"\n [style.min-width]=\"_treeIndex ? null : _indexWidth\"\n [style.max-width]=\"_treeIndex ? null : _indexWidth\"\n [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\">\n {{ _tableOption.index?.title || '#' }}\n </th>\n <td\n [class.px-4]=\"!_treeIndex\"\n [class.text-center]=\"!_treeIndex\"\n [class.sd-tree-index-cell]=\"_treeIndex\"\n mat-cell\n *matCellDef=\"let item; let i = dataIndex\"\n [style.width]=\"_treeIndex ? '1px' : null\"\n [style.min-width]=\"_treeIndex ? null : _indexWidth\"\n [style.max-width]=\"_treeIndex ? null : _indexWidth\">\n @if (!item?.meta?.group?.isGroupHeader) {\n @if (_treeIndex) {\n <!-- why: tree \u2192 chevron + indent + STT ph\u00E2n c\u1EA5p (1, 1.2, 1.2.1). T\u00F4 \u0111\u1EADm root (level 0). -->\n <span class=\"sd-tree-line\">\n <ng-container *ngTemplateOutlet=\"sdTreeToggle; context: { row: item }\"></ng-container>\n @if (item?.meta?.tree?.indexPath?.length) {\n <span class=\"sd-tree-stt\" [class.sd-stt-root]=\"(item.meta.tree.level ?? 0) === 0\">\n {{ item.meta.tree.indexPath.join('.') }}\n </span>\n }\n </span>\n } @else {\n {{ _pageOffset + i + 1 }}\n }\n }\n </td>\n <td mat-footer-cell *matFooterCellDef [style.width]=\"_treeIndex ? '1px' : null\" [style.min-width]=\"_treeIndex ? null : _indexWidth\" [style.max-width]=\"_treeIndex ? null : _indexWidth\"></td>\n </ng-container>\n }\n <ng-container\n matColumnDef=\"sdCommand\"\n [sticky]=\"_tableOption.command?.align !== 'right'\"\n [stickyEnd]=\"_tableOption.command?.align === 'right'\">\n <th class=\"p-0\" mat-header-cell *matHeaderCellDef style=\"width: 50px\" [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\"></th>\n <td class=\"px-8\" mat-cell *matCellDef=\"let item\">\n <desktop-command\n [autoId]=\"_autoId\"\n [commands]=\"_tableOption.command?.commands || _tableOption.commands || []\"\n [item]=\"item\"\n [itemIndex]=\"groupedItems.indexOf(item)\"></desktop-command>\n </td>\n <td mat-footer-cell *matFooterCellDef></td>\n </ng-container>\n <!-- why: group header row d\u00F9ng matRowDef RI\u00CANG v\u1EDBi column list ['sdGroupHeader']\n qua predicate when:isGroupHeaderRow. Cell colspan = displayedColumns.length \u0111\u1EC3\n span TO\u00C0N B\u1ED8 width data row. Tr\u00E1nh colspan trick overflow row khi c\u00E1c TD kh\u00E1c render. -->\n <ng-container matColumnDef=\"sdGroupHeader\">\n <td\n class=\"p-0 sd-group-header-cell\"\n mat-cell\n *matCellDef=\"let item\"\n [attr.colspan]=\"sdGroupColspan()\">\n <div class=\"sd-group-row d-flex align-items-center py-8\">\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. -->\n @if (_tableOption.selector?.visible && !_tableOption.selector?.single) {\n <div class=\"sd-group-selection-slot d-flex align-items-center justify-content-center\">\n <mat-checkbox\n class=\"c-selection\"\n color=\"primary\"\n [checked]=\"isGroupAllSelected(item)\"\n [indeterminate]=\"isGroupIndeterminate(item)\"\n (change)=\"onSelectGroup(item, $event.checked)\">\n </mat-checkbox>\n </div>\n } @else {\n <div class=\"sd-group-selection-slot\"></div>\n }\n @if (_tableOption.group?.collapsible) {\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-group-toggle-' + (item.meta.group?.key || item.meta.id) : null\"\n type=\"link\"\n [prefixIcon]=\"item.meta.group.isExpanded ? 'expand_more' : 'chevron_right'\"\n width=\"32px\"\n (click)=\"toggleGroupExpand(item)\">\n </sd-button>\n }\n @if (sdGroupDef()?.templateRef; as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: groupContext(item)\"></ng-container>\n } @else {\n <span class=\"T14R\">{{ item.meta.group.values | json }}</span>\n }\n </div>\n </td>\n </ng-container>\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. -->\n <ng-container matColumnDef=\"sdFiller\">\n <th class=\"sd-filler-cell p-0\" mat-header-cell *matHeaderCellDef [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\"></th>\n <td class=\"sd-filler-cell p-0\" mat-cell *matCellDef=\"let item\"></td>\n <td class=\"sd-filler-cell p-0\" mat-footer-cell *matFooterCellDef></td>\n </ng-container>\n @for (column of _configuration.firstColumns; track column.field) {\n <ng-container [matColumnDef]=\"column.field\" [sticky]=\"_configuration.fixedColumn[column.field]\">\n <th\n mat-header-cell\n *matHeaderCellDef\n class=\"px-8 py-8 c-th\"\n [sdColumnResize]=\"!!_tableOption.config?.resizable && column.type !== 'children'\"\n [minWidth]=\"column.minWidth\"\n [maxWidth]=\"column.maxWidth\"\n (resizeEnd)=\"onColumnResize(column.field, $event)\"\n [style.width]=\"column.width\"\n [style.min-width]=\"column.minWidth || column.width\"\n [style.max-width]=\"column.maxWidth\"\n [attr.rowspan]=\"_configuration.multipleHeader && column.type !== 'children' ? 2 : 1\"\n [attr.colspan]=\"column.type === 'children' ? column.children.length : 1\">\n <div>\n @if (column.type === 'children') {\n <div\n aria-hidden=\"true\"\n class=\"c-header-title\"\n [class.justify-content-end]=\"column.align === 'right'\"\n [class.text-right]=\"column.align === 'right'\">\n <column-title [column]=\"column\" [titleDef]=\"_titleDef[column.field]\"></column-title>\n </div>\n } @else {\n <div\n aria-hidden=\"true\"\n mat-sort-header\n class=\"c-header-title\"\n [class.justify-content-end]=\"column.align === 'right'\"\n [class.text-right]=\"column.align === 'right'\"\n [disabled]=\"!column.sortable\">\n <column-title [column]=\"column\" [titleDef]=\"_titleDef[column.field]\"></column-title>\n </div>\n @let hideInlineFilter =\n _tableOption.filter?.disabled ||\n _tableOption.filter?.hideInlineFilter === true ||\n (_tableOption.filter?.hideInlineFilter === 'auto' && !_isFiltered && _total! <= 10);\n @if (!hideInlineFilter) {\n <ng-container *sdDesktop>\n <column-filter\n [autoId]=\"_autoId\"\n [value]=\"columnFilter?.[column.field!]!\"\n [operator]=\"columnOperator[column.field]\"\n (operatorChange)=\"columnOperator[column.field] = $event!; onOperatorChange(column, $event)\"\n [columnFilter]=\"columnFilter!\"\n [cacheValues]=\"cacheValues\"\n [column]=\"column\"\n (filterChange)=\"onFilterChange()\"\n (filterCommit)=\"onFilterCommit()\">\n </column-filter>\n </ng-container>\n }\n }\n </div>\n </th>\n <td\n class=\"c-td px-0\"\n [class.d-none]=\"column.type === 'children'\"\n mat-cell\n *matCellDef=\"let item\"\n [sdHoverCopy]=\"(item.data && item.data[column.field]) || ''\"\n [sdHoverCopyDisabled]=\"!column.cell?.copiable\">\n @if (column.type !== 'children' && !item?.meta?.group?.isGroupHeader) {\n @if (_treeFirstField && column.field === _treeFirstField) {\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). -->\n <span class=\"sd-tree-line px-8\">\n <ng-container *ngTemplateOutlet=\"sdTreeToggle; context: { row: item }\"></ng-container>\n <desktop-cell class=\"d-block\" [column]=\"column\" [item]=\"item\" [cellDef]=\"_cellDef\"></desktop-cell>\n </span>\n } @else {\n <desktop-cell class=\"d-block px-8\" [column]=\"column\" [item]=\"item\" [cellDef]=\"_cellDef\"> </desktop-cell>\n }\n }\n </td>\n <td mat-footer-cell *matFooterCellDef>\n <ng-container *ngIf=\"_footerDef[column.field]\">\n <ng-container *ngTemplateOutlet=\"_footerDef[column.field].templateRef; context: { items: _items, column: column }\">\n </ng-container>\n </ng-container>\n </td>\n </ng-container>\n }\n @for (column of _configuration.secondColumns; track column.field) {\n <ng-container [matColumnDef]=\"column.field\">\n <th\n mat-header-cell\n *matHeaderCellDef\n class=\"c-th px-8\"\n [style.width]=\"column.width\"\n [style.min-width]=\"column.minWidth || column.width\"\n [style.max-width]=\"column.maxWidth\">\n <div>\n <div\n aria-hidden=\"true\"\n mat-sort-header\n class=\"c-header-title\"\n [class.justify-content-end]=\"column.align === 'right'\"\n [class.text-right]=\"column.align === 'right'\"\n [disabled]=\"!column.sortable\">\n <column-title [column]=\"column\" [titleDef]=\"_titleDef[column.field]\"></column-title>\n </div>\n @if (!_tableOption.filter?.disabled && !_tableOption.filter?.hideInlineFilter && columnOperator) {\n <ng-container *sdDesktop>\n <column-filter\n [autoId]=\"_autoId\"\n [value]=\"columnFilter?.[column.field!]!\"\n [operator]=\"columnOperator[column.field]\"\n [columnFilter]=\"columnFilter!\"\n [cacheValues]=\"cacheValues\"\n [column]=\"column\"\n (operatorChange)=\"columnOperator[column.field] = $event!; onOperatorChange(column, $event)\"\n (filterChange)=\"onFilterChange()\"\n (filterCommit)=\"onFilterCommit()\">\n </column-filter>\n </ng-container>\n }\n </div>\n </th>\n <td\n class=\"c-td px-0\"\n mat-cell\n *matCellDef=\"let item\"\n [sdHoverCopy]=\"item[column.field]\"\n [sdHoverCopyDisabled]=\"!column.cell?.copiable\">\n @if (column.type !== 'children') {\n <desktop-cell class=\"d-block px-8\" [column]=\"column\" [item]=\"item\" [cellDef]=\"_cellDef\"> </desktop-cell>\n }\n </td>\n <td mat-footer-cell *matFooterCellDef>\n <ng-container *ngIf=\"_footerDef[column.field]\">\n <ng-container *ngTemplateOutlet=\"_footerDef[column.field].templateRef; context: { items: _items, column: column }\">\n </ng-container>\n </ng-container>\n </td>\n </ng-container>\n }\n <tr class=\"c-first-header\" mat-header-row *matHeaderRowDef=\"_configuration.firstHeaders; sticky: true\"></tr>\n @if (!!_configuration.secondHeaders.length) {\n <tr class=\"c-second-header\" mat-header-row *matHeaderRowDef=\"_configuration.secondHeaders; sticky: true\"></tr>\n }\n <!-- Group header row \u2014 ch\u1EC9 1 cell sdGroupHeader colspan to\u00E0n width. -->\n <tr\n mat-row\n *matRowDef=\"let row; columns: ['sdGroupHeader']; when: isGroupHeaderRow\"\n class=\"c-group-row-tr\"></tr>\n\n <!-- Data row \u2014 d\u00F9ng displayedColumns \u0111\u1EA7y \u0111\u1EE7; when isDataRow \u0111\u1EC3 lo\u1EA1i tr\u1EEB group header. -->\n <tr\n mat-row\n cdkDrag\n [cdkDragData]=\"row\"\n [cdkDragDisabled]=\"\n !_tableOption.rowReorder?.enabled ||\n !!row.meta?.group?.items?.length ||\n (row.meta?.tree?.level ?? 0) > 0 ||\n isReorderDisabled(row)\n \"\n *matRowDef=\"let row; columns: _configuration.displayedColumns; when: isDataRow\"\n matRipple\n class=\"c-row\"\n [class.sd-tree-row]=\"!!_tableOption.tree\"\n [ngClass]=\"_tableOption.tree ? 'sd-tree-level-' + (row.meta?.tree?.level ?? 0) : null\"\n [ngStyle]=\"rowStyle(row)\"\n [class.selected]=\"row.meta.selector.isSelected\"></tr>\n\n <!-- Sub-information row \u2014 also gated b\u1EDFi isDataRow \u0111\u1EC3 KH\u00D4NG render empty expand row d\u01B0\u1EDBi group header. -->\n <tr mat-row *matRowDef=\"let row; columns: ['sdSubInformation']; when: isDataRow\" class=\"c-detail-row\"></tr>\n @if (hasFooter() && !!_configuration.displayedFooters.length) {\n <tr mat-footer-row *matFooterRowDef=\"_configuration.displayedFooters; sticky: true\"></tr>\n }\n </table>\n @if (!_loading && !_total) {\n <div class=\"c-no-data-row\">\n @if (_isFiltered) {\n @if (tableConfiguration?.images?.filterEmpty) {\n <img class=\"c-image\" [src]=\"tableConfiguration!.images!.filterEmpty\" alt=\"filter-empty\" />\n } @else {\n <img class=\"c-image sd-image-filter-empty\" alt=\"filter-empty\" />\n }\n <div class=\"T16M\">{{ 'core.component.table.no-results' | translate }}</div>\n <div class=\"T16R text-secondary\">{{ 'core.component.table.no-results-hint' | translate }}</div>\n } @else {\n @if (_requireFiltered) {\n @if (tableConfiguration?.images?.filterRequired) {\n <img class=\"c-image\" [src]=\"tableConfiguration!.images!.filterRequired\" alt=\"filter-required\" />\n } @else {\n <img class=\"c-image sd-image-filter-required\" alt=\"filter-required\" />\n }\n <div class=\"T16R text-secondary\">{{ 'core.component.table.choose-filter-hint' | translate }}</div>\n } @else {\n @if (tableConfiguration?.images?.dataEmpty) {\n <img class=\"c-image\" [src]=\"tableConfiguration!.images!.dataEmpty\" alt=\"data-empty\" />\n } @else {\n <img class=\"c-image sd-image-data-empty\" alt=\"data-empty\" />\n }\n <div class=\"T16R text-secondary\">{{ 'core.component.table.no-data' | translate }}</div>\n }\n }\n </div>\n }\n </div>\n <div class=\"c-paginator\">\n <div class=\"c-action\">\n <ng-container *sdDesktop>\n @if (_tableOption.reload?.visible) {\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-reload' : null\"\n class=\"mr-8\"\n [title]=\"'core.component.table.reload' | translate\"\n prefixIcon=\"refresh\"\n (click)=\"reload()\"\n [disabled]=\"!_items.length\"\n type=\"link\">\n </sd-button>\n }\n </ng-container>\n @if (_export && _items.length) {\n @if (_export.type === 'custom') {\n <sd-button [autoId]=\"_autoId ? _autoId + '-export' : null\" class=\"mr-8\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" (click)=\"exportCustom()\" type=\"link\"> </sd-button>\n } @else {\n @if (_export.visible) {\n @if (_exporting) {\n <sd-button [autoId]=\"_autoId ? _autoId + '-export' : null\" class=\"mr-8\" [loading]=\"_exporting\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" type=\"link\"> </sd-button>\n } @else {\n @if (_export.visible === 'ALL' || !_export.visible) {\n <sd-button [autoId]=\"_autoId ? _autoId + '-export' : null\" class=\"mr-8\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" [matMenuTriggerFor]=\"menu\" type=\"link\"> </sd-button>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item [attr.data-autoid]=\"_autoId ? _autoId + '-export-excel' : null\" (click)=\"exportExcel()\" type=\"button\">\n <mat-icon fontSet=\"material-icons-outlined\">file_download</mat-icon>\n <span> {{ 'core.component.table.export-excel' | translate }}</span>\n </button>\n <button mat-menu-item [attr.data-autoid]=\"_autoId ? _autoId + '-export-csv' : null\" (click)=\"exportCSV()\" type=\"button\">\n <mat-icon fontSet=\"material-icons-outlined\">file_download</mat-icon>\n <span> {{ 'core.component.table.export-csv' | translate }}</span>\n </button>\n </mat-menu>\n } @else if (_export.visible === 'EXCEL') {\n <sd-button [autoId]=\"_autoId ? _autoId + '-export-excel' : null\" class=\"mr-8\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" (click)=\"exportExcel()\" type=\"link\"> </sd-button>\n } @else if (_export.visible === 'CSV') {\n <sd-button [autoId]=\"_autoId ? _autoId + '-export-csv' : null\" class=\"mr-8\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" (click)=\"exportCSV()\" type=\"link\"> </sd-button>\n }\n }\n }\n }\n }\n\n <ng-container *sdDesktop>\n @if (_configComponent) {\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-config' : null\"\n class=\"mr-8\"\n [title]=\"'core.component.table.setup-short' | translate\"\n prefixIcon=\"settings\"\n (click)=\"_configComponent.open()\"\n type=\"link\">\n </sd-button>\n }\n </ng-container>\n <!-- Mobile: n\u00FAt Filter m\u1EDF mobile-filter drawer (inline filter \u1EA9n tr\u00EAn mobile) -->\n <ng-container *sdMobile>\n @if (!_tableOption.filter?.disabled && _mobileFilter) {\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-mobile-filter-open' : null\"\n class=\"mr-8\"\n prefixIcon=\"filter_alt\"\n (click)=\"_mobileFilter.open()\"\n type=\"link\">\n </sd-button>\n }\n </ng-container>\n </div>\n @let hidePaginator = !_tableOption.paginate?.pageSize || _total === undefined || _total <= _tableOption.paginate?.pageSize!;\n <!-- Desktop paginator: full config (pageSize dropdown, first/last buttons) -->\n <ng-container *sdDesktop>\n <mat-paginator\n [class.d-none]=\"_tableOption.paginate?.hidden || hidePaginator\"\n [length]=\"_total\"\n [pageSize]=\"_tableOption.paginate?.pageSize\"\n [pageSizeOptions]=\"_tableOption.paginate?.pages!\"\n [showFirstLastButtons]=\"_tableOption.paginate?.showFirstLastButtons\"\n [hidePageSize]=\"_tableOption.paginate?.hidePageSize\"></mat-paginator>\n </ng-container>\n <!-- Mobile paginator: \u1EA9n pageSize dropdown + first/last buttons \u0111\u1EC3 ti\u1EBFt ki\u1EC7m kh\u00F4ng gian -->\n <ng-container *sdMobile>\n <mat-paginator\n [class.d-none]=\"_tableOption.paginate?.hidden || hidePaginator\"\n [length]=\"_total\"\n [pageSize]=\"_tableOption.paginate?.pageSize\"\n [showFirstLastButtons]=\"false\"\n hidePageSize></mat-paginator>\n </ng-container>\n <!-- \"\u0110ang hi\u1EC3n th\u1ECB...\" ch\u1EC9 desktop -->\n <ng-container *sdDesktop>\n @if (!_tableOption.paginate?.hidden && hidePaginator && _total !== undefined && _total > 0) {\n <div class=\"T14R pr-16\">\n {{ 'core.component.table.showing' | translate }} <span class=\"T14M ml-2\">1-{{ _total }}/{{ _total }}</span>\n </div>\n }\n </ng-container>\n </div>\n </div>\n <selector-action [autoId]=\"_autoId\" [tableOption]=\"_tableOption\" [selectedTableItems]=\"_selectedTableItems\" (clear)=\"onClearSelection(groupedItems)\" />\n @if (_tableOption.config?.visible) {\n <config [autoId]=\"_autoId\" [tableOption]=\"_tableOption\" />\n }\n <!-- Mobile filter drawer \u2014 ch\u1EC9 instantiate khi c\u1EA7n render tr\u00EAn mobile -->\n @if (!_tableOption.filter?.disabled && filterRegister) {\n <mobile-filter\n [autoId]=\"_autoId\"\n [filter]=\"_tableOption.filter!\"\n [externalFilters]=\"_tableOption.filter?.externalFilters!\"\n [columns]=\"_configuration.firstColumns\"\n [filterDefs]=\"sdFilterDefs()\"\n [filterRegister]=\"filterRegister\"\n [cacheValues]=\"cacheValues\">\n </mobile-filter>\n }\n}\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
4608
|
}], ctorParameters: () => [], propDecorators: { autoIdInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoId", required: false }] }], option: [{ type: i0.Input, args: [{ isSignal: true, alias: "option", required: true }] }], table: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MatTable), { isSignal: true }] }], configComponent: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ConfigComponent), { isSignal: true }] }], sdPopupExport: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SdPopupExport), { isSignal: true }] }], scroll: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SdScrollDirective), { isSignal: true }] }], quickAction: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SdQuickAction), { isSignal: true }] }], externalFilter: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ExternalFilterComponent), { isSignal: true }] }], mobileFilter: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MobileFilterComponent), { isSignal: true }] }], paginator: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MatPaginator), { isSignal: true }] }], sort: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MatSort), { isSignal: true }] }], sdSubInformation: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdTableExpandDefDirective), { isSignal: true }] }], sdGroupDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdTableGroupDefDirective), { isSignal: true }] }], sdCellDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SdTableCellDefDirective), { isSignal: true }] }], sdFooterDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SdMaterialFooterDefDirective), { isSignal: true }] }], sdFilterDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SdTableFilterDefDirective), { isSignal: true }] }], sdTitleDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SdTableTitleDefDirective), { isSignal: true }] }] } });
|
|
4469
4609
|
|
|
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
4610
|
/**
|
|
4648
4611
|
* Generated bundle index. Do not edit.
|
|
4649
4612
|
*/
|