-
- {{ opt.key }}
-
+ @if (iotHubCategoryOptions.length >= 10) {
+
+ search
+
+ @if (iotHubFilterSearch['category']) {
+
+ }
+
+ }
+
8"
+ (mouseenter)="getFilteredIotHubItems(iotHubCategoryOptions, 'category').length > 8 && (iotHubFilterItemsHovered = true)"
+ (mouseleave)="iotHubFilterItemsHovered = false">
+ @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) {
+
+ search
+
+ @if (iotHubFilterSearch['useCase']) {
+
+ }
+
+ }
+
8"
+ (mouseenter)="getFilteredIotHubItems(iotHubUseCaseOptions, 'useCase').length > 8 && (iotHubFilterItemsHovered = true)"
+ (mouseleave)="iotHubFilterItemsHovered = false">
+ @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);
});
}