@pega/angular-sdk-overrides 0.26.2 → 0.26.4
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/designSystemExtension/field-group/field-group.component.ts +11 -3
- package/lib/designSystemExtension/material-case-summary/material-case-summary.component.html +12 -3
- package/lib/field/auto-complete/auto-complete.component.html +44 -4
- package/lib/field/auto-complete/auto-complete.component.scss +92 -0
- package/lib/field/auto-complete/auto-complete.component.ts +341 -16
- package/lib/field/cancel-alert/cancel-alert.component.ts +38 -15
- package/lib/field/group/group.component.html +1 -1
- package/lib/field/group/group.component.ts +1 -3
- package/lib/field/semantic-link/semantic-link.component.html +1 -1
- package/lib/field/semantic-link/semantic-link.component.scss +4 -0
- package/lib/infra/Containers/flow-container/flow-container.component.html +1 -1
- package/lib/infra/Containers/modal-view-container/data-view-action-buttons/data-view-action-buttons.component.html +12 -0
- package/lib/infra/Containers/modal-view-container/data-view-action-buttons/data-view-action-buttons.component.spec.ts +130 -0
- package/lib/infra/Containers/modal-view-container/data-view-action-buttons/data-view-action-buttons.component.ts +77 -0
- package/lib/infra/Containers/modal-view-container/modal-view-container.component.html +35 -8
- package/lib/infra/Containers/modal-view-container/modal-view-container.component.ts +179 -184
- package/lib/infra/assignment/assignment.component.html +2 -1
- package/lib/infra/assignment/assignment.component.ts +30 -13
- package/lib/infra/defer-load/defer-load.component.ts +6 -3
- package/lib/infra/multi-step/multi-step.component.html +25 -33
- package/lib/infra/multi-step/multi-step.component.scss +48 -0
- package/lib/infra/multi-step/multi-step.component.ts +11 -0
- package/lib/infra/view/view.component.ts +17 -39
- package/lib/template/base/details-template-base.ts +35 -54
- package/lib/template/case-summary/case-summary.component.ts +15 -1
- package/lib/template/case-view/case-view.component.html +13 -1
- package/lib/template/case-view/case-view.component.spec.ts +119 -10
- package/lib/template/case-view/case-view.component.ts +52 -3
- package/lib/template/data-reference/data-reference.component.ts +27 -18
- package/lib/template/data-reference/search-form/search-form.component.html +1 -1
- package/lib/template/data-reference/search-form/search-form.component.ts +2 -0
- package/lib/template/details/details.component.html +23 -3
- package/lib/template/details/details.component.scss +9 -2
- package/lib/template/details/details.component.ts +3 -32
- package/lib/template/details-narrow-wide/details-narrow-wide.component.html +21 -2
- package/lib/template/details-narrow-wide/details-narrow-wide.component.scss +8 -1
- package/lib/template/details-narrow-wide/details-narrow-wide.component.ts +3 -37
- package/lib/template/details-one-column/details-one-column.component.html +23 -1
- package/lib/template/details-one-column/details-one-column.component.scss +14 -0
- package/lib/template/details-one-column/details-one-column.component.ts +3 -38
- package/lib/template/details-three-column/details-three-column.component.html +21 -2
- package/lib/template/details-three-column/details-three-column.component.scss +8 -1
- package/lib/template/details-three-column/details-three-column.component.ts +3 -46
- package/lib/template/details-two-column/details-two-column.component.html +21 -2
- package/lib/template/details-two-column/details-two-column.component.scss +8 -1
- package/lib/template/details-two-column/details-two-column.component.ts +3 -44
- package/lib/template/details-wide-narrow/details-wide-narrow.component.html +21 -2
- package/lib/template/details-wide-narrow/details-wide-narrow.component.scss +8 -1
- package/lib/template/details-wide-narrow/details-wide-narrow.component.ts +3 -41
- package/lib/template/field-value-list/field-value-list.component.scss +17 -0
- package/lib/template/list-view/list-view.component.ts +39 -0
- package/lib/template/self-service-case-view/self-service-case-view.component.html +13 -1
- package/lib/template/self-service-case-view/self-service-case-view.component.spec.ts +96 -11
- package/lib/template/self-service-case-view/self-service-case-view.component.ts +52 -3
- package/lib/template/simple-table-manual/helpers.ts +27 -6
- package/lib/template/simple-table-manual/simple-table-manual.component.html +1 -3
- package/lib/template/simple-table-manual/simple-table-manual.component.scss +4 -0
- package/lib/template/simple-table-manual/simple-table-manual.component.ts +51 -20
- package/lib/template/single-reference-readonly/single-reference-readonly.component.scss +0 -4
- package/package.json +1 -1
- package/lib/designSystemExtension/material-details/material-details.component.html +0 -40
- package/lib/designSystemExtension/material-details/material-details.component.scss +0 -116
- package/lib/designSystemExtension/material-details/material-details.component.spec.ts +0 -24
- package/lib/designSystemExtension/material-details/material-details.component.ts +0 -31
- package/lib/designSystemExtension/material-details-fields/material-details-fields.component.html +0 -50
- package/lib/designSystemExtension/material-details-fields/material-details-fields.component.scss +0 -34
- package/lib/designSystemExtension/material-details-fields/material-details-fields.component.spec.ts +0 -22
- package/lib/designSystemExtension/material-details-fields/material-details-fields.component.ts +0 -61
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CommonModule } from '@angular/common';
|
|
2
|
-
import { Component, Input, TemplateRef } from '@angular/core';
|
|
2
|
+
import { Component, Input, OnChanges, SimpleChanges, TemplateRef } from '@angular/core';
|
|
3
3
|
import { MatGridListModule } from '@angular/material/grid-list';
|
|
4
4
|
import { MatIconModule } from '@angular/material/icon';
|
|
5
5
|
|
|
@@ -9,14 +9,22 @@ import { MatIconModule } from '@angular/material/icon';
|
|
|
9
9
|
styleUrls: ['./field-group.component.scss'],
|
|
10
10
|
imports: [CommonModule, MatGridListModule, MatIconModule]
|
|
11
11
|
})
|
|
12
|
-
export class FieldGroupComponent {
|
|
12
|
+
export class FieldGroupComponent implements OnChanges {
|
|
13
13
|
@Input() name?: string;
|
|
14
|
-
@Input()
|
|
14
|
+
@Input() collapseOnLoad: 'none' | 'expanded' | 'collapsed' = 'none';
|
|
15
15
|
@Input() instructions: string;
|
|
16
16
|
@Input() childrenTemplate: TemplateRef<any>;
|
|
17
17
|
|
|
18
|
+
collapsible = false;
|
|
18
19
|
collapsed = false;
|
|
19
20
|
|
|
21
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
22
|
+
if (changes['collapseOnLoad']) {
|
|
23
|
+
this.collapsible = this.collapseOnLoad !== 'none';
|
|
24
|
+
this.collapsed = this.collapseOnLoad === 'collapsed';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
headerClickHandler() {
|
|
21
29
|
this.collapsed = !this.collapsed;
|
|
22
30
|
}
|
package/lib/designSystemExtension/material-case-summary/material-case-summary.component.html
CHANGED
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
<a *ngSwitchCase="'email'" class="psdk-primary-color" href="mailto: {{ field.config.value }}">{{ field.config.value }}</a>
|
|
19
19
|
<span *ngSwitchCase="'date'" class="psdk-csf-text-style">{{ field.config.value }}</span>
|
|
20
20
|
<label *ngSwitchCase="'caseoperator'">operator</label>
|
|
21
|
+
<div *ngSwitchCase="'objectreference'" class="psdk-csf-object-reference-value">
|
|
22
|
+
<component-mapper name="ObjectReference" [props]="{ pConn$: field.kid, displayMode: 'DISPLAY_ONLY', hideLabel: true }"></component-mapper>
|
|
23
|
+
</div>
|
|
21
24
|
<span *ngSwitchDefault class="psdk-csf-text-style">{{ field.config.value }}</span>
|
|
22
25
|
</dd>
|
|
23
26
|
</ng-template>
|
|
@@ -46,9 +49,15 @@
|
|
|
46
49
|
{{ field.config?.displayLabel || field.config?.label }}
|
|
47
50
|
</dt>
|
|
48
51
|
<dd class="psdk-csf-secondary-field-data">
|
|
49
|
-
<div [ngSwitch]="field.type">
|
|
50
|
-
<label *ngSwitchCase="'
|
|
51
|
-
<label *ngSwitchCase="'
|
|
52
|
+
<div [ngSwitch]="field.type.toLowerCase()">
|
|
53
|
+
<label *ngSwitchCase="'userreference'" class="psdk-secondary-value">{{ field.config.value.userName || '---' }}</label>
|
|
54
|
+
<label *ngSwitchCase="'checkbox'" class="psdk-secondary-value">{{ field.config.falseLabel || '---' }}</label>
|
|
55
|
+
<div *ngSwitchCase="'objectreference'" class="psdk-csf-object-reference-value">
|
|
56
|
+
<component-mapper
|
|
57
|
+
name="ObjectReference"
|
|
58
|
+
[props]="{ pConn$: field.kid, displayMode: 'DISPLAY_ONLY', hideLabel: true }"
|
|
59
|
+
></component-mapper>
|
|
60
|
+
</div>
|
|
52
61
|
<label *ngSwitchDefault class="psdk-secondary-value">{{ field.config.value || '---' }}</label>
|
|
53
62
|
</div>
|
|
54
63
|
</dd>
|
|
@@ -15,10 +15,50 @@
|
|
|
15
15
|
[matAutocomplete]="auto"
|
|
16
16
|
(input)="fieldOnChange($event)"
|
|
17
17
|
/>
|
|
18
|
-
<mat-autocomplete #auto="matAutocomplete" autoActiveFirstOption (optionSelected)="optionChanged($event)">
|
|
19
|
-
<
|
|
20
|
-
<
|
|
21
|
-
|
|
18
|
+
<mat-autocomplete #auto="matAutocomplete" [class]="'psdk-autocomplete-panel'" autoActiveFirstOption (optionSelected)="optionChanged($event)">
|
|
19
|
+
<ng-container *ngIf="hasGroupBy; else flatOptionsList">
|
|
20
|
+
<mat-optgroup *ngFor="let group of groupedFilteredOptions$ | async" [label]="group.label">
|
|
21
|
+
<mat-option *ngFor="let opt of group.options" [value]="opt.value">
|
|
22
|
+
<span class="psdk-autocomplete-option-primary">{{ opt.value }}</span>
|
|
23
|
+
<div class="psdk-autocomplete-option-secondary" *ngIf="opt.secondaryComponents?.length">
|
|
24
|
+
<ng-container *ngFor="let secondary of opt.secondaryComponents; let last = last">
|
|
25
|
+
<component-mapper
|
|
26
|
+
[name]="secondary.getPConnect().getComponentName()"
|
|
27
|
+
[props]="{ pConn$: secondary.getPConnect(), formGroup$ }"
|
|
28
|
+
></component-mapper>
|
|
29
|
+
<span *ngIf="!last" class="psdk-autocomplete-option-separator" aria-hidden="true"> · </span>
|
|
30
|
+
</ng-container>
|
|
31
|
+
</div>
|
|
32
|
+
</mat-option>
|
|
33
|
+
</mat-optgroup>
|
|
34
|
+
</ng-container>
|
|
35
|
+
<ng-template #flatOptionsList>
|
|
36
|
+
<mat-option *ngFor="let opt of filteredOptions | async" [value]="opt.value">
|
|
37
|
+
<span class="psdk-autocomplete-option-primary">{{ opt.value }}</span>
|
|
38
|
+
<div class="psdk-autocomplete-option-secondary" *ngIf="opt.secondaryComponents?.length">
|
|
39
|
+
<ng-container *ngFor="let secondary of opt.secondaryComponents; let last = last">
|
|
40
|
+
<component-mapper
|
|
41
|
+
[name]="secondary.getPConnect().getComponentName()"
|
|
42
|
+
[props]="{ pConn$: secondary.getPConnect(), formGroup$ }"
|
|
43
|
+
></component-mapper>
|
|
44
|
+
<span *ngIf="!last" class="psdk-autocomplete-option-separator" aria-hidden="true"> · </span>
|
|
45
|
+
</ng-container>
|
|
46
|
+
</div>
|
|
47
|
+
</mat-option>
|
|
48
|
+
</ng-template>
|
|
49
|
+
<div *ngIf="showCreateButton" class="psdk-autocomplete-create-new-wrapper">
|
|
50
|
+
<mat-divider></mat-divider>
|
|
51
|
+
<button
|
|
52
|
+
mat-button
|
|
53
|
+
type="button"
|
|
54
|
+
class="psdk-autocomplete-create-new"
|
|
55
|
+
(mousedown)="$event.preventDefault()"
|
|
56
|
+
(click)="createNewButtonHandler()"
|
|
57
|
+
>
|
|
58
|
+
<mat-icon>add</mat-icon>
|
|
59
|
+
{{ createNewLabel }}
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
22
62
|
</mat-autocomplete>
|
|
23
63
|
<mat-hint *ngIf="helperText" [appFieldWarning]="bFieldMessageVisible$">{{ helperText }}</mat-hint>
|
|
24
64
|
<mat-error *ngIf="fieldControl.invalid">{{ getErrorMessage() }}</mat-error>
|
|
@@ -18,3 +18,95 @@
|
|
|
18
18
|
::ng-deep .mat-mdc-form-field-infix {
|
|
19
19
|
width: auto;
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
// Scoped to this component's own panel via mat-autocomplete's [class] input — does not affect
|
|
23
|
+
// other mat-option/mat-autocomplete instances in the app (research.md §3).
|
|
24
|
+
::ng-deep .psdk-autocomplete-panel {
|
|
25
|
+
overflow-x: hidden;
|
|
26
|
+
|
|
27
|
+
.mat-mdc-option {
|
|
28
|
+
.mdc-list-item__primary-text {
|
|
29
|
+
width: 100%;
|
|
30
|
+
white-space: normal;
|
|
31
|
+
overflow: visible;
|
|
32
|
+
text-overflow: unset;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Only grow/top-align options that actually render secondary content — leaves options
|
|
36
|
+
// without secondary text at Material's default single-line height/vertical centering.
|
|
37
|
+
&:has(.psdk-autocomplete-option-secondary) {
|
|
38
|
+
height: auto;
|
|
39
|
+
min-height: 48px;
|
|
40
|
+
align-items: flex-start;
|
|
41
|
+
padding-top: 8px;
|
|
42
|
+
padding-bottom: 8px;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.psdk-autocomplete-option-secondary {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-wrap: wrap;
|
|
50
|
+
align-items: baseline;
|
|
51
|
+
column-gap: 0.25rem;
|
|
52
|
+
width: 100%;
|
|
53
|
+
overflow-wrap: break-word;
|
|
54
|
+
word-break: break-word;
|
|
55
|
+
color: var(--mat-sys-on-surface-variant);
|
|
56
|
+
|
|
57
|
+
// Each secondary field renders through FieldValueList's own DISPLAY_ONLY template, which
|
|
58
|
+
// defaults to a two-column grid block (.psdk-container-labels-left) or an unset-display
|
|
59
|
+
// block (.psdk-container-nolabels). Override both to render inline here so multiple fields
|
|
60
|
+
// join on one line via the `·` separators, instead of stacking as separate block rows
|
|
61
|
+
// (FR-002/FR-004). FieldValueListComponent itself is not modified (research.md §6).
|
|
62
|
+
::ng-deep .psdk-container-labels-left {
|
|
63
|
+
display: inline-flex;
|
|
64
|
+
column-gap: 0.25rem;
|
|
65
|
+
row-gap: 0;
|
|
66
|
+
padding-block: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
::ng-deep .psdk-container-nolabels {
|
|
70
|
+
display: inline;
|
|
71
|
+
padding-block: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
::ng-deep .psdk-grid-label::after {
|
|
75
|
+
content: ':';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
::ng-deep .psdk-val-labels-left {
|
|
79
|
+
white-space: normal;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
::ng-deep .psdk-autocomplete-create-new-wrapper {
|
|
84
|
+
position: sticky;
|
|
85
|
+
bottom: 0;
|
|
86
|
+
z-index: 1;
|
|
87
|
+
background-color: var(--mat-sys-surface-container, var(--mat-sys-surface, #fff));
|
|
88
|
+
|
|
89
|
+
.mat-divider {
|
|
90
|
+
margin: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.psdk-autocomplete-create-new {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
justify-content: flex-start;
|
|
97
|
+
width: 100%;
|
|
98
|
+
height: 48px;
|
|
99
|
+
padding: 0 16px;
|
|
100
|
+
text-align: left;
|
|
101
|
+
text-transform: none;
|
|
102
|
+
color: var(--mat-sys-primary);
|
|
103
|
+
|
|
104
|
+
.mat-mdc-button-touch-target {
|
|
105
|
+
width: 100%;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.mat-icon {
|
|
109
|
+
margin-right: 8px;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { Component, EventEmitter, OnInit, Output, forwardRef, inject } from '@angular/core';
|
|
1
|
+
import { Component, EventEmitter, OnInit, Output, ViewChild, forwardRef, inject } from '@angular/core';
|
|
2
2
|
import { CommonModule } from '@angular/common';
|
|
3
3
|
import { ReactiveFormsModule } from '@angular/forms';
|
|
4
4
|
import { MatOptionModule } from '@angular/material/core';
|
|
5
|
-
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
|
5
|
+
import { MatAutocompleteModule, MatAutocompleteTrigger } from '@angular/material/autocomplete';
|
|
6
6
|
import { MatInputModule } from '@angular/material/input';
|
|
7
7
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
8
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
9
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
10
|
+
import { MatDividerModule } from '@angular/material/divider';
|
|
8
11
|
import { Observable } from 'rxjs';
|
|
9
12
|
import { map, startWith } from 'rxjs/operators';
|
|
10
13
|
|
|
@@ -15,10 +18,22 @@ import { DatapageService } from '@pega/angular-sdk-components';
|
|
|
15
18
|
import { handleEvent } from '@pega/angular-sdk-components';
|
|
16
19
|
import { PConnFieldProps } from '@pega/angular-sdk-components';
|
|
17
20
|
|
|
18
|
-
interface
|
|
21
|
+
interface AutoCompleteOption {
|
|
19
22
|
key: string;
|
|
20
23
|
value: string;
|
|
24
|
+
// Present only when at least one secondary column resolves to a non-empty value
|
|
25
|
+
secondaryComponents?: any[];
|
|
26
|
+
secondarySearchText?: string;
|
|
27
|
+
// Present only when a group-by field is configured for this (datapage-sourced) field
|
|
28
|
+
group?: string;
|
|
21
29
|
}
|
|
30
|
+
|
|
31
|
+
// Internal, render-time-only view-model — never part of the PConnect contract
|
|
32
|
+
interface AutoCompleteGroup {
|
|
33
|
+
label: string;
|
|
34
|
+
options: AutoCompleteOption[];
|
|
35
|
+
}
|
|
36
|
+
|
|
22
37
|
interface AutoCompleteProps extends PConnFieldProps {
|
|
23
38
|
// If any, enter additional props that only exist on AutoComplete here
|
|
24
39
|
deferDatasource?: boolean;
|
|
@@ -29,6 +44,12 @@ interface AutoCompleteProps extends PConnFieldProps {
|
|
|
29
44
|
parameters?: any;
|
|
30
45
|
datasource: any;
|
|
31
46
|
columns: any[];
|
|
47
|
+
allowCreatingRecords?: boolean;
|
|
48
|
+
onCreateNew?: () => void;
|
|
49
|
+
createNewLabel?: string;
|
|
50
|
+
createNewRecord?: () => Promise<unknown>;
|
|
51
|
+
contextClass?: string;
|
|
52
|
+
referenceType?: string;
|
|
32
53
|
}
|
|
33
54
|
|
|
34
55
|
@Component({
|
|
@@ -42,6 +63,9 @@ interface AutoCompleteProps extends PConnFieldProps {
|
|
|
42
63
|
MatInputModule,
|
|
43
64
|
MatAutocompleteModule,
|
|
44
65
|
MatOptionModule,
|
|
66
|
+
MatButtonModule,
|
|
67
|
+
MatIconModule,
|
|
68
|
+
MatDividerModule,
|
|
45
69
|
FieldWarningDirective,
|
|
46
70
|
forwardRef(() => ComponentMapperComponent)
|
|
47
71
|
],
|
|
@@ -52,15 +76,29 @@ export class AutoCompleteComponent extends FieldBase implements OnInit {
|
|
|
52
76
|
|
|
53
77
|
@Output() onRecordChange: EventEmitter<any> = new EventEmitter();
|
|
54
78
|
|
|
79
|
+
@ViewChild(MatAutocompleteTrigger) private autocompleteTrigger?: MatAutocompleteTrigger;
|
|
80
|
+
|
|
55
81
|
configProps$: AutoCompleteProps;
|
|
56
82
|
|
|
57
|
-
options$:
|
|
83
|
+
options$: AutoCompleteOption[];
|
|
58
84
|
listType: string;
|
|
59
|
-
columns = [];
|
|
85
|
+
columns: any[] = [];
|
|
60
86
|
parameters: {};
|
|
61
|
-
|
|
87
|
+
datasource: any;
|
|
88
|
+
filteredOptions: Observable<AutoCompleteOption[]>;
|
|
89
|
+
// Grouped view of filteredOptions, only rendered when hasGroupBy is true
|
|
90
|
+
groupedFilteredOptions$: Observable<AutoCompleteGroup[]>;
|
|
91
|
+
hasGroupBy = false;
|
|
62
92
|
filterValue = '';
|
|
63
93
|
|
|
94
|
+
// "Create new" footer button state
|
|
95
|
+
showCreateButton = false;
|
|
96
|
+
createNewLabel = 'Create new';
|
|
97
|
+
contextClass?: string;
|
|
98
|
+
referenceType?: string;
|
|
99
|
+
private onCreateNewFn?: () => void;
|
|
100
|
+
private createNewRecordFn?: () => Promise<unknown>;
|
|
101
|
+
|
|
64
102
|
// Override ngOnInit method
|
|
65
103
|
override async ngOnInit(): Promise<void> {
|
|
66
104
|
super.ngOnInit();
|
|
@@ -69,18 +107,36 @@ export class AutoCompleteComponent extends FieldBase implements OnInit {
|
|
|
69
107
|
startWith(''),
|
|
70
108
|
map(value => this._filter((value as string) || ''))
|
|
71
109
|
);
|
|
110
|
+
|
|
111
|
+
this.groupedFilteredOptions$ = this.filteredOptions.pipe(map(options => this.buildGroups(options)));
|
|
72
112
|
}
|
|
73
113
|
|
|
74
|
-
setOptions(options:
|
|
114
|
+
setOptions(options: AutoCompleteOption[]) {
|
|
75
115
|
this.options$ = options;
|
|
76
116
|
const index = this.options$?.findIndex(element => element.key === this.configProps$.value);
|
|
77
117
|
this.value$ = index > -1 ? this.options$[index].value : this.configProps$.value;
|
|
78
118
|
this.fieldControl.setValue(this.value$);
|
|
79
119
|
}
|
|
80
120
|
|
|
81
|
-
|
|
121
|
+
// Matches only primary text and secondary search text — group value is never used for search
|
|
122
|
+
private _filter(value: string): AutoCompleteOption[] {
|
|
82
123
|
const filterVal = (value || this.filterValue).toLowerCase();
|
|
83
|
-
return this.options$?.filter(option => option.value?.toLowerCase().includes(filterVal));
|
|
124
|
+
return this.options$?.filter(option => option.value?.toLowerCase().includes(filterVal) || option.secondarySearchText?.includes(filterVal));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Buckets the already-sorted option list into contiguous groups by exact group value
|
|
128
|
+
buildGroups(options: AutoCompleteOption[]): AutoCompleteGroup[] {
|
|
129
|
+
const groups: AutoCompleteGroup[] = [];
|
|
130
|
+
options?.forEach(option => {
|
|
131
|
+
const label = option.group ?? '';
|
|
132
|
+
const lastGroup = groups[groups.length - 1];
|
|
133
|
+
if (lastGroup && lastGroup.label === label) {
|
|
134
|
+
lastGroup.options.push(option);
|
|
135
|
+
} else {
|
|
136
|
+
groups.push({ label, options: [option] });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return groups;
|
|
84
140
|
}
|
|
85
141
|
|
|
86
142
|
/**
|
|
@@ -94,18 +150,29 @@ export class AutoCompleteComponent extends FieldBase implements OnInit {
|
|
|
94
150
|
this.updateComponentCommonProperties(this.configProps$);
|
|
95
151
|
|
|
96
152
|
// Set component specific properties
|
|
97
|
-
const { value, listType, parameters } =
|
|
153
|
+
const { value, listType, parameters, allowCreatingRecords, onCreateNew, createNewLabel, createNewRecord, contextClass, referenceType } =
|
|
154
|
+
this.configProps$;
|
|
98
155
|
|
|
99
156
|
this.listType = listType;
|
|
100
157
|
this.parameters = parameters;
|
|
101
158
|
|
|
159
|
+
this.showCreateButton = allowCreatingRecords === true;
|
|
160
|
+
this.createNewLabel = createNewLabel || 'Create new';
|
|
161
|
+
this.contextClass = contextClass;
|
|
162
|
+
this.referenceType = referenceType;
|
|
163
|
+
this.onCreateNewFn = onCreateNew;
|
|
164
|
+
this.createNewRecordFn = createNewRecord;
|
|
165
|
+
|
|
102
166
|
const context = this.pConn$.getContextName();
|
|
103
167
|
const { columns, datasource } = this.generateColumnsAndDataSource();
|
|
168
|
+
this.datasource = datasource;
|
|
104
169
|
|
|
105
170
|
if (columns) {
|
|
106
171
|
this.columns = this.preProcessColumns(columns);
|
|
107
172
|
}
|
|
108
173
|
|
|
174
|
+
this.hasGroupBy = this.columns?.some(col => col.groupBy === 'true') ?? false;
|
|
175
|
+
|
|
109
176
|
if (this.listType === 'associated') {
|
|
110
177
|
const optionsList = this.utils.getOptionList(this.configProps$, this.pConn$.getDataObject('')); // 1st arg empty string until typedef marked correctly
|
|
111
178
|
this.setOptions(optionsList);
|
|
@@ -152,22 +219,157 @@ export class AutoCompleteComponent extends FieldBase implements OnInit {
|
|
|
152
219
|
];
|
|
153
220
|
}
|
|
154
221
|
|
|
222
|
+
// Secondary text and grouping are both out of scope for associated/local list options
|
|
223
|
+
if (this.listType !== 'associated') {
|
|
224
|
+
const secondaryColumns = this.getSecondaryColumnsFromMetadata();
|
|
225
|
+
if (secondaryColumns.length > 0) {
|
|
226
|
+
columns = [...(columns || []), ...secondaryColumns];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const groupByColumns = this.getGroupByColumnsFromMetadata();
|
|
230
|
+
if (groupByColumns.length > 0) {
|
|
231
|
+
columns = [...(columns || []), ...groupByColumns];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
155
235
|
return { columns, datasource };
|
|
156
236
|
}
|
|
157
237
|
|
|
238
|
+
// Reads unresolved groupsFields metadata to derive group-by column descriptor(s); not a
|
|
239
|
+
// display/search column, so grouping stays independent of primary/secondary text
|
|
240
|
+
getGroupByColumnsFromMetadata() {
|
|
241
|
+
const groupsFields = (this.pConn$.getRawMetadata()?.config as any)?.groupsFields;
|
|
242
|
+
if (!Array.isArray(groupsFields)) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
return this.mapMetadataColumns(groupsFields, { display: 'false', groupBy: 'true', useForSearch: false });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Reads unresolved columnsFormatter metadata to derive secondary (contextual) display columns
|
|
249
|
+
getSecondaryColumnsFromMetadata() {
|
|
250
|
+
const columnsFormatter = (this.pConn$.getRawMetadata()?.config as any)?.columnsFormatter;
|
|
251
|
+
if (!Array.isArray(columnsFormatter)) {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
return this.mapMetadataColumns(columnsFormatter, { display: 'true', secondary: 'true', useForSearch: true });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Shared by getSecondaryColumnsFromMetadata/getGroupByColumnsFromMetadata: value must stay an
|
|
258
|
+
// unresolved property reference (e.g. "@P .propName") for use as a raw-row lookup key
|
|
259
|
+
mapMetadataColumns(rawColumns: any[], columnFlags: object): any[] {
|
|
260
|
+
return rawColumns
|
|
261
|
+
.map(item => {
|
|
262
|
+
const property = item?.config?.value;
|
|
263
|
+
if (typeof property !== 'string' || !property) {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
let value = property;
|
|
267
|
+
if (property.startsWith('@P ')) {
|
|
268
|
+
value = property.substring(3);
|
|
269
|
+
} else if (property.startsWith('@USER ')) {
|
|
270
|
+
value = property.substring(6);
|
|
271
|
+
}
|
|
272
|
+
return { value, type: item?.type, label: item?.config?.label, ...columnFlags };
|
|
273
|
+
})
|
|
274
|
+
.filter(Boolean);
|
|
275
|
+
}
|
|
276
|
+
|
|
158
277
|
fillOptions(results: any) {
|
|
159
|
-
const optionsData:
|
|
278
|
+
const optionsData: AutoCompleteOption[] = [];
|
|
160
279
|
const displayColumn = this.getDisplayFieldsMetaData(this.columns);
|
|
280
|
+
const secondaryColumns = this.columns?.filter(col => col.display === 'true' && col.secondary === 'true') || [];
|
|
281
|
+
const groupByColumn = this.columns?.find(col => col.groupBy === 'true');
|
|
282
|
+
|
|
161
283
|
results?.forEach(element => {
|
|
162
|
-
const obj = {
|
|
284
|
+
const obj: AutoCompleteOption = {
|
|
163
285
|
key: element[displayColumn.key] || element.pyGUID,
|
|
164
286
|
value: element[displayColumn.primary]?.toString()
|
|
165
287
|
};
|
|
288
|
+
|
|
289
|
+
if (secondaryColumns.length > 0) {
|
|
290
|
+
const secondaryComponents = this.buildSecondaryComponents(element, secondaryColumns);
|
|
291
|
+
if (secondaryComponents.length > 0) {
|
|
292
|
+
obj.secondaryComponents = secondaryComponents;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const secondarySearchText = this.buildSecondarySearchText(element, secondaryColumns);
|
|
296
|
+
if (secondarySearchText) {
|
|
297
|
+
obj.secondarySearchText = secondarySearchText;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (groupByColumn) {
|
|
302
|
+
obj.group = this.resolveGroupValue(element[groupByColumn.value as string]);
|
|
303
|
+
}
|
|
304
|
+
|
|
166
305
|
optionsData.push(obj);
|
|
167
306
|
});
|
|
307
|
+
|
|
308
|
+
if (groupByColumn) {
|
|
309
|
+
this.sortByGroup(optionsData);
|
|
310
|
+
}
|
|
311
|
+
|
|
168
312
|
this.setOptions(optionsData);
|
|
169
313
|
}
|
|
170
314
|
|
|
315
|
+
// Null/undefined/whitespace-only source values normalize to '' — the shared blank group
|
|
316
|
+
resolveGroupValue(rawValue: any): string {
|
|
317
|
+
if (rawValue === null || rawValue === undefined) {
|
|
318
|
+
return '';
|
|
319
|
+
}
|
|
320
|
+
const stringValue = rawValue.toString();
|
|
321
|
+
return stringValue.trim() ? stringValue : '';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Ascending, case-sensitive, stable sort so same-group options keep their original relative order
|
|
325
|
+
sortByGroup(options: AutoCompleteOption[]): void {
|
|
326
|
+
options.sort((a, b) => {
|
|
327
|
+
const groupA = a.group ?? '';
|
|
328
|
+
const groupB = b.group ?? '';
|
|
329
|
+
if (groupA < groupB) {
|
|
330
|
+
return -1;
|
|
331
|
+
}
|
|
332
|
+
if (groupA > groupB) {
|
|
333
|
+
return 1;
|
|
334
|
+
}
|
|
335
|
+
return 0;
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Rendering only — one read-only PConnect component per configured secondary field, in
|
|
340
|
+
// configured order, regardless of whether its value is empty (FieldValueList's own
|
|
341
|
+
// empty-value fallback renders the placeholder, e.g. "Label: ---"). Mirrors ScalarListComponent's
|
|
342
|
+
// createComponent/DISPLAY_ONLY pattern.
|
|
343
|
+
buildSecondaryComponents(element: any, secondaryColumns): any[] {
|
|
344
|
+
return secondaryColumns.map(col =>
|
|
345
|
+
this.pConn$.createComponent(
|
|
346
|
+
{
|
|
347
|
+
type: col.type,
|
|
348
|
+
config: {
|
|
349
|
+
value: element[col.value as string],
|
|
350
|
+
displayMode: 'DISPLAY_ONLY',
|
|
351
|
+
readOnly: true,
|
|
352
|
+
label: col.label
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
'',
|
|
356
|
+
0,
|
|
357
|
+
{}
|
|
358
|
+
)
|
|
359
|
+
); // 2nd, 3rd, and 4th args empty string/object/null until typedef marked correctly as optional
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Search only — independent of buildSecondaryComponents; never derived from rendered output.
|
|
363
|
+
buildSecondarySearchText(element: any, secondaryColumns): string {
|
|
364
|
+
return secondaryColumns
|
|
365
|
+
.map(col => {
|
|
366
|
+
const rawValue = element[col.value as string];
|
|
367
|
+
return rawValue === null || rawValue === undefined ? '' : rawValue.toString().trim().toLowerCase();
|
|
368
|
+
})
|
|
369
|
+
.filter(Boolean)
|
|
370
|
+
.join(' ');
|
|
371
|
+
}
|
|
372
|
+
|
|
171
373
|
flattenParameters(params = {}) {
|
|
172
374
|
const flatParams = {};
|
|
173
375
|
Object.keys(params).forEach(key => {
|
|
@@ -182,12 +384,12 @@ export class AutoCompleteComponent extends FieldBase implements OnInit {
|
|
|
182
384
|
const displayColumns = columnList.filter(col => col.display === 'true');
|
|
183
385
|
const metaDataObj: any = { key: '', primary: '', secondary: [] };
|
|
184
386
|
const keyCol = columnList.filter(col => col.key === 'true');
|
|
185
|
-
metaDataObj.key = keyCol.length > 0 ? keyCol[0].value : 'auto';
|
|
387
|
+
metaDataObj.key = keyCol.length > 0 ? (keyCol[0].value ?? 'auto') : 'auto';
|
|
186
388
|
for (let index = 0; index < displayColumns.length; index += 1) {
|
|
187
389
|
if (displayColumns[index].primary === 'true') {
|
|
188
|
-
metaDataObj.primary = displayColumns[index].value;
|
|
189
|
-
} else {
|
|
190
|
-
metaDataObj.secondary.push(displayColumns[index].value);
|
|
390
|
+
metaDataObj.primary = displayColumns[index].value ?? '';
|
|
391
|
+
} else if (displayColumns[index].secondary === 'true') {
|
|
392
|
+
metaDataObj.secondary.push(displayColumns[index].value ?? '');
|
|
191
393
|
}
|
|
192
394
|
}
|
|
193
395
|
return metaDataObj;
|
|
@@ -222,4 +424,127 @@ export class AutoCompleteComponent extends FieldBase implements OnInit {
|
|
|
222
424
|
this.onRecordChange.emit(value);
|
|
223
425
|
}
|
|
224
426
|
}
|
|
427
|
+
|
|
428
|
+
// Re-fetches the options list
|
|
429
|
+
refreshOptionsList(): void {
|
|
430
|
+
if (!this.displayMode$ && this.listType !== 'associated') {
|
|
431
|
+
const context = this.pConn$.getContextName();
|
|
432
|
+
this.dataPageService
|
|
433
|
+
.getDataPageData(this.datasource, this.parameters, context)
|
|
434
|
+
.then((results: any) => this.fillOptions(results))
|
|
435
|
+
.catch(e => console.error(e));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Sets values for all columns that have setProperty defined
|
|
440
|
+
setValuesToAdditionalFields(record: Record<string, unknown>): void {
|
|
441
|
+
const setPropertyList = this.columns.filter(col => col.setProperty).map(col => ({ source: col.value, target: col.setProperty, key: col.key }));
|
|
442
|
+
|
|
443
|
+
setPropertyList.forEach(prop => {
|
|
444
|
+
let valueToSet: string;
|
|
445
|
+
if (prop.key === 'true') {
|
|
446
|
+
valueToSet = record[prop.source]?.toString() || (record as any).pyGUID || '';
|
|
447
|
+
} else {
|
|
448
|
+
valueToSet = record[prop.source]?.toString() || '';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (prop.target === 'Associated property') {
|
|
452
|
+
handleEvent(this.actionsApi, 'changeNblur', this.propName, valueToSet);
|
|
453
|
+
} else {
|
|
454
|
+
const target = typeof prop.target === 'string' ? prop.target : '';
|
|
455
|
+
const targetProp = target.startsWith('.') ? target : `.${target}`;
|
|
456
|
+
(this.actionsApi as any).updateFieldValue(targetProp, valueToSet, { associatedProperty: this.propName });
|
|
457
|
+
(this.actionsApi as any).triggerFieldChange(targetProp, valueToSet);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
createNewButtonHandler(): void {
|
|
463
|
+
// Close the options panel so it doesn't remain open on top of the create-new modal
|
|
464
|
+
this.autocompleteTrigger?.closePanel();
|
|
465
|
+
|
|
466
|
+
if (this.onCreateNewFn) {
|
|
467
|
+
this.onCreateNewFn();
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (!this.contextClass) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const context = this.pConn$.getContextName();
|
|
476
|
+
const normalizedReferenceType = typeof this.referenceType === 'string' ? this.referenceType.toLowerCase() : '';
|
|
477
|
+
const isDataReference = normalizedReferenceType === 'data';
|
|
478
|
+
const { CREATE_STAGE_DONE } = PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS;
|
|
479
|
+
const DATA_OBJECT_CREATED = (PCore.getConstants().PUB_SUB_EVENTS as any).DATA_EVENTS?.DATA_OBJECT_CREATED;
|
|
480
|
+
const eventType = isDataReference && DATA_OBJECT_CREATED ? DATA_OBJECT_CREATED : CREATE_STAGE_DONE;
|
|
481
|
+
const contextClass = this.contextClass;
|
|
482
|
+
|
|
483
|
+
const createNewCallback = isDataReference
|
|
484
|
+
? (data: { data?: { responseData?: Record<string, unknown> } }) => {
|
|
485
|
+
// Clear contexted cache before re-fetching
|
|
486
|
+
PCore.getDataApi().clearContextedCache(context);
|
|
487
|
+
|
|
488
|
+
const responseData = data?.data?.responseData;
|
|
489
|
+
if (responseData) {
|
|
490
|
+
this.setValuesToAdditionalFields(responseData);
|
|
491
|
+
const displayColumn = this.getDisplayFieldsMetaData(this.columns);
|
|
492
|
+
const newKey = responseData[displayColumn.key]?.toString() || (responseData as any).pyGUID;
|
|
493
|
+
if (this.onRecordChange && newKey) {
|
|
494
|
+
this.onRecordChange.emit({ id: newKey });
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
this.refreshOptionsList();
|
|
499
|
+
PCore.getPubSubUtils().unsubscribe(eventType, contextClass);
|
|
500
|
+
}
|
|
501
|
+
: (data: { caseId?: string; caseType?: string; ID?: string }) => {
|
|
502
|
+
// Clear contexted cache before re-fetching
|
|
503
|
+
PCore.getDataApi().clearContextedCache(context);
|
|
504
|
+
|
|
505
|
+
const newCaseId = data.caseId?.split(' ').pop();
|
|
506
|
+
if (data.caseType === contextClass) {
|
|
507
|
+
const selectKey = data.ID || newCaseId;
|
|
508
|
+
|
|
509
|
+
if (selectKey && this.listType !== 'associated' && this.datasource) {
|
|
510
|
+
this.dataPageService
|
|
511
|
+
.getDataPageData(this.datasource, this.parameters, context)
|
|
512
|
+
.then((results: any) => {
|
|
513
|
+
this.fillOptions(results);
|
|
514
|
+
|
|
515
|
+
const displayColumn = this.getDisplayFieldsMetaData(this.columns);
|
|
516
|
+
const newRecord = results?.find((el: any) => el.ID === data.ID || (el[displayColumn.key] || el.pyGUID) === selectKey);
|
|
517
|
+
if (newRecord) {
|
|
518
|
+
this.setValuesToAdditionalFields(newRecord);
|
|
519
|
+
} else {
|
|
520
|
+
handleEvent(this.actionsApi, 'changeNblur', this.propName, selectKey);
|
|
521
|
+
}
|
|
522
|
+
if (this.onRecordChange) {
|
|
523
|
+
this.onRecordChange.emit({ id: selectKey });
|
|
524
|
+
}
|
|
525
|
+
})
|
|
526
|
+
.catch(e => console.error(e));
|
|
527
|
+
}
|
|
528
|
+
PCore.getPubSubUtils().unsubscribe(eventType, contextClass);
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// Build the create action if createNewRecord fn is not provided
|
|
533
|
+
const triggerCreate = this.createNewRecordFn
|
|
534
|
+
? this.createNewRecordFn()
|
|
535
|
+
: isDataReference
|
|
536
|
+
? this.pConn$.getActionsApi().showDataObjectCreateView(contextClass)
|
|
537
|
+
: this.pConn$.getActionsApi().createWork(contextClass, {
|
|
538
|
+
openCaseViewAfterCreate: false,
|
|
539
|
+
startingFields: {}
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
Promise.resolve(triggerCreate)
|
|
543
|
+
.then(() => {
|
|
544
|
+
PCore.getPubSubUtils().subscribe(eventType, createNewCallback, contextClass);
|
|
545
|
+
// Re-initialize the list
|
|
546
|
+
this.refreshOptionsList();
|
|
547
|
+
})
|
|
548
|
+
.catch(e => console.error(e));
|
|
549
|
+
}
|
|
225
550
|
}
|