From 52f05a2ee8d32c8ee274918f5c17bca2aa1a67ed Mon Sep 17 00:00:00 2001 From: CSimoesJr Date: Mon, 2 Oct 2023 19:25:51 -0300 Subject: [PATCH] feat(dynamic-view): implementa novas propriedades em fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Permite visualização de lista ou objeto de uma propriedade. Permite utilização de fieldLabel e fieldValue para itens. Permite concatenar fieldLabel e fieldValue para exibição do item. Criação da propriedade searchService. fixes DTHFUI-7549 --- .../ui/src/lib/components/po-dynamic/index.ts | 1 + .../po-dynamic-view-request.interface.ts | 19 ++ .../po-dynamic-view-base.component.spec.ts | 194 +++++++++++++++++- .../po-dynamic-view-base.component.ts | 111 +++++++++- .../po-dynamic-view-field.interface.ts | 48 +++++ .../po-dynamic-view.component.spec.ts | 14 +- .../po-dynamic-view.component.ts | 14 +- .../po-dynamic-view.service.spec.ts | 44 ---- .../po-dynamic-view.service.ts | 13 -- ...dynamic-view-employee-on-load.component.ts | 38 +++- ...o-dynamic-view-employee-on-load.service.ts | 29 +++ .../services/po-dynamic-view.service.spec.ts | 130 ++++++++++++ .../services/po-dynamic-view.service.ts | 45 ++++ .../po-dynamic/po-dynamic.module.ts | 2 +- 14 files changed, 622 insertions(+), 80 deletions(-) create mode 100644 projects/ui/src/lib/components/po-dynamic/po-dynamic-view/interfaces/po-dynamic-view-request.interface.ts delete mode 100644 projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.service.spec.ts delete mode 100644 projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.service.ts create mode 100644 projects/ui/src/lib/components/po-dynamic/po-dynamic-view/samples/sample-po-dynamic-view-employee-on-load/sample-po-dynamic-view-employee-on-load.service.ts create mode 100644 projects/ui/src/lib/components/po-dynamic/po-dynamic-view/services/po-dynamic-view.service.spec.ts create mode 100644 projects/ui/src/lib/components/po-dynamic/po-dynamic-view/services/po-dynamic-view.service.ts diff --git a/projects/ui/src/lib/components/po-dynamic/index.ts b/projects/ui/src/lib/components/po-dynamic/index.ts index 6dec5ea4a9..9fc529ecd6 100644 --- a/projects/ui/src/lib/components/po-dynamic/index.ts +++ b/projects/ui/src/lib/components/po-dynamic/index.ts @@ -1,6 +1,7 @@ export * from './po-dynamic-field-type.enum'; export * from './po-dynamic-field-force-component.enum'; export * from './po-dynamic-form/po-dynamic-form-field.interface'; +export * from './po-dynamic-view/interfaces/po-dynamic-view-request.interface'; export * from './po-dynamic-form/po-dynamic-form-load/po-dynamic-form-load.interface'; export * from './po-dynamic-form/po-dynamic-form-validation/po-dynamic-form-field-changed.interface'; export * from './po-dynamic-form/po-dynamic-form-validation/po-dynamic-form-field-validation.interface'; diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/interfaces/po-dynamic-view-request.interface.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/interfaces/po-dynamic-view-request.interface.ts new file mode 100644 index 0000000000..092407fd86 --- /dev/null +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/interfaces/po-dynamic-view-request.interface.ts @@ -0,0 +1,19 @@ +import { Observable } from 'rxjs'; + +/** + * @usedBy PoDynamicViewComponent + * + * @description + * + * Define o tipo de busca customizada para um campo em específico. + */ +export interface PoDynamicViewRequest { + /** + * Método responsável por enviar um valor que será buscado no serviço. + * + * + * @param {string|Array} value Valor único a ser buscado na fonte de dados. + * @param {any} filterParams Valor opcional para informar filtros customizados. + */ + getObjectByValue(value: string | Array, filterParams?: any): Observable; +} diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view-base.component.spec.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view-base.component.spec.ts index 4e0f02b812..6bd0e38c4d 100644 --- a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view-base.component.spec.ts +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view-base.component.spec.ts @@ -1,14 +1,28 @@ import { CurrencyPipe, DatePipe, DecimalPipe, TitleCasePipe } from '@angular/common'; import { HttpClient, HttpHandler } from '@angular/common/http'; -import { TestBed } from '@angular/core/testing'; +import { TestBed, fakeAsync, inject, tick } from '@angular/core/testing'; -import { expectPropertiesValues, expectArraysSameOrdering } from '../../../util-test/util-expect.spec'; import { PoTimePipe } from '../../../pipes/po-time/po-time.pipe'; +import { expectArraysSameOrdering, expectPropertiesValues } from '../../../util-test/util-expect.spec'; +import { Observable, of } from 'rxjs'; import * as PoDynamicUtil from '../po-dynamic.util'; +import { PoDynamicViewRequest } from './interfaces/po-dynamic-view-request.interface'; import { PoDynamicViewBaseComponent } from './po-dynamic-view-base.component'; -import { PoDynamicViewService } from './po-dynamic-view.service'; import { PoDynamicViewField } from './po-dynamic-view-field.interface'; +import { PoDynamicViewService } from './services/po-dynamic-view.service'; + +class DynamicViewService implements PoDynamicViewRequest { + getObjectByValue(id: string): Observable { + return of({ value: 123, label: 'teste' }); + } +} + +class TestService implements PoDynamicViewRequest { + getObjectByValue(id: string): Observable { + return of({ value: 123, label: 'teste' }); + } +} describe('PoDynamicViewBaseComponent:', () => { let component: PoDynamicViewBaseComponent; @@ -30,7 +44,8 @@ describe('PoDynamicViewBaseComponent:', () => { PoTimePipe, PoDynamicViewService, HttpClient, - HttpHandler + HttpHandler, + DynamicViewService ] }); @@ -189,8 +204,107 @@ describe('PoDynamicViewBaseComponent:', () => { expectArraysSameOrdering(newFields, expectedFields); }); + + it('should return ordering fields with property searchService', fakeAsync( + inject([DynamicViewService], (dynamicService: DynamicViewService) => { + component.service = dynamicService; + const fields: Array = [ + { property: 'test 1', order: 6 }, + { property: 'test 0', order: 2, searchService: 'url.test.com', fieldLabel: 'name', fieldValue: 'id' }, + { property: 'test 2', order: 3 }, + { property: 'test 3', order: 1 }, + { property: 'test 4', order: 5 }, + { property: 'test 5', order: 4 } + ]; + component.value[fields[1].property] = '123'; + spyOn(component.service, 'getObjectByValue').and.returnValue(of([{ id: 1, name: 'po' }])); + spyOn(component, 'searchById').and.returnValue(of([{ id: 1, name: 'po' }])); + + const expectedFields = [ + { property: 'test 3' }, + { property: 'test 0', value: 'po' }, + { property: 'test 2' }, + { property: 'test 5' }, + { property: 'test 4' }, + { property: 'test 1' } + ]; + + component.fields = [...fields]; + + const newFields = component['getConfiguredFields'](); + tick(500); + console.log(newFields); + + expectArraysSameOrdering(newFields, expectedFields); + }) + )); + + it('should return ordering fields with property searchService using service type', fakeAsync( + inject([DynamicViewService], (dynamicService: DynamicViewService) => { + component.service = dynamicService; + const fields: Array = [ + { property: 'test 1' }, + { property: 'test 0', searchService: new TestService(), fieldLabel: 'name', fieldValue: 'id' }, + { property: 'test 2', searchService: 'url.com' }, + { property: 'test 3', searchService: 'url.com' }, + { property: 'test 4' }, + { property: 'test 5' } + ]; + component.value[fields[1].property] = '123'; + component.value[fields[2].property] = [{ test: 123 }]; + component.value[fields[3].property] = { test: 123 }; + + spyOn(component.service, 'getObjectByValue').and.returnValue(of([{ id: 1, name: 'po' }])); + + const expectedFields = [ + { property: 'test 1', value: undefined }, + { property: 'test 0', value: 'teste' }, + { property: 'test 2', value: null }, + { property: 'test 3', value: null }, + { property: 'test 4' }, + { property: 'test 5' } + ]; + + component.fields = [...fields]; + + const newFields = component['getConfiguredFields'](); + tick(500); + console.log(newFields); + + expectArraysSameOrdering(newFields, expectedFields); + }) + )); + }); + + it('searchById: should return null if value is empty', done => { + const value = ''; + const field: any = { property: 'test' }; + + component['searchById'](value, field).subscribe(result => { + expect(result).toBeNull(); // Verifique se o resultado é nulo + done(); + }); }); + it('createFieldWithService: should call searchById and update newFields correctly', fakeAsync(() => { + const field = { property: 'test' }; + const newFields = []; + const index = 0; + + const valueToSearch = '123'; + const expectedResult = 'transformedValue'; + + const mockSearchById = spyOn(component, 'searchById').and.returnValue(of(expectedResult)); + + component.value[field.property] = valueToSearch; + component['createFieldWithService'](field, newFields, index); + + tick(); + + expect(mockSearchById).toHaveBeenCalledWith(valueToSearch, field); + expect(newFields[index].value).toBe(expectedResult); + })); + it('getMergedFields: should return a merged array between configuredFields and valueFields', () => { const configuredFields = [{ property: 'name', value: 'po' }]; const valueFields = [{ property: 'email' }]; @@ -236,6 +350,78 @@ describe('PoDynamicViewBaseComponent:', () => { expect(component['transformValue']).toHaveBeenCalled(); }); + it(`createField: should call 'transformArrayValue', return a + object and value is a label property`, () => { + const field = { property: 'name', label: 'Nome', isArrayOrObject: true }; + component.value = { name: { label: 'Test1', value: 123 } }; + + const newField = component['createField'](field); + + expect(newField.value).toBe('Test1'); + }); + + it(`createField: should call 'transformArrayValue', return a + list and value is a title property`, () => { + const field = { + property: 'name', + label: 'Nome', + isArrayOrObject: true, + concatLabelValue: true, + fieldLabel: 'title', + fieldValue: 'id' + }; + component.value = { + name: [ + { title: 'Test1', id: 123 }, + { title: 'Test2', id: 321 } + ] + }; + + const newField = component['createField'](field); + + expect(newField.value).toBe('Test1 - 123, Test2 - 321'); + }); + + it(`createField: should call 'transformArrayValue' and return a empty value if fieldLabel is a property invalid`, () => { + const field = { property: 'name', label: 'Nome', isArrayOrObject: true, fieldLabel: 'item', fieldValue: 'other' }; + const listName = [ + { title: 'Test1', id: 123 }, + { title: 'Test2', id: 321 } + ]; + component.value = { + name: listName + }; + + const newField = component['createField'](field); + + expect(newField.value).toEqual(listName); + }); + + it(`createField: should call 'transformFieldLabel' and return a fieldLabel property`, () => { + const field = { property: 'name', label: 'Nome', fieldLabel: 'title', fieldValue: 'id' }; + component.value = { name: 'Test Name', title: 'Title Test', id: 123 }; + + const newField = component['createField'](field); + + expect(newField.value).toBe('Title Test'); + }); + + it('createField: should call `transformFieldLabel`, return a `fieldLabel` and `fieldValue` property if `concatLabelValue` is true', () => { + const field = { + property: 'name', + label: 'Nome', + fieldLabel: 'title', + fieldValue: 'id', + concatLabelValue: true, + type: 'currency' + }; + component.value = { name: 'Test Name', title: 'Test Title', id: 123 }; + + const newField = component['createField'](field); + + expect(newField.value).toBe('Test Title - 123'); + }); + it('getValueFields: should return an array converting the value object', () => { component.value = { name: 'Po' }; diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view-base.component.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view-base.component.ts index 4211d8222f..faf3ae98b1 100644 --- a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view-base.component.ts +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view-base.component.ts @@ -1,12 +1,13 @@ -import { Input, Directive } from '@angular/core'; import { CurrencyPipe, DatePipe, DecimalPipe, TitleCasePipe } from '@angular/common'; +import { Directive, Input } from '@angular/core'; -import { convertToBoolean, isTypeof, sortFields } from '../../../utils/util'; import { PoTimePipe } from '../../../pipes/po-time/po-time.pipe'; +import { convertToBoolean, isTypeof, sortFields } from '../../../utils/util'; +import { Observable, catchError, map, of } from 'rxjs'; import { getGridColumnsClasses, isVisibleField } from '../po-dynamic.util'; import { PoDynamicViewField } from './po-dynamic-view-field.interface'; -import { PoDynamicViewService } from './po-dynamic-view.service'; +import { PoDynamicViewService } from './services/po-dynamic-view.service'; /** * @@ -55,6 +56,7 @@ export class PoDynamicViewBaseComponent { @Input('p-load') load: string | Function; visibleFields = []; + service: any; private _fields: Array = []; private _showAllValue: boolean = false; @@ -132,16 +134,29 @@ export class PoDynamicViewBaseComponent { protected dynamicViewService: PoDynamicViewService ) {} - // retorna os fields com os valores recuperados do objeto value. - protected getConfiguredFields() { + protected getConfiguredFields(useSearchService = true) { const newFields = []; - this.fields.forEach(field => { + this.fields.forEach((field, index) => { if (isVisibleField(field)) { - newFields.push(this.createField(field)); + if (!field.searchService) { + newFields.splice(index, 0, this.createField(field)); + } else if ( + this.value[field.property]?.length || + (!Array.isArray(this.value[field.property]) && this.value[field.property] && useSearchService) + ) { + if (isTypeof(field.searchService, 'object')) { + this.service = field.searchService; + } + if (field.searchService && isTypeof(field.searchService, 'string')) { + this.service = this.dynamicViewService; + this.service.setConfig(field.searchService as string); + } + const indexUpdated = field.order ? field.order : index; + this.createFieldWithService(field, newFields, indexUpdated); + } } }); - return sortFields(newFields); } @@ -168,8 +183,33 @@ export class PoDynamicViewBaseComponent { private createField(field: PoDynamicViewField) { const property = field.property; - const value = this.transformValue(field.type, this.value[property], field.format); + let value; + if (field.isArrayOrObject && this.value[property]) { + value = this.transformArrayValue(this.value[property], field); + } else if (field.fieldLabel) { + value = this.transformFieldLabel(property, field); + } + + if (!value) { + value = this.transformValue(field.type, this.value[property], field.format); + } + + return this.returnValues(field, value); + } + + private createFieldWithService(field: PoDynamicViewField, newFields?, index?) { + const property = field.property; + + this.searchById(this.value[property], field).subscribe(response => { + const value = response; + const allValues = this.returnValues(field, value); + newFields.splice(index, 0, allValues); + sortFields(newFields); + }); + } + private returnValues(field: PoDynamicViewField, value: any) { + const property = field.property; const classesGridColumns = getGridColumnsClasses( field.gridColumns, field.offsetColumns, @@ -202,6 +242,59 @@ export class PoDynamicViewBaseComponent { }; } + private searchById(value: any, field: PoDynamicViewField): Observable { + if (typeof value === 'string') { + value = value.trim(); + } + + if (value !== '') { + return this.service + .getObjectByValue(value) + .pipe(map(res => this.transformArrayValue(res, field))) + .pipe(catchError(() => of(null))); + } else { + return of(null); + } + } + + private transformArrayValue(valueProperty: any, field: PoDynamicViewField) { + const valueArray = Array.isArray(valueProperty) ? valueProperty : [valueProperty]; + const arrayWithLabel = valueArray.map(item => ({ + value: item[field.fieldValue] || item.value, + label: item[field.fieldLabel] || item.label + })); + + const labels = arrayWithLabel.map(optionValue => { + if (optionValue.label) { + const labelTranformed = this.transformValue(field.type, optionValue.label, field.format); + if (field.concatLabelValue && optionValue.value) { + return `${labelTranformed} - ${optionValue.value}`; + } else { + return labelTranformed; + } + } + }); + + if (labels[0] !== undefined && labels.join()) { + return labels.join(', '); + } else { + valueProperty = ''; + return undefined; + } + } + + private transformFieldLabel(property: string, field: PoDynamicViewField) { + if (field.concatLabelValue && field.fieldLabel && field.fieldValue && !field.isArrayOrObject) { + const transformedValue = this.transformValue(field.type, this.value[field.fieldLabel], field.format); + return `${transformedValue} - ${this.value[field.fieldValue]}`; + } + + if (field.fieldLabel && !field.concatLabelValue && !field.isArrayOrObject) { + this.value[property] = this.value[field.fieldLabel]; + } + return undefined; + } + private transformValue(type: string, value, format) { let transformedValue = value; diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view-field.interface.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view-field.interface.ts index b207ae5f18..3b56b18039 100644 --- a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view-field.interface.ts +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view-field.interface.ts @@ -1,4 +1,5 @@ import { PoDynamicField } from '../po-dynamic-field.interface'; +import { PoDynamicViewRequest } from './interfaces/po-dynamic-view-request.interface'; /** * @usedBy PoDynamicViewComponent @@ -39,6 +40,32 @@ export interface PoDynamicViewField extends PoDynamicField { */ color?: string; + /** + * Permite que seja exibido em tela, de forma concatenada as propriedades `fieldLabel` + `fieldValue`. + * A ordem sempre será `fieldLabel` e depois `fieldValue`, não sendo possível alterar. + * + * > Propriedade funciona corretamente caso as propriedades `fieldLabel` e `fielValue` sejam válidas. + * + * @default `false` + */ + concatLabelValue?: boolean; + + /** + * Nome da propriedade do objeto retornado que será utilizado como descrição do campo. + * + * O valor padrão é: `label`. + * + */ + fieldLabel?: string; + + /** + * Nome da propriedade do objeto retornado que será utilizado como valor do campo. + * + * O valor padrão é: `value`. + * + */ + fieldValue?: string; + /** * Define um ícone que será exibido ao lado do valor para o campo do tipo *tag*. * @@ -57,6 +84,16 @@ export interface PoDynamicViewField extends PoDynamicField { */ inverse?: boolean; + /** + * Define que a propriedade `property` é uma lista ou um objeto. + * + * > Por padrão, espera-se que a lista ou o objeto esteja com as propriedades `label` e `value`. + * Caso estejam com nomes diferentes, deve-se usar as propriedades `fieldLabel` e `fieldValue`. + * + * @default `false` + */ + isArrayOrObject?: boolean; + /** * Formato de exibição do valor do campo. * @@ -181,4 +218,15 @@ export interface PoDynamicViewField extends PoDynamicField { * */ options?: Array<{ label: string; value: string | number }>; + + /** + * Serviço customizado para um campo em específico. + * Pode ser ser informada uma URL ou uma instancia do serviço baseado em PoDynamicViewRequest. + * **Importante:** + * > A propriedade `property` deve receber um valor válido independente de sua utilização para + * execução correta. + * > Para que funcione corretamente, é importante que o serviço siga o + * [guia de API do PO UI](https://po-ui.io/guides/api). + */ + searchService?: string | PoDynamicViewRequest; } diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.component.spec.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.component.spec.ts index c3b1751724..62039a3992 100644 --- a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.component.spec.ts +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.component.spec.ts @@ -6,7 +6,7 @@ import { of } from 'rxjs'; import { PoDynamicModule } from '../po-dynamic.module'; import { PoDynamicViewComponent } from './po-dynamic-view.component'; -import { PoDynamicViewService } from './po-dynamic-view.service'; +import { PoDynamicViewService } from './services/po-dynamic-view.service'; describe('PoDynamicViewComponent:', () => { let component: PoDynamicViewComponent; @@ -80,6 +80,18 @@ describe('PoDynamicViewComponent:', () => { expect(component['getVisibleFields']).toHaveBeenCalled(); expect(component.visibleFields.length).toBe(returnedValue.length); }); + + it(`should call not 'getVisibleFields' if 'load' is true and initChanges is false`, () => { + const changes = { showAllValue: {} }; + component.load = 'url.test.com'; + component.visibleFields = []; + + spyOn(component, 'getVisibleFields'); + + component.ngOnChanges(changes); + + expect(component['getVisibleFields']).not.toHaveBeenCalled(); + }); }); it('ngOnInit: should call `updateValuesAndFieldsOnLoad` if typeof `load` is truthy', () => { diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.component.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.component.ts index 02274b3b1b..6e1cc778e5 100644 --- a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.component.ts +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.component.ts @@ -1,11 +1,11 @@ -import { Component, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { CurrencyPipe, DatePipe, DecimalPipe, TitleCasePipe } from '@angular/common'; +import { Component, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { PoTimePipe } from '../../../pipes/po-time/po-time.pipe'; import { PoDynamicViewField } from './../po-dynamic-view/po-dynamic-view-field.interface'; import { PoDynamicViewBaseComponent } from './po-dynamic-view-base.component'; -import { PoDynamicViewService } from './po-dynamic-view.service'; +import { PoDynamicViewService } from './services/po-dynamic-view.service'; /** * @docsExtends PoDynamicViewBaseComponent @@ -25,6 +25,7 @@ import { PoDynamicViewService } from './po-dynamic-view.service'; * * * + * * */ @Component({ @@ -32,6 +33,7 @@ import { PoDynamicViewService } from './po-dynamic-view.service'; templateUrl: './po-dynamic-view.component.html' }) export class PoDynamicViewComponent extends PoDynamicViewBaseComponent implements OnChanges, OnInit { + initChanges; constructor( currencyPipe: CurrencyPipe, datePipe: DatePipe, @@ -44,7 +46,12 @@ export class PoDynamicViewComponent extends PoDynamicViewBaseComponent implement } ngOnChanges(changes: SimpleChanges) { - if (changes.fields || changes.value || changes.showAllValue) { + if (this.load && !this.initChanges) { + this.initChanges = false; + } else { + this.initChanges = true; + } + if ((changes.fields || changes.value || changes.showAllValue) && this.initChanges) { this.visibleFields = this.getVisibleFields(); } } @@ -113,5 +120,6 @@ export class PoDynamicViewComponent extends PoDynamicViewBaseComponent implement this.setFieldsOnLoad(fields); this.visibleFields = this.getVisibleFields(); + this.initChanges = true; } } diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.service.spec.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.service.spec.ts deleted file mode 100644 index 67f9509425..0000000000 --- a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.service.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { TestBed } from '@angular/core/testing'; - -import { PoDynamicViewService } from './po-dynamic-view.service'; - -describe('PoDynamicViewService:', () => { - let httpMock: HttpTestingController; - let poDynamicViewService: PoDynamicViewService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [PoDynamicViewService] - }); - - poDynamicViewService = TestBed.inject(PoDynamicViewService); - httpMock = TestBed.inject(HttpTestingController); - }); - - afterEach(() => { - httpMock.verify(); - }); - - it('should be created', () => { - expect(poDynamicViewService).toBeTruthy(); - expect(poDynamicViewService instanceof PoDynamicViewService).toBeTruthy(); - }); - - describe('Methods', () => { - it('onLoad: should call POST method with `url` and `value`', () => { - const url = 'url'; - const value = {}; - const expectedResponse = { value: {}, fields: [] }; - - poDynamicViewService.onLoad(url, value).then(response => { - expect(response).toBeDefined(); - }); - - const req = httpMock.expectOne(`${url}`); - expect(req.request.method).toBe('POST'); - req.flush(expectedResponse); - }); - }); -}); diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.service.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.service.ts deleted file mode 100644 index 7d845d9284..0000000000 --- a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/po-dynamic-view.service.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; - -import { PoDynamicViewField } from '../po-dynamic-view/po-dynamic-view-field.interface'; - -@Injectable() -export class PoDynamicViewService { - constructor(private http: HttpClient) {} - - onLoad(url: string, value): Promise<{ value?: any; fields?: Array }> { - return this.http.post(url, value).toPromise(); - } -} diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/samples/sample-po-dynamic-view-employee-on-load/sample-po-dynamic-view-employee-on-load.component.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/samples/sample-po-dynamic-view-employee-on-load/sample-po-dynamic-view-employee-on-load.component.ts index bd1e7bd668..3cbb1dd784 100644 --- a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/samples/sample-po-dynamic-view-employee-on-load/sample-po-dynamic-view-employee-on-load.component.ts +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/samples/sample-po-dynamic-view-employee-on-load/sample-po-dynamic-view-employee-on-load.component.ts @@ -1,12 +1,14 @@ -import { Component } from '@angular/core'; +import { Component, OnInit, inject } from '@angular/core'; import { PoDynamicViewField } from '@po-ui/ng-components'; +import { SamplePoDynamicViewEmployeeOnLoadService } from './sample-po-dynamic-view-employee-on-load.service'; @Component({ selector: 'sample-po-dynamic-view-employee-on-load', - templateUrl: './sample-po-dynamic-view-employee-on-load.component.html' + templateUrl: './sample-po-dynamic-view-employee-on-load.component.html', + providers: [SamplePoDynamicViewEmployeeOnLoadService] }) -export class SamplePoDynamicViewEmployeeOnLoadComponent { +export class SamplePoDynamicViewEmployeeOnLoadComponent implements OnInit { employee = { name: 'Jhon Doe', age: '20', @@ -21,9 +23,23 @@ export class SamplePoDynamicViewEmployeeOnLoadComponent { addressStreet: 'Avenida Braz Leme', addressNumber: '1000', zipCode: '02511-000', - city: 'São Paulo', + city: 'A', wage: 8000.5, availability: 'Available', + cities: [ + { + city: 'São Paulo', + id: 'SP' + }, + { + city: 'Joinville', + id: 'SC' + }, + { + city: 'Belo Horizonte', + id: 'MG' + } + ], admissionDate: '2014-10-14T13:45:00-00:00', hoursPerDay: '08:30:00', profile: 'admin', @@ -43,6 +59,7 @@ export class SamplePoDynamicViewEmployeeOnLoadComponent { { property: 'hoursPerDay', label: 'Hours per day', type: 'time' }, { property: 'wage', label: 'Wage', type: 'currency' }, { property: 'availability', tag: true, color: '#C596E7', icon: 'po-icon-ok' }, + { property: 'cities', isArrayOrObject: true, fieldLabel: 'city', fieldValue: 'id', concatLabelValue: true }, { property: 'city', label: 'City', divider: 'Address' }, { property: 'addressStreet', label: 'Street' }, { property: 'addressNumber', label: 'Number' }, @@ -50,6 +67,12 @@ export class SamplePoDynamicViewEmployeeOnLoadComponent { { property: 'image', divider: 'Image', image: true, alt: 'image', height: '250' } ]; + private _newService = inject(SamplePoDynamicViewEmployeeOnLoadService); + + ngOnInit(): void { + this._newService.setConfig('https://po-sample-api.fly.dev/v1/hotels', { id: 1485976673002 }); + } + customEmployeeData() { return { value: { @@ -63,7 +86,12 @@ export class SamplePoDynamicViewEmployeeOnLoadComponent { { property: 'rg', tag: true, color: 'color-07', order: 3 }, { property: 'wage', type: 'string', tag: true, color: 'color-07' }, { property: 'genre', visible: false }, - { property: 'job', tag: false } + { property: 'job', tag: false }, + { + searchService: this._newService, + fieldLabel: 'address_city', + property: 'city' + } ] }; } diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/samples/sample-po-dynamic-view-employee-on-load/sample-po-dynamic-view-employee-on-load.service.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/samples/sample-po-dynamic-view-employee-on-load/sample-po-dynamic-view-employee-on-load.service.ts new file mode 100644 index 0000000000..6a1df9be28 --- /dev/null +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/samples/sample-po-dynamic-view-employee-on-load/sample-po-dynamic-view-employee-on-load.service.ts @@ -0,0 +1,29 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable, map } from 'rxjs'; + +@Injectable() +export class SamplePoDynamicViewEmployeeOnLoadService { + readonly headers: HttpHeaders = new HttpHeaders({ + 'X-PO-No-Message': 'true' + }); + + url: string; + filterParams; + + constructor(private httpClient: HttpClient) {} + + getObjectByValue(value: string | Array, filterParams?: any): Observable | { [key: string]: any }> { + return this.httpClient + .get(this.url, { + headers: this.headers, + params: this.filterParams + }) + .pipe(map((response: any) => ('items' in response ? response.items : response))); + } + + setConfig(url: string, filterParams) { + this.url = url; + this.filterParams = filterParams; + } +} diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/services/po-dynamic-view.service.spec.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/services/po-dynamic-view.service.spec.ts new file mode 100644 index 0000000000..8236113d60 --- /dev/null +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/services/po-dynamic-view.service.spec.ts @@ -0,0 +1,130 @@ +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { TestBed, fakeAsync, tick } from '@angular/core/testing'; + +import { PoDynamicViewService } from './po-dynamic-view.service'; + +describe('PoDynamicViewService:', () => { + let httpMock: HttpTestingController; + let poDynamicViewService: PoDynamicViewService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [PoDynamicViewService] + }); + + poDynamicViewService = TestBed.inject(PoDynamicViewService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + it('should be created', () => { + expect(poDynamicViewService).toBeTruthy(); + expect(poDynamicViewService instanceof PoDynamicViewService).toBeTruthy(); + }); + + describe('Methods', () => { + it('onLoad: should call POST method with `url` and `value`', () => { + const url = 'url'; + const value = {}; + const expectedResponse = { value: {}, fields: [] }; + + poDynamicViewService.onLoad(url, value).then(response => { + expect(response).toBeDefined(); + }); + + const req = httpMock.expectOne(`${url}`); + expect(req.request.method).toBe('POST'); + req.flush(expectedResponse); + }); + + it(`getObjectByValue: should return the request response and create request with url, headers and 'filterParams' correctly`, fakeAsync(() => { + poDynamicViewService['url'] = 'http://url.com'; + const filterParams = { name: 'test' }; + const value = 'test/encoding'; + const expectedResponse = { user: 'test' }; + + spyOn(poDynamicViewService, 'validateParams').and.returnValue(filterParams); + + poDynamicViewService.getObjectByValue(value, filterParams).subscribe(response => { + expect(poDynamicViewService['validateParams']).toHaveBeenCalledWith(filterParams); + expect(response).toEqual(expectedResponse); + }); + + const req = httpMock.expectOne( + httpRequest => + httpRequest.url === 'http://url.com/test%2Fencoding' && + httpRequest.method === 'GET' && + httpRequest.params.get('name') === 'test' + ); + + expect(req.request.headers.get('X-PO-No-Message')).toBe('true'); + + req.flush(expectedResponse); + tick(); + })); + + it(`getObjectByValue: should call 'validateParams' and set its return as the request parameter`, () => { + poDynamicViewService['url'] = 'http://url.com'; + const filterParams = { name: 'test' }; + const value = '1'; + + spyOn(poDynamicViewService, 'validateParams').and.returnValue(filterParams); + + poDynamicViewService.getObjectByValue(value, filterParams).subscribe(() => { + expect(poDynamicViewService['validateParams']).toHaveBeenCalledWith(filterParams); + }); + + httpMock.expectOne(httpRequest => httpRequest.params.get('name') === 'test').flush({}); + }); + + it(`getObjectByValue: should call 'validateParams' with 'multiple' is true and array of value and set its return as the request parameter`, () => { + poDynamicViewService['url'] = 'http://url.com'; + poDynamicViewService['multiple'] = true; + const filterParams = { name: 'test' }; + const value = [1, 2]; + + spyOn(poDynamicViewService, 'validateParams').and.returnValue(filterParams); + + poDynamicViewService.getObjectByValue(value, filterParams).subscribe(() => { + expect(poDynamicViewService['validateParams']).toHaveBeenCalledWith(filterParams); + }); + + httpMock.expectOne(httpRequest => httpRequest.params.get('name') === 'test').flush({ items: [] }); + }); + + it('setConfig: should set `url` to the value of the parameter', () => { + poDynamicViewService['url'] = undefined; + const paramValue = 'http://url.com.br'; + + poDynamicViewService.setConfig(paramValue); + + expect(poDynamicViewService['url']).toBe(paramValue); + }); + + it('validateParams: should return param if it`s a object', () => { + const param = { key: 'value' }; + + expect(poDynamicViewService['validateParams'](param)).toEqual(param); + }); + + it('validateParams: should return undefined if it isn`t an object', () => { + let param: any; + + param = 'value'; + expect(poDynamicViewService['validateParams'](param)).toBe(undefined); + + param = 1; + expect(poDynamicViewService['validateParams'](param)).toBe(undefined); + + param = false; + expect(poDynamicViewService['validateParams'](param)).toBe(undefined); + + param = ['value']; + expect(poDynamicViewService['validateParams'](param)).toBe(undefined); + }); + }); +}); diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/services/po-dynamic-view.service.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/services/po-dynamic-view.service.ts new file mode 100644 index 0000000000..5e5b680c09 --- /dev/null +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-view/services/po-dynamic-view.service.ts @@ -0,0 +1,45 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable, map } from 'rxjs'; +import { isTypeof } from '../../../../utils/util'; +import { PoDynamicViewField } from '../po-dynamic-view-field.interface'; + +/** + * @docsPrivate + * + * @description + * + * Serviço padrão utilizado para filtrar os dados do componente po-lookup. + */ +@Injectable() +export class PoDynamicViewService { + readonly headers: HttpHeaders = new HttpHeaders({ + 'X-PO-No-Message': 'true' + }); + url: string; + + constructor(private httpClient: HttpClient) {} + + getObjectByValue(value: any, filterParams?: any): Observable | { [key: string]: any }> { + const validatedFilterParams = this.validateParams(filterParams); + + const encodedValue = encodeURIComponent(value); + const newURL = `${this.url}/${encodedValue}`; + + return this.httpClient + .get(newURL, { headers: this.headers, params: validatedFilterParams }) + .pipe(map((response: any) => ('items' in response ? response.items : response))); + } + + onLoad(url: string, value): Promise<{ value?: any; fields?: Array }> { + return this.httpClient.post(url, value).toPromise(); + } + + setConfig(url: string) { + this.url = url; + } + + private validateParams(params: any) { + return isTypeof(params, 'object') && !Array.isArray(params) ? params : undefined; + } +} diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic.module.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic.module.ts index ae96594ecc..c7b97b18a2 100644 --- a/projects/ui/src/lib/components/po-dynamic/po-dynamic.module.ts +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic.module.ts @@ -14,8 +14,8 @@ import { PoDynamicFormFieldsComponent } from './po-dynamic-form/po-dynamic-form- import { PoDynamicFormLoadService } from './po-dynamic-form/po-dynamic-form-load/po-dynamic-form-load.service'; import { PoDynamicFormValidationService } from './po-dynamic-form/po-dynamic-form-validation/po-dynamic-form-validation.service'; import { PoDynamicViewComponent } from './po-dynamic-view/po-dynamic-view.component'; -import { PoDynamicViewService } from './po-dynamic-view/po-dynamic-view.service'; import { PoImageModule } from '../po-image'; +import { PoDynamicViewService } from './po-dynamic-view/services/po-dynamic-view.service'; @NgModule({ imports: [