This is an automated email from the ASF dual-hosted git repository.

dominikriemer pushed a commit to branch load-summary-charts-in-dashboard
in repository https://gitbox.apache.org/repos/asf/streampipes.git

commit 4380fb7288c71db12fac01f0634b4427ea96ad9d
Author: Dominik Riemer <[email protected]>
AuthorDate: Sat Jun 13 10:07:38 2026 +0200

    Improve chart selection, add chart search
---
 .../chart-preview/chart-preview.component.html     |  85 +++++++++++----
 .../chart-preview/chart-preview.component.scss     | 110 +++++++++++++++++++-
 .../chart-preview/chart-preview.component.ts       |  26 ++++-
 .../chart-selection/chart-selection.component.html | 114 +++++++++++++++++----
 .../chart-selection/chart-selection.component.scss |  56 +++++++++-
 .../chart-selection/chart-selection.component.ts   |  59 ++++++++++-
 6 files changed, 398 insertions(+), 52 deletions(-)

diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.html
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.html
index 52d80b569e..56576d9cf0 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.html
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.html
@@ -18,32 +18,75 @@
 
 <div
     fxLayout="row"
+    fxLayoutAlign="space-between center"
+    fxLayoutGap="12px"
     class="data-view-preview-outer"
     [attr.data-cy]="'add-data-view-btn-' + chart.name.replaceAll(' ', '')"
-    (click)="addChartEmitter.emit(chart.elementId)"
+    (click)="addChart()"
 >
-    <div fxFlex fxLayout="column">
-        <h5>{{ chart.name }}</h5>
-        <div fxLayout="row" fxLayoutGap="15px" class="text-sm">
-            <span fxLayoutAlign="start center"
-                ><mat-icon color="primary">insert_chart</mat-icon></span
-            >
-            <span fxFlex fxLayoutAlign="start center">{{
-                widgetTypeLabel
-            }}</span>
+    <div
+        fxLayout="row"
+        fxLayoutAlign="start center"
+        fxLayoutGap="12px"
+        fxFlex
+        class="chart-preview-main"
+    >
+        <div
+            class="chart-preview-icon-shell"
+            fxLayout="row"
+            fxLayoutAlign="center center"
+            [matTooltip]="widgetTypeLabel"
+        >
+            <mat-icon>{{ widgetTypeIcon }}</mat-icon>
         </div>
-        @if (chart.datasetName) {
-            <div fxLayout="row" fxLayoutGap="15px" class="text-sm">
-                <span fxLayoutAlign="start center"
-                    ><mat-icon color="primary">folder</mat-icon></span
-                >
-                <span fxFlex fxLayoutAlign="start center">{{
-                    chart.datasetName
-                }}</span>
+
+        <div fxFlex fxLayout="column" class="chart-preview-copy">
+            <div
+                fxLayout="row"
+                fxLayoutAlign="start center"
+                fxLayoutGap="8px"
+                class="chart-preview-title-row"
+                [matTooltip]="chart.name"
+            >
+                <h5 fxFlex class="chart-preview-title">{{ chart.name }}</h5>
             </div>
-        }
+
+            <div
+                fxLayout="row"
+                fxLayoutAlign="start center"
+                fxLayoutGap="12px"
+                class="chart-preview-meta"
+            >
+                @if (chart.datasetName) {
+                    <span class="chart-preview-meta-item 
chart-preview-dataset">
+                        {{ chart.datasetName }}
+                    </span>
+                }
+            </div>
+        </div>
     </div>
-    <div fxLayoutAlign="end center">
-        <i class="material-icons">add</i>
+
+    <div
+        fxLayout="row"
+        fxLayoutAlign="end center"
+        fxLayoutGap="4px"
+        class="chart-preview-actions"
+    >
+        <button
+            mat-icon-button
+            type="button"
+            [matTooltip]="'Preview chart' | translate"
+            (click)="openPreview($event)"
+            data-cy="chart-preview-feature-card"
+        >
+            <mat-icon>visibility</mat-icon>
+        </button>
+        <div
+            class="chart-preview-add-indicator"
+            fxLayout="row"
+            fxLayoutAlign="center center"
+        >
+            <mat-icon>add</mat-icon>
+        </div>
     </div>
 </div>
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.scss
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.scss
index 1a98fbb087..5282cd1fc1 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.scss
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.scss
@@ -17,16 +17,118 @@
  */
 
 .data-view-preview-outer {
-    border: 1px solid var(--color-bg-2);
-    padding: 10px;
+    min-height: 72px;
+    border: 1px solid color-mix(in srgb, var(--color-bg-3) 68%, transparent);
+    border-radius: 14px;
+    padding: 10px 12px;
+    background: color-mix(
+        in srgb,
+        var(--color-bg-0) 90%,
+        var(--color-bg-1) 10%
+    );
+    transition:
+        border-color 120ms ease,
+        background-color 120ms ease,
+        transform 120ms ease,
+        box-shadow 120ms ease;
 }
 
 .data-view-preview-outer:hover {
-    border-color: var(--color-bg-3);
-    background: var(--color-bg-1);
+    border-color: color-mix(
+        in srgb,
+        var(--color-primary) 34%,
+        var(--color-bg-3)
+    );
+    background: color-mix(in srgb, var(--color-bg-1) 86%, white 14%);
+    box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08);
+    transform: translateY(-1px);
     cursor: pointer;
 }
 
 mat-icon {
     --mat-icon-color: var(--mat-sys-secondary);
 }
