From 94b5478de379bec9cdd19f170c9fca90dc37de6a Mon Sep 17 00:00:00 2001 From: Alexander Trakhimenok <533159+trakhimenok@users.noreply.github.com> Date: Sun, 10 Nov 2024 19:12:37 +0000 Subject: [PATCH] fix: improves country selector --- .../src/lib/country-selector/countries.ts | 9 ++- .../country-selector.component.html | 25 +++---- .../country-selector.component.ts | 70 ++++++++++++------- .../select-from-list.component.html | 46 +++++++----- .../select-from-list.component.ts | 7 ++ .../src/lib/selector/selector-interfaces.ts | 2 + .../inlist-age-group.component.ts | 33 ++++++--- .../inlist-options.component.ts | 18 ++--- .../age-group/age-group-form.component.ts | 2 +- .../edit-dwelling-card.component.html | 2 +- 10 files changed, 131 insertions(+), 83 deletions(-) diff --git a/libs/components/src/lib/country-selector/countries.ts b/libs/components/src/lib/country-selector/countries.ts index 0d1c51410..219b117d8 100644 --- a/libs/components/src/lib/country-selector/countries.ts +++ b/libs/components/src/lib/country-selector/countries.ts @@ -1,6 +1,7 @@ export type GeoRegion = | 'Europe' | 'Asia' + | 'Americas' | 'South America' | 'North America' | 'Pacific Ocean' @@ -11,6 +12,8 @@ export interface ICountry { id3: string; geoRegions: GeoRegion[]; title: string; + longTitle?: string; + shortTitle?: string; emoji: string; } @@ -726,7 +729,8 @@ export const countriesByID: Record = { id: 'IR', id3: 'IRN', geoRegions: ['Asia'], - title: 'Iran (Islamic Republic of)', + title: 'Iran', + longTitle: 'Iran (Islamic Republic of)', emoji: '🇮🇷', }, IQ: { @@ -741,6 +745,7 @@ export const countriesByID: Record = { id3: 'IRL', geoRegions: ['Europe'], title: 'Ireland', + longTitle: 'Ireland (Republic of)', emoji: '🇮🇪', }, IM: { @@ -1637,6 +1642,7 @@ export const countriesByID: Record = { id3: 'GBR', geoRegions: ['Europe'], title: 'United Kingdom', + shortTitle: 'UK', emoji: '🇬🇧', }, US: { @@ -1644,6 +1650,7 @@ export const countriesByID: Record = { id3: 'USA', geoRegions: ['North America'], title: 'United States of America', + shortTitle: 'USA', emoji: '🇺🇸', }, UM: { diff --git a/libs/components/src/lib/country-selector/country-selector.component.html b/libs/components/src/lib/country-selector/country-selector.component.html index 41fc12619..9a04b56b4 100644 --- a/libs/components/src/lib/country-selector/country-selector.component.html +++ b/libs/components/src/lib/country-selector/country-selector.component.html @@ -3,31 +3,24 @@ [isFilterable]="true" [filterLabel]="label" [label]="label" - [items]="countries" + [items]="countries()" [(ngModel)]="countryID" (ngModelChange)="onChanged()" [isReadonly]="readonly" [filterItem]="filterCountryByCode" + (filterChanged)="onFilterChanged($event)" labelPlacement="start" > All - Europe - Asia - Americas - Africa - Pacific + @for (region of geoRegions; track region.id) { + + {{ region.id }} + + } diff --git a/libs/components/src/lib/country-selector/country-selector.component.ts b/libs/components/src/lib/country-selector/country-selector.component.ts index 57dfa9e28..e6b29863c 100644 --- a/libs/components/src/lib/country-selector/country-selector.component.ts +++ b/libs/components/src/lib/country-selector/country-selector.component.ts @@ -1,10 +1,12 @@ import { CommonModule } from '@angular/common'; import { Component, + computed, EventEmitter, Input, OnChanges, Output, + signal, SimpleChanges, } from '@angular/core'; import { FormsModule } from '@angular/forms'; @@ -20,6 +22,16 @@ import { countries, GeoRegion, ICountry, unknownCountry } from './countries'; imports: [CommonModule, FormsModule, IonicModule, SelectFromListModule], }) export class CountrySelectorComponent implements OnChanges { + protected readonly geoRegions: readonly { + readonly id: string; + title?: string; + }[] = [ + { id: 'Europe' }, + { id: 'Asia' }, + { id: 'Americas' }, + { id: 'Africa' }, + ]; + @Input({ required: true }) countryID?: string; @Input() defaultCountryID?: string; @@ -31,41 +43,58 @@ export class CountrySelectorComponent implements OnChanges { @Output() readonly countryIDChange = new EventEmitter(); - constructor() { - this.setCountries(); - } + protected readonly filter = signal(''); + protected readonly geoRegion = signal('All'); - protected countries: ISelectItem[] = countries; + protected readonly countries = computed(() => { + const filter = this.filter(); + let countriesToShow: readonly ISelectItem[] = filter + ? countries + : countries.filter( + (c) => + this.geoRegion() === 'All' || + (this.geoRegion() === 'Americas' && + (c.geoRegions.includes('North America') || + c.geoRegions.includes('South America'))) || + c.geoRegions.includes(this.geoRegion() as GeoRegion), + ); + if (this.canBeUnknown) { + countriesToShow = [...countriesToShow, unknownCountry]; + } + return countriesToShow; + }); - protected geoRegion: GeoRegion | 'All' | 'Americas' = 'All'; - private geoRegions: (GeoRegion | 'All')[] = ['All']; + // protected geoRegion: GeoRegion | 'All' | 'Americas' = 'All'; - onChanged(): void { + protected onChanged(): void { console.log('CountrySelectorComponent.onChanged()', this.countryID); this.countryIDChange.emit(this.countryID); } + protected onFilterChanged(filter: string): void { + console.log('CountrySelectorComponent.onFilterChanged()', filter); + this.filter.set(filter); + } + ngOnChanges(changes: SimpleChanges): void { - if (changes['canBeUnknown']) { - this.countries = this.canBeUnknown - ? [...countries, unknownCountry] - : countries; - } if (changes['country'] && this.countryID === '--' && !this.canBeUnknown) { this.countryID = undefined; } if ( changes['defaultCountryID'] && !this.countryID && - this.defaultCountryID + this.defaultCountryID && + this.defaultCountryID !== '--' ) { this.countryID = this.defaultCountryID; this.countryIDChange.emit(this.countryID); } } - onRegionChanged(): void { - this.setCountries(); + protected onRegionChanged(event: Event): void { + this.geoRegion.set( + (event as CustomEvent).detail.value as GeoRegion | 'All' | 'Americas', + ); } protected readonly filterCountryByCode = ( @@ -76,15 +105,4 @@ export class CountrySelectorComponent implements OnChanges { const c = item as ICountry; return c.id === f || c.id3.startsWith(f); }; - - private setCountries(): void { - this.countries = countries.filter( - (c) => - this.geoRegion === 'All' || - (this.geoRegion === 'Americas' && - (c.geoRegions.includes('North America') || - c.geoRegions.includes('South America'))) || - c.geoRegions.includes(this.geoRegion as GeoRegion), - ); - } } diff --git a/libs/components/src/lib/selector/select-from-list/select-from-list.component.html b/libs/components/src/lib/selector/select-from-list/select-from-list.component.html index 070bbb3e0..b92db57b0 100644 --- a/libs/components/src/lib/selector/select-from-list/select-from-list.component.html +++ b/libs/components/src/lib/selector/select-from-list/select-from-list.component.html @@ -10,9 +10,15 @@ > @for (item of items; track item.id) { - {{ item.emoji }} {{ item.title }} + {{ item.emoji }} + @if (item.shortTitle) { + {{ item.shortTitle }} + } @else { + {{ item.title }} + } - } + } + @@ -59,10 +65,7 @@ @for (item of displayItems; track item.id) { - + {{ label }} @for (item of displayItems; track item.id) { - + - {{ item.emoji }} {{ item.title }} + {{ item.emoji }} + @if (item.shortTitle) { + @if (item.longTitle) { + {{ item.longTitle }} - {{ item.shortTitle }} + } @else { + {{ item.title }} - {{ item.shortTitle }} + } + } @else if (item.longTitle) { + {{ item.longTitle }} + } @else { + {{ item.title }} + } } @@ -109,7 +119,7 @@ {{ hiddenCount }} out of {{ items?.length }} items are hidden by filter + >{{ hiddenCount }} out of {{ items?.length }} items are hidden by filter diff --git a/libs/components/src/lib/selector/select-from-list/select-from-list.component.ts b/libs/components/src/lib/selector/select-from-list/select-from-list.component.ts index 7326db776..7a170d17f 100644 --- a/libs/components/src/lib/selector/select-from-list/select-from-list.component.ts +++ b/libs/components/src/lib/selector/select-from-list/select-from-list.component.ts @@ -52,6 +52,8 @@ export class SelectFromListComponent @Input() canAdd = false; @Input() filterItem?: (item: ISelectItem, filter: string) => boolean; + @Output() readonly filterChanged = new EventEmitter(); + @Input() isReadonly = false; // @Input() ngModel?: string; @@ -110,6 +112,7 @@ export class SelectFromListComponent ? this.items?.filter( (v) => v.title.toLowerCase().includes(f) || + v.longTitle?.toLowerCase().includes(f) || (this.filterItem && this.filterItem(v, f)), ) : this.items; @@ -121,11 +124,13 @@ export class SelectFromListComponent console.log('SelectFromListComponent.select()', item); this.value = item.id; this.onChange(this.value); + this.clearFilter(); } protected onRadioChanged(event: Event): void { this.value = (event as CustomEvent).detail['value'] as string; this.onChange(this.value); + this.clearFilter(); } protected onSelectChanged(event: Event): void { @@ -161,10 +166,12 @@ export class SelectFromListComponent protected onFilterChanged(): void { console.log('SelectFromListComponent.onFilterChanged()', this.filter); this.applyFilter(); + this.filterChanged.emit(this.filter); } protected clearFilter(): void { this.filter = ''; + this.filterChanged.emit(this.filter); this.applyFilter(); } diff --git a/libs/components/src/lib/selector/selector-interfaces.ts b/libs/components/src/lib/selector/selector-interfaces.ts index dd73ab494..841e1236c 100644 --- a/libs/components/src/lib/selector/selector-interfaces.ts +++ b/libs/components/src/lib/selector/selector-interfaces.ts @@ -1,6 +1,8 @@ export interface ISelectItem { readonly id: string; readonly title: string; + readonly shortTitle?: string; + readonly longTitle?: string; readonly emoji?: string; readonly iconName?: string; readonly labelColor?: diff --git a/libs/contactus/shared/src/lib/components/inlist-options/inlist-age-group.component.ts b/libs/contactus/shared/src/lib/components/inlist-options/inlist-age-group.component.ts index f3d2d8a27..bf78462a6 100644 --- a/libs/contactus/shared/src/lib/components/inlist-options/inlist-age-group.component.ts +++ b/libs/contactus/shared/src/lib/components/inlist-options/inlist-age-group.component.ts @@ -6,12 +6,16 @@ import { } from '@sneat/contactus-services'; import { ErrorLogger, IErrorLogger } from '@sneat/logging'; import { ISpaceContext } from '@sneat/team-models'; -import { IdEvent, InlistOptionsComponent } from './inlist-options.component'; +import { + OptionEvent, + Option, + InlistOptionsComponent, +} from './inlist-options.component'; @Component({ selector: 'sneat-inlist-age-group', template: - '', + '', standalone: true, imports: [InlistOptionsComponent], }) @@ -21,22 +25,25 @@ export class InlistAgeGroupComponent { private readonly contactService: ContactService, ) {} + protected selectedOption?: Option; + @Input({ required: true }) public space?: ISpaceContext; @Input({ required: true }) public contactID = ''; - protected readonly ageOptions: { id: string; title: string }[] = [ + protected readonly ageOptions: readonly Option[] = [ { id: 'adult', title: 'Adult' }, { id: 'child', title: 'Child' }, ]; - protected onAgeGroupSelected(idEvent: IdEvent): void { + protected onAgeGroupSelected(optionEvent: OptionEvent): void { console.log( 'MembersListComponent.setAgeGroup()', this.contactID, - idEvent.id, + optionEvent.option.id, ); - idEvent.uiEvent.preventDefault(); - idEvent.uiEvent.stopPropagation(); + optionEvent.uiEvent.preventDefault(); + optionEvent.uiEvent.stopPropagation(); + this.selectedOption = optionEvent.option; const spaceID = this.space?.id; if (!spaceID) { return; @@ -44,13 +51,17 @@ export class InlistAgeGroupComponent { const request: IUpdateContactRequest = { spaceID, contactID: this.contactID, - ageGroup: idEvent.id as AgeGroupID, + ageGroup: optionEvent.option.id as AgeGroupID, }; this.contactService.updateContact(request).subscribe({ next: () => console.log('age group updated'), - error: this.errorLogger.logErrorHandler( - 'failed to update contact with age group', - ), + error: (err) => { + this.errorLogger.logError( + err, + 'failed to update contact with age group', + ); + setTimeout(() => (this.selectedOption = undefined), 500); + }, }); } } diff --git a/libs/contactus/shared/src/lib/components/inlist-options/inlist-options.component.ts b/libs/contactus/shared/src/lib/components/inlist-options/inlist-options.component.ts index c5d6a4335..c96a5ee3f 100644 --- a/libs/contactus/shared/src/lib/components/inlist-options/inlist-options.component.ts +++ b/libs/contactus/shared/src/lib/components/inlist-options/inlist-options.component.ts @@ -2,16 +2,16 @@ import { CommonModule } from '@angular/common'; import { Component, EventEmitter, Input, Output } from '@angular/core'; import { IonicModule } from '@ionic/angular'; -export interface IdEvent { - uiEvent: Event; - id: string; -} - -interface Option { +export interface Option { readonly id: string; readonly title?: string; } +export interface OptionEvent { + readonly uiEvent: Event; + readonly option: Option; +} + @Component({ selector: 'sneat-inlist-options', templateUrl: './inlist-options.component.html', @@ -19,8 +19,8 @@ interface Option { imports: [CommonModule, IonicModule], }) export class InlistOptionsComponent { - @Input() public options?: Option[]; - @Output() public readonly optionSelected = new EventEmitter(); + @Input() public options?: readonly Option[]; + @Output() public readonly optionSelected = new EventEmitter(); @Input() public selectedOption?: Option; @@ -28,6 +28,6 @@ export class InlistOptionsComponent { $event.stopPropagation(); $event.preventDefault(); this.selectedOption = selected; - this.optionSelected.emit({ uiEvent: $event, id: selected.id }); + this.optionSelected.emit({ uiEvent: $event, option: selected }); } } diff --git a/libs/contactus/shared/src/lib/components/person-form/age-group/age-group-form.component.ts b/libs/contactus/shared/src/lib/components/person-form/age-group/age-group-form.component.ts index a694564dc..26adf789f 100644 --- a/libs/contactus/shared/src/lib/components/person-form/age-group/age-group-form.component.ts +++ b/libs/contactus/shared/src/lib/components/person-form/age-group/age-group-form.component.ts @@ -23,7 +23,7 @@ export class AgeGroupFormComponent { @Input() hideCompanyOption = false; @Input() hideUndisclosedOption = false; - onAgeGroupChanged(event: Event): void { + protected onAgeGroupChanged(event: Event): void { event.stopPropagation(); this.ageGroupChange.emit(this.ageGroup); } diff --git a/libs/extensions/assetus/components/src/lib/edit-dwelling-card/edit-dwelling-card.component.html b/libs/extensions/assetus/components/src/lib/edit-dwelling-card/edit-dwelling-card.component.html index 65eb6bb7f..7ed30eb1a 100644 --- a/libs/extensions/assetus/components/src/lib/edit-dwelling-card/edit-dwelling-card.component.html +++ b/libs/extensions/assetus/components/src/lib/edit-dwelling-card/edit-dwelling-card.component.html @@ -12,7 +12,7 @@ [countryID]="dwellingAsset?.brief?.countryID" [readonly]="!!dwellingAsset?.brief?.countryID" [defaultCountryID]="space?.dbo?.countryID" - label="Country of registration" + label="Country" (countryIDChange)="onCountryChanged($event)" />