diff --git a/packages/angular-sdk-components/src/lib/_bridge/helpers/sdk-pega-component-map.ts b/packages/angular-sdk-components/src/lib/_bridge/helpers/sdk-pega-component-map.ts index cab9ac15..e77cbf97 100644 --- a/packages/angular-sdk-components/src/lib/_bridge/helpers/sdk-pega-component-map.ts +++ b/packages/angular-sdk-components/src/lib/_bridge/helpers/sdk-pega-component-map.ts @@ -31,6 +31,7 @@ import { GroupComponent } from '../../_components/field/group/group.component'; import { IntegerComponent } from '../../_components/field/integer/integer.component'; import { ListViewActionButtonsComponent } from '../../_components/field/list-view-action-buttons/list-view-action-buttons.component'; import { LocationComponent } from '../../_components/field/location/location.component'; +import { ObjectReferenceComponent } from '../../_components/field/object-reference/object-reference.component'; import { PercentageComponent } from '../../_components/field/percentage/percentage.component'; import { PhoneComponent } from '../../_components/field/phone/phone.component'; import { RadioButtonsComponent } from '../../_components/field/radio-buttons/radio-buttons.component'; @@ -74,6 +75,7 @@ import { ListViewComponent } from '../../_components/template/list-view/list-vie import { MultiReferenceReadonlyComponent } from '../../_components/template/multi-reference-readonly/multi-reference-readonly.component'; import { MultiselectComponent } from '../../_components/field/multiselect/multiselect.component'; import { NarrowWideFormComponent } from '../../_components/template/narrow-wide-form/narrow-wide-form.component'; +import { ObjectPageComponent } from '../../_components/template/object-page/object-page.component'; import { OneColumnComponent } from '../../_components/template/one-column/one-column.component'; import { OneColumnPageComponent } from '../../_components/template/one-column-page/one-column-page.component'; import { OneColumnTabComponent } from '../../_components/template/one-column-tab/one-column-tab.component'; @@ -200,6 +202,8 @@ const pegaSdkComponentMap = { NarrowWideForm: NarrowWideFormComponent, // 'NarrowWidePage': NarrowWidePage, NavBar: NavbarComponent, + ObjectPage: ObjectPageComponent, + ObjectReference: ObjectReferenceComponent, OneColumn: OneColumnComponent, OneColumnPage: OneColumnPageComponent, OneColumnTab: OneColumnTabComponent, diff --git a/packages/angular-sdk-components/src/lib/_components/field/auto-complete/auto-complete.component.ts b/packages/angular-sdk-components/src/lib/_components/field/auto-complete/auto-complete.component.ts index db9799e3..5530967a 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/auto-complete/auto-complete.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/auto-complete/auto-complete.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy, Output, EventEmitter } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { MatOptionModule } from '@angular/material/core'; @@ -47,6 +47,7 @@ interface AutoCompleteProps extends PConnFieldProps { export class AutoCompleteComponent implements OnInit, OnDestroy { @Input() pConn$: typeof PConnect; @Input() formGroup$: FormGroup; + @Output() onRecordChange: EventEmitter = new EventEmitter(); // Used with AngularPConnect angularPConnectData: AngularPConnectData = {}; @@ -331,8 +332,9 @@ export class AutoCompleteComponent implements OnInit, OnDestroy { } const value = key; handleEvent(this.actionsApi, 'changeNblur', this.propName, value); - if (this.configProps$?.onRecordChange) { - this.configProps$.onRecordChange(event); + + if (this.onRecordChange) { + this.onRecordChange.emit(value); } } diff --git a/packages/angular-sdk-components/src/lib/_components/field/dropdown/dropdown.component.ts b/packages/angular-sdk-components/src/lib/_components/field/dropdown/dropdown.component.ts index f40af442..70c9037d 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/dropdown/dropdown.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/dropdown/dropdown.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core'; +import { Component, OnInit, Input, ChangeDetectorRef, forwardRef, OnDestroy, EventEmitter, Output } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { MatOptionModule } from '@angular/material/core'; @@ -73,6 +73,7 @@ interface DropdownProps extends PConnFieldProps { export class DropdownComponent implements OnInit, OnDestroy { @Input() pConn$: typeof PConnect; @Input() formGroup$: FormGroup; + @Output() onRecordChange: EventEmitter = new EventEmitter(); // Used with AngularPConnect angularPConnectData: AngularPConnectData = {}; @@ -336,12 +337,13 @@ export class DropdownComponent implements OnInit, OnDestroy { event.value = ''; } handleEvent(this.actionsApi, 'changeNblur', this.propName, event.value); - if (this.configProps$?.onRecordChange) { - this.configProps$.onRecordChange(event); - } + this.pConn$.clearErrorMessages({ property: this.propName }); + if (this.onRecordChange) { + this.onRecordChange.emit(event.value); + } } getLocalizedOptionValue(opt: IOption) { diff --git a/packages/angular-sdk-components/src/lib/_components/field/object-reference/object-reference.component.html b/packages/angular-sdk-components/src/lib/_components/field/object-reference/object-reference.component.html new file mode 100644 index 00000000..17cf877d --- /dev/null +++ b/packages/angular-sdk-components/src/lib/_components/field/object-reference/object-reference.component.html @@ -0,0 +1,17 @@ +
+ +
+ +
+ +
+
+ + + diff --git a/packages/angular-sdk-components/src/lib/_components/field/object-reference/object-reference.component.scss b/packages/angular-sdk-components/src/lib/_components/field/object-reference/object-reference.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/angular-sdk-components/src/lib/_components/field/object-reference/object-reference.component.spec.ts b/packages/angular-sdk-components/src/lib/_components/field/object-reference/object-reference.component.spec.ts new file mode 100644 index 00000000..c1a049ab --- /dev/null +++ b/packages/angular-sdk-components/src/lib/_components/field/object-reference/object-reference.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ObjectReferenceComponent } from './object-reference.component'; + +describe('ObjectReferenceComponent', () => { + let component: ObjectReferenceComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ObjectReferenceComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(ObjectReferenceComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/packages/angular-sdk-components/src/lib/_components/field/object-reference/object-reference.component.ts b/packages/angular-sdk-components/src/lib/_components/field/object-reference/object-reference.component.ts new file mode 100644 index 00000000..fb98a182 --- /dev/null +++ b/packages/angular-sdk-components/src/lib/_components/field/object-reference/object-reference.component.ts @@ -0,0 +1,256 @@ +import { CommonModule } from '@angular/common'; +import { Component, Input, OnInit, forwardRef, OnDestroy } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { ComponentMetadataConfig } from '@pega/pcore-pconnect-typedefs/interpreter/types'; +import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; +import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; +import { generateColumns, getDataRelationshipContextFromKey } from '../../../_helpers/objectReference-utils'; +import { PConnFieldProps } from '../../../_types/PConnProps.interface'; + +interface ObjectReferenceProps extends PConnFieldProps { + showPromotedFilters: boolean; + inline: boolean; + parameters: Object; + mode: string; + targetObjectType: any; + allowAndPersistChangesInReviewMode: boolean; +} + +@Component({ + selector: 'app-object-reference', + imports: [CommonModule, forwardRef(() => ComponentMapperComponent)], + templateUrl: './object-reference.component.html', + styleUrl: './object-reference.component.scss' +}) +export class ObjectReferenceComponent implements OnInit, OnDestroy { + @Input() pConn$: typeof PConnect; + @Input() formGroup$: FormGroup; + + angularPConnectData: AngularPConnectData = {}; + configProps: ObjectReferenceProps; + value: { [key: string]: any }; + readOnly: boolean; + isForm: boolean; + type: string; + isDisplayModeEnabled: boolean; + canBeChangedInReviewMode: boolean; + newComponentName: string; + newPconn: typeof PConnect; + rawViewMetadata: ComponentMetadataConfig | undefined; + + constructor(private angularPConnect: AngularPConnectService) {} + + ngOnInit() { + this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); + this.checkAndUpdate(); + } + + onStateChange() { + this.checkAndUpdate(); + } + + ngOnDestroy() { + if (this.angularPConnectData.unsubscribeFn) { + this.angularPConnectData.unsubscribeFn(); + } + } + + checkAndUpdate() { + const shouldUpdate = this.angularPConnect.shouldComponentUpdate(this); + if (shouldUpdate) { + this.updateSelf(); + } + } + + updateSelf() { + this.configProps = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as ObjectReferenceProps; + const displayMode = this.configProps.displayMode; + const editableInReview = this.configProps.allowAndPersistChangesInReviewMode ?? false; + const targetObjectType = this.configProps.targetObjectType; + const mode = this.configProps.mode; + const parameters = this.configProps.parameters; + const hideLabel = this.configProps.hideLabel; + const inline = this.configProps.inline; + const showPromotedFilters = this.configProps.showPromotedFilters; + const referenceType: string = targetObjectType === 'case' ? 'Case' : 'Data'; + this.rawViewMetadata = this.pConn$.getRawMetadata(); + const refFieldMetadata = this.pConn$.getFieldMetadata(this.rawViewMetadata?.config?.value?.split('.', 2)[1] ?? ''); + + // Destructured properties + const propsToUse = { ...this.pConn$.getInheritedProps(), ...this.configProps }; + + // Computed variables + this.isDisplayModeEnabled = displayMode === 'DISPLAY_ONLY'; + this.canBeChangedInReviewMode = editableInReview && ['Autocomplete', 'Dropdown'].includes((this.rawViewMetadata?.config as any)?.componentType); + // componentType is not defined in ComponentMetadataConfig type so using any + this.type = (this.rawViewMetadata?.config as any)?.componentType; + + if (this.type === 'SemanticLink' && !this.canBeChangedInReviewMode) { + const config: any = { + ...this.rawViewMetadata?.config, + primaryField: (this.rawViewMetadata?.config as any).displayField + }; + config.caseClass = (this.rawViewMetadata?.config as any).targetObjectClass; + config.text = config.primaryField; + config.caseID = config.value; + config.contextPage = `@P .${ + (this.rawViewMetadata?.config as any)?.displayField + ? getDataRelationshipContextFromKey((this.rawViewMetadata?.config as any).displayField) + : null + }`; + config.resourceParams = { + workID: config.value + }; + config.resourcePayload = { + caseClassName: config.caseClass + }; + + const component = this.pConn$.createComponent( + { + type: 'SemanticLink', + config: { + ...config, + displayMode, + referenceType, + hideLabel, + dataRelationshipContext: (this.rawViewMetadata?.config as any)?.displayField + ? getDataRelationshipContextFromKey((this.rawViewMetadata?.config as any).displayField) + : null + } + }, + '', + 0, + {} + ); + this.newPconn = component?.getPConnect(); + } + + if (this.type !== 'SemanticLink' && !this.isDisplayModeEnabled) { + // 1) Set datasource + const config: any = { ...this.rawViewMetadata?.config }; + generateColumns(config, this.pConn$, referenceType); + config.deferDatasource = true; + config.listType = 'datapage'; + if (['Dropdown', 'AutoComplete'].includes(this.type) && !config.placeholder) { + config.placeholder = '@L Select...'; + } + + // 2) Pass through configs + config.showPromotedFilters = showPromotedFilters; + + if (!this.canBeChangedInReviewMode) { + config.displayMode = displayMode; + } + + // 3) Define field meta + + const fieldMetaData = { + datasourceMetadata: { + datasource: { + parameters: {}, + propertyForDisplayText: false, + propertyForValue: false, + name: '' + } + } + }; + if (config?.parameters) { + fieldMetaData.datasourceMetadata.datasource.parameters = parameters; + } + fieldMetaData.datasourceMetadata.datasource.propertyForDisplayText = config?.datasource?.fields?.text?.startsWith('@P') + ? config?.datasource?.fields?.text?.substring(3) + : config?.datasource?.fields?.text; + fieldMetaData.datasourceMetadata.datasource.propertyForValue = config?.datasource?.fields?.value?.startsWith('@P') + ? config?.datasource?.fields?.value?.substring(3) + : config?.datasource?.fields?.value; + fieldMetaData.datasourceMetadata.datasource.name = config?.referenceList ?? ''; + + const component = this.pConn$.createComponent( + { + type: this.type, + config: { + ...config, + descriptors: mode === 'single' ? refFieldMetadata?.descriptors : null, + datasourceMetadata: fieldMetaData?.datasourceMetadata, + required: propsToUse.required, + visibility: propsToUse.visibility, + disabled: propsToUse.disabled, + label: propsToUse.label, + parameters: config.parameters, + readOnly: false, + localeReference: config.localeReference, + ...(mode === 'single' ? { referenceType } : ''), + contextClass: config.targetObjectClass, + primaryField: config?.displayField, + dataRelationshipContext: config?.displayField ? getDataRelationshipContextFromKey(config.displayField) : null, + hideLabel, + inline + } + }, + '', + 0, + {} + ); + this.newComponentName = component?.getPConnect().getComponentName(); + this.newPconn = component?.getPConnect(); + if (this.rawViewMetadata?.config) { + this.rawViewMetadata.config = config ? { ...config } : this.rawViewMetadata.config; + } + } + } + + onRecordChange(value) { + const caseKey = this.pConn$.getCaseInfo().getKey() ?? ''; + const refreshOptions = { autoDetectRefresh: true, propertyName: '' }; + refreshOptions.propertyName = this.rawViewMetadata?.config?.value ?? ''; + + if (!this.canBeChangedInReviewMode || !this.pConn$.getValue('__currentPageTabViewName')) { + const pgRef = this.pConn$.getPageReference().replace('caseInfo.content', '') ?? ''; + const viewName = this.rawViewMetadata?.name; + if (viewName && viewName.length > 0) { + getPConnect().getActionsApi().refreshCaseView(caseKey, viewName, pgRef, refreshOptions); + } + } + + const propValue = value; + const propName = + this.rawViewMetadata?.type === 'SimpleTableSelect' && this.configProps.mode === 'multi' + ? PCore.getAnnotationUtils().getPropertyName(this.rawViewMetadata?.config?.selectionList ?? '') + : PCore.getAnnotationUtils().getPropertyName(this.rawViewMetadata?.config?.value ?? ''); + + if (propValue && this.canBeChangedInReviewMode && this.isDisplayModeEnabled) { + PCore.getCaseUtils() + .getCaseEditLock(caseKey, '') + .then(caseResponse => { + const pageTokens = this.pConn$.getPageReference().replace('caseInfo.content', '').split('.'); + let curr = {}; + const commitData = curr; + + pageTokens?.forEach(el => { + if (el !== '') { + curr[el] = {}; + curr = curr[el]; + } + }); + + // expecting format like {Customer: {pyID:"C-100"}} + const propArr = propName.split('.'); + propArr.forEach((element, idx) => { + if (idx + 1 === propArr.length) { + curr[element] = propValue; + } else { + curr[element] = {}; + curr = curr[element]; + } + }); + + PCore.getCaseUtils() + .updateCaseEditFieldsData(caseKey, { [caseKey]: commitData }, caseResponse.headers.etag, this.pConn$?.getContextName() ?? '') + .then(response => { + PCore.getContainerUtils().updateParentLastUpdateTime(this.pConn$.getContextName() ?? '', response.data.data.caseInfo.lastUpdateTime); + PCore.getContainerUtils().updateRelatedContextEtag(this.pConn$.getContextName() ?? '', response.headers.etag); + }); + }); + } + } +} diff --git a/packages/angular-sdk-components/src/lib/_components/field/selectable-card/selectable-card.component.ts b/packages/angular-sdk-components/src/lib/_components/field/selectable-card/selectable-card.component.ts index 0b3ef2fd..e2b83c37 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/selectable-card/selectable-card.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/selectable-card/selectable-card.component.ts @@ -179,7 +179,7 @@ export class SelectableCardComponent implements OnInit, OnDestroy { this.selectionList = this.configProps$.selectionList; this.selectedvalues = this.configProps$.readonlyContextList; - this.showNoValue = this.readOnly && this.selectedvalues.length === 0; // not used + this.showNoValue = this.readOnly && this.selectedvalues?.length === 0; // not used this.primaryField = this.configProps$.primaryField; } diff --git a/packages/angular-sdk-components/src/lib/_components/field/semantic-link/semantic-link.component.html b/packages/angular-sdk-components/src/lib/_components/field/semantic-link/semantic-link.component.html index db7ea4b1..fb7f9983 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/semantic-link/semantic-link.component.html +++ b/packages/angular-sdk-components/src/lib/_components/field/semantic-link/semantic-link.component.html @@ -1,10 +1,7 @@ -
- +
+
{{ label$ }}
+ {{ value$ || '---' }}
- - -
-
{{ label$ }}
-
{{ value$ || '---' }}
-
+ +
{{ value$ || '---' }}
diff --git a/packages/angular-sdk-components/src/lib/_components/field/semantic-link/semantic-link.component.scss b/packages/angular-sdk-components/src/lib/_components/field/semantic-link/semantic-link.component.scss index 7abd63e0..87dcf72d 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/semantic-link/semantic-link.component.scss +++ b/packages/angular-sdk-components/src/lib/_components/field/semantic-link/semantic-link.component.scss @@ -6,6 +6,11 @@ align-items: start; } +.psdk-container-nolabels { + align-items: start; + padding-block: 8px; +} + .psdk-label { color: var(--app-label-color); margin: 8px 0px; diff --git a/packages/angular-sdk-components/src/lib/_components/field/semantic-link/semantic-link.component.ts b/packages/angular-sdk-components/src/lib/_components/field/semantic-link/semantic-link.component.ts index e8b089d8..561e6bcb 100644 --- a/packages/angular-sdk-components/src/lib/_components/field/semantic-link/semantic-link.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/field/semantic-link/semantic-link.component.ts @@ -1,21 +1,27 @@ -import { Component, OnInit, Input, forwardRef, OnDestroy } from '@angular/core'; +import { Component, OnInit, Input, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormGroup } from '@angular/forms'; import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; -import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; +import { getDataReferenceInfo, isLinkTextEmpty } from '../../../_helpers/semanticLink-utils'; import { Utils } from '../../../_helpers/utils'; import { PConnFieldProps } from '../../../_types/PConnProps.interface'; interface SemanticLinkProps extends PConnFieldProps { // If any, enter additional props that only exist on SemanticLink here text: string; + resourcePayload: any; + resourceParams: any; + previewKey: string; + referenceType: string; + dataRelationshipContext: string; + contextPage: any; } @Component({ selector: 'app-semantic-link', templateUrl: './semantic-link.component.html', styleUrls: ['./semantic-link.component.scss'], - imports: [CommonModule, forwardRef(() => ComponentMapperComponent)] + imports: [CommonModule] }) export class SemanticLinkComponent implements OnInit, OnDestroy { @Input() pConn$: typeof PConnect; @@ -28,6 +34,15 @@ export class SemanticLinkComponent implements OnInit, OnDestroy { value$ = ''; displayMode$?: string = ''; bVisible$ = true; + linkURL = ''; + dataResourcePayLoad: any; + referenceType: string; + shouldTreatAsDataReference: boolean; + previewKey: string; + resourcePayload: any = {}; + payload: object; + dataViewName = ''; + isLinkTextEmpty = false; constructor( private angularPConnect: AngularPConnectService, @@ -37,7 +52,7 @@ export class SemanticLinkComponent implements OnInit, OnDestroy { ngOnInit(): void { // First thing in initialization is registering and subscribing to the AngularPConnect service this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); - this.updateSelf(); + this.checkAndUpdate(); } ngOnDestroy(): void { @@ -47,6 +62,10 @@ export class SemanticLinkComponent implements OnInit, OnDestroy { } onStateChange() { + this.updateSelf(); + } + + checkAndUpdate() { // Should always check the bridge to see if the component should // update itself (re-render) const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); @@ -59,11 +78,135 @@ export class SemanticLinkComponent implements OnInit, OnDestroy { updateSelf() { this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as SemanticLinkProps; - this.value$ = this.configProps$.text || '---'; + this.value$ = this.configProps$.text ? this.configProps$.text : this.configProps$.value || ''; this.displayMode$ = this.configProps$.displayMode; this.label$ = this.configProps$.label; if (this.configProps$.visibility) { this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility); } + const { resourceParams = {}, dataRelationshipContext = null, contextPage } = this.configProps$; + this.referenceType = this.configProps$.referenceType; + this.previewKey = this.configProps$.previewKey; + this.resourcePayload = this.configProps$.resourcePayload ?? {}; + const { ACTION_OPENWORKBYHANDLE, ACTION_SHOWDATA, ACTION_GETOBJECT } = PCore.getSemanticUrlUtils().getActions() as any; + this.dataResourcePayLoad = this.resourcePayload?.resourceType === 'DATA' ? this.resourcePayload : null; + const { + RESOURCE_TYPES: { DATA }, + WORKCLASS, + CASE_INFO: { CASE_INFO_CLASSID } + } = PCore.getConstants(); + + this.payload = {}; + let isData = false; + this.shouldTreatAsDataReference = !this.previewKey && this.resourcePayload?.caseClassName; + if (contextPage?.classID) { + this.resourcePayload.caseClassName = contextPage.classID; + } + /* TODO : In case of duplicate search case the classID is Work- need to set it to + the current case class ID */ + if (this.resourcePayload.caseClassName === WORKCLASS) { + this.resourcePayload.caseClassName = this.pConn$.getValue(CASE_INFO_CLASSID); + } + + if ((this.referenceType && this.referenceType.toUpperCase() === DATA) || this.shouldTreatAsDataReference) { + try { + isData = true; + const dataRefContext = getDataReferenceInfo(this.pConn$, dataRelationshipContext, contextPage); + this.dataViewName = dataRefContext.dataContext ?? ''; + this.payload = dataRefContext.dataContextParameters ?? {}; + } catch (error) { + console.log('Error in getting the data reference info', error); + } + } else if (this.resourcePayload && this.resourcePayload.resourceType === 'DATA') { + isData = true; + this.dataViewName = PCore.getDataTypeUtils().getLookUpDataPage(this.resourcePayload.className); + const lookUpDataPageInfo: any = PCore.getDataTypeUtils().getLookUpDataPageInfo(this.resourcePayload.className); + const { content } = this.resourcePayload; + if (lookUpDataPageInfo) { + const { parameters } = lookUpDataPageInfo; + this.payload = Object.keys(parameters).reduce((acc, param) => { + const paramValue = parameters[param]; + return { + ...acc, + [param]: PCore.getAnnotationUtils().isProperty(paramValue) ? content[PCore.getAnnotationUtils().getPropertyName(paramValue)] : paramValue + }; + }, {}); + } else { + const keysInfo = PCore.getDataTypeUtils().getDataPageKeys(this.dataViewName) ?? []; + this.payload = keysInfo.reduce((acc, curr) => { + return { + ...acc, + [curr.keyName]: content[curr.isAlternateKeyStorage ? curr.linkedField : curr.keyName] + }; + }, {}); + } + } + + if (isData && this.dataViewName && this.payload) { + this.linkURL = PCore.getSemanticUrlUtils().getResolvedSemanticURL( + ACTION_SHOWDATA, + { pageName: 'pyDetails', dataViewName: this.dataViewName }, + { + ...this.payload + } + ); + } else { + // BUG-678282 fix to handle scenario when workID was not populated. + // Check renderParentLink in Caseview / CasePreview. comment from constellation + const isObjectType = (PCore.getCaseUtils() as any).isObjectCaseType(this.resourcePayload.caseClassName); + resourceParams[isObjectType ? 'objectID' : 'workID'] = + resourceParams.workID === '' && typeof this.previewKey === 'string' ? this.previewKey.split(' ')[1] : resourceParams.workID; + if (this.previewKey) { + resourceParams.id = this.previewKey; + } + + this.linkURL = PCore.getSemanticUrlUtils().getResolvedSemanticURL( + isObjectType ? ACTION_GETOBJECT : ACTION_OPENWORKBYHANDLE, + this.resourcePayload, + resourceParams + ); + } + this.isLinkTextEmpty = isLinkTextEmpty(this.value$); + } + + showDataAction() { + if (this.dataResourcePayLoad && this.dataResourcePayLoad.resourceType === 'DATA') { + const { content } = this.dataResourcePayLoad; + const lookUpDataPageInfo = PCore.getDataTypeUtils().getLookUpDataPageInfo(this.dataResourcePayLoad?.className); + const lookUpDataPage = PCore.getDataTypeUtils().getLookUpDataPage(this.dataResourcePayLoad?.className); + if (lookUpDataPageInfo) { + const { parameters } = lookUpDataPageInfo as any; + this.payload = Object.keys(parameters).reduce((acc, param) => { + const paramValue = parameters[param]; + return { + ...acc, + [param]: PCore.getAnnotationUtils().isProperty(paramValue) ? content[PCore.getAnnotationUtils().getPropertyName(paramValue)] : paramValue + }; + }, {}); + } + this.pConn$.getActionsApi().showData('pyDetails', lookUpDataPage, { + ...this.payload + }); + } + if ((this.referenceType && this.referenceType.toUpperCase() === 'DATA') || this.shouldTreatAsDataReference) { + this.pConn$.getActionsApi().showData('pyDetails', this.dataViewName, { + ...this.payload + }); + } + } + + openLinkClick(e) { + if (!e.metaKey && !e.ctrlKey) { + e.preventDefault(); + if ( + (this.dataResourcePayLoad && this.dataResourcePayLoad.resourceType === 'DATA') || + (this.referenceType && this.referenceType.toUpperCase() === 'DATA') || + this.shouldTreatAsDataReference + ) { + this.showDataAction(); + } else if (this.previewKey) { + this.pConn$.getActionsApi().openWorkByHandle(this.previewKey, this.resourcePayload.caseClassName); + } + } } } diff --git a/packages/angular-sdk-components/src/lib/_components/infra/Containers/flow-container/flow-container.component.ts b/packages/angular-sdk-components/src/lib/_components/infra/Containers/flow-container/flow-container.component.ts index 55a7aec9..c9bffdf3 100644 --- a/packages/angular-sdk-components/src/lib/_components/infra/Containers/flow-container/flow-container.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/infra/Containers/flow-container/flow-container.component.ts @@ -202,9 +202,9 @@ export class FlowContainerComponent extends FlowContainerBaseComponent implement const kid = this.pConn$.getChildren()[0]; const todoKid = kid.getPConnect().getChildren()[0]; - this.todo_pConn$ = todoKid.getPConnect(); + this.todo_pConn$ = todoKid?.getPConnect(); - return true; + return !!this.todo_pConn$; } return !(caseViewMode && caseViewMode === 'perform'); diff --git a/packages/angular-sdk-components/src/lib/_components/infra/defer-load/defer-load.component.ts b/packages/angular-sdk-components/src/lib/_components/infra/defer-load/defer-load.component.ts index 72f39d5a..1644ec7f 100644 --- a/packages/angular-sdk-components/src/lib/_components/infra/defer-load/defer-load.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/infra/defer-load/defer-load.component.ts @@ -97,7 +97,7 @@ export class DeferLoadComponent implements OnInit, OnDestroy, OnChanges { getViewOptions = () => ({ viewContext: this.resourceType, - pageClass: this.loadViewCaseID ? '' : this.pConn$.getDataObject().pyPortal.classID, + pageClass: this.loadViewCaseID ? '' : this.pConn$.getDataObject()?.pyPortal?.classID, container: this.isContainerPreview ? 'preview' : undefined, containerName: this.isContainerPreview ? 'preview' : undefined, updateData: this.isContainerPreview diff --git a/packages/angular-sdk-components/src/lib/_components/template/case-view/case-view.component.html b/packages/angular-sdk-components/src/lib/_components/template/case-view/case-view.component.html index 82f3cfcd..11528cda 100644 --- a/packages/angular-sdk-components/src/lib/_components/template/case-view/case-view.component.html +++ b/packages/angular-sdk-components/src/lib/_components/template/case-view/case-view.component.html @@ -1,7 +1,7 @@
- +
diff --git a/packages/angular-sdk-components/src/lib/_components/template/case-view/case-view.component.scss b/packages/angular-sdk-components/src/lib/_components/template/case-view/case-view.component.scss index cc69b85c..00295907 100644 --- a/packages/angular-sdk-components/src/lib/_components/template/case-view/case-view.component.scss +++ b/packages/angular-sdk-components/src/lib/_components/template/case-view/case-view.component.scss @@ -105,3 +105,9 @@ h1 { button { margin: 0rem 0.3125rem; } + +.psdk-case-view-toolbar-row { + padding-left: 1rem; + white-space: normal; + height: auto; +} diff --git a/packages/angular-sdk-components/src/lib/_components/template/data-reference/data-reference.component.html b/packages/angular-sdk-components/src/lib/_components/template/data-reference/data-reference.component.html index b0271939..44661228 100644 --- a/packages/angular-sdk-components/src/lib/_components/template/data-reference/data-reference.component.html +++ b/packages/angular-sdk-components/src/lib/_components/template/data-reference/data-reference.component.html @@ -2,17 +2,15 @@
-
-
-
- -
-
-
- -
-
- +
+ +
+
+ +
+
+
+
diff --git a/packages/angular-sdk-components/src/lib/_components/template/data-reference/data-reference.component.ts b/packages/angular-sdk-components/src/lib/_components/template/data-reference/data-reference.component.ts index f07954ff..f992bf7c 100644 --- a/packages/angular-sdk-components/src/lib/_components/template/data-reference/data-reference.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/template/data-reference/data-reference.component.ts @@ -54,6 +54,8 @@ export class DataReferenceComponent implements OnInit, OnDestroy { showAdvancedSearch: boolean; pyID: any; allowImplicitRefresh: any; + displayChild = false; + dataRelationshipContext: any; constructor( private angularPConnect: AngularPConnectService, @@ -135,6 +137,8 @@ export class DataReferenceComponent implements OnInit, OnDestroy { this.viewName = this.rawViewMetadata.name; this.firstChildMeta = this.rawViewMetadata.children[0]; this.refList = this.rawViewMetadata.config.referenceList; + this.dataRelationshipContext = + this.rawViewMetadata.config.contextClass && this.rawViewMetadata.config.name ? this.rawViewMetadata.config.name : null; this.canBeChangedInReviewMode = theConfigProps.allowAndPersistChangesInReviewMode && (displayAs === 'autocomplete' || displayAs === 'dropdown'); // this.childrenToRender = this.children; this.isDisplayModeEnabled = ['DISPLAY_ONLY', 'STACKED_LARGE_VAL'].includes(displayMode); @@ -177,6 +181,7 @@ export class DataReferenceComponent implements OnInit, OnDestroy { } this.generateChildrenToRender(); + this.displayChild = !(this.displaySingleRef || this.displayMultiRef); } } diff --git a/packages/angular-sdk-components/src/lib/_components/template/object-page/object-page.component.html b/packages/angular-sdk-components/src/lib/_components/template/object-page/object-page.component.html new file mode 100644 index 00000000..86ab3c10 --- /dev/null +++ b/packages/angular-sdk-components/src/lib/_components/template/object-page/object-page.component.html @@ -0,0 +1 @@ + diff --git a/packages/angular-sdk-components/src/lib/_components/template/object-page/object-page.component.scss b/packages/angular-sdk-components/src/lib/_components/template/object-page/object-page.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/angular-sdk-components/src/lib/_components/template/object-page/object-page.component.spec.ts b/packages/angular-sdk-components/src/lib/_components/template/object-page/object-page.component.spec.ts new file mode 100644 index 00000000..f9405f6c --- /dev/null +++ b/packages/angular-sdk-components/src/lib/_components/template/object-page/object-page.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ObjectPageComponent } from './object-page.component'; + +describe('ObjectPageComponent', () => { + let component: ObjectPageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ObjectPageComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(ObjectPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/packages/angular-sdk-components/src/lib/_components/template/object-page/object-page.component.ts b/packages/angular-sdk-components/src/lib/_components/template/object-page/object-page.component.ts new file mode 100644 index 00000000..fc18ca55 --- /dev/null +++ b/packages/angular-sdk-components/src/lib/_components/template/object-page/object-page.component.ts @@ -0,0 +1,14 @@ +import { Component, forwardRef, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; + +@Component({ + selector: 'lib-object-page', + imports: [forwardRef(() => ComponentMapperComponent)], + templateUrl: './object-page.component.html', + styleUrl: './object-page.component.scss' +}) +export class ObjectPageComponent { + @Input() pConn$: typeof PConnect; + @Input() formGroup$: FormGroup; +} diff --git a/packages/angular-sdk-components/src/lib/_components/template/simple-table-manual/simple-table-manual.component.ts b/packages/angular-sdk-components/src/lib/_components/template/simple-table-manual/simple-table-manual.component.ts index 8081e3e6..228930e8 100644 --- a/packages/angular-sdk-components/src/lib/_components/template/simple-table-manual/simple-table-manual.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/template/simple-table-manual/simple-table-manual.component.ts @@ -921,7 +921,7 @@ export class SimpleTableManualComponent implements OnInit, OnDestroy { const refKeys: string[] = inColKey.split('.'); let valBuilder = inRowData; for (const key of refKeys) { - valBuilder = valBuilder[key]; + valBuilder = valBuilder ?? valBuilder[key]; } return valBuilder; } diff --git a/packages/angular-sdk-components/src/lib/_components/template/single-reference-readonly/single-reference-readonly.component.html b/packages/angular-sdk-components/src/lib/_components/template/single-reference-readonly/single-reference-readonly.component.html index fdeb4e31..b3960fd6 100644 --- a/packages/angular-sdk-components/src/lib/_components/template/single-reference-readonly/single-reference-readonly.component.html +++ b/packages/angular-sdk-components/src/lib/_components/template/single-reference-readonly/single-reference-readonly.component.html @@ -1 +1 @@ - + diff --git a/packages/angular-sdk-components/src/lib/_components/template/single-reference-readonly/single-reference-readonly.component.ts b/packages/angular-sdk-components/src/lib/_components/template/single-reference-readonly/single-reference-readonly.component.ts index 253f6ea0..53d463f9 100644 --- a/packages/angular-sdk-components/src/lib/_components/template/single-reference-readonly/single-reference-readonly.component.ts +++ b/packages/angular-sdk-components/src/lib/_components/template/single-reference-readonly/single-reference-readonly.component.ts @@ -1,14 +1,108 @@ -import { Component, Input, forwardRef } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Component, Input, forwardRef, OnInit, OnDestroy } from '@angular/core'; import { FormGroup } from '@angular/forms'; +import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect'; import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component'; +import { getDataRelationshipContextFromKey } from '../../../_helpers/objectReference-utils'; @Component({ selector: 'app-single-reference-readonly', templateUrl: './single-reference-readonly.component.html', styleUrls: ['./single-reference-readonly.component.scss'], - imports: [forwardRef(() => ComponentMapperComponent)] + imports: [CommonModule, forwardRef(() => ComponentMapperComponent)] }) -export class SingleReferenceReadonlyComponent { +export class SingleReferenceReadonlyComponent implements OnInit, OnDestroy { @Input() pConn$: typeof PConnect; @Input() formGroup$: FormGroup; + @Input() dataRelationshipContext?: any; + + angularPConnectData: AngularPConnectData = {}; + configProps: any; + component: any; + label: string; + newPconn: typeof PConnect; + + constructor(private angularPConnect: AngularPConnectService) {} + + ngOnInit(): void { + // First thing in initialization is registering and subscribing to the AngularPConnect service + this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange); + this.checkAndUpdate(); + } + + ngOnDestroy(): void { + if (this.angularPConnectData.unsubscribeFn) { + this.angularPConnectData.unsubscribeFn(); + } + } + + // Callback passed when subscribing to store change + onStateChange() { + this.checkAndUpdate(); + } + + checkAndUpdate() { + // Should always check the bridge to see if the component should + // update itself (re-render) + const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this); + + // ONLY call updateSelf when the component should update + if (bUpdateSelf) { + this.updateSelf(); + } + } + + updateSelf() { + this.configProps = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()); + const rawViewMetadata = this.pConn$.getRawMetadata(); + const propsToUse = { ...this.pConn$.getInheritedProps(), ...this.configProps }; + const type = (rawViewMetadata?.config as any)?.componentType; + const displayMode = this.configProps.displayMode; + const targetObjectType = this.configProps.targetObjectType; + const referenceType = targetObjectType === 'case' ? 'Case' : 'Data'; + const hideLabel = this.configProps.hideLabel; + // const additionalFields = this.configProps.additionalFields; + const displayAs = this.configProps.displayAs ?? 'readonly'; + const dataRelationshipContext = (rawViewMetadata?.config as any)?.displayField + ? getDataRelationshipContextFromKey((rawViewMetadata?.config as any)?.displayField) + : this.dataRelationshipContext; + this.label = propsToUse.label; + + const editableComponents = ['AutoComplete', 'SimpleTableSelect', 'Dropdown', 'RadioButtons']; + const config: any = { + ...rawViewMetadata?.config, + primaryField: (rawViewMetadata?.config as any)?.displayField + }; + + const activeViewRuleClass = (rawViewMetadata?.config as any)?.targetObjectClass; + if (editableComponents.includes(type)) { + config.caseClass = activeViewRuleClass; + config.text = config.primaryField; + config.caseID = config.value; + config.contextPage = `@P .${dataRelationshipContext}`; + config.resourceParams = { + workID: displayAs === 'table' ? (config as any)?.selectionKey : config.value + }; + config.resourcePayload = { + caseClassName: activeViewRuleClass + }; + } + + this.component = this.pConn$.createComponent( + { + type: 'SemanticLink', + config: { + ...config, + displayMode, + referenceType, + hideLabel, + dataRelationshipContext + } + }, + '', + 0, + {} + ); + this.newPconn = this.component.getPConnect(); + } } diff --git a/packages/angular-sdk-components/src/lib/_helpers/objectReference-utils.ts b/packages/angular-sdk-components/src/lib/_helpers/objectReference-utils.ts new file mode 100644 index 00000000..10cb611b --- /dev/null +++ b/packages/angular-sdk-components/src/lib/_helpers/objectReference-utils.ts @@ -0,0 +1,117 @@ +const PERIOD = '.'; +const AT = '@'; +const SQUARE_BRACKET_START = '['; +const SQUARE_BRACKET_END = ']'; + +function getMappedKey(key) { + const mappedKey = PCore.getEnvironmentInfo().getKeyMapping(key); + if (!mappedKey) { + return key; + } + return mappedKey; +} + +function updatePageListPropertyValue(value) { + value = value.substring(0, value.indexOf(SQUARE_BRACKET_START)) + value.substring(value.indexOf(SQUARE_BRACKET_END) + 1); + return value; +} + +function getPropertyValue(value) { + if (value.startsWith(AT)) { + value = value.substring(value.indexOf(' ') + 1); + if (value.startsWith(PERIOD)) value = value.substring(1); + } + if (value.includes(SQUARE_BRACKET_START)) { + value = updatePageListPropertyValue(value); + } + return value; +} + +function getLeafNameFromPropertyName(property): string { + return property?.substr(property.lastIndexOf('.')); +} + +function isSelfReferencedProperty(param, referenceProp): boolean { + return param === referenceProp?.split('.', 2)[1]; +} + +function getCompositeKeys(c11nEnv, property): any { + const { datasource: { parameters = {} } = {} } = c11nEnv.getFieldMetadata(property) || {}; + return Object.values(parameters).reduce((compositeKeys: any, param: any) => { + if (isSelfReferencedProperty(property, param)) { + let propName = getPropertyValue(param); + propName = propName.substring(propName.indexOf('.')); + compositeKeys.push(propName); + } + return compositeKeys; + }, []); +} + +function generateColumns(config, pConn, referenceType) { + const displayField = getLeafNameFromPropertyName(config.displayField); + const referenceProp = config.value.split('.', 2)[1]; + const compositeKeys = getCompositeKeys(pConn, referenceProp); + let value = getLeafNameFromPropertyName(config.value); + + const columns: any[] = []; + if (displayField) { + columns.push({ + value: displayField, + display: 'true', + useForSearch: true, + primary: 'true' + }); + } + if (value && compositeKeys.indexOf(value) !== -1) { + columns.push({ + value, + setProperty: 'Associated property', + key: 'true' + }); + } else { + const actualValue = compositeKeys.length > 0 ? compositeKeys[0] : value; + config.value = `@P .${referenceProp}${actualValue}`; + value = actualValue; + columns.push({ + value: actualValue, + setProperty: 'Associated property', + key: 'true' + }); + } + + config.datasource = { + fields: { + key: getLeafNameFromPropertyName(config.value), + text: getLeafNameFromPropertyName(config.displayField), + value: getLeafNameFromPropertyName(config.value) + } + }; + + if (referenceType === 'Case') { + columns.push({ + secondary: 'true', + display: 'true', + value: getMappedKey('pyID'), + useForSearch: true + }); + } + + compositeKeys.forEach(key => { + if (value !== key) + columns.push({ + value: key, + display: 'false', + secondary: 'true', + useForSearch: false, + setProperty: `.${referenceProp}${key}` + }); + }); + + config.columns = columns; +} + +function getDataRelationshipContextFromKey(key) { + return key.split('.', 2)[1]; +} + +export { getLeafNameFromPropertyName, isSelfReferencedProperty, getCompositeKeys, generateColumns, getDataRelationshipContextFromKey }; diff --git a/packages/angular-sdk-components/src/lib/_helpers/semanticLink-utils.ts b/packages/angular-sdk-components/src/lib/_helpers/semanticLink-utils.ts new file mode 100644 index 00000000..e6de8077 --- /dev/null +++ b/packages/angular-sdk-components/src/lib/_helpers/semanticLink-utils.ts @@ -0,0 +1,53 @@ +function getDataReferenceInfo(pConnect, dataRelationshipContext, contextPage) { + if (!pConnect) { + throw Error('PConnect parameter is required'); + } + + let dataContext = ''; + const payload = {}; + const pageReference = pConnect.getPageReference(); + const annotationUtils = PCore.getAnnotationUtils(); + let fieldMetadata; + + if (pageReference) { + /* + For page list the page reference will be something like caseInfo.content.EmployeeRef[1]. + Need to extract EmployeeRef from caseInfo.content.EmployeeRef[1] + */ + const propertySplit = pageReference.split('.'); + + // Regex to match if the property is list type. Eg: EmployeeRef[1] + const listPropertyRegex = /([a-z|A-Z]*[[][\d]*)[\]]$/gm; + // Regex to match [1] part of the property EmployeeRef[1] + const indexRegex = /([[][\d]*[\]])+/gm; + + let contextProperty = dataRelationshipContext !== null ? dataRelationshipContext : propertySplit.pop(); + const isListProperty = listPropertyRegex.test(contextProperty); + contextProperty = isListProperty ? contextProperty.replace(indexRegex, '') : contextProperty; + fieldMetadata = pConnect.getFieldMetadata(contextProperty); + } + + if (!!fieldMetadata && fieldMetadata.datasource) { + const { name, parameters } = fieldMetadata.datasource; + dataContext = name; + for (const [key, value] of Object.entries(parameters)) { + if (contextPage && Object.hasOwn(contextPage, key)) { + payload[key] = contextPage[key]; + } else { + const isProperty = PCore.getAnnotationUtils().isProperty(value as string); + const property = + dataRelationshipContext !== null ? annotationUtils.getPropertyName(value as string) : annotationUtils.getLeafPropertyName(value as string); + payload[key] = isProperty ? pConnect.getValue(`.${property}`) : value; + } + } + return { dataContext, dataContextParameters: payload }; + } + + return {}; +} + +function isLinkTextEmpty(text) { + return text === '' || text === undefined || text === null; +} + +export { getDataReferenceInfo, isLinkTextEmpty };