+
+.chart-preview-icon-shell {
+    width: 40px;
+    min-width: 40px;
+    height: 40px;
+    border-radius: 12px;
+    background: linear-gradient(
+        180deg,
+        color-mix(in srgb, var(--color-primary) 14%, white) 0%,
+        color-mix(in srgb, var(--color-primary) 8%, var(--color-bg-1)) 100%
+    );
+    color: var(--color-primary);
+}
+
+.chart-preview-main {
+    min-width: 0;
+}
+
+.chart-preview-copy {
+    min-width: 0;
+}
+
+.chart-preview-title-row {
+    min-width: 0;
+    width: 100%;
+}
+
+.chart-preview-title {
+    min-width: 0;
+    flex: 1 1 auto;
+    margin: 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    font-size: var(--font-size-sm);
+    line-height: 1.25;
+}
+
+.chart-preview-meta {
+    min-width: 0;
+    width: 100%;
+    margin-top: 2px;
+    color: var(--color-paragraph);
+    font-size: var(--font-size-sm);
+}
+
+.chart-preview-meta-item {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.chart-preview-dataset {
+    color: var(--color-text);
+}
+
+.chart-preview-meta-dot {
+    width: 4px;
+    height: 4px;
+    border-radius: 50%;
+    background: var(--color-secondary);
+    flex-shrink: 0;
+}
+
+.chart-preview-actions {
+    flex-shrink: 0;
+}
+
+.chart-preview-add-indicator {
+    width: 28px;
+    height: 28px;
+    border-radius: 50%;
+    background: color-mix(in srgb, var(--color-primary) 14%, transparent);
+    color: var(--color-primary);
+}
+
+.chart-preview-actions button {
+    opacity: 0.82;
+}
+
+.data-view-preview-outer:hover .chart-preview-actions button,
+.data-view-preview-outer:hover .chart-preview-add-indicator {
+    opacity: 1;
+}
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.ts
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.ts
index 15fefe9e9d..153a49c417 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.ts
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.ts
@@ -33,6 +33,10 @@ import {
     LayoutGapDirective,
 } from '@ngbracket/ngx-layout/flex';
 import { MatIcon } from '@angular/material/icon';
+import { MatIconButton } from '@angular/material/button';
+import { MatTooltip } from '@angular/material/tooltip';
+import { FeatureCardService } from '@streampipes/shared-ui';
+import { TranslatePipe } from '@ngx-translate/core';
 
 @Component({
     selector: 'sp-chart-preview',
@@ -44,22 +48,38 @@ import { MatIcon } from '@angular/material/icon';
         LayoutGapDirective,
         LayoutAlignDirective,
         MatIcon,
+        MatIconButton,
+        MatTooltip,
+        TranslatePipe,
     ],
 })
 export class ChartPreviewComponent implements OnInit {
     private widgetRegistryService = inject(ChartRegistry);
+    private featureCardService = inject(FeatureCardService);
 
     @Input()
     chart: ChartSummaryDto;
 
-    widgetTypeLabel: string;
+    widgetTypeLabel = '';
+    widgetTypeIcon = 'insert_chart';
 
     @Output()
     addChartEmitter: EventEmitter<string> = new EventEmitter<string>();
 
     ngOnInit(): void {
-        this.widgetTypeLabel = this.widgetRegistryService.getChartTemplate(
+        const template = this.widgetRegistryService.getChartTemplate(
             this.chart.widgetType,
-        ).label;
+        );
+        this.widgetTypeLabel = template?.label ?? this.chart.widgetType;
+        this.widgetTypeIcon = template?.icon ?? 'insert_chart';
+    }
+
+    addChart(): void {
+        this.addChartEmitter.emit(this.chart.elementId);
+    }
+
+    openPreview(event: MouseEvent): void {
+        event.stopPropagation();
+        this.featureCardService.openFeatureCard('chart', this.chart.elementId);
     }
 }
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
index 377df74878..150d24b364 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
@@ -16,35 +16,111 @@
 ~
 -->
 
