tailjng 0.1.9 → 0.1.11
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/lib/interfaces/crud/filter.interface.d.ts +6 -0
- package/package.json +1 -1
- package/src/lib/components/filter/filter-complete/complete-filter.component.html +62 -57
- package/src/lib/components/filter/filter-complete/complete-filter.component.ts +2 -1
- package/src/lib/components/select/select-dropdown/dropdown-select.component.html +20 -6
- package/src/lib/components/select/select-dropdown/dropdown-select.component.ts +371 -64
- package/src/lib/components/select/select-dropdown/dropdown-select.util.ts +154 -3
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.html +20 -5
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.ts +357 -59
- package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.util.ts +7 -0
|
@@ -35,13 +35,18 @@ import type {
|
|
|
35
35
|
} from './dropdown-select.types';
|
|
36
36
|
import {
|
|
37
37
|
DROPDOWN_PANEL_DEFAULTS,
|
|
38
|
+
canResolveSelectedByFindOne,
|
|
39
|
+
extractOptionsFromResponse,
|
|
38
40
|
filterOptionsBySearch,
|
|
41
|
+
getOptionValueFilterKey,
|
|
39
42
|
mapObjectOptions,
|
|
40
43
|
mapPrimitiveOptions,
|
|
44
|
+
mergeDropdownOptions,
|
|
45
|
+
optionValuesEqual,
|
|
41
46
|
positionDropdownPanel,
|
|
47
|
+
readDropdownPaginationMeta,
|
|
42
48
|
rebuildEndpointUrl,
|
|
43
49
|
resetDropdownPanelStyles,
|
|
44
|
-
resolveOptionLabel,
|
|
45
50
|
} from './dropdown-select.util';
|
|
46
51
|
|
|
47
52
|
export type {
|
|
@@ -55,14 +60,21 @@ export type {
|
|
|
55
60
|
} from './dropdown-select.types';
|
|
56
61
|
export {
|
|
57
62
|
DROPDOWN_PANEL_DEFAULTS,
|
|
63
|
+
extractOptionsFromResponse,
|
|
58
64
|
filterOptionsBySearch,
|
|
59
65
|
getNestedValue,
|
|
60
66
|
mapObjectOptions,
|
|
61
67
|
mapPrimitiveOptions,
|
|
68
|
+
mergeDropdownOptions,
|
|
69
|
+
optionValuesEqual,
|
|
62
70
|
positionDropdownPanel,
|
|
71
|
+
readDropdownPaginationMeta,
|
|
63
72
|
rebuildEndpointUrl,
|
|
64
73
|
resetDropdownPanelStyles,
|
|
65
74
|
resolveOptionLabel,
|
|
75
|
+
resolveOptionLabelFromTemplate,
|
|
76
|
+
getOptionValueFilterKey,
|
|
77
|
+
canResolveSelectedByFindOne,
|
|
66
78
|
} from './dropdown-select.util';
|
|
67
79
|
|
|
68
80
|
/**
|
|
@@ -79,6 +91,16 @@ export {
|
|
|
79
91
|
* optionValue="value"
|
|
80
92
|
* [showClear]="true"
|
|
81
93
|
* />
|
|
94
|
+
*
|
|
95
|
+
* <!-- Nested API payload: data.typeAccess.options[] -->
|
|
96
|
+
* <JDropdownSelect
|
|
97
|
+
* type="searchable"
|
|
98
|
+
* endpoint="enum/typeAccess"
|
|
99
|
+
* dataPath="typeAccess.options"
|
|
100
|
+
* optionLabel="label"
|
|
101
|
+
* optionValue="value"
|
|
102
|
+
* optionLabelTemplate="Acceso {label} (id {value})"
|
|
103
|
+
* />
|
|
82
104
|
* ```
|
|
83
105
|
*
|
|
84
106
|
* Icons in template: `[icon]="Icons.ChevronDown"` (see `readonly Icons = Icons`).
|
|
@@ -132,12 +154,34 @@ export class JDropdownSelectComponent
|
|
|
132
154
|
// Property name(s) used to build option text (supports dot paths)
|
|
133
155
|
@Input() optionLabel: string | string[] = 'text';
|
|
134
156
|
|
|
135
|
-
// Property name used as the option value
|
|
157
|
+
// Property name used as the option value (supports dot paths)
|
|
136
158
|
@Input() optionValue = 'value';
|
|
137
159
|
|
|
138
160
|
// Separator when `optionLabel` is an array of keys
|
|
139
161
|
@Input() labelSeparator = ' ';
|
|
140
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Custom label template per option. Use `{field}` placeholders with dot paths.
|
|
165
|
+
* Example: `El valor es {value} de {label}`.
|
|
166
|
+
* Takes precedence over `optionLabel` / `labelSeparator`.
|
|
167
|
+
*/
|
|
168
|
+
@Input() optionLabelTemplate = '';
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Optional formatter for full control over the displayed label.
|
|
172
|
+
* Takes precedence over `optionLabelTemplate` when provided.
|
|
173
|
+
*/
|
|
174
|
+
@Input() optionLabelFn?: (option: Record<string, unknown>) => string;
|
|
175
|
+
|
|
176
|
+
/** @deprecated Alias of `dataPath` for a single top-level key in `response.data`. */
|
|
177
|
+
@Input() responseKey = '';
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Dot path from `response.data` to the options array.
|
|
181
|
+
* Examples: `users`, `typeAccess.options`, `catalog.items`.
|
|
182
|
+
*/
|
|
183
|
+
@Input() dataPath = '';
|
|
184
|
+
|
|
141
185
|
// Prepends a synthetic “TODOS” option with `value: null` (searchable mode)
|
|
142
186
|
@Input() showAllOption = false;
|
|
143
187
|
|
|
@@ -164,8 +208,20 @@ export class JDropdownSelectComponent
|
|
|
164
208
|
// Searchable: sort order sent to the API
|
|
165
209
|
@Input() sort: DropdownSortOrder = 'ASC';
|
|
166
210
|
|
|
167
|
-
// Searchable:
|
|
168
|
-
@Input() limit =
|
|
211
|
+
// Searchable: records per page (infinite scroll loads next pages)
|
|
212
|
+
@Input() limit = 30;
|
|
213
|
+
|
|
214
|
+
/** Appends next API pages when scrolling the options list (searchable mode). */
|
|
215
|
+
@Input() infiniteScroll = true;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Fetches the selected row when it is not in loaded pages (edit forms).
|
|
219
|
+
* Uses `findOne` for simple CRUD endpoints or `filter[optionValue]` as fallback.
|
|
220
|
+
*/
|
|
221
|
+
@Input() resolveSelected = true;
|
|
222
|
+
|
|
223
|
+
/** Override filter key used to resolve the selected value (default: last segment of `optionValue`). */
|
|
224
|
+
@Input() resolveSelectedFilterKey = '';
|
|
169
225
|
|
|
170
226
|
// Searchable panel: show inline search input
|
|
171
227
|
@Input() isSearch = true;
|
|
@@ -211,6 +267,7 @@ export class JDropdownSelectComponent
|
|
|
211
267
|
|
|
212
268
|
@ViewChild('selectButton') selectButton!: ElementRef<HTMLElement>;
|
|
213
269
|
@ViewChild('dropdownPanel') dropdownPanel?: ElementRef<HTMLElement>;
|
|
270
|
+
@ViewChild('optionsList') optionsList?: ElementRef<HTMLElement>;
|
|
214
271
|
|
|
215
272
|
isColumnSelectorOpen = false;
|
|
216
273
|
selectedValue: unknown = null;
|
|
@@ -218,6 +275,9 @@ export class JDropdownSelectComponent
|
|
|
218
275
|
internalOptions: DropdownOption[] = [];
|
|
219
276
|
filteredOptions: DropdownOption[] = [];
|
|
220
277
|
|
|
278
|
+
isLoadingMore = false;
|
|
279
|
+
hasMorePages = false;
|
|
280
|
+
|
|
221
281
|
searchTerm = '';
|
|
222
282
|
dropdownWidth = 0;
|
|
223
283
|
|
|
@@ -231,8 +291,14 @@ export class JDropdownSelectComponent
|
|
|
231
291
|
|
|
232
292
|
private readonly searchSubject = new Subject<string>();
|
|
233
293
|
private searchSubscription?: Subscription;
|
|
294
|
+
private apiSubscription?: Subscription;
|
|
234
295
|
private clickOutsideListener?: (event: MouseEvent) => void;
|
|
235
296
|
|
|
297
|
+
private currentPage = 1;
|
|
298
|
+
private totalPages = 1;
|
|
299
|
+
private resolvedSelectedOption: DropdownOption | null = null;
|
|
300
|
+
private isResolvingSelected = false;
|
|
301
|
+
|
|
236
302
|
private onChange: (value: unknown) => void = () => {};
|
|
237
303
|
private onTouched: () => void = () => {};
|
|
238
304
|
|
|
@@ -260,12 +326,12 @@ export class JDropdownSelectComponent
|
|
|
260
326
|
.pipe(debounceTime(1000), distinctUntilChanged())
|
|
261
327
|
.subscribe(() => {
|
|
262
328
|
if (this.type === 'searchable') {
|
|
263
|
-
this.loadData();
|
|
329
|
+
this.loadData(true);
|
|
264
330
|
}
|
|
265
331
|
});
|
|
266
332
|
|
|
267
333
|
if (this.loadOnInit && this.type === 'searchable') {
|
|
268
|
-
this.loadData();
|
|
334
|
+
this.loadData(true);
|
|
269
335
|
}
|
|
270
336
|
|
|
271
337
|
this.updateSelectedLabel();
|
|
@@ -290,6 +356,7 @@ export class JDropdownSelectComponent
|
|
|
290
356
|
}
|
|
291
357
|
|
|
292
358
|
this.searchSubscription?.unsubscribe();
|
|
359
|
+
this.apiSubscription?.unsubscribe();
|
|
293
360
|
}
|
|
294
361
|
|
|
295
362
|
// =====================================================
|
|
@@ -301,15 +368,21 @@ export class JDropdownSelectComponent
|
|
|
301
368
|
* @param value Selected option value or `null`.
|
|
302
369
|
*/
|
|
303
370
|
writeValue(value: unknown): void {
|
|
371
|
+
if (!optionValuesEqual(this.selectedValue, value)) {
|
|
372
|
+
if (
|
|
373
|
+
value == null ||
|
|
374
|
+
!this.resolvedSelectedOption ||
|
|
375
|
+
!optionValuesEqual(this.resolvedSelectedOption.value, value)
|
|
376
|
+
) {
|
|
377
|
+
this.resolvedSelectedOption = null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
304
381
|
this.selectedValue = value;
|
|
382
|
+
this.updateSelectedLabel();
|
|
305
383
|
|
|
306
|
-
if (this.
|
|
307
|
-
this.
|
|
308
|
-
} else {
|
|
309
|
-
setTimeout(() => {
|
|
310
|
-
this.updateSelectedLabel();
|
|
311
|
-
this.cdr.detectChanges();
|
|
312
|
-
});
|
|
384
|
+
if (value != null && this.type === 'searchable' && !this.findSelectedOption()) {
|
|
385
|
+
this.resolveSelectedOption();
|
|
313
386
|
}
|
|
314
387
|
|
|
315
388
|
this.cdr.markForCheck();
|
|
@@ -344,12 +417,7 @@ export class JDropdownSelectComponent
|
|
|
344
417
|
if (this.options?.length > 0 && typeof this.options[0] !== 'object') {
|
|
345
418
|
this.internalOptions = mapPrimitiveOptions(this.options);
|
|
346
419
|
} else if (this.options?.length > 0) {
|
|
347
|
-
this.internalOptions =
|
|
348
|
-
this.options as Record<string, unknown>[],
|
|
349
|
-
this.optionLabel,
|
|
350
|
-
this.optionValue,
|
|
351
|
-
this.labelSeparator,
|
|
352
|
-
);
|
|
420
|
+
this.internalOptions = this.mapOptions(this.options as Record<string, unknown>[]);
|
|
353
421
|
}
|
|
354
422
|
|
|
355
423
|
this.filteredOptions = [...this.internalOptions];
|
|
@@ -357,6 +425,26 @@ export class JDropdownSelectComponent
|
|
|
357
425
|
this.cdr.detectChanges();
|
|
358
426
|
}
|
|
359
427
|
|
|
428
|
+
/** Maps raw API/static objects to normalized panel options. */
|
|
429
|
+
private mapOptions(options: Record<string, unknown>[]): DropdownOption[] {
|
|
430
|
+
const mapped = mapObjectOptions(
|
|
431
|
+
options,
|
|
432
|
+
this.optionLabel,
|
|
433
|
+
this.optionValue,
|
|
434
|
+
this.labelSeparator,
|
|
435
|
+
this.optionLabelFn ? undefined : this.optionLabelTemplate || undefined,
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
if (!this.optionLabelFn) {
|
|
439
|
+
return mapped;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return mapped.map((option) => ({
|
|
443
|
+
...option,
|
|
444
|
+
text: this.optionLabelFn!(option.original as Record<string, unknown>),
|
|
445
|
+
}));
|
|
446
|
+
}
|
|
447
|
+
|
|
360
448
|
/**
|
|
361
449
|
* Selects an option, updates the form model and closes the panel.
|
|
362
450
|
* @param option Normalized option from the panel list.
|
|
@@ -364,6 +452,7 @@ export class JDropdownSelectComponent
|
|
|
364
452
|
selectOption(option: DropdownOption): void {
|
|
365
453
|
this.selectedValue = option.value;
|
|
366
454
|
this.selectedLabel = option.text;
|
|
455
|
+
this.resolvedSelectedOption = option;
|
|
367
456
|
this.onChange(this.selectedValue);
|
|
368
457
|
this.selectionChange.emit(option.original ?? option.value);
|
|
369
458
|
this.closePanel();
|
|
@@ -375,11 +464,21 @@ export class JDropdownSelectComponent
|
|
|
375
464
|
*/
|
|
376
465
|
clearSelection(event: Event): void {
|
|
377
466
|
event.stopPropagation();
|
|
467
|
+
this.resolvedSelectedOption = null;
|
|
378
468
|
this.writeValue(null);
|
|
379
469
|
this.onChange(null);
|
|
380
470
|
this.selectionChange.emit(null);
|
|
381
471
|
}
|
|
382
472
|
|
|
473
|
+
/** Whether an option value matches the current selection. */
|
|
474
|
+
isOptionSelected(value: unknown): boolean {
|
|
475
|
+
return optionValuesEqual(this.selectedValue, value);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
private findSelectedOption(): DropdownOption | undefined {
|
|
479
|
+
return this.internalOptions.find((option) => this.isOptionSelected(option.value));
|
|
480
|
+
}
|
|
481
|
+
|
|
383
482
|
/**
|
|
384
483
|
* Syncs `selectedLabel` with `selectedValue` and `internalOptions`.
|
|
385
484
|
*/
|
|
@@ -389,8 +488,62 @@ export class JDropdownSelectComponent
|
|
|
389
488
|
return;
|
|
390
489
|
}
|
|
391
490
|
|
|
392
|
-
const selectedOption = this.
|
|
393
|
-
|
|
491
|
+
const selectedOption = this.findSelectedOption();
|
|
492
|
+
if (selectedOption) {
|
|
493
|
+
this.selectedLabel = selectedOption.text;
|
|
494
|
+
this.resolvedSelectedOption = selectedOption;
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (
|
|
499
|
+
this.resolvedSelectedOption &&
|
|
500
|
+
optionValuesEqual(this.resolvedSelectedOption.value, this.selectedValue)
|
|
501
|
+
) {
|
|
502
|
+
this.selectedLabel = this.resolvedSelectedOption.text;
|
|
503
|
+
this.ensureSelectedOptionPinned();
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (this.type === 'searchable' && this.resolveSelected) {
|
|
508
|
+
this.resolveSelectedOption();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (!this.selectedLabel || this.selectedLabel === this.placeholder) {
|
|
512
|
+
this.selectedLabel = this.placeholder;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** Keeps a resolved selection visible even if it is not in the current page. */
|
|
517
|
+
private ensureSelectedOptionPinned(): void {
|
|
518
|
+
if (!this.resolvedSelectedOption) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const exists = this.internalOptions.some((option) =>
|
|
523
|
+
optionValuesEqual(option.value, this.resolvedSelectedOption!.value),
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
if (!exists) {
|
|
527
|
+
const leading = this.showAllOption ? [{ value: null, text: 'TODOS' }] : [];
|
|
528
|
+
this.internalOptions = mergeDropdownOptions(
|
|
529
|
+
this.internalOptions,
|
|
530
|
+
[this.resolvedSelectedOption],
|
|
531
|
+
leading,
|
|
532
|
+
);
|
|
533
|
+
this.filteredOptions = [...this.internalOptions];
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private applyResolvedRecord(record: Record<string, unknown>): void {
|
|
538
|
+
const [option] = this.mapOptions([record]);
|
|
539
|
+
if (!option) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
this.resolvedSelectedOption = option;
|
|
544
|
+
this.selectedLabel = option.text;
|
|
545
|
+
this.ensureSelectedOptionPinned();
|
|
546
|
+
this.cdr.detectChanges();
|
|
394
547
|
}
|
|
395
548
|
|
|
396
549
|
/**
|
|
@@ -410,17 +563,209 @@ export class JDropdownSelectComponent
|
|
|
410
563
|
|
|
411
564
|
/**
|
|
412
565
|
* Loads options from the CRUD API (searchable mode).
|
|
566
|
+
* @param reset When `true`, restarts from page 1 and replaces the list.
|
|
413
567
|
*/
|
|
414
|
-
loadData(): void {
|
|
568
|
+
loadData(reset = false): void {
|
|
415
569
|
if (!this.endpoint || !this.genericService) {
|
|
416
570
|
return;
|
|
417
571
|
}
|
|
418
572
|
|
|
419
|
-
|
|
573
|
+
if (reset) {
|
|
574
|
+
this.currentPage = 1;
|
|
575
|
+
this.totalPages = 1;
|
|
576
|
+
this.hasMorePages = false;
|
|
577
|
+
this.apiSubscription?.unsubscribe();
|
|
578
|
+
} else if (!this.hasMorePages || this.isLoadingMore) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (reset) {
|
|
583
|
+
this.isLoading = true;
|
|
584
|
+
} else {
|
|
585
|
+
this.isLoadingMore = true;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const params = this.buildListParams(this.currentPage);
|
|
589
|
+
|
|
590
|
+
this.apiSubscription = this.genericService
|
|
591
|
+
.findAll<Record<string, unknown>>({
|
|
592
|
+
endpoint: this._finalEndpoint,
|
|
593
|
+
params,
|
|
594
|
+
})
|
|
595
|
+
.subscribe({
|
|
596
|
+
next: (response) => {
|
|
597
|
+
const data = extractOptionsFromResponse(
|
|
598
|
+
response.data as Record<string, unknown>,
|
|
599
|
+
this.endpoint,
|
|
600
|
+
this.responseKey || undefined,
|
|
601
|
+
this.dataPath || undefined,
|
|
602
|
+
) as Record<string, unknown>[];
|
|
603
|
+
|
|
604
|
+
const mapped = this.mapOptions(data);
|
|
605
|
+
const pagination = readDropdownPaginationMeta(response.meta);
|
|
606
|
+
this.currentPage = pagination.currentPage;
|
|
607
|
+
this.totalPages = pagination.totalPages;
|
|
608
|
+
this.hasMorePages = this.infiniteScroll && pagination.hasMore;
|
|
609
|
+
|
|
610
|
+
const leading = this.showAllOption ? [{ value: null, text: 'TODOS' as const }] : [];
|
|
611
|
+
|
|
612
|
+
if (reset) {
|
|
613
|
+
this.options = data;
|
|
614
|
+
this.internalOptions = mergeDropdownOptions([], mapped, leading);
|
|
615
|
+
} else {
|
|
616
|
+
this.options = [...(this.options as Record<string, unknown>[]), ...data];
|
|
617
|
+
this.internalOptions = mergeDropdownOptions(this.internalOptions, mapped, leading);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
this.filteredOptions = [...this.internalOptions];
|
|
621
|
+
this.fullData.emit(this.options);
|
|
622
|
+
this.isLoading = false;
|
|
623
|
+
this.isLoadingMore = false;
|
|
624
|
+
this.ensureSelectedOptionPinned();
|
|
625
|
+
this.updateSelectedLabel();
|
|
626
|
+
|
|
627
|
+
if (this.selectedValue != null && !this.findSelectedOption()) {
|
|
628
|
+
this.resolveSelectedOption();
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (this.isColumnSelectorOpen) {
|
|
632
|
+
this.updateDropdownPosition();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
this.cdr.detectChanges();
|
|
636
|
+
},
|
|
637
|
+
error: (error) => {
|
|
638
|
+
console.error('Error fetching data:', error);
|
|
639
|
+
this.isLoading = false;
|
|
640
|
+
this.isLoadingMore = false;
|
|
641
|
+
this.cdr.detectChanges();
|
|
642
|
+
},
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/** Loads the next API page and appends options. */
|
|
647
|
+
loadMore(): void {
|
|
648
|
+
if (
|
|
649
|
+
this.type !== 'searchable' ||
|
|
650
|
+
!this.infiniteScroll ||
|
|
651
|
+
!this.hasMorePages ||
|
|
652
|
+
this.isLoading ||
|
|
653
|
+
this.isLoadingMore
|
|
654
|
+
) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
this.currentPage += 1;
|
|
659
|
+
this.loadData(false);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/** Infinite scroll handler for the options list. */
|
|
663
|
+
onOptionsListScroll(event: Event): void {
|
|
664
|
+
if (this.type !== 'searchable' || !this.infiniteScroll || !this.hasMorePages) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const element = event.target as HTMLElement;
|
|
669
|
+
const threshold = 48;
|
|
420
670
|
|
|
671
|
+
if (element.scrollTop + element.clientHeight >= element.scrollHeight - threshold) {
|
|
672
|
+
this.loadMore();
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/** Fetches the selected row when editing and the value is outside loaded pages. */
|
|
677
|
+
private resolveSelectedOption(): void {
|
|
678
|
+
if (
|
|
679
|
+
!this.resolveSelected ||
|
|
680
|
+
this.selectedValue == null ||
|
|
681
|
+
this.type !== 'searchable' ||
|
|
682
|
+
!this.genericService ||
|
|
683
|
+
!this.endpoint ||
|
|
684
|
+
this.isResolvingSelected ||
|
|
685
|
+
this.findSelectedOption()
|
|
686
|
+
) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
this.isResolvingSelected = true;
|
|
691
|
+
|
|
692
|
+
if (
|
|
693
|
+
canResolveSelectedByFindOne(
|
|
694
|
+
this.endpoint,
|
|
695
|
+
this.dataPath,
|
|
696
|
+
this.responseKey,
|
|
697
|
+
this.selectedValue,
|
|
698
|
+
)
|
|
699
|
+
) {
|
|
700
|
+
this.genericService
|
|
701
|
+
.findOne<Record<string, unknown>>({
|
|
702
|
+
endpoint: this._finalEndpoint,
|
|
703
|
+
id: Number(this.selectedValue),
|
|
704
|
+
})
|
|
705
|
+
.subscribe({
|
|
706
|
+
next: (record) => {
|
|
707
|
+
this.isResolvingSelected = false;
|
|
708
|
+
if (record) {
|
|
709
|
+
this.applyResolvedRecord(record);
|
|
710
|
+
} else {
|
|
711
|
+
this.resolveSelectedByFilter();
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
error: () => {
|
|
715
|
+
this.isResolvingSelected = false;
|
|
716
|
+
this.resolveSelectedByFilter();
|
|
717
|
+
},
|
|
718
|
+
});
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
this.resolveSelectedByFilter();
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
private resolveSelectedByFilter(): void {
|
|
726
|
+
if (!this.genericService || this.selectedValue == null) {
|
|
727
|
+
this.isResolvingSelected = false;
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const filterKey =
|
|
732
|
+
this.resolveSelectedFilterKey.trim() || getOptionValueFilterKey(this.optionValue);
|
|
733
|
+
const params = this.buildListParams(1, {
|
|
734
|
+
[`filter[${filterKey}]`]: this.selectedValue,
|
|
735
|
+
});
|
|
736
|
+
params['limit'] = 1;
|
|
737
|
+
|
|
738
|
+
this.genericService
|
|
739
|
+
.findAll<Record<string, unknown>>({
|
|
740
|
+
endpoint: this._finalEndpoint,
|
|
741
|
+
params,
|
|
742
|
+
})
|
|
743
|
+
.subscribe({
|
|
744
|
+
next: (response) => {
|
|
745
|
+
this.isResolvingSelected = false;
|
|
746
|
+
const data = extractOptionsFromResponse(
|
|
747
|
+
response.data as Record<string, unknown>,
|
|
748
|
+
this.endpoint,
|
|
749
|
+
this.responseKey || undefined,
|
|
750
|
+
this.dataPath || undefined,
|
|
751
|
+
) as Record<string, unknown>[];
|
|
752
|
+
|
|
753
|
+
if (data[0]) {
|
|
754
|
+
this.applyResolvedRecord(data[0]);
|
|
755
|
+
}
|
|
756
|
+
},
|
|
757
|
+
error: () => {
|
|
758
|
+
this.isResolvingSelected = false;
|
|
759
|
+
},
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
private buildListParams(page: number, extra: Record<string, unknown> = {}): Record<string, unknown> {
|
|
421
764
|
const params: Record<string, unknown> = {
|
|
422
765
|
sortOrder: this.sort,
|
|
423
766
|
limit: this.limit,
|
|
767
|
+
page,
|
|
768
|
+
...extra,
|
|
424
769
|
};
|
|
425
770
|
|
|
426
771
|
Object.keys(this.defaultFilters).forEach((key) => {
|
|
@@ -433,45 +778,7 @@ export class JDropdownSelectComponent
|
|
|
433
778
|
params['searchFields'] = [...optionLabels, ...this.searchFields];
|
|
434
779
|
}
|
|
435
780
|
|
|
436
|
-
|
|
437
|
-
endpoint: this._finalEndpoint,
|
|
438
|
-
params,
|
|
439
|
-
}).subscribe({
|
|
440
|
-
next: (response) => {
|
|
441
|
-
const data = (response.data[this.mainEndpoint] as Record<string, unknown>[]) || [];
|
|
442
|
-
this.options = data;
|
|
443
|
-
|
|
444
|
-
if (this.showAllOption) {
|
|
445
|
-
this.internalOptions = [{ value: null, text: 'TODOS' }];
|
|
446
|
-
this.internalOptions.push(
|
|
447
|
-
...mapObjectOptions(data, this.optionLabel, this.optionValue, this.labelSeparator),
|
|
448
|
-
);
|
|
449
|
-
} else {
|
|
450
|
-
this.internalOptions = mapObjectOptions(
|
|
451
|
-
data,
|
|
452
|
-
this.optionLabel,
|
|
453
|
-
this.optionValue,
|
|
454
|
-
this.labelSeparator,
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
this.filteredOptions = [...this.internalOptions];
|
|
459
|
-
this.fullData.emit(this.options);
|
|
460
|
-
this.isLoading = false;
|
|
461
|
-
this.updateSelectedLabel();
|
|
462
|
-
|
|
463
|
-
if (this.isColumnSelectorOpen) {
|
|
464
|
-
this.updateDropdownPosition();
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
this.cdr.detectChanges();
|
|
468
|
-
},
|
|
469
|
-
error: (error) => {
|
|
470
|
-
console.error('Error fetching data:', error);
|
|
471
|
-
this.isLoading = false;
|
|
472
|
-
this.cdr.detectChanges();
|
|
473
|
-
},
|
|
474
|
-
});
|
|
781
|
+
return params;
|
|
475
782
|
}
|
|
476
783
|
|
|
477
784
|
/**
|
|
@@ -513,11 +820,11 @@ export class JDropdownSelectComponent
|
|
|
513
820
|
this.updateDropdownPosition();
|
|
514
821
|
|
|
515
822
|
if (this.type === 'searchable' && this.loadOpen) {
|
|
516
|
-
this.loadData();
|
|
823
|
+
this.loadData(true);
|
|
517
824
|
}
|
|
518
825
|
|
|
519
826
|
if (this.type === 'searchable' && !this.loadOnInit && this.shouldTriggerLoad()) {
|
|
520
|
-
this.loadData();
|
|
827
|
+
this.loadData(true);
|
|
521
828
|
}
|
|
522
829
|
|
|
523
830
|
return;
|