From 10645be6792cbc2d36ea06c38e9fb4a3cecb6bc0 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 9 Apr 2026 18:13:13 +0300 Subject: [PATCH] feat(iot-hub): replace vendor text input with checkbox filter from API Fetch distinct vendor list from IoT Hub API (GET /versions/published/vendors) when DEVICE tab is activated. Render as checkboxes (same pattern as Hardware Type filter). Vendor is the first filter section for devices. Replaces the debounced text input approach with dynamic checkbox list. --- .../src/app/core/http/iot-hub-api.service.ts | 7 +++ .../iot-hub/iot-hub-browse.component.html | 29 +++++++---- .../iot-hub/iot-hub-browse.component.ts | 52 ++++++++++++------- 3 files changed, 58 insertions(+), 30 deletions(-) diff --git a/ui-ngx/src/app/core/http/iot-hub-api.service.ts b/ui-ngx/src/app/core/http/iot-hub-api.service.ts index 26e897eea5..ae3951fca8 100644 --- a/ui-ngx/src/app/core/http/iot-hub-api.service.ts +++ b/ui-ngx/src/app/core/http/iot-hub-api.service.ts @@ -84,6 +84,13 @@ export class IotHubApiService { ); } + public getPublishedDeviceVendors(config?: IotHubRequestConfig): Observable { + return this.http.get( + `${this.baseUrl}/api/versions/published/vendors`, + { params: this.buildParams(config) } + ); + } + public getVersionInfo(versionId: string, config?: IotHubRequestConfig): Observable { return this.http.get( `${this.baseUrl}/api/versions/${versionId}`, diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html index 921ea238f5..0641c14b25 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html @@ -76,17 +76,12 @@ {{ 'iot-hub.vendor' | translate }} - - - @if (vendorFilter) { - - } - + @for (v of availableVendors; track v) { + + {{ v }} + + }
@@ -193,6 +188,12 @@ close } + @for (v of getActiveVendorsArray(); track v) { + + {{ v }} + close + + } @for (key of getActiveUseCasesArray(); track key) { {{ getUseCaseLabel(key) }} @@ -277,6 +278,12 @@ close } + @for (v of getActiveVendorsArray(); track v) { + + {{ v }} + close + + } @for (key of getActiveUseCasesArray(); track key) { {{ getUseCaseLabel(key) }} diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts index fd7be964a3..66fae7a9dc 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts @@ -96,8 +96,8 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { activeRuleChainTypes = new Set(); activeConnectivity = new Set(); activeHardwareTypes = new Set(); - vendorFilter = ''; - private vendorDebounceTimer: any; + activeVendors = new Set(); + availableVendors: string[] = []; sortOptions: SortOption[] = [ { value: 'totalInstallCount', label: 'iot-hub.sort-most-installed', direction: Direction.DESC }, @@ -152,6 +152,8 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { this.loadInstalledWidgets(); } else if (this.activeType === ItemType.SOLUTION_TEMPLATE) { this.loadInstalledSolutionTemplates(); + } else if (this.activeType === ItemType.DEVICE) { + this.loadVendors(); } this.loadItems(); } @@ -183,13 +185,15 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { this.activeRuleChainTypes.clear(); this.activeConnectivity.clear(); this.activeHardwareTypes.clear(); - this.vendorFilter = ''; + this.activeVendors.clear(); this.updateCategories(); this.pageIndex = 0; if (type === ItemType.WIDGET) { this.loadInstalledWidgets(); } else if (type === ItemType.SOLUTION_TEMPLATE) { this.loadInstalledSolutionTemplates(); + } else if (type === ItemType.DEVICE) { + this.loadVendors(); } this.loadItems(); } @@ -322,22 +326,24 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { return this.activeHardwareTypes.has(value); } - onVendorInput(event: Event): void { - const value = (event.target as HTMLInputElement).value?.trim() || ''; - this.vendorFilter = value; - clearTimeout(this.vendorDebounceTimer); - this.vendorDebounceTimer = setTimeout(() => { - this.pageIndex = 0; - this.loadItems(); - }, 400); - } - - clearVendorFilter(): void { - this.vendorFilter = ''; + onVendorToggle(vendor: string): void { + if (this.activeVendors.has(vendor)) { + this.activeVendors.delete(vendor); + } else { + this.activeVendors.add(vendor); + } this.pageIndex = 0; this.loadItems(); } + isVendorActive(vendor: string): boolean { + return this.activeVendors.has(vendor); + } + + getActiveVendorsArray(): string[] { + return Array.from(this.activeVendors); + } + getActiveConnectivityArray(): string[] { return Array.from(this.activeConnectivity); } @@ -442,7 +448,7 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { this.activeRuleChainTypes.clear(); this.activeConnectivity.clear(); this.activeHardwareTypes.clear(); - this.vendorFilter = ''; + this.activeVendors.clear(); if (this.fixedSubType) { this.getActiveSubtypes()?.add(this.fixedSubType); } @@ -455,7 +461,7 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { get activeFilterCount(): number { const subtypeCount = this.fixedSubType ? this.getActiveSubtypesArray().length : (this.getActiveSubtypes()?.size || 0); return subtypeCount + this.activeCategories.size + this.activeUseCases.size + - this.activeConnectivity.size + this.activeHardwareTypes.size + (this.vendorFilter ? 1 : 0); + this.activeConnectivity.size + this.activeHardwareTypes.size + this.activeVendors.size; } hasActiveDropdownFilters(): boolean { @@ -463,7 +469,7 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { return this.activeCategories.size > 0 || this.activeUseCases.size > 0 || subtypeCount > 0 || this.activeConnectivity.size > 0 || this.activeHardwareTypes.size > 0 || - !!this.vendorFilter; + this.activeVendors.size > 0; } hasActiveFilters(): boolean { @@ -573,6 +579,14 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { }); } + private loadVendors(): void { + this.iotHubApiService.getPublishedDeviceVendors({ignoreLoading: true, ignoreErrors: true}).subscribe({ + next: (vendors) => { + this.availableVendors = vendors?.sort() || []; + } + }); + } + private loadInstalledSolutionTemplates(): void { if (this.installedSolutionTemplates !== null) { return; @@ -622,7 +636,7 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { undefined, this.activeHardwareTypes.size > 0 ? Array.from(this.activeHardwareTypes) : undefined, this.activeConnectivity.size > 0 ? Array.from(this.activeConnectivity) : undefined, - this.vendorFilter || undefined + this.activeVendors.size === 1 ? Array.from(this.activeVendors)[0] : undefined ); this.iotHubApiService.getPublishedVersions( query,