From 2d9d43f068022f8415c995a53346c0137bb3299a Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 28 Apr 2025 18:27:35 +0300 Subject: [PATCH] UI: Add unit input group name --- .../app/core/services/unit/converter-unit.ts | 120 +++++++++++++++++- .../app/core/services/unit/definitions/all.ts | 4 +- .../app/core/services/unit/unit.service.ts | 13 +- .../components/unit-input.component.html | 23 +++- .../components/unit-input.component.scss | 8 ++ .../shared/components/unit-input.component.ts | 43 +++++-- ui-ngx/src/app/shared/models/unit.models.ts | 7 +- .../assets/locale/locale.constant-en_US.json | 16 ++- 8 files changed, 208 insertions(+), 26 deletions(-) diff --git a/ui-ngx/src/app/core/services/unit/converter-unit.ts b/ui-ngx/src/app/core/services/unit/converter-unit.ts index 6a33688523..207d943d10 100644 --- a/ui-ngx/src/app/core/services/unit/converter-unit.ts +++ b/ui-ngx/src/app/core/services/unit/converter-unit.ts @@ -14,7 +14,14 @@ /// limitations under the License. /// -import { TbMeasure, TbUnitConvertor, Unit, UnitDescription, UnitSystem } from '@shared/models/unit.models'; +import { + TbMeasure, + TbUnitConvertor, + Unit, + UnitDescription, + UnitDescriptionGroupByMeasure, + UnitSystem +} from '@shared/models/unit.models'; import { AllMeasures } from '@core/services/unit/definitions/all'; import { TranslateService } from '@ngx-translate/core'; import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils'; @@ -340,6 +347,117 @@ export class Converter< return list; } + listGroupByMeasure(measureName?: TMeasures | (string & {}), unitSystem?: UnitSystem): UnitDescriptionGroupByMeasure | never { + const list: UnitDescriptionGroupByMeasure = {}; + + if (isDefinedAndNotNull(measureName)) { + if (!this.isMeasure(measureName)) { + console.log(`Measure "${measureName}" not found.`); + return list; + } + const measure = this.measureData[measureName]; + if (isDefinedAndNotNull(unitSystem)) { + let currentUnitSystem = unitSystem; + let units = measure[currentUnitSystem]; + if (isUndefinedOrNull(units)) { + if (currentUnitSystem === UnitSystem.IMPERIAL) { + currentUnitSystem = UnitSystem.METRIC; + units = measure[currentUnitSystem]; + } + if (!units) { + console.log(`Measure "${measureName}" in ${currentUnitSystem} system is not found.`); + return list; + } + } + list[measureName] = []; + const unitsDescription = list[measureName]; + for (const [abbr, unit] of Object.entries( + units.units + )) { + unitsDescription.push( + this.describeUnit({ + abbr: abbr as TUnits, + measure: measureName as TMeasures, + system: currentUnitSystem, + unit: unit as Unit, + }) + ); + } + } else { + for (const [systemName, units] of Object.entries( + measure + ) as Entries, UnitSystem>[]) { + list[measureName] = []; + const unitsDescription = list[measureName]; + for (const [abbr, unit] of Object.entries( + units.units as Partial> + )) { + unitsDescription.push( + this.describeUnit({ + abbr: abbr as TUnits, + measure: measureName as TMeasures, + system: systemName, + unit: unit as Unit, + }) + ); + } + } + } + } else { + for (const [name, measure] of Object.entries(this.measureData)) { + if (isDefinedAndNotNull(unitSystem)) { + let currentUnitSystem = unitSystem; + let units = (measure as TbMeasure)[currentUnitSystem]?.units; + if (isUndefinedOrNull(units)) { + if (currentUnitSystem === UnitSystem.IMPERIAL) { + currentUnitSystem = UnitSystem.METRIC; + units = (measure as TbMeasure)[currentUnitSystem]?.units; + } + if (!units) { + console.log(`Measure "${name}" in ${currentUnitSystem} system is not found.`); + continue; + } + } + list[name] = []; + const unitsDescription = list[name]; + for (const [abbr, unit] of Object.entries( + units as Partial> + )) { + unitsDescription.push( + this.describeUnit({ + abbr: abbr as TUnits, + measure: name as TMeasures, + system: currentUnitSystem, + unit: unit as Unit, + }) + ); + } + } else { + list[name] = []; + const unitsDescription = list[name]; + for (const [systemName, units] of Object.entries( + measure + ) as Entries, UnitSystem>[]) { + for (const [abbr, unit] of Object.entries( + units.units as Partial> + )) { + unitsDescription.push( + this.describeUnit({ + abbr: abbr as TUnits, + measure: name as TMeasures, + system: systemName, + unit: unit as Unit, + }) + ); + } + } + } + } + } + + return list; + } + private isMeasure(measureName: string): measureName is TMeasures { return measureName in this.measureData; } diff --git a/ui-ngx/src/app/core/services/unit/definitions/all.ts b/ui-ngx/src/app/core/services/unit/definitions/all.ts index 300f985f70..275f9c4c6f 100644 --- a/ui-ngx/src/app/core/services/unit/definitions/all.ts +++ b/ui-ngx/src/app/core/services/unit/definitions/all.ts @@ -33,7 +33,7 @@ export type AllMeasuresUnits = export type AllMeasures = | 'acceleration' | 'angle' - | 'angularAcceleration' + | 'angular-acceleration' | 'area' | 'temperature' | 'time'; @@ -44,7 +44,7 @@ const allMeasures: Record< > = { acceleration, angle, - angularAcceleration, + 'angular-acceleration': angularAcceleration, area, temperature, time, diff --git a/ui-ngx/src/app/core/services/unit/unit.service.ts b/ui-ngx/src/app/core/services/unit/unit.service.ts index 9a977de9e7..f86fd3e36b 100644 --- a/ui-ngx/src/app/core/services/unit/unit.service.ts +++ b/ui-ngx/src/app/core/services/unit/unit.service.ts @@ -16,7 +16,12 @@ import { Injectable } from '@angular/core'; import moment from 'moment-timezone'; -import { TbUnitConvertor, UnitDescription, UnitSystem } from '@shared/models/unit.models'; +import { + TbUnitConvertor, + UnitDescription, + UnitDescriptionGroupByMeasure, + UnitSystem +} from '@shared/models/unit.models'; import { isNotEmptyStr } from '@core/utils'; import { configureMeasurements, Converter } from '@core/services/unit/converter-unit'; import allMeasures, { AllMeasures, AllMeasuresUnits } from '@core/services/unit/definitions/all'; @@ -60,7 +65,11 @@ export class UnitService { } getUnits(measure?: AllMeasures, unitSystem?: UnitSystem): UnitDescription[] { - return this.converter?.list(measure, unitSystem) ?? []; + return this.converter?.list(measure, unitSystem); + } + + getUnitsGroupByMeasure(measure?: AllMeasures, unitSystem?: UnitSystem): UnitDescriptionGroupByMeasure { + return this.converter?.listGroupByMeasure(measure, unitSystem); } getUnitDescription(abbr: AllMeasuresUnits | string): UnitDescription { diff --git a/ui-ngx/src/app/shared/components/unit-input.component.html b/ui-ngx/src/app/shared/components/unit-input.component.html index ab5932dcb9..f338bae697 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.html +++ b/ui-ngx/src/app/shared/components/unit-input.component.html @@ -46,11 +46,24 @@ class="tb-autocomplete tb-unit-input-autocomplete" panelWidth="fit-content" [displayWith]="displayUnitFn.bind(this)"> - @for(unit of filteredUnits | async; track unit.abbr) { - - - - + @for (group of filteredUnits | async; track group[0]) { + @if ((fetchUnits$ | async).length > 1) { + + @for(unit of group[1]; track unit.abbr) { + + + + + } + + } @else { + @for(unit of group[1]; track unit.abbr) { + + + + + } + } } diff --git a/ui-ngx/src/app/shared/components/unit-input.component.scss b/ui-ngx/src/app/shared/components/unit-input.component.scss index 2b88edfd73..e5a4122ef8 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.scss +++ b/ui-ngx/src/app/shared/components/unit-input.component.scss @@ -14,6 +14,14 @@ * limitations under the License. */ .tb-autocomplete.tb-unit-input-autocomplete { + .mat-mdc-optgroup-label { + .mdc-list-item__primary-text { + font-size: 14px; + font-weight: 500; + line-height: 20px; + letter-spacing: 0.2px; + } + } .mat-mdc-option { border-bottom: none; .mdc-list-item__primary-text { diff --git a/ui-ngx/src/app/shared/components/unit-input.component.ts b/ui-ngx/src/app/shared/components/unit-input.component.ts index f53b93b085..312040da2a 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.ts +++ b/ui-ngx/src/app/shared/components/unit-input.component.ts @@ -37,6 +37,7 @@ import { AllMeasures } from '@core/services/unit/definitions/all'; import { UnitService } from '@core/services/unit/unit.service'; import { TbPopoverService } from '@shared/components/popover.service'; import { ConvertUnitSettingsPanelComponent } from '@shared/components/convert-unit-settings-panel.component'; +import { isNotEmptyStr } from '@core/utils'; @Component({ selector: 'tb-unit-input', @@ -76,15 +77,17 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang @Input({transform: booleanAttribute}) allowConverted = false; - filteredUnits: Observable>; + filteredUnits: Observable]>>; searchText = ''; + isGroupOption = false; + private dirty = false; private modelValue: TbUnit | null; - private fetchUnits$: Observable> = null; + fetchUnits$: Observable]>> = null; private propagateChange = (_val: any) => {}; @@ -106,6 +109,9 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang }), mergeMap(symbol => this.fetchUnits(symbol)) ); + if (!!this.measure || !!this.tagFilter) { + this.isGroupOption = true; + } } ngOnChanges(changes: SimpleChanges) { @@ -115,6 +121,11 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang if (propName === 'measure' || propName === 'unitSystem') { this.fetchUnits$ = null; this.dirty = true; + if (!!this.measure || !!this.tagFilter) { + this.isGroupOption = true; + } else { + this.isGroupOption = false; + } } } } @@ -205,21 +216,25 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang } } - private fetchUnits(searchText?: string): Observable> { + private fetchUnits(searchText?: string): Observable]>> { this.searchText = searchText; return this.unitsConstant().pipe( - map(unit => searchUnits(unit, searchText)) + map(unit => this.searchUnit(unit, searchText)) ); } - private unitsConstant(): Observable> { + private unitsConstant(): Observable]>> { if (this.fetchUnits$ === null) { - this.fetchUnits$ = of(this.unitService.getUnits(this.measure, this.unitSystem)).pipe( - map((units) => { + this.fetchUnits$ = of(this.unitService.getUnitsGroupByMeasure(this.measure, this.unitSystem)).pipe( + map(data => { + let objectData = Object.entries(data) as Array<[AllMeasures, UnitDescription[]]>; + if (this.tagFilter) { - units = units.filter(u => u.tags.includes(this.tagFilter)); + objectData = objectData + .map((measure) => [measure[0], measure[1].filter(u => u.tags.includes(this.tagFilter))] as [AllMeasures, UnitDescription[]]) + .filter((measure) => measure[1].length > 0); } - return units; + return objectData; }), shareReplay(1) ); @@ -227,6 +242,16 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang return this.fetchUnits$; } + private searchUnit(units: Array<[AllMeasures, Array]>, searchText?: string): Array<[AllMeasures, Array]> { + if (isNotEmptyStr(searchText)) { + const filterValue = searchText.trim().toUpperCase() + return units + .map(measure => [measure[0], searchUnits(measure[1], filterValue)] as [AllMeasures, UnitDescription[]]) + .filter((measure) => measure[1].length > 0); + } + return units; + } + private getUnitSymbol(value: TbUnit | UnitDescription | null): string { if (value === null) { return ''; diff --git a/ui-ngx/src/app/shared/models/unit.models.ts b/ui-ngx/src/app/shared/models/unit.models.ts index c5322d99e0..60b9e4f753 100644 --- a/ui-ngx/src/app/shared/models/unit.models.ts +++ b/ui-ngx/src/app/shared/models/unit.models.ts @@ -24,6 +24,7 @@ export enum UnitsType { } export type TbUnitConvertor = (value: number) => number; +export type UnitDescriptionGroupByMeasure = Partial>; export interface UnitDescription { abbr: string; @@ -66,11 +67,11 @@ export interface TbMeasureUnits { } const searchUnitTags = (unit: UnitDescription, searchText: string): boolean => - !!unit.tags.find(t => t.toUpperCase().includes(searchText.toUpperCase())); + !!unit.tags.find(t => t.toUpperCase().includes(searchText)); export const searchUnits = (_units: Array, searchText: string): Array => _units.filter( - u => u.abbr.toUpperCase().includes(searchText.toUpperCase()) || - u.name.toUpperCase().includes(searchText.toUpperCase()) || + u => u.abbr.toUpperCase().includes(searchText) || + u.name.toUpperCase().includes(searchText) || searchUnitTags(u, searchText) ); diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index fb3de537cf..560f85dfe6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5849,10 +5849,18 @@ "unit": { "unit-system": "Unit system", "unit-system-type": { - "AUTO": "Auto", - "METRIC": "Metric", - "IMPERIAL": "Imperial", - "HYBRID": "Hybrid" + "AUTO": "Auto", + "METRIC": "Metric", + "IMPERIAL": "Imperial", + "HYBRID": "Hybrid" + }, + "measures": { + "acceleration": "Acceleration", + "angle": "Angle", + "angular-acceleration": "Angular acceleration", + "area": "Area", + "temperature": "Temperature", + "time": "Time" }, "millimeter": "Millimeter", "centimeter": "Centimeter",