diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html index 087d320f83..16ebbdf8f7 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html @@ -200,19 +200,41 @@
{{ 'iot-hub.filters' | translate }}
-
+ +
{{ 'iot-hub.type' | translate }} - {{ iotHubPendingWidgetTypes.size }} + @if (iotHubPendingWidgetTypes.size) { + {{ iotHubPendingWidgetTypes.size }} + } -
- - {{ getIotHubWidgetTypeLabel(opt.key) }} - + @if (iotHubWidgetTypeOptions.length >= 10) { + + } +
+ @for (opt of getFilteredIotHubItems(iotHubWidgetTypeOptions, 'widgetType'); track opt.key) { +
+ + {{ getIotHubWidgetTypeLabel(opt.key) }} + +
+ }
@@ -220,14 +242,35 @@ {{ 'iot-hub.category' | translate }} - {{ iotHubPendingCategories.size }} + @if (iotHubPendingCategories.size) { + {{ iotHubPendingCategories.size }} + } -
- - {{ opt.key }} - + @if (iotHubCategoryOptions.length >= 10) { + + } +
+ @for (opt of getFilteredIotHubItems(iotHubCategoryOptions, 'category'); track opt.key) { +
+ + {{ opt.key }} + +
+ }
@@ -235,19 +278,47 @@ {{ 'iot-hub.use-case' | translate }} - {{ iotHubPendingUseCases.size }} + @if (iotHubPendingUseCases.size) { + {{ iotHubPendingUseCases.size }} + } -
- - {{ opt.key }} - + @if (iotHubUseCaseOptions.length >= 10) { + + } +
+ @for (group of getGroupedIotHubFilterItems(iotHubUseCaseOptions, 'useCase'); track group.label) { + @if (group.label) { +
{{ group.label | translate }}
+ } + @for (opt of group.items; track opt.key) { +
+ + {{ opt.key }} + +
+ } + }
- + @if (iotHubPendingWidgetTypes.size || iotHubPendingCategories.size || iotHubPendingUseCases.size) { + + } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss index f0066912b4..4137f195f1 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss @@ -224,6 +224,34 @@ .tb-iot-hub-filter-sections { overflow-y: auto; + padding-right: 8px; + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-track { + background-color: rgba(0, 0, 0, 0.04); + } + + &::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 3px; + + &:hover { + background-color: rgba(0, 0, 0, 0.35); + } + } + + &.filter-items-hovered { + &::-webkit-scrollbar-track { + background-color: transparent; + } + + &::-webkit-scrollbar-thumb { + background-color: transparent; + } + } mat-expansion-panel { box-shadow: none; @@ -245,11 +273,105 @@ } } + .tb-iot-hub-filter-search { + display: flex; + align-items: center; + gap: 8px; + height: 40px; + margin: 4px 8px; + padding: 8px 12px; + border: 1px solid rgba(0, 0, 0, 0.12); + border-radius: 8px; + background: white; + + mat-icon { + font-size: 24px; + width: 24px; + height: 24px; + color: rgba(0, 0, 0, 0.54); + flex-shrink: 0; + } + + input { + flex: 1; + border: none; + outline: none; + font-size: 14px; + font-weight: 400; + line-height: 20px; + letter-spacing: 0.2px; + color: rgba(0, 0, 0, 0.87); + background: transparent; + padding: 0; + min-width: 0; + + &::placeholder { + color: rgba(0, 0, 0, 0.38); + } + } + + .tb-iot-hub-filter-search-clear.mat-mdc-icon-button { + width: 24px; + height: 24px; + padding: 0; + flex-shrink: 0; + color: rgba(0, 0, 0, 0.38); + --mdc-icon-button-state-layer-size: 24px; + + mat-icon { + font-size: 18px; + width: 18px; + height: 18px; + } + } + } + + .tb-iot-hub-filter-items { + max-height: 340px; + overflow-y: auto; + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-track { + background-color: transparent; + } + + &::-webkit-scrollbar-thumb { + background-color: transparent; + border-radius: 3px; + } + + &:hover { + &::-webkit-scrollbar-track { + background-color: rgba(0, 0, 0, 0.04); + } + + &::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + + &:hover { + background-color: rgba(0, 0, 0, 0.35); + } + } + } + } + + .tb-iot-hub-filter-group-label { + font-size: 14px; + font-weight: 500; + color: rgba(0, 0, 0, 0.54); + letter-spacing: 0.5px; + padding: 12px 0 4px 12px; + } + .tb-iot-hub-filter-option { height: 40px; display: flex; align-items: center; padding: 8px 0; + overflow: hidden; } .tb-iot-hub-filter-badge { diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts index f45b18b24a..71c68bed61 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts @@ -213,6 +213,7 @@ export class DashboardWidgetSelectComponent implements OnInit { iotHubCategoryOptions: FilterParamInfo[] = []; iotHubUseCaseOptions: FilterParamInfo[] = []; iotHubFilterCount = 0; + iotHubFilterSearch: Record = {}; constructor(private widgetsService: WidgetService, private iotHubApiService: IotHubApiService, @@ -401,11 +402,39 @@ export class DashboardWidgetSelectComponent implements OnInit { this.iotHubFilterDirty = false; } + iotHubFilterItemsHovered = false; + getIotHubWidgetTypeLabel(key: string): string { const translationKey = widgetTypeTranslations.get(key); return translationKey ? this.translate.instant(translationKey) : key; } + getFilteredIotHubItems(items: FilterParamInfo[], searchKey: string): FilterParamInfo[] { + const search = (this.iotHubFilterSearch[searchKey] || '').toLowerCase(); + if (!search) { return items; } + return items.filter(item => item.key.toLowerCase().includes(search)); + } + + getGroupedIotHubFilterItems(items: FilterParamInfo[], searchKey: string): { label: string; items: FilterParamInfo[] }[] { + const filtered = this.getFilteredIotHubItems(items, searchKey); + if (items.length < 11) { + return [{ label: null, items: filtered }]; + } + const topKeys = new Set( + [...items].sort((a, b) => b.totalInstallCount - a.totalInstallCount).slice(0, 10).map(i => i.key) + ); + const popular = filtered.filter(i => topKeys.has(i.key)); + const rest = filtered.filter(i => !topKeys.has(i.key)); + const groups: { label: string; items: FilterParamInfo[] }[] = []; + if (popular.length) { + groups.push({ label: 'iot-hub.most-popular', items: popular }); + } + if (rest.length) { + groups.push({ label: 'iot-hub.all', items: rest }); + } + return groups; + } + toggleIotHubWidgetType(key: string): void { if (this.iotHubPendingWidgetTypes.has(key)) { this.iotHubPendingWidgetTypes.delete(key); @@ -437,6 +466,7 @@ export class DashboardWidgetSelectComponent implements OnInit { this.iotHubPendingWidgetTypes.clear(); this.iotHubPendingCategories.clear(); this.iotHubPendingUseCases.clear(); + this.iotHubFilterSearch = {}; this.updateFilterDirty(); } @@ -498,10 +528,11 @@ export class DashboardWidgetSelectComponent implements OnInit { } private loadIotHubFilterInfo(): void { + const hasItems = (i: FilterParamInfo) => i.totalItems > 0; this.iotHubApiService.getFilterInfo(ItemType.WIDGET, { ignoreLoading: true }).subscribe(info => { - this.iotHubWidgetTypeOptions = info.types || []; - this.iotHubCategoryOptions = info.categories || []; - this.iotHubUseCaseOptions = info.useCases || []; + this.iotHubWidgetTypeOptions = (info.types || []).filter(hasItems); + this.iotHubCategoryOptions = (info.categories || []).filter(hasItems); + this.iotHubUseCaseOptions = (info.useCases || []).filter(hasItems); }); }