From d81a54715663bf654371a09225c16a6b443887c1 Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Tue, 20 Aug 2024 08:52:48 +0200 Subject: [PATCH] contacts may now be ordered --- apps/metadata-editor-e2e/src/e2e/edit.cy.ts | 4 +- .../contact-card/contact-card.component.html | 47 ++-- .../contact-card.component.spec.ts | 61 ++--- .../contact-card/contact-card.component.ts | 9 +- ...field-contacts-for-resource.component.html | 40 +-- ...ld-contacts-for-resource.component.spec.ts | 229 +++++++++++------- ...m-field-contacts-for-resource.component.ts | 221 ++++++++++------- .../form-field-keywords.component.html | 2 +- .../fuzzy-search/fuzzy-search.component.html | 2 +- .../autocomplete.component.spec.ts | 6 +- .../autocomplete/autocomplete.component.ts | 15 +- 11 files changed, 363 insertions(+), 273 deletions(-) diff --git a/apps/metadata-editor-e2e/src/e2e/edit.cy.ts b/apps/metadata-editor-e2e/src/e2e/edit.cy.ts index ebd6b39f61..d0b5ff0033 100644 --- a/apps/metadata-editor-e2e/src/e2e/edit.cy.ts +++ b/apps/metadata-editor-e2e/src/e2e/edit.cy.ts @@ -107,7 +107,9 @@ describe('editor form', () => { .find('gn-ui-autocomplete') .type('bar') - cy.get('mat-option').should('have.text', ' Barbara Roberts ').click() + cy.get('mat-option') + .should('have.text', ' Barbara Roberts (Barbie Inc.) ') + .click() cy.get('[data-test=displayedRoles]') .children() diff --git a/libs/feature/editor/src/lib/components/contact-card/contact-card.component.html b/libs/feature/editor/src/lib/components/contact-card/contact-card.component.html index 511e194a22..a205e4c41f 100644 --- a/libs/feature/editor/src/lib/components/contact-card/contact-card.component.html +++ b/libs/feature/editor/src/lib/components/contact-card/contact-card.component.html @@ -1,28 +1,25 @@ - -
-
- -
-
- {{ contact.firstName }} {{ contact.lastName }} -
-
{{ contact.email }}
+
+
+ +
+
+ {{ contact.firstName }} {{ contact.lastName }}
+
{{ contact.email }}
- close -
- + close + +
diff --git a/libs/feature/editor/src/lib/components/contact-card/contact-card.component.spec.ts b/libs/feature/editor/src/lib/components/contact-card/contact-card.component.spec.ts index 1ce24cb81e..320fc59d43 100644 --- a/libs/feature/editor/src/lib/components/contact-card/contact-card.component.spec.ts +++ b/libs/feature/editor/src/lib/components/contact-card/contact-card.component.spec.ts @@ -6,12 +6,27 @@ import { } from '@geonetwork-ui/common/domain/model/record' import { MatIconModule } from '@angular/material/icon' import { CommonModule } from '@angular/common' -import { ButtonComponent } from '@geonetwork-ui/ui/inputs' +import { + AutocompleteComponent, + ButtonComponent, +} from '@geonetwork-ui/ui/inputs' +import { ChangeDetectionStrategy } from '@angular/core' describe('ContactCardComponent', () => { let component: ContactCardComponent let fixture: ComponentFixture + const mockContact: Individual = { + firstName: 'John', + lastName: 'Doe', + organization: { name: 'Org1' } as Organization, + email: 'john.doe@example.com', + role: 'admin', + address: '', + phone: '', + position: '', + } + beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ @@ -20,12 +35,17 @@ describe('ContactCardComponent', () => { ButtonComponent, ContactCardComponent, ], - }).compileComponents() + }) + .overrideComponent(AutocompleteComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents() }) beforeEach(() => { fixture = TestBed.createComponent(ContactCardComponent) component = fixture.componentInstance + component.contact = mockContact fixture.detectChanges() }) @@ -33,44 +53,7 @@ describe('ContactCardComponent', () => { expect(component).toBeTruthy() }) - it('should have a defined contact input', () => { - const mockContact: Individual = { - firstName: 'John', - lastName: 'Doe', - organization: { name: 'Org1' } as Organization, - email: 'john.doe@example.com', - role: 'admin', - address: '', - phone: '', - position: '', - } - component.contact = mockContact - fixture.detectChanges() - expect(component.contact).toEqual(mockContact) - }) - - it('should have a defined organization input', () => { - const mockOrganization: Organization = { - name: 'Org1', - } - component.organization = mockOrganization - fixture.detectChanges() - expect(component.organization).toEqual(mockOrganization) - }) - it('should emit contactRemoved event with the correct contact', () => { - const mockContact: Individual = { - firstName: 'John', - lastName: 'Doe', - organization: { name: 'Org1' } as Organization, - email: 'john.doe@example.com', - role: 'admin', - address: '', - phone: '', - position: '', - } - component.contact = mockContact - const contactRemovedSpy = jest.spyOn(component.contactRemoved, 'emit') component.removeContact(mockContact) expect(contactRemovedSpy).toHaveBeenCalledWith(mockContact) diff --git a/libs/feature/editor/src/lib/components/contact-card/contact-card.component.ts b/libs/feature/editor/src/lib/components/contact-card/contact-card.component.ts index 494bed0ad9..10fb9c6ffa 100644 --- a/libs/feature/editor/src/lib/components/contact-card/contact-card.component.ts +++ b/libs/feature/editor/src/lib/components/contact-card/contact-card.component.ts @@ -5,13 +5,11 @@ import { Input, Output, } from '@angular/core' -import { - Individual, - Organization, -} from '@geonetwork-ui/common/domain/model/record' +import { Individual } from '@geonetwork-ui/common/domain/model/record' import { MatIconModule } from '@angular/material/icon' import { CommonModule } from '@angular/common' import { ButtonComponent } from '@geonetwork-ui/ui/inputs' +import { ThumbnailComponent } from '@geonetwork-ui/ui/elements' @Component({ selector: 'gn-ui-contact-card', @@ -19,11 +17,10 @@ import { ButtonComponent } from '@geonetwork-ui/ui/inputs' styleUrls: ['./contact-card.component.css'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [CommonModule, MatIconModule, ButtonComponent], + imports: [CommonModule, MatIconModule, ButtonComponent, ThumbnailComponent], }) export class ContactCardComponent { @Input() contact: Individual - @Input() organization: Organization @Input() removable = true @Output() contactRemoved = new EventEmitter() diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.html b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.html index 291b27bba4..69d8a2f22e 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.html +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.html @@ -16,11 +16,18 @@
@@ -38,20 +45,23 @@ [clearOnSelection]="true" > - - + + + + + + + + - - - +
diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.spec.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.spec.ts index 680de56f29..97f597a4c0 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.spec.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.spec.ts @@ -5,13 +5,15 @@ import { BehaviorSubject } from 'rxjs' import { Individual, Organization, + Role, } from '@geonetwork-ui/common/domain/model/record' -import { ChangeDetectorRef, SimpleChanges } from '@angular/core' +import { ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core' import { UserModel } from '@geonetwork-ui/common/domain/model/user' import { CommonModule } from '@angular/common' import { TranslateModule } from '@ngx-translate/core' import { ContactCardComponent } from '../../../contact-card/contact-card.component' import { + AutocompleteComponent, DropdownSelectorComponent, UiInputsModule, } from '@geonetwork-ui/ui/inputs' @@ -38,22 +40,6 @@ describe('FormFieldContactsForResourceComponent', () => { let component: FormFieldContactsForResourceComponent let fixture: ComponentFixture - const contactJohn: Individual = { - firstName: 'John', - lastName: 'Doe', - organization: { name: 'Org1' } as Organization, - email: 'john.doe@example.com', - role: 'author', - } - - const contactJane: Individual = { - firstName: 'John', - lastName: 'Doe', - organization: { name: 'Org1' } as Organization, - email: 'john.doe@example.com', - role: 'custodian', - } - beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ @@ -75,107 +61,164 @@ describe('FormFieldContactsForResourceComponent', () => { }, ChangeDetectorRef, ], - }).compileComponents() - }) + }) + .overrideComponent(AutocompleteComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents() - beforeEach(() => { fixture = TestBed.createComponent(FormFieldContactsForResourceComponent) component = fixture.componentInstance component.control = new FormControl([]) fixture.detectChanges() }) - it('should create', () => { + it('should create the component', () => { expect(component).toBeTruthy() }) - it('should initialize with default values', () => { - expect(component.rolesToPick).toEqual([ - 'resource_provider', - 'custodian', - 'owner', - 'point_of_contact', - 'author', - ]) - expect(component.rolesToDisplay).toEqual([]) - expect(component.allOrganizations.size).toBe(2) + describe('ngOnInit', () => { + it('should initialize organizations', async () => { + await component.ngOnInit() + + expect(component.allOrganizations.size).toBe(2) + }) }) - it('should fetch users on initialization', () => { - const platformService = TestBed.inject(PlatformServiceInterface) - expect(platformService.getUsers).toHaveBeenCalled() + describe('addRoleToDisplay', () => { + it('should add role to display and filter roles to pick', () => { + const initialRolesToPick = [...component.rolesToPick] + const roleToAdd = initialRolesToPick[0] + + component.addRoleToDisplay(roleToAdd) + + expect(component.roleSectionsToDisplay).toContain(roleToAdd) + expect(component.rolesToPick).not.toContain(roleToAdd) + }) }) - it('should subscribe to organizations and update the allOrganizations map', () => { - component.ngOnInit() - expect(component.allOrganizations.size).toBe(2) - expect(component.allOrganizations.get('Barbie Inc.')).toEqual( - organizationBarbie - ) + describe('filterRolesToPick', () => { + it('should filter roles already in roleSectionsToDisplay', () => { + component.rolesToPick = ['custodian', 'owner'] as Role[] + component.roleSectionsToDisplay = ['custodian'] as Role[] + + component.filterRolesToPick() + + expect(component.rolesToPick).toEqual(['owner']) + }) }) - it('should add role to rolesToDisplay and remove from rolesToPick', () => { - component.addRoleToDisplay('owner') - expect(component.rolesToPick).not.toContain('owner') - expect(component.rolesToDisplay).toContain('owner') + describe('updateContactsForRessource', () => { + it('should update contactsForRessourceByRole and contactsAsDynElemByRole', () => { + const mockContact: Individual = { + role: 'owner', + organization: { name: 'Org1' } as Organization, + } as Individual + + component.allOrganizations.set('Org1', { name: 'Org1' } as Organization) + component.control.setValue([mockContact]) + + component.updateContactsForRessource() + + expect(component.contactsForRessourceByRole.get('owner')).toEqual([ + mockContact, + ]) + expect(component.contactsAsDynElemByRole.get('owner').length).toBe(1) + }) }) - it('should remove contact by index', () => { - component.control.setValue([ - { firstName: 'John', lastName: 'Doe' } as Individual, - ]) - component.removeContact(0) - expect(component.control.value.length).toBe(0) + describe('manageRoleSectionsToDisplay', () => { + it('should add new roles to roleSectionsToDisplay', () => { + const mockContact: Individual = { + role: 'owner', + organization: { name: 'Org1' } as Organization, + } as Individual + + component.manageRoleSectionsToDisplay([mockContact]) + + expect(component.roleSectionsToDisplay).toContain('owner') + }) }) - it('should add a new contact with correct data', async () => { - const contact = { - name: 'John', - surname: 'Doe', - organisation: 'Org1', - email: 'john.doe@example.com', - } as UserModel - jest - .spyOn(component, 'getOrganizationByName') - .mockReturnValue({ name: 'Org1' } as Organization) - await component.addContact(contact, 'owner') - expect(component.control.value.length).toBe(1) - expect(component.control.value[0].firstName).toBe('John') - expect(component.control.value[0].lastName).toBe('Doe') - expect(component.control.value[0].organization.name).toBe('Org1') + describe('removeContact', () => { + it('should remove contact at specified index', () => { + const mockContacts: Individual[] = [ + { + role: 'owner', + organization: { name: 'Org1' } as Organization, + } as Individual, + { + role: 'custodian', + organization: { name: 'Org2' } as Organization, + } as Individual, + ] + + component.control.setValue(mockContacts) + component.removeContact(0) + + expect(component.control.value.length).toBe(1) + expect(component.control.value[0]).toEqual(mockContacts[1]) + }) }) - it('should handle ngOnChanges and update rolesToDisplay', () => { - const changes = { - control: { - currentValue: { - value: [contactJane, contactJohn], - }, - previousValue: { value: [] }, - firstChange: false, - }, - } as unknown as SimpleChanges - component.ngOnChanges(changes) - expect(component.rolesToDisplay).toEqual([ - contactJane.role, - contactJohn.role, - ]) + describe('handleContactsChanged', () => { + it('should update contacts based on reordered dynamic elements', () => { + const mockContacts: Individual[] = [ + { + role: 'owner', + organization: { name: 'Org1' } as Organization, + } as Individual, + { + role: 'owner', + organization: { name: 'Org2' } as Organization, + } as Individual, + ] + + component.contactsForRessourceByRole.set('owner', [mockContacts[0]]) + component.contactsForRessourceByRole.set('owner', [mockContacts[1]]) + + const reorderedElements = [ + { inputs: { contact: mockContacts[1] } } as any, + { inputs: { contact: mockContacts[0] } } as any, + ] + + component.handleContactsChanged(reorderedElements) + + const newControlValue = component.control.value + expect(newControlValue[0]).toEqual(mockContacts[1]) + expect(newControlValue[1]).toEqual(mockContacts[0]) + }) }) - it('should return contacts by role', () => { - const contacts: Individual[] = [ - { role: 'owner' } as Individual, - { role: 'custodian' } as Individual, - { role: 'owner' } as Individual, - ] - component.control.setValue(contacts) - const result = component.getContactsByRole('owner') - expect(result.length).toBe(2) + describe('addContact', () => { + it('should add a new contact to the control value', () => { + const mockUser: UserModel = { + username: 'user1', + name: 'John', + surname: 'Doe', + organisation: 'Org1', + } as UserModel + + component.allOrganizations.set('Org1', { name: 'Org1' } as Organization) + const initialContacts = component.control.value.length + + component.addContact(mockUser, 'owner') + + expect(component.control.value.length).toBe(initialContacts + 1) + expect(component.control.value[initialContacts].role).toBe('owner') + expect(component.control.value[initialContacts].organization.name).toBe( + 'Org1' + ) + }) }) - it('should return the correct organization by name', () => { - const org = { name: 'Org1' } as Organization - component.allOrganizations.set('Org1', org) - expect(component.getOrganizationByName('Org1')).toBe(org) + describe('ngOnDestroy', () => { + it('should unsubscribe from all subscriptions', () => { + const subscriptionSpy = jest.spyOn(component.subscription, 'unsubscribe') + + component.ngOnDestroy() + + expect(subscriptionSpy).toHaveBeenCalled() + }) }) }) diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.ts index bfd2fdccf8..9890c61fae 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.ts @@ -4,10 +4,8 @@ import { ChangeDetectorRef, Component, Input, - OnChanges, + OnDestroy, OnInit, - SimpleChanges, - Type, } from '@angular/core' import { FormControl } from '@angular/forms' import { @@ -19,10 +17,18 @@ import { UiWidgetsModule } from '@geonetwork-ui/ui/widgets' import { Individual, Organization, + Role, RoleLabels, } from '@geonetwork-ui/common/domain/model/record' import { TranslateModule } from '@ngx-translate/core' -import { debounceTime, distinctUntilChanged, Observable, switchMap } from 'rxjs' +import { + debounceTime, + distinctUntilChanged, + firstValueFrom, + Observable, + Subscription, + switchMap, +} from 'rxjs' import { UserModel } from '@geonetwork-ui/common/domain/model/user' import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' import { OrganizationsServiceInterface } from '@geonetwork-ui/common/domain/organizations.service.interface' @@ -31,6 +37,8 @@ import { DynamicElement, SortableListComponent, } from '@geonetwork-ui/ui/elements' +import { createFuzzyFilter } from '@geonetwork-ui/util/shared' +import { map } from 'rxjs/operators' @Component({ selector: 'gn-ui-form-field-contacts-for-resource', @@ -50,13 +58,19 @@ import { ], }) export class FormFieldContactsForResourceComponent - implements OnInit, OnChanges + implements OnInit, OnDestroy { @Input() control: FormControl + subscription: Subscription = new Subscription() + allUsers$: Observable - rolesToPick: string[] = [ + contactsForRessourceByRole: Map = new Map() + + contactsAsDynElemByRole: Map = new Map() + + rolesToPick: Role[] = [ 'resource_provider', 'custodian', 'owner', @@ -64,12 +78,10 @@ export class FormFieldContactsForResourceComponent 'author', ] - rolesToDisplay = [] + roleSectionsToDisplay: Role[] = [] allOrganizations: Map = new Map() - addOptions: Array<{ buttonLabel: string; eventName: string }> = [] - constructor( private platformServiceInterface: PlatformServiceInterface, private organizationsServiceInterface: OrganizationsServiceInterface, @@ -78,53 +90,109 @@ export class FormFieldContactsForResourceComponent this.allUsers$ = this.platformServiceInterface.getUsers() } - ngOnInit(): void { - this.organizationsServiceInterface.organisations$.subscribe( - (organizations) => { - this.allOrganizations = new Map( - organizations.map((organization) => [organization.name, organization]) - ) - this.changeDetectorRef.markForCheck() - } + async ngOnInit(): Promise { + this.allOrganizations = new Map( + ( + await firstValueFrom(this.organizationsServiceInterface.organisations$) + ).map((organization) => [organization.name, organization]) ) - } - - ngOnChanges(changes: SimpleChanges) { - const contactsForResource = changes['control'] + this.updateContactsForRessource() + this.manageRoleSectionsToDisplay(this.control.value) + this.filterRolesToPick() - if ( - contactsForResource && - contactsForResource.currentValue !== contactsForResource.previousValue - ) { - const rolesToAdd = contactsForResource.currentValue.value.map( - (contact: Individual) => contact.role - ) + this.changeDetectorRef.markForCheck() - console.log(contactsForResource.currentValue) + this.subscription.add( + this.control.valueChanges.subscribe((contactsForResource) => { + this.updateContactsForRessource() + this.manageRoleSectionsToDisplay(contactsForResource) + this.filterRolesToPick() - rolesToAdd.forEach((role: string) => { - if (!this.rolesToDisplay.includes(role)) { - this.rolesToDisplay.push(role) - } + this.changeDetectorRef.markForCheck() }) - - this.filterRolesToPick() - - this.changeDetectorRef.markForCheck() - } + ) } addRoleToDisplay(roleToAdd: string) { - this.rolesToDisplay.push(roleToAdd) + this.roleSectionsToDisplay.push(roleToAdd) this.filterRolesToPick() } filterRolesToPick() { this.rolesToPick = this.rolesToPick.filter( - (role) => !this.rolesToDisplay.includes(role) + (role) => !this.roleSectionsToDisplay.includes(role) ) } + updateContactsForRessource() { + this.contactsForRessourceByRole = this.control.value.reduce( + (acc, contact) => { + const completeOrganization = this.allOrganizations.get( + contact.organization.name + ) + + const updatedContact = { + ...contact, + organization: + completeOrganization ?? + ({ name: contact.organization.name } as Organization), + } + + if (!acc.has(contact.role)) { + acc.set(contact.role, []) + } + + acc.get(contact.role).push(updatedContact) + + return acc + }, + new Map() + ) + + this.contactsAsDynElemByRole = this.control.value.reduce((acc, contact) => { + const completeOrganization = this.allOrganizations.get( + contact.organization.name + ) + + const updatedContact = { + ...contact, + organization: + completeOrganization ?? + ({ name: contact.organization.name } as Organization), + } + + const contactAsDynElem = { + component: ContactCardComponent, + inputs: { + contact: updatedContact, + removable: false, + }, + } as DynamicElement + + if (!acc.has(contact.role)) { + acc.set(contact.role, []) + } + + acc.get(contact.role).push(contactAsDynElem) + + return acc + }, new Map()) + + this.changeDetectorRef.markForCheck() + } + + manageRoleSectionsToDisplay(contactsForResource: Individual[]) { + const roles = contactsForResource.map( + (contact: Individual) => contact.role + ) as Role[] + + roles.forEach((role: Role) => { + if (!this.roleSectionsToDisplay.includes(role)) { + this.roleSectionsToDisplay.push(role) + } + }) + } + removeContact(index: number) { const newContactsforRessource = this.control.value.filter( (_, i) => i !== index @@ -132,6 +200,22 @@ export class FormFieldContactsForResourceComponent this.control.setValue(newContactsforRessource) } + handleContactsChanged(event: DynamicElement[]) { + const newContactsOrdered = event.map( + (contactAsDynElem) => contactAsDynElem.inputs['contact'] + ) as Individual[] + + const role = newContactsOrdered[0].role + + this.contactsForRessourceByRole.set(role, newContactsOrdered) + + const newControlValue = Array.from( + this.contactsForRessourceByRole.values() + ).flat() + + this.control.setValue(newControlValue) + } + protected roleToLabel(role: string): string { return RoleLabels.get(role) } @@ -140,16 +224,20 @@ export class FormFieldContactsForResourceComponent * gn-ui-autocomplete */ displayWithFn: (user: UserModel) => string = (user) => - `${user.name} ${user.surname}` + `${user.name} ${user.surname} ${ + user.organisation ? `(${user.organisation})` : '' + }` /** * gn-ui-autocomplete */ autoCompleteAction = (query: string) => { + const fuzzyFilter = createFuzzyFilter(query) return this.allUsers$.pipe( switchMap((users) => [ - users.filter((user) => user.username.includes(query)), + users.filter((user) => fuzzyFilter(user.username)), ]), + map((results) => results.slice(0, 10)), debounceTime(300), distinctUntilChanged() ) @@ -159,12 +247,12 @@ export class FormFieldContactsForResourceComponent * gn-ui-autocomplete */ addContact(contact: UserModel, role: string) { - let newContactsForRessource = { + const newContactsForRessource = { firstName: contact.name ?? '', lastName: contact.surname ?? '', - organization: { - name: contact.organisation, - }, + organization: + this.allOrganizations.get(contact.organisation) ?? + ({ name: contact.organisation } as Organization), email: contact.email ?? '', role, address: '', @@ -172,49 +260,12 @@ export class FormFieldContactsForResourceComponent position: '', } as Individual - const newContactOrganization = this.getOrganizationByName( - contact.organisation - ) - - newContactsForRessource = { - ...newContactsForRessource, - organization: newContactOrganization, - } - const newControlValue = [...this.control.value, newContactsForRessource] this.control.setValue(newControlValue) } - getContactsByRole(role: string): Individual[] { - return this.control.value.filter((contact: Individual) => { - return contact.role === role - }) - } - - getOrganizationByName(name: string): Organization { - return this.allOrganizations.get(name) - } - - getContactByRoleForSortableList(role: string): Array { - return this.control.value - .filter((contact: Individual) => { - return contact.role === role - }) - .map((contact) => ({ - component: ContactCardComponent, - inputs: { - contact, - organization: contact.organization, - removable: false, - }, - })) as Array<{ - component: Type - inputs: Record - }> - } - - handleContactOrderChange(event) { - console.log(event) + ngOnDestroy(): void { + this.subscription.unsubscribe() } } diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.html b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.html index ce21d8e1b7..a2ea32005e 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.html +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.html @@ -4,7 +4,7 @@ [displayWithFn]="displayWithFn" [action]="autoCompleteAction" (itemSelected)="handleItemSelection($event)" - [clearOnSelection]="true" + [preventCompleteOnSelection]="true" [minCharacterCount]="0" [allowSubmit]="false" > diff --git a/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.html b/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.html index 11bf550ee3..7c11bb2881 100644 --- a/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.html +++ b/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.html @@ -6,6 +6,6 @@ (inputSubmitted)="handleInputSubmission($event)" (inputCleared)="handleInputCleared()" [value]="searchInputValue$ | async" - [clearOnSelection]="true" + [preventCompleteOnSelection]="true" [autoFocus]="autoFocus" > diff --git a/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.spec.ts b/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.spec.ts index 847aedbfed..f97d73fc49 100644 --- a/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.spec.ts +++ b/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.spec.ts @@ -312,10 +312,10 @@ describe('AutocompleteComponent', () => { it('emits selection event', () => { expect(selectionEmitted).toEqual('first') }) - describe('if completeOnSelection on selection', () => { + describe('if preventCompleteOnSelection on selection', () => { it('set input value to last entered text', () => { component.clearOnSelection = false - component.completeOnSelection = true + component.preventCompleteOnSelection = true component.control.setValue('second') component.handleSelection(selectionEvent) @@ -328,7 +328,7 @@ describe('AutocompleteComponent', () => { describe('if clearOnSelection on selection', () => { it('set input value to empty string', () => { component.clearOnSelection = true - component.completeOnSelection = false + component.preventCompleteOnSelection = false component.control.setValue('second') component.handleSelection(selectionEvent) diff --git a/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts b/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts index 3e5efe0d8a..3d6a2114c3 100644 --- a/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +++ b/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts @@ -61,7 +61,7 @@ export class AutocompleteComponent @Input() action: (value: string) => Observable @Input() value?: AutocompleteItem @Input() clearOnSelection = false - @Input() completeOnSelection = false + @Input() preventCompleteOnSelection = false @Input() autoFocus = false @Input() minCharacterCount? = 3 @Input() allowSubmit = true @@ -207,12 +207,19 @@ export class AutocompleteComponent this.inputSubmitted.emit(this.inputRef.nativeElement.value) } + /** + * This function is triggered when an item is selected in the list of displayed items. + * If preventCompleteOnSelection is true then the input will be left as entered by the user. + * If preventCompleteOnSelection is false (by default) then the input will be completed with the item selected by the user. + * If clearOnSelection is true then the input will be cleared upon selection. + * @param event + */ handleSelection(event: MatAutocompleteSelectedEvent) { this.cancelEnter = true this.itemSelected.emit(event.option.value) - if (this.completeOnSelection) { - this.lastInputValue$.pipe(first()).subscribe((any) => { - this.inputRef.nativeElement.value = any + if (this.preventCompleteOnSelection) { + this.lastInputValue$.pipe(first()).subscribe((lastInputValue) => { + this.inputRef.nativeElement.value = lastInputValue }) return }