From 9b5b8ca71eb356a172dae2b29787a0ddddcc5cd9 Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Fri, 27 Jun 2025 21:23:35 +0000 Subject: [PATCH 1/3] country widget --- .../country/country.component.css | 3 + .../country/country.component.html | 12 + .../country/country.component.ts | 38 +++ .../row-fields/country/country.component.css | 3 + .../row-fields/country/country.component.html | 12 + .../row-fields/country/country.component.ts | 39 +++ frontend/src/app/consts/countries.ts | 256 ++++++++++++++++++ frontend/src/app/consts/field-types.ts | 2 + frontend/src/app/consts/filter-types.ts | 4 +- 9 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 frontend/src/app/components/ui-components/filter-fields/country/country.component.css create mode 100644 frontend/src/app/components/ui-components/filter-fields/country/country.component.html create mode 100644 frontend/src/app/components/ui-components/filter-fields/country/country.component.ts create mode 100644 frontend/src/app/components/ui-components/row-fields/country/country.component.css create mode 100644 frontend/src/app/components/ui-components/row-fields/country/country.component.html create mode 100644 frontend/src/app/components/ui-components/row-fields/country/country.component.ts create mode 100644 frontend/src/app/consts/countries.ts diff --git a/frontend/src/app/components/ui-components/filter-fields/country/country.component.css b/frontend/src/app/components/ui-components/filter-fields/country/country.component.css new file mode 100644 index 000000000..6ed2dfbd1 --- /dev/null +++ b/frontend/src/app/components/ui-components/filter-fields/country/country.component.css @@ -0,0 +1,3 @@ +.country-form-field { + width: 100%; +} \ No newline at end of file diff --git a/frontend/src/app/components/ui-components/filter-fields/country/country.component.html b/frontend/src/app/components/ui-components/filter-fields/country/country.component.html new file mode 100644 index 000000000..753077c48 --- /dev/null +++ b/frontend/src/app/components/ui-components/filter-fields/country/country.component.html @@ -0,0 +1,12 @@ + + {{normalizedLabel}} + + + {{country.label}} + + + \ No newline at end of file diff --git a/frontend/src/app/components/ui-components/filter-fields/country/country.component.ts b/frontend/src/app/components/ui-components/filter-fields/country/country.component.ts new file mode 100644 index 000000000..dc2b7f293 --- /dev/null +++ b/frontend/src/app/components/ui-components/filter-fields/country/country.component.ts @@ -0,0 +1,38 @@ +import { CUSTOM_ELEMENTS_SCHEMA, Component, Input } from '@angular/core'; + +import { BaseFilterFieldComponent } from '../base-filter-field/base-filter-field.component'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { MatSelectModule } from '@angular/material/select'; +import { COUNTRIES } from '../../../../consts/countries'; + +@Component({ + selector: 'app-filter-country', + templateUrl: './country.component.html', + styleUrls: ['./country.component.css'], + imports: [CommonModule, FormsModule, MatSelectModule], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class CountryFilterComponent extends BaseFilterFieldComponent { + @Input() value: string; + + public countries: {value: string | null, label: string}[] = []; + + originalOrder = () => { return 0; } + + ngOnInit(): void { + super.ngOnInit(); + this.loadCountries(); + } + + private loadCountries(): void { + this.countries = COUNTRIES.map(country => ({ + value: country.name, + label: country.name + })); + + if (this.widgetStructure?.widget_params?.allow_null || this.structure?.allow_null) { + this.countries = [{ value: null, label: '' }, ...this.countries]; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/components/ui-components/row-fields/country/country.component.css b/frontend/src/app/components/ui-components/row-fields/country/country.component.css new file mode 100644 index 000000000..6ed2dfbd1 --- /dev/null +++ b/frontend/src/app/components/ui-components/row-fields/country/country.component.css @@ -0,0 +1,3 @@ +.country-form-field { + width: 100%; +} \ No newline at end of file diff --git a/frontend/src/app/components/ui-components/row-fields/country/country.component.html b/frontend/src/app/components/ui-components/row-fields/country/country.component.html new file mode 100644 index 000000000..753077c48 --- /dev/null +++ b/frontend/src/app/components/ui-components/row-fields/country/country.component.html @@ -0,0 +1,12 @@ + + {{normalizedLabel}} + + + {{country.label}} + + + \ No newline at end of file diff --git a/frontend/src/app/components/ui-components/row-fields/country/country.component.ts b/frontend/src/app/components/ui-components/row-fields/country/country.component.ts new file mode 100644 index 000000000..504f3c334 --- /dev/null +++ b/frontend/src/app/components/ui-components/row-fields/country/country.component.ts @@ -0,0 +1,39 @@ +import { CUSTOM_ELEMENTS_SCHEMA, Component, Input } from '@angular/core'; + +import { BaseRowFieldComponent } from '../base-row-field/base-row-field.component'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { COUNTRIES } from '../../../../consts/countries'; + +@Component({ + selector: 'app-row-country', + imports: [CommonModule, FormsModule, MatFormFieldModule, MatSelectModule], + templateUrl: './country.component.html', + styleUrls: ['./country.component.css'], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class CountryRowComponent extends BaseRowFieldComponent { + @Input() value: string; + + public countries: {value: string | null, label: string}[] = []; + + originalOrder = () => { return 0; } + + ngOnInit(): void { + super.ngOnInit(); + this.loadCountries(); + } + + private loadCountries(): void { + this.countries = COUNTRIES.map(country => ({ + value: country.name, + label: country.name + })); + + if (this.widgetStructure?.widget_params?.allow_null || this.structure?.allow_null) { + this.countries = [{ value: null, label: '' }, ...this.countries]; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/consts/countries.ts b/frontend/src/app/consts/countries.ts new file mode 100644 index 000000000..6537882a8 --- /dev/null +++ b/frontend/src/app/consts/countries.ts @@ -0,0 +1,256 @@ +export interface Country { + code: string; + name: string; +} + +export const COUNTRIES: Country[] = [ + { code: 'AD', name: 'Andorra' }, + { code: 'AE', name: 'United Arab Emirates' }, + { code: 'AF', name: 'Afghanistan' }, + { code: 'AG', name: 'Antigua and Barbuda' }, + { code: 'AI', name: 'Anguilla' }, + { code: 'AL', name: 'Albania' }, + { code: 'AM', name: 'Armenia' }, + { code: 'AO', name: 'Angola' }, + { code: 'AQ', name: 'Antarctica' }, + { code: 'AR', name: 'Argentina' }, + { code: 'AS', name: 'American Samoa' }, + { code: 'AT', name: 'Austria' }, + { code: 'AU', name: 'Australia' }, + { code: 'AW', name: 'Aruba' }, + { code: 'AX', name: 'Åland Islands' }, + { code: 'AZ', name: 'Azerbaijan' }, + { code: 'BA', name: 'Bosnia and Herzegovina' }, + { code: 'BB', name: 'Barbados' }, + { code: 'BD', name: 'Bangladesh' }, + { code: 'BE', name: 'Belgium' }, + { code: 'BF', name: 'Burkina Faso' }, + { code: 'BG', name: 'Bulgaria' }, + { code: 'BH', name: 'Bahrain' }, + { code: 'BI', name: 'Burundi' }, + { code: 'BJ', name: 'Benin' }, + { code: 'BL', name: 'Saint Barthélemy' }, + { code: 'BM', name: 'Bermuda' }, + { code: 'BN', name: 'Brunei Darussalam' }, + { code: 'BO', name: 'Bolivia' }, + { code: 'BQ', name: 'Bonaire, Sint Eustatius and Saba' }, + { code: 'BR', name: 'Brazil' }, + { code: 'BS', name: 'Bahamas' }, + { code: 'BT', name: 'Bhutan' }, + { code: 'BV', name: 'Bouvet Island' }, + { code: 'BW', name: 'Botswana' }, + { code: 'BY', name: 'Belarus' }, + { code: 'BZ', name: 'Belize' }, + { code: 'CA', name: 'Canada' }, + { code: 'CC', name: 'Cocos (Keeling) Islands' }, + { code: 'CD', name: 'Congo, Democratic Republic of the' }, + { code: 'CF', name: 'Central African Republic' }, + { code: 'CG', name: 'Congo' }, + { code: 'CH', name: 'Switzerland' }, + { code: 'CI', name: 'Côte d\'Ivoire' }, + { code: 'CK', name: 'Cook Islands' }, + { code: 'CL', name: 'Chile' }, + { code: 'CM', name: 'Cameroon' }, + { code: 'CN', name: 'China' }, + { code: 'CO', name: 'Colombia' }, + { code: 'CR', name: 'Costa Rica' }, + { code: 'CU', name: 'Cuba' }, + { code: 'CV', name: 'Cape Verde' }, + { code: 'CW', name: 'Curaçao' }, + { code: 'CX', name: 'Christmas Island' }, + { code: 'CY', name: 'Cyprus' }, + { code: 'CZ', name: 'Czech Republic' }, + { code: 'DE', name: 'Germany' }, + { code: 'DJ', name: 'Djibouti' }, + { code: 'DK', name: 'Denmark' }, + { code: 'DM', name: 'Dominica' }, + { code: 'DO', name: 'Dominican Republic' }, + { code: 'DZ', name: 'Algeria' }, + { code: 'EC', name: 'Ecuador' }, + { code: 'EE', name: 'Estonia' }, + { code: 'EG', name: 'Egypt' }, + { code: 'EH', name: 'Western Sahara' }, + { code: 'ER', name: 'Eritrea' }, + { code: 'ES', name: 'Spain' }, + { code: 'ET', name: 'Ethiopia' }, + { code: 'FI', name: 'Finland' }, + { code: 'FJ', name: 'Fiji' }, + { code: 'FK', name: 'Falkland Islands (Malvinas)' }, + { code: 'FM', name: 'Micronesia, Federated States of' }, + { code: 'FO', name: 'Faroe Islands' }, + { code: 'FR', name: 'France' }, + { code: 'GA', name: 'Gabon' }, + { code: 'GB', name: 'United Kingdom' }, + { code: 'GD', name: 'Grenada' }, + { code: 'GE', name: 'Georgia' }, + { code: 'GF', name: 'French Guiana' }, + { code: 'GG', name: 'Guernsey' }, + { code: 'GH', name: 'Ghana' }, + { code: 'GI', name: 'Gibraltar' }, + { code: 'GL', name: 'Greenland' }, + { code: 'GM', name: 'Gambia' }, + { code: 'GN', name: 'Guinea' }, + { code: 'GP', name: 'Guadeloupe' }, + { code: 'GQ', name: 'Equatorial Guinea' }, + { code: 'GR', name: 'Greece' }, + { code: 'GS', name: 'South Georgia and the South Sandwich Islands' }, + { code: 'GT', name: 'Guatemala' }, + { code: 'GU', name: 'Guam' }, + { code: 'GW', name: 'Guinea-Bissau' }, + { code: 'GY', name: 'Guyana' }, + { code: 'HK', name: 'Hong Kong' }, + { code: 'HM', name: 'Heard Island and McDonald Islands' }, + { code: 'HN', name: 'Honduras' }, + { code: 'HR', name: 'Croatia' }, + { code: 'HT', name: 'Haiti' }, + { code: 'HU', name: 'Hungary' }, + { code: 'ID', name: 'Indonesia' }, + { code: 'IE', name: 'Ireland' }, + { code: 'IL', name: 'Israel' }, + { code: 'IM', name: 'Isle of Man' }, + { code: 'IN', name: 'India' }, + { code: 'IO', name: 'British Indian Ocean Territory' }, + { code: 'IQ', name: 'Iraq' }, + { code: 'IR', name: 'Iran, Islamic Republic of' }, + { code: 'IS', name: 'Iceland' }, + { code: 'IT', name: 'Italy' }, + { code: 'JE', name: 'Jersey' }, + { code: 'JM', name: 'Jamaica' }, + { code: 'JO', name: 'Jordan' }, + { code: 'JP', name: 'Japan' }, + { code: 'KE', name: 'Kenya' }, + { code: 'KG', name: 'Kyrgyzstan' }, + { code: 'KH', name: 'Cambodia' }, + { code: 'KI', name: 'Kiribati' }, + { code: 'KM', name: 'Comoros' }, + { code: 'KN', name: 'Saint Kitts and Nevis' }, + { code: 'KP', name: 'Korea, Democratic People\'s Republic of' }, + { code: 'KR', name: 'Korea, Republic of' }, + { code: 'KW', name: 'Kuwait' }, + { code: 'KY', name: 'Cayman Islands' }, + { code: 'KZ', name: 'Kazakhstan' }, + { code: 'LA', name: 'Lao People\'s Democratic Republic' }, + { code: 'LB', name: 'Lebanon' }, + { code: 'LC', name: 'Saint Lucia' }, + { code: 'LI', name: 'Liechtenstein' }, + { code: 'LK', name: 'Sri Lanka' }, + { code: 'LR', name: 'Liberia' }, + { code: 'LS', name: 'Lesotho' }, + { code: 'LT', name: 'Lithuania' }, + { code: 'LU', name: 'Luxembourg' }, + { code: 'LV', name: 'Latvia' }, + { code: 'LY', name: 'Libya' }, + { code: 'MA', name: 'Morocco' }, + { code: 'MC', name: 'Monaco' }, + { code: 'MD', name: 'Moldova, Republic of' }, + { code: 'ME', name: 'Montenegro' }, + { code: 'MF', name: 'Saint Martin (French part)' }, + { code: 'MG', name: 'Madagascar' }, + { code: 'MH', name: 'Marshall Islands' }, + { code: 'MK', name: 'North Macedonia' }, + { code: 'ML', name: 'Mali' }, + { code: 'MM', name: 'Myanmar' }, + { code: 'MN', name: 'Mongolia' }, + { code: 'MO', name: 'Macao' }, + { code: 'MP', name: 'Northern Mariana Islands' }, + { code: 'MQ', name: 'Martinique' }, + { code: 'MR', name: 'Mauritania' }, + { code: 'MS', name: 'Montserrat' }, + { code: 'MT', name: 'Malta' }, + { code: 'MU', name: 'Mauritius' }, + { code: 'MV', name: 'Maldives' }, + { code: 'MW', name: 'Malawi' }, + { code: 'MX', name: 'Mexico' }, + { code: 'MY', name: 'Malaysia' }, + { code: 'MZ', name: 'Mozambique' }, + { code: 'NA', name: 'Namibia' }, + { code: 'NC', name: 'New Caledonia' }, + { code: 'NE', name: 'Niger' }, + { code: 'NF', name: 'Norfolk Island' }, + { code: 'NG', name: 'Nigeria' }, + { code: 'NI', name: 'Nicaragua' }, + { code: 'NL', name: 'Netherlands' }, + { code: 'NO', name: 'Norway' }, + { code: 'NP', name: 'Nepal' }, + { code: 'NR', name: 'Nauru' }, + { code: 'NU', name: 'Niue' }, + { code: 'NZ', name: 'New Zealand' }, + { code: 'OM', name: 'Oman' }, + { code: 'PA', name: 'Panama' }, + { code: 'PE', name: 'Peru' }, + { code: 'PF', name: 'French Polynesia' }, + { code: 'PG', name: 'Papua New Guinea' }, + { code: 'PH', name: 'Philippines' }, + { code: 'PK', name: 'Pakistan' }, + { code: 'PL', name: 'Poland' }, + { code: 'PM', name: 'Saint Pierre and Miquelon' }, + { code: 'PN', name: 'Pitcairn' }, + { code: 'PR', name: 'Puerto Rico' }, + { code: 'PS', name: 'Palestine, State of' }, + { code: 'PT', name: 'Portugal' }, + { code: 'PW', name: 'Palau' }, + { code: 'PY', name: 'Paraguay' }, + { code: 'QA', name: 'Qatar' }, + { code: 'RE', name: 'Réunion' }, + { code: 'RO', name: 'Romania' }, + { code: 'RS', name: 'Serbia' }, + { code: 'RU', name: 'Russian Federation' }, + { code: 'RW', name: 'Rwanda' }, + { code: 'SA', name: 'Saudi Arabia' }, + { code: 'SB', name: 'Solomon Islands' }, + { code: 'SC', name: 'Seychelles' }, + { code: 'SD', name: 'Sudan' }, + { code: 'SE', name: 'Sweden' }, + { code: 'SG', name: 'Singapore' }, + { code: 'SH', name: 'Saint Helena, Ascension and Tristan da Cunha' }, + { code: 'SI', name: 'Slovenia' }, + { code: 'SJ', name: 'Svalbard and Jan Mayen' }, + { code: 'SK', name: 'Slovakia' }, + { code: 'SL', name: 'Sierra Leone' }, + { code: 'SM', name: 'San Marino' }, + { code: 'SN', name: 'Senegal' }, + { code: 'SO', name: 'Somalia' }, + { code: 'SR', name: 'Suriname' }, + { code: 'SS', name: 'South Sudan' }, + { code: 'ST', name: 'Sao Tome and Principe' }, + { code: 'SV', name: 'El Salvador' }, + { code: 'SX', name: 'Sint Maarten (Dutch part)' }, + { code: 'SY', name: 'Syrian Arab Republic' }, + { code: 'SZ', name: 'Eswatini' }, + { code: 'TC', name: 'Turks and Caicos Islands' }, + { code: 'TD', name: 'Chad' }, + { code: 'TF', name: 'French Southern Territories' }, + { code: 'TG', name: 'Togo' }, + { code: 'TH', name: 'Thailand' }, + { code: 'TJ', name: 'Tajikistan' }, + { code: 'TK', name: 'Tokelau' }, + { code: 'TL', name: 'Timor-Leste' }, + { code: 'TM', name: 'Turkmenistan' }, + { code: 'TN', name: 'Tunisia' }, + { code: 'TO', name: 'Tonga' }, + { code: 'TR', name: 'Turkey' }, + { code: 'TT', name: 'Trinidad and Tobago' }, + { code: 'TV', name: 'Tuvalu' }, + { code: 'TW', name: 'Taiwan, Province of China' }, + { code: 'TZ', name: 'Tanzania, United Republic of' }, + { code: 'UA', name: 'Ukraine' }, + { code: 'UG', name: 'Uganda' }, + { code: 'UM', name: 'United States Minor Outlying Islands' }, + { code: 'US', name: 'United States' }, + { code: 'UY', name: 'Uruguay' }, + { code: 'UZ', name: 'Uzbekistan' }, + { code: 'VA', name: 'Holy See (Vatican City State)' }, + { code: 'VC', name: 'Saint Vincent and the Grenadines' }, + { code: 'VE', name: 'Venezuela' }, + { code: 'VG', name: 'Virgin Islands, British' }, + { code: 'VI', name: 'Virgin Islands, U.S.' }, + { code: 'VN', name: 'Viet Nam' }, + { code: 'VU', name: 'Vanuatu' }, + { code: 'WF', name: 'Wallis and Futuna' }, + { code: 'WS', name: 'Samoa' }, + { code: 'YE', name: 'Yemen' }, + { code: 'YT', name: 'Mayotte' }, + { code: 'ZA', name: 'South Africa' }, + { code: 'ZM', name: 'Zambia' }, + { code: 'ZW', name: 'Zimbabwe' } +]; \ No newline at end of file diff --git a/frontend/src/app/consts/field-types.ts b/frontend/src/app/consts/field-types.ts index 4262b1179..8c2f3edc8 100644 --- a/frontend/src/app/consts/field-types.ts +++ b/frontend/src/app/consts/field-types.ts @@ -18,6 +18,7 @@ import { FileRowComponent } from '../components/ui-components/row-fields/file/fi import { CodeRowComponent } from '../components/ui-components/row-fields/code/code.component'; import { ImageRowComponent } from '../components/ui-components/row-fields/image/image.component'; import { UrlRowComponent } from '../components/ui-components/row-fields/url/url.component'; +import { CountryRowComponent } from '../components/ui-components/row-fields/country/country.component'; export const timestampTypes = ['timestamp without time zone', 'timestamp with time zone', 'timestamp', 'date', 'time without time zone', 'time with time zone' , 'time', 'datetime', 'date time', 'datetime2', 'datetimeoffset', 'curdate', 'curtime', 'now', 'localtime', 'localtimestamp']; export const defaultTimestampValues = { @@ -42,6 +43,7 @@ export const UIwidgets = { Code: CodeRowComponent, Image: ImageRowComponent, URL: UrlRowComponent, + Country: CountryRowComponent, Foreign_key: ForeignKeyRowComponent, } diff --git a/frontend/src/app/consts/filter-types.ts b/frontend/src/app/consts/filter-types.ts index 8a9df7aeb..29fe6b050 100644 --- a/frontend/src/app/consts/filter-types.ts +++ b/frontend/src/app/consts/filter-types.ts @@ -15,6 +15,7 @@ import { TimeFilterComponent } from '../components/ui-components/filter-fields/t import { TimeIntervalFilterComponent } from '../components/ui-components/filter-fields/time-interval/time-interval.component'; import { IdFilterComponent } from '../components/ui-components/filter-fields/id/id.component'; import { FileFilterComponent } from '../components/ui-components/filter-fields/file/file.component'; +import { CountryFilterComponent } from '../components/ui-components/filter-fields/country/country.component'; export const UIwidgets = { Default: '', @@ -29,7 +30,8 @@ export const UIwidgets = { Number: NumberFilterComponent, Select: SelectFilterComponent, Password: PasswordFilterComponent, - File: FileFilterComponent + File: FileFilterComponent, + Country: CountryFilterComponent } export const filterTypes = { From fd63488531fa3e0dcfde5dc5983043e23bea89a1 Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Wed, 2 Jul 2025 09:37:45 +0000 Subject: [PATCH 2/3] countries improvements --- .../country/country.component.css | 20 +++++++ .../country/country.component.html | 17 ++++-- .../country/country.component.ts | 60 ++++++++++++++++--- .../row-fields/country/country.component.css | 20 +++++++ .../row-fields/country/country.component.html | 17 ++++-- .../row-fields/country/country.component.ts | 59 +++++++++++++++--- frontend/src/app/consts/countries.ts | 9 +++ 7 files changed, 176 insertions(+), 26 deletions(-) diff --git a/frontend/src/app/components/ui-components/filter-fields/country/country.component.css b/frontend/src/app/components/ui-components/filter-fields/country/country.component.css index 6ed2dfbd1..8301b28e1 100644 --- a/frontend/src/app/components/ui-components/filter-fields/country/country.component.css +++ b/frontend/src/app/components/ui-components/filter-fields/country/country.component.css @@ -1,3 +1,23 @@ .country-form-field { width: 100%; +} + +.country-flag { + margin-right: 8px; + font-size: 16px; +} + +.country-name { + flex: 1; +} + +.country-code { + margin-left: 8px; + opacity: 0.7; + font-size: 12px; +} + +mat-option { + display: flex; + align-items: center; } \ No newline at end of file diff --git a/frontend/src/app/components/ui-components/filter-fields/country/country.component.html b/frontend/src/app/components/ui-components/filter-fields/country/country.component.html index 753077c48..23a3dabdd 100644 --- a/frontend/src/app/components/ui-components/filter-fields/country/country.component.html +++ b/frontend/src/app/components/ui-components/filter-fields/country/country.component.html @@ -1,12 +1,17 @@ {{normalizedLabel}} - - - {{country.label}} + [formControl]="countryControl" + [matAutocomplete]="auto"> + + + {{country.flag}} + {{country.label}} + ({{country.value}}) - + \ No newline at end of file diff --git a/frontend/src/app/components/ui-components/filter-fields/country/country.component.ts b/frontend/src/app/components/ui-components/filter-fields/country/country.component.ts index dc2b7f293..6cea24064 100644 --- a/frontend/src/app/components/ui-components/filter-fields/country/country.component.ts +++ b/frontend/src/app/components/ui-components/filter-fields/country/country.component.ts @@ -3,36 +3,82 @@ import { CUSTOM_ELEMENTS_SCHEMA, Component, Input } from '@angular/core'; import { BaseFilterFieldComponent } from '../base-filter-field/base-filter-field.component'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; -import { MatSelectModule } from '@angular/material/select'; -import { COUNTRIES } from '../../../../consts/countries'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatInputModule } from '@angular/material/input'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { COUNTRIES, getCountryFlag } from '../../../../consts/countries'; +import { map, startWith } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { FormControl } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; @Component({ selector: 'app-filter-country', templateUrl: './country.component.html', styleUrls: ['./country.component.css'], - imports: [CommonModule, FormsModule, MatSelectModule], + imports: [CommonModule, FormsModule, ReactiveFormsModule, MatFormFieldModule, MatAutocompleteModule, MatInputModule], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class CountryFilterComponent extends BaseFilterFieldComponent { @Input() value: string; - public countries: {value: string | null, label: string}[] = []; + public countries: {value: string | null, label: string, flag: string}[] = []; + public countryControl = new FormControl(''); + public filteredCountries: Observable<{value: string | null, label: string, flag: string}[]>; originalOrder = () => { return 0; } + getCountryFlag = getCountryFlag; + ngOnInit(): void { super.ngOnInit(); this.loadCountries(); + this.setupAutocomplete(); + this.setInitialValue(); + } + + private setupAutocomplete(): void { + this.filteredCountries = this.countryControl.valueChanges.pipe( + startWith(''), + map(value => this._filter(value || '')) + ); + } + + private setInitialValue(): void { + if (this.value) { + const country = this.countries.find(c => c.value === this.value); + if (country) { + this.countryControl.setValue(country.label); + } + } + } + + private _filter(value: string): {value: string | null, label: string, flag: string}[] { + const filterValue = value.toLowerCase(); + return this.countries.filter(country => + country.label?.toLowerCase().includes(filterValue) || + (country.value && country.value.toLowerCase().includes(filterValue)) + ); + } + + onCountrySelected(selectedCountry: {value: string | null, label: string, flag: string}): void { + this.value = selectedCountry.value; + this.onFieldChange.emit(this.value); + } + + displayFn(country: any): string { + return country ? country.label : ''; } private loadCountries(): void { this.countries = COUNTRIES.map(country => ({ - value: country.name, - label: country.name + value: country.code, + label: country.name, + flag: getCountryFlag(country.code) })); if (this.widgetStructure?.widget_params?.allow_null || this.structure?.allow_null) { - this.countries = [{ value: null, label: '' }, ...this.countries]; + this.countries = [{ value: null, label: '', flag: '' }, ...this.countries]; } } } \ No newline at end of file diff --git a/frontend/src/app/components/ui-components/row-fields/country/country.component.css b/frontend/src/app/components/ui-components/row-fields/country/country.component.css index 6ed2dfbd1..8301b28e1 100644 --- a/frontend/src/app/components/ui-components/row-fields/country/country.component.css +++ b/frontend/src/app/components/ui-components/row-fields/country/country.component.css @@ -1,3 +1,23 @@ .country-form-field { width: 100%; +} + +.country-flag { + margin-right: 8px; + font-size: 16px; +} + +.country-name { + flex: 1; +} + +.country-code { + margin-left: 8px; + opacity: 0.7; + font-size: 12px; +} + +mat-option { + display: flex; + align-items: center; } \ No newline at end of file diff --git a/frontend/src/app/components/ui-components/row-fields/country/country.component.html b/frontend/src/app/components/ui-components/row-fields/country/country.component.html index 753077c48..23a3dabdd 100644 --- a/frontend/src/app/components/ui-components/row-fields/country/country.component.html +++ b/frontend/src/app/components/ui-components/row-fields/country/country.component.html @@ -1,12 +1,17 @@ {{normalizedLabel}} - - - {{country.label}} + [formControl]="countryControl" + [matAutocomplete]="auto"> + + + {{country.flag}} + {{country.label}} + ({{country.value}}) - + \ No newline at end of file diff --git a/frontend/src/app/components/ui-components/row-fields/country/country.component.ts b/frontend/src/app/components/ui-components/row-fields/country/country.component.ts index 504f3c334..df0432acc 100644 --- a/frontend/src/app/components/ui-components/row-fields/country/country.component.ts +++ b/frontend/src/app/components/ui-components/row-fields/country/country.component.ts @@ -4,12 +4,17 @@ import { BaseRowFieldComponent } from '../base-row-field/base-row-field.componen import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; -import { COUNTRIES } from '../../../../consts/countries'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatInputModule } from '@angular/material/input'; +import { COUNTRIES, getCountryFlag } from '../../../../consts/countries'; +import { map, startWith } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { FormControl } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; @Component({ selector: 'app-row-country', - imports: [CommonModule, FormsModule, MatFormFieldModule, MatSelectModule], + imports: [CommonModule, FormsModule, ReactiveFormsModule, MatFormFieldModule, MatAutocompleteModule, MatInputModule], templateUrl: './country.component.html', styleUrls: ['./country.component.css'], schemas: [CUSTOM_ELEMENTS_SCHEMA] @@ -17,23 +22,63 @@ import { COUNTRIES } from '../../../../consts/countries'; export class CountryRowComponent extends BaseRowFieldComponent { @Input() value: string; - public countries: {value: string | null, label: string}[] = []; + public countries: {value: string | null, label: string, flag: string}[] = []; + public countryControl = new FormControl(''); + public filteredCountries: Observable<{value: string | null, label: string, flag: string}[]>; originalOrder = () => { return 0; } + getCountryFlag = getCountryFlag; + ngOnInit(): void { super.ngOnInit(); this.loadCountries(); + this.setupAutocomplete(); + this.setInitialValue(); + } + + private setupAutocomplete(): void { + this.filteredCountries = this.countryControl.valueChanges.pipe( + startWith(''), + map(value => this._filter(value || '')) + ); + } + + private setInitialValue(): void { + if (this.value) { + const country = this.countries.find(c => c.value === this.value); + if (country) { + this.countryControl.setValue(country.label); + } + } + } + + private _filter(value: string): {value: string | null, label: string, flag: string}[] { + const filterValue = value.toLowerCase(); + return this.countries.filter(country => + country.label?.toLowerCase().includes(filterValue) || + (country.value && country.value.toLowerCase().includes(filterValue)) + ); + } + + onCountrySelected(selectedCountry: {value: string | null, label: string, flag: string}): void { + this.value = selectedCountry.value; + this.onFieldChange.emit(this.value); + } + + displayFn(country: any): string { + return country ? country.label : ''; } private loadCountries(): void { this.countries = COUNTRIES.map(country => ({ - value: country.name, - label: country.name + value: country.code, + label: country.name, + flag: getCountryFlag(country.code) })); if (this.widgetStructure?.widget_params?.allow_null || this.structure?.allow_null) { - this.countries = [{ value: null, label: '' }, ...this.countries]; + this.countries = [{ value: null, label: '', flag: '' }, ...this.countries]; } } } \ No newline at end of file diff --git a/frontend/src/app/consts/countries.ts b/frontend/src/app/consts/countries.ts index 6537882a8..c25680735 100644 --- a/frontend/src/app/consts/countries.ts +++ b/frontend/src/app/consts/countries.ts @@ -3,6 +3,15 @@ export interface Country { name: string; } +export function getCountryFlag(countryCode: string): string { + if (!countryCode || countryCode.length !== 2) return ''; + const codePoints = countryCode + .toUpperCase() + .split('') + .map(char => 127397 + char.charCodeAt(0)); + return String.fromCodePoint(...codePoints); +} + export const COUNTRIES: Country[] = [ { code: 'AD', name: 'Andorra' }, { code: 'AE', name: 'United Arab Emirates' }, From bd1687f4cee3d3feac20b5e027937644b5552286 Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Wed, 2 Jul 2025 12:20:24 +0000 Subject: [PATCH 3/3] fix yarn lock --- yarn.lock | 9 --------- 1 file changed, 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index 045c13af0..563ddae5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10569,13 +10569,6 @@ __metadata: languageName: node linkType: hard -"libphonenumber-js@npm:^1.12.9": - version: 1.12.9 - resolution: "libphonenumber-js@npm:1.12.9" - checksum: 9d01151ffa1d0f634ebbc4e7d5cde6baa7c53765e162745cb2dd85815e04b42847ff2e4c54fe172121c335c86cd6f5ce9192af62237099213e799de653d5a6dd - languageName: node - linkType: hard - "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -12928,8 +12921,6 @@ __metadata: "root@workspace:.": version: 0.0.0-use.local resolution: "root@workspace:." - dependencies: - libphonenumber-js: ^1.12.9 languageName: unknown linkType: soft