-<div fxFlex="100" class="m-10" fxLayoutGap="10px" fxLayout="column">
-    @if (charts.length > 0) {
-        @for (chart of charts; track chart) {
-            <div>
-                <sp-chart-preview
-                    [chart]="chart"
-                    (addChartEmitter)="addChartEmitter.emit($event)"
-                >
-                </sp-chart-preview>
+<div
+    fxFlex="100"
+    fxLayout="column"
+    fxLayoutGap="12px"
+    class="chart-selection-shell"
+>
+    <div fxLayout="column" fxLayoutGap="10px" class="chart-selection-toolbar">
+        <div
+            fxLayout="row"
+            fxLayoutAlign="space-between center"
+            fxLayoutGap="12px"
+        >
+            <div fxLayout="column" class="chart-selection-heading">
+                <span class="chart-selection-caption">
+                    {{ filteredCharts.length }}
+                    @if (filteredCharts.length === 1) {
+                        {{ 'chart available' | translate }}
+                    } @else {
+                        {{ 'charts available' | translate }}
+                    }
+                </span>
             </div>
-        }
-    } @else {
-        <div fxLayoutAlign="center center" fxLayout="column" fxFlex="100">
-            <span class="no-widget-hint">{{
-                'No charts found - create a new chart first to add it to this 
dashboard.'
-                    | translate
-            }}</span>
 
             @if (hasChartWritePrivileges) {
                 <button
-                    mat-flat-button
-                    class="mt-10 mat-basic"
+                    mat-icon-button
+                    class="chart-selection-create"
                     data-cy="create-chart-button"
+                    [matTooltip]="'Create chart' | translate"
                     (click)="navigateToDataViewCreation()"
                 >
                     <mat-icon>add</mat-icon>
-                    {{ 'Create chart' | translate }}
                 </button>
             }
         </div>
+
+        <mat-form-field
+            appearance="outline"
+            subscriptSizing="dynamic"
+            class="chart-selection-search"
+        >
+            <mat-icon matPrefix>search</mat-icon>
+            <input
+                [placeholder]="'Search charts' | translate"
+                matInput
+                [value]="searchTerm"
+                (input)="onSearchTermChanged($any($event.target).value)"
+                data-cy="chart-selection-search"
+            />
+            @if (hasActiveSearch()) {
+                <button
+                    mat-icon-button
+                    matSuffix
+                    type="button"
+                    (click)="clearSearch()"
+                    [attr.aria-label]="'Clear search' | translate"
+                >
+                    <mat-icon>close</mat-icon>
+                </button>
+            }
+        </mat-form-field>
+    </div>
+
+    @if (charts.length > 0 && filteredCharts.length > 0) {
+        <div
+            fxFlexFill
+            fxLayout="column"
+            fxLayoutGap="8px"
+            class="chart-selection-results"
+        >
+            @for (chart of filteredCharts; track chart.elementId) {
+                <sp-chart-preview
+                    [chart]="chart"
+                    (addChartEmitter)="addChartEmitter.emit($event)"
+                >
+                </sp-chart-preview>
+            }
+        </div>
+    } @else if (charts.length === 0) {
+        <div fxLayoutAlign="center center" fxLayout="column" fxFlex="100">
+            <span class="no-widget-hint">{{
+                'No charts found - create a new chart first to add it to this 
dashboard.'
+                    | translate
+            }}</span>
+        </div>
+    } @else {
+        <div
+            fxLayoutAlign="center center"
+            fxLayout="column"
+            fxLayoutGap="8px"
+            fxFlex="100"
+            class="chart-selection-empty"
+        >
+            <mat-icon class="chart-selection-empty-icon">search_off</mat-icon>
+            <span class="no-widget-hint">{{
+                'No charts match your search.' | translate
+            }}</span>
+            <button
+                mat-button
+                type="button"
+                class="chart-selection-reset"
+                (click)="clearSearch()"
+            >
+                {{ 'Clear search' | translate }}
+            </button>
+        </div>
     }
 </div>
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.scss
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.scss
index 8cf72cbca9..548782df84 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.scss
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.scss
@@ -16,11 +16,63 @@
  *
  */
 
-.m-10 {
-    margin: 10px;
+.chart-selection-shell {
+    height: 100%;
+    padding: 6px 6px 8px;
+    background: linear-gradient(
+        180deg,
+        var(--color-bg-0) 0%,
+        color-mix(in srgb, var(--color-bg-0) 96%, transparent) 100%
+    );
+}
+
+.chart-selection-toolbar {
+    position: sticky;
+    top: 0;
+    z-index: 2;
+    padding: 2px 0 4px;
+}
+
+.chart-selection-heading {
+    min-width: 0;
+}
+
+.chart-selection-caption {
+    font-size: var(--font-size-sm);
+    color: var(--color-paragraph);
+}
+
+.chart-selection-create {
+    flex-shrink: 0;
+}
+
+.chart-selection-search {
+    width: 100%;
+}
+
+.chart-selection-results {
+    padding-top: 2px;
+}
+
+.chart-selection-empty {
+    min-height: 220px;
+    text-align: center;
+}
+
+.chart-selection-empty-icon {
+    width: 32px;
+    height: 32px;
+    font-size: 32px;
+    color: var(--color-secondary);
+}
+
+.chart-selection-reset {
+    min-width: 0;
 }
 
 .no-widget-hint {
     font-size: var(--font-size-md);
     text-align: center;
+    color: var(--color-paragraph);
+    max-width: 280px;
 }
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
index 8d362f4c31..c253cc2e7d 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
@@ -17,20 +17,29 @@
  */
 
 import { Component, EventEmitter, inject, OnInit, Output } from 
'@angular/core';
-import { ChartSummaryDto, ChartService } from '@streampipes/platform-services';
+import { ChartService, ChartSummaryDto } from '@streampipes/platform-services';
 import { Router } from '@angular/router';
 import { AuthService } from '../../../../../services/auth.service';
 import { UserPrivilege } from '../../../../../core/auth/user-privilege.enum';
+import { ChartRegistry } from 
'../../../../../chart-shared/registry/chart-registry.service';
 import {
     FlexDirective,
+    FlexFillDirective,
     LayoutAlignDirective,
     LayoutDirective,
     LayoutGapDirective,
 } from '@ngbracket/ngx-layout/flex';
 import { ChartPreviewComponent } from 
'./chart-preview/chart-preview.component';
-import { MatButton } from '@angular/material/button';
+import { MatButton, MatIconButton } from '@angular/material/button';
 import { MatIcon } from '@angular/material/icon';
 import { TranslatePipe } from '@ngx-translate/core';
+import {
+    MatFormField,
+    MatPrefix,
+    MatSuffix,
+} from '@angular/material/form-field';
+import { MatInput } from '@angular/material/input';
+import { MatTooltip } from '@angular/material/tooltip';
 
 @Component({
     selector: 'sp-chart-selection',
@@ -38,25 +47,34 @@ import { TranslatePipe } from '@ngx-translate/core';
     styleUrls: ['./chart-selection.component.scss'],
     imports: [
         FlexDirective,
+        FlexFillDirective,
         LayoutGapDirective,
         LayoutDirective,
         ChartPreviewComponent,
         LayoutAlignDirective,
         MatButton,
+        MatIconButton,
         MatIcon,
+        MatFormField,
+        MatPrefix,
+        MatSuffix,
+        MatInput,
         TranslatePipe,
+        MatTooltip,
     ],
 })
 export class ChartSelectionComponent implements OnInit {
     private dataViewService = inject(ChartService);
     private router = inject(Router);
-
     private authService = inject(AuthService);
+    private chartRegistryService = inject(ChartRegistry);
 
     @Output()
     addChartEmitter: EventEmitter<string> = new EventEmitter();
 
     charts: ChartSummaryDto[] = [];
+    filteredCharts: ChartSummaryDto[] = [];
+    searchTerm = '';
 
     hasChartWritePrivileges: boolean = false;
 
@@ -65,6 +83,7 @@ export class ChartSelectionComponent implements OnInit {
             this.charts = chartSummary.resources.sort((a, b) =>
                 a.name.localeCompare(b.name),
             );
+            this.applySearch();
         });
 
         this.hasChartWritePrivileges = this.authService.hasRole(
@@ -78,4 +97,38 @@ export class ChartSelectionComponent implements OnInit {
             state: { omitConfirm: true },
         });
     }
+
+    onSearchTermChanged(value: string): void {
+        this.searchTerm = value;
+        this.applySearch();
+    }
+
+    clearSearch(): void {
+        this.searchTerm = '';
+        this.applySearch();
+    }
+
+    hasActiveSearch(): boolean {
+        return this.searchTerm.trim().length > 0;
+    }
+
+    private applySearch(): void {
+        const query = this.searchTerm.trim().toLowerCase();
+        if (!query) {
+            this.filteredCharts = this.charts;
+            return;
+        }
+
+        this.filteredCharts = this.charts.filter(chart =>
+            [
+                chart.name,
+                chart.datasetName,
+                chart.widgetType,
+                this.chartRegistryService.getChartTemplate(chart.widgetType)
+                    ?.label,
+            ]
+                .filter((value): value is string => !!value)
+                .some(value => value.toLowerCase().includes(query)),
+        );
+    }
 }

Reply via email to