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

dominikriemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to refs/heads/dev by this push:
     new ce0374e565 feat: Improve chart selection in dashboard (#4553)
ce0374e565 is described below

commit ce0374e565740c15b51ea44d04418557641ebb7e
Author: Dominik Riemer <[email protected]>
AuthorDate: Sat Jun 13 13:01:00 2026 +0200

    feat: Improve chart selection in dashboard (#4553)
---
 .../model/datalake/ChartSummaryDto.java            |   1 +
 .../DataExplorerWidgetResourceManager.java         |  22 ++++
 ui/deployment/i18n/de.json                         |   8 +-
 ui/deployment/i18n/en.json                         |   8 +-
 ui/deployment/i18n/pl.json                         |   8 +-
 .../lib/model/resource/resource-summary.model.ts   |   1 +
 .../chart-preview/chart-preview.component.html     |  86 +++++++++----
 .../chart-preview/chart-preview.component.scss     | 110 ++++++++++++++++-
 .../chart-preview/chart-preview.component.ts       |  32 ++++-
 .../chart-selection/chart-selection.component.html | 134 ++++++++++++++++++---
 .../chart-selection/chart-selection.component.scss |  69 ++++++++++-
 .../chart-selection/chart-selection.component.ts   | 101 ++++++++++++----
 12 files changed, 503 insertions(+), 77 deletions(-)

diff --git 
a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/ChartSummaryDto.java
 
b/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/ChartSummaryDto.java
index eb6f6035a5..a4c0e98537 100644
--- 
a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/ChartSummaryDto.java
+++ 
b/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/ChartSummaryDto.java
@@ -20,6 +20,7 @@ package org.apache.streampipes.model.datalake;
 
 public record ChartSummaryDto(String elementId,
                               String name,
+                              String datasetName,
                               Long createdAtEpochMs,
                               Long lastModifiedEpochMs,
                               String widgetType,
diff --git 
a/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/DataExplorerWidgetResourceManager.java
 
b/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/DataExplorerWidgetResourceManager.java
index 7d6b841d1e..09fe152f01 100644
--- 
a/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/DataExplorerWidgetResourceManager.java
+++ 
b/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/DataExplorerWidgetResourceManager.java
@@ -26,6 +26,8 @@ import 
org.apache.streampipes.storage.api.explorer.IDataExplorerWidgetStorage;
 import org.springframework.security.core.Authentication;
 
 import java.util.Collection;
+import java.util.List;
+import java.util.Map;
 
 public class DataExplorerWidgetResourceManager extends 
CrudResourceManager<DataExplorerWidgetModel> {
 
@@ -44,6 +46,7 @@ public class DataExplorerWidgetResourceManager extends 
CrudResourceManager<DataE
         .map(chart -> new ChartSummaryDto(
             chart.getElementId(),
             getChartName(chart),
+            getDatasetName(chart),
             getCreatedAt(chart),
             getLastModified(chart),
             chart.getWidgetType(),
@@ -81,6 +84,25 @@ public class DataExplorerWidgetResourceManager extends 
CrudResourceManager<DataE
     return false;
   }
 
+  private String getDatasetName(DataExplorerWidgetModel chart) {
+    if (chart == null || chart.getDataConfig() == null) {
+      return null;
+    }
+
+    Object sourceConfigs = chart.getDataConfig().get("sourceConfigs");
+    if (!(sourceConfigs instanceof List<?> configs) || configs.isEmpty()) {
+      return null;
+    }
+
+    Object firstConfig = configs.get(0);
+    if (!(firstConfig instanceof Map<?, ?> config)) {
+      return null;
+    }
+
+    Object measureName = config.get("measureName");
+    return measureName instanceof String ? (String) measureName : null;
+  }
+
   private String getChartName(DataExplorerWidgetModel chart) {
     if (chart == null || chart.getBaseAppearanceConfig() == null) {
       return chart != null ? chart.getElementId() : null;
diff --git a/ui/deployment/i18n/de.json b/ui/deployment/i18n/de.json
index be13f060a4..27fccbbc08 100644
--- a/ui/deployment/i18n/de.json
+++ b/ui/deployment/i18n/de.json
@@ -241,7 +241,7 @@
   "Create adapter": "Adapter erstellen",
   "Create asset": "Asset erstellen",
   "Create asset links": "Asset-Links erstellen",
-  "Create chart": "Diagramm erstellen",
+  "Create chart in new tab": "Diagramm in neuem Tab erstellen",
   "Create link": "Link erstellen",
   "Create new API key": "Neuen API-Schlüssel erstellen",
   "Create new account": "Neuen Account erstellen",
@@ -702,6 +702,7 @@
   "No assets found.": "Keine Assets gefunden",
   "No change": "Keine Änderung",
   "No charts found - create a new chart first to add it to this dashboard.": 
"Keine Diagramme gefunden - erstellen Sie zuerst ein neues Diagramm, um es zu 
diesem Dashboard hinzuzufügen.",
+  "No charts match your search.": "Keine Diagramme entsprechen deiner Suche.",
   "No data available.": "Keine Daten verfügbar",
   "No datasets available": "Keine Datensätze verfügbar",
   "No entries available.": "Keine Einträge vorhanden.",
@@ -785,6 +786,7 @@
   "Press 'Next' to start the process.": "Auf \"Weiter\" drücken, um den 
Vorgang zu starten.",
   "Press Next to start the process.": "Klicken Sie auf Weiter, um den Vorgang 
zu starten.",
   "Preview": "Vorschau",
+  "Preview chart": "Diagrammvorschau",
   "Preview info": "Vorschauinfo",
   "Preview is being prepared from the current CSV configuration.": "Die 
Vorschau wird aus der aktuellen CSV-Konfiguration erstellt.",
   "Preview is currently unavailable.": "Die Vorschau ist derzeit nicht 
verfügbar.",
@@ -816,6 +818,7 @@
   "Refresh Fields": "Felder aktualisieren",
   "Refresh adapters": "Adapter neu laden",
   "Refresh assets": "Assets aktualisieren",
+  "Refresh charts": "Diagramme aktualisieren",
   "Refresh interval": "Aktualisierungsintervall",
   "Refresh metrics": "Metriken aktualisieren",
   "Region": "Region",
@@ -895,6 +898,7 @@
   "Schema fields": "Schemafelder",
   "Script": "Skript",
   "Search": "Suche",
+  "Search charts": "Diagramme suchen",
   "Searching for available extensions, please wait...": "Nach verfügbaren 
Extensions suchen, bitte warten...",
   "Second": "Sekunde",
   "Secret Key": "Secret Key",
@@ -1230,6 +1234,8 @@
   "Your recent changes might have altered the data format. Reloading the 
sample ensures you are writing scripts for the most current data. Check your 
transformation rules after the refresh to ensure everything still aligns.": 
"Ihre letzten Änderungen haben möglicherweise das Datenformat verändert. Durch 
das Neuladen des Beispiels wird sichergestellt, dass Sie Skripte für die 
aktuellsten Daten schreiben. Prüfen Sie nach der Aktualisierung Ihre 
Transformationsregeln, um sicherzustellen, da [...]
   "assets are hidden because no valid location is configured.": "Assets sind 
ausgeblendet, weil kein gültiger Standort konfiguriert ist.",
   "available": "verfügbar",
+  "chart available": "Diagramm verfügbar",
+  "charts available": "Diagramme verfügbar",
   "days": "Tage",
   "delete": "löschen",
   "error": "Fehler",
diff --git a/ui/deployment/i18n/en.json b/ui/deployment/i18n/en.json
index 81888abf2b..86d7ca4e3f 100644
--- a/ui/deployment/i18n/en.json
+++ b/ui/deployment/i18n/en.json
@@ -241,7 +241,7 @@
   "Create adapter": null,
   "Create asset": null,
   "Create asset links": null,
-  "Create chart": null,
+  "Create chart in new tab": null,
   "Create link": null,
   "Create new API key": null,
   "Create new account": null,
@@ -702,6 +702,7 @@
   "No assets found.": null,
   "No change": null,
   "No charts found - create a new chart first to add it to this dashboard.": 
null,
+  "No charts match your search.": null,
   "No data available.": null,
   "No datasets available": null,
   "No entries available.": null,
@@ -785,6 +786,7 @@
   "Press 'Next' to start the process.": null,
   "Press Next to start the process.": null,
   "Preview": null,
+  "Preview chart": null,
   "Preview info": null,
   "Preview is being prepared from the current CSV configuration.": null,
   "Preview is currently unavailable.": null,
@@ -816,6 +818,7 @@
   "Refresh Fields": null,
   "Refresh adapters": null,
   "Refresh assets": null,
+  "Refresh charts": null,
   "Refresh interval": null,
   "Refresh metrics": null,
   "Region": null,
@@ -895,6 +898,7 @@
   "Schema fields": null,
   "Script": null,
   "Search": null,
+  "Search charts": null,
   "Searching for available extensions, please wait...": null,
   "Second": null,
   "Secret Key": null,
@@ -1230,6 +1234,8 @@
   "Your recent changes might have altered the data format. Reloading the 
sample ensures you are writing scripts for the most current data. Check your 
transformation rules after the refresh to ensure everything still aligns.": 
null,
   "assets are hidden because no valid location is configured.": null,
   "available": null,
+  "chart available": null,
+  "charts available": null,
   "days": null,
   "delete": null,
   "error": null,
diff --git a/ui/deployment/i18n/pl.json b/ui/deployment/i18n/pl.json
index c888b0c757..d17c3bcc58 100644
--- a/ui/deployment/i18n/pl.json
+++ b/ui/deployment/i18n/pl.json
@@ -241,7 +241,7 @@
   "Create adapter": "Utwórz adapter",
   "Create asset": "Utwórz zasób",
   "Create asset links": "Utwórz linki zasobu",
-  "Create chart": "Utwórz wykres",
+  "Create chart in new tab": "Utwórz wykres w nowej karcie",
   "Create link": "Utwórz link",
   "Create new API key": "Utwórz nowy klucz API",
   "Create new account": "Utwórz nowe konto",
@@ -702,6 +702,7 @@
   "No assets found.": "Nie znaleziono zasobów.",
   "No change": "Brak zmian",
   "No charts found - create a new chart first to add it to this dashboard.": 
"Nie znaleziono wykresów — utwórz nowy wykres, aby dodać go do tego pulpitu.",
+  "No charts match your search.": "Żadne wykresy nie pasują do wyszukiwania.",
   "No data available.": "Brak dostępnych danych.",
   "No datasets available": "Brak dostępnych zbiorów danych",
   "No entries available.": "Brak dostępnych wpisów.",
@@ -785,6 +786,7 @@
   "Press 'Next' to start the process.": "Naciśnij „Dalej”, aby rozpocząć 
proces.",
   "Press Next to start the process.": "Kliknij Dalej, aby rozpocząć proces.",
   "Preview": "Podgląd",
+  "Preview chart": "Podgląd wykresu",
   "Preview info": "Informacje o podglądzie",
   "Preview is being prepared from the current CSV configuration.": "Podgląd 
jest przygotowywany na podstawie bieżącej konfiguracji CSV.",
   "Preview is currently unavailable.": "Podgląd jest obecnie niedostępny.",
@@ -816,6 +818,7 @@
   "Refresh Fields": "Odśwież pola",
   "Refresh adapters": "Odśwież adaptery",
   "Refresh assets": "Odswiez zasoby",
+  "Refresh charts": "Odśwież wykresy",
   "Refresh interval": "Interwał odświeżania",
   "Refresh metrics": "Odśwież metryki",
   "Region": "Region",
@@ -895,6 +898,7 @@
   "Schema fields": "Pola schematu",
   "Script": "Skrypt",
   "Search": "Szukaj",
+  "Search charts": "Szukaj wykresów",
   "Searching for available extensions, please wait...": "Wyszukiwanie 
dostępnych rozszerzeń, proszę czekać...",
   "Second": "Sekunda",
   "Secret Key": "Klucz tajny",
@@ -1230,6 +1234,8 @@
   "Your recent changes might have altered the data format. Reloading the 
sample ensures you are writing scripts for the most current data. Check your 
transformation rules after the refresh to ensure everything still aligns.": 
"Twoje ostatnie zmiany mogły zmienić format danych. Ponowne wczytanie próbki 
zapewnia, że piszesz skrypty dla najnowszych danych. Po odświeżeniu sprawdź 
reguły transformacji, aby upewnić się, że wszystko nadal pasuje.",
   "assets are hidden because no valid location is configured.": "Zasoby są 
ukryte, ponieważ nie skonfigurowano prawidłowej lokalizacji.",
   "available": "dostępne",
+  "chart available": "wykres dostępny",
+  "charts available": "wykresy dostępne",
   "days": "dni",
   "delete": "usuń",
   "error": "błąd",
diff --git 
a/ui/projects/streampipes/platform-services/src/lib/model/resource/resource-summary.model.ts
 
b/ui/projects/streampipes/platform-services/src/lib/model/resource/resource-summary.model.ts
index b4348b7d19..b3ca4026a8 100644
--- 
a/ui/projects/streampipes/platform-services/src/lib/model/resource/resource-summary.model.ts
+++ 
b/ui/projects/streampipes/platform-services/src/lib/model/resource/resource-summary.model.ts
@@ -74,6 +74,7 @@ export interface PipelineSummaryDto {
 export interface ChartSummaryDto {
     elementId: string;
     name: string;
+    datasetName: string | null;
     createdAtEpochMs: number | null;
     lastModifiedEpochMs: number | null;
     multiSourceChart: boolean;
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 dd3be00b33..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,33 +18,75 @@
 
 <div
     fxLayout="row"
+    fxLayoutAlign="space-between center"
+    fxLayoutGap="12px"
     class="data-view-preview-outer"
-    [attr.data-cy]="
-        'add-data-view-btn-' +
-        chart.baseAppearanceConfig.widgetTitle.replaceAll(' ', '')
-    "
-    (click)="addChartEmitter.emit(chart.elementId)"
+    [attr.data-cy]="'add-data-view-btn-' + chart.name.replaceAll(' ', '')"
+    (click)="addChart()"
 >
-    <div fxFlex fxLayout="column">
-        <h5>{{ chart.baseAppearanceConfig.widgetTitle }}</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>
-        <div fxLayout="row" fxLayoutGap="15px" class="text-sm">
-            <span fxLayoutAlign="start center"
-                ><mat-icon color="primary">folder</mat-icon></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"
             >
-            <span fxFlex fxLayoutAlign="start center">{{
-                chart.dataConfig.sourceConfigs[0].measureName
-            }}</span>
+                @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 f1e5d677ec..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
@@ -24,7 +24,7 @@ import {
     Output,
     inject,
 } from '@angular/core';
-import { DataExplorerWidgetModel } from '@streampipes/platform-services';
+import { ChartSummaryDto } from '@streampipes/platform-services';
 import { ChartRegistry } from 
'../../../../../../chart-shared/registry/chart-registry.service';
 import {
     FlexDirective,
@@ -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: DataExplorerWidgetModel;
+    chart: ChartSummaryDto;
 
-    widgetTypeLabel: string;
+    widgetTypeLabel = '';
+    widgetTypeIcon = 'insert_chart';
 
     @Output()
     addChartEmitter: EventEmitter<string> = new EventEmitter<string>();
 
-    ngOnInit() {
-        this.widgetTypeLabel = this.widgetRegistryService.getChartTemplate(
+    ngOnInit(): void {
+        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..d77aeb90f0 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,131 @@
 ~
 -->
 
-<div fxFlex="100" class="m-10" fxLayoutGap="10px" fxLayout="column">
-    @if (charts.length > 0) {
-        @for (chart of charts; track chart) {
-            <div>
+<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>
+
+            <div
+                fxLayout="row"
+                fxLayoutAlign="end center"
+                fxLayoutGap="4px"
+                class="chart-selection-actions"
+            >
+                <button
+                    mat-icon-button
+                    type="button"
+                    class="chart-selection-refresh"
+                    data-cy="refresh-chart-button"
+                    [disabled]="isRefreshing"
+                    [matTooltip]="'Refresh charts' | translate"
+                    (click)="refreshCharts()"
+                >
+                    <mat-icon>refresh</mat-icon>
+                </button>
+
+                @if (hasChartWritePrivileges) {
+                    <button
+                        mat-icon-button
+                        type="button"
+                        class="chart-selection-create"
+                        data-cy="create-chart-button"
+                        [matTooltip]="'Create chart in new tab' | translate"
+                        (click)="navigateToDataViewCreation()"
+                    >
+                        <mat-icon>add</mat-icon>
+                    </button>
+                }
+            </div>
+        </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 {
+            }
+        </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>
-
-            @if (hasChartWritePrivileges) {
-                <button
-                    mat-flat-button
-                    class="mt-10 mat-basic"
-                    data-cy="create-chart-button"
-                    (click)="navigateToDataViewCreation()"
-                >
-                    <mat-icon>add</mat-icon>
-                    {{ 'Create chart' | translate }}
-                </button>
-            }
+        </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..445d919e70 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,76 @@
  *
  */
 
-.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;
+    background: linear-gradient(
+        180deg,
+        var(--color-bg-0) 0%,
+        color-mix(in srgb, var(--color-bg-0) 96%, transparent) 100%
+    );
+}
+
+.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-actions {
+    flex-shrink: 0;
+}
+
+.chart-selection-refresh:disabled {
+    opacity: 0.6;
+}
+
+.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 07b3f7eec3..9059f2b978 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,23 +17,29 @@
  */
 
 import { Component, EventEmitter, inject, OnInit, Output } from 
'@angular/core';
-import {
-    ChartService,
-    DataExplorerWidgetModel,
-} from '@streampipes/platform-services';
-import { Router } from '@angular/router';
+import { ChartService, ChartSummaryDto } from '@streampipes/platform-services';
 import { AuthService } from '../../../../../services/auth.service';
 import { UserPrivilege } from '../../../../../core/auth/user-privilege.enum';
+import { ChartRegistry } from 
'../../../../../chart-shared/registry/chart-registry.service';
+import { ChartRoutingService } from 
'../../../../../chart-shared/services/chart-routing.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',
@@ -41,46 +47,99 @@ 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);
+    private chartRoutingService = inject(ChartRoutingService);
 
     @Output()
     addChartEmitter: EventEmitter<string> = new EventEmitter();
 
-    charts: DataExplorerWidgetModel[] = [];
+    charts: ChartSummaryDto[] = [];
+    filteredCharts: ChartSummaryDto[] = [];
+    searchTerm = '';
+    isRefreshing = false;
 
     hasChartWritePrivileges: boolean = false;
 
-    ngOnInit() {
-        this.dataViewService.getAllCharts().subscribe(charts => {
-            this.charts = charts.sort((a, b) =>
-                a.baseAppearanceConfig.widgetTitle.localeCompare(
-                    b.baseAppearanceConfig.widgetTitle,
-                ),
-            );
-        });
-
+    ngOnInit(): void {
         this.hasChartWritePrivileges = this.authService.hasRole(
             UserPrivilege.PRIVILEGE_WRITE_DATA_EXPLORER_VIEW,
         );
+
+        this.refreshCharts();
     }
 
     navigateToDataViewCreation(): void {
-        this.router.navigate(['chart', 'create'], {
-            queryParams: { editMode: true },
-            state: { omitConfirm: true },
+        this.chartRoutingService.navigateToCreateChart(true, undefined, true);
+    }
+
+    refreshCharts(): void {
+        this.isRefreshing = true;
+        this.dataViewService.getChartSummary().subscribe({
+            next: chartSummary => {
+                this.charts = chartSummary.resources.sort((a, b) =>
+                    a.name.localeCompare(b.name),
+                );
+                this.applySearch();
+            },
+            complete: () => {
+                this.isRefreshing = false;
+            },
+            error: () => {
+                this.isRefreshing = false;
+            },
         });
     }
+
+    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