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 081769c71b fix: Improve chart selection loading (#4582)
081769c71b is described below

commit 081769c71b6daad6b1fa9c70b977681db377874a
Author: Dominik Riemer <[email protected]>
AuthorDate: Wed Jun 17 18:17:08 2026 +0200

    fix: Improve chart selection loading (#4582)
---
 ui/deployment/i18n/de.json                         |   1 +
 ui/deployment/i18n/en.json                         |   1 +
 ui/deployment/i18n/pl.json                         |   1 +
 .../lib/components/sp-table/sp-table.component.ts  |  13 +-
 .../chart-overview-table.component.html            | 294 +++++++++++----------
 .../chart-overview-table.component.ts              | 105 +++++---
 .../chart-overview/chart-overview.component.scss   |  13 +
 .../chart-selection-panel.component.html           |   2 +-
 .../chart-selection-panel.component.scss           |   5 +
 .../chart-preview/chart-preview.component.html     |  61 ++---
 .../chart-preview/chart-preview.component.scss     |  32 ++-
 .../chart-preview/chart-preview.component.ts       |  22 +-
 .../chart-selection/chart-selection.component.html |  37 ++-
 .../chart-selection/chart-selection.component.scss |  38 ++-
 .../chart-selection/chart-selection.component.ts   |  33 ++-
 .../panel/dashboard-panel.component.html           |  12 +-
 .../panel/dashboard-panel.component.scss           |  21 ++
 17 files changed, 438 insertions(+), 253 deletions(-)

diff --git a/ui/deployment/i18n/de.json b/ui/deployment/i18n/de.json
index 56d3b9f2c0..da3470b010 100644
--- a/ui/deployment/i18n/de.json
+++ b/ui/deployment/i18n/de.json
@@ -616,6 +616,7 @@
   "Live preview": "Live-Vorschau",
   "Load anyway": "Trotzdem laden",
   "Loading": "Laden",
+  "Loading charts...": "Lade Diagramme...",
   "Location": "Lage",
   "Location configuration updated": "Standortkonfiguration aktualisiert",
   "Locked": "Gesperrt",
diff --git a/ui/deployment/i18n/en.json b/ui/deployment/i18n/en.json
index e58a1a807e..2600d4fae4 100644
--- a/ui/deployment/i18n/en.json
+++ b/ui/deployment/i18n/en.json
@@ -616,6 +616,7 @@
   "Live preview": null,
   "Load anyway": null,
   "Loading": null,
+  "Loading charts...": null,
   "Location": null,
   "Location configuration updated": null,
   "Locked": null,
diff --git a/ui/deployment/i18n/pl.json b/ui/deployment/i18n/pl.json
index b9062bcd9f..e2f55e67af 100644
--- a/ui/deployment/i18n/pl.json
+++ b/ui/deployment/i18n/pl.json
@@ -616,6 +616,7 @@
   "Live preview": "Podgląd na żywo",
   "Load anyway": "Załaduj mimo to",
   "Loading": "Ładowanie",
+  "Loading charts...": "ładowanie wykresów",
   "Location": "Położenie",
   "Location configuration updated": "Zaktualizowano konfigurację lokalizacji",
   "Locked": "Zablokowane",
diff --git 
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.ts
 
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.ts
index d2a5cae146..d7f0b5c3ce 100644
--- 
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.ts
+++ 
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.ts
@@ -514,9 +514,16 @@ export class SpTableComponent<T>
             return;
         }
 
-        this.dataSource.filter = value.trim().toLocaleLowerCase();
-        this.paginator?.firstPage();
-        this.refreshRenderedRows();
+        const normalizedFilter = value.trim().toLocaleLowerCase();
+        if (this.dataSource.filter === normalizedFilter) {
+            return;
+        }
+
+        if (this.paginator && this.paginator.pageIndex !== 0) {
+            this.paginator.pageIndex = 0;
+        }
+
+        this.dataSource.filter = normalizedFilter;
     }
 
     clearNameSearch() {
diff --git 
a/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.html
 
b/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.html
index 1be768d6a4..9b55684743 100644
--- 
a/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.html
+++ 
b/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.html
@@ -20,161 +20,165 @@
     <sp-basic-header-title-component
         [title]="'Charts' | translate"
     ></sp-basic-header-title-component>
-    <div fxFlex="100" fxLayout="row" fxLayoutAlign="center start">
-        <sp-table
+    @if (isLoading) {
+        <div
             fxFlex="100"
-            [columns]="displayedColumns"
-            [dataSource]="dataSource"
-            [nameSearchConfig]="{ enabled: true, placeholder: 'Search charts' 
}"
-            [assetContextConfig]="assetContextConfig"
-            featureCardId="chart"
-            [showActionsMenu]="true"
-            [rowsClickable]="true"
-            (rowClicked)="openChart($event, true)"
-            matSort
+            fxLayout="row"
+            fxLayoutAlign="center center"
+            class="chart-overview-loading"
         >
-            <ng-container matColumnDef="name">
-                <th mat-header-cell mat-sort-header *matHeaderCellDef>
-                    {{ 'Chart' | translate }}
-                </th>
-                <td
-                    mat-cell
-                    data-cy="data-views-table-overview"
-                    *matCellDef="let element"
-                >
-                    <div
-                        fxLayout="row"
-                        fxLayoutAlign="start center"
-                        fxLayoutGap="5px"
+            <div
+                fxLayout="column"
+                fxLayoutAlign="center center"
+                fxLayoutGap="12px"
+            >
+                <mat-progress-spinner
+                    mode="indeterminate"
+                    diameter="40"
+                ></mat-progress-spinner>
+                <span class="chart-overview-loading-text">{{
+                    'Loading' | translate
+                }}</span>
+            </div>
+        </div>
+    } @else {
+        <div fxFlex="100" fxLayout="row" fxLayoutAlign="center start">
+            <sp-table
+                fxFlex="100"
+                [columns]="displayedColumns"
+                [dataSource]="dataSource"
+                [nameSearchConfig]="nameSearchConfig"
+                [assetContextConfig]="assetContextConfig"
+                featureCardId="chart"
+                [showActionsMenu]="true"
+                [rowsClickable]="true"
+                (rowClicked)="openChart($event, true)"
+                matSort
+            >
+                <ng-container matColumnDef="name">
+                    <th mat-header-cell mat-sort-header *matHeaderCellDef>
+                        {{ 'Chart' | translate }}
+                    </th>
+                    <td
+                        mat-cell
+                        data-cy="data-views-table-overview"
+                        *matCellDef="let element"
                     >
-                        @if (isLegacyMultiSourceChart(element)) {
-                            <mat-icon
-                                [matTooltip]="
-                                    'This chart is outdated and must be 
migrated.'
-                                        | translate
-                                "
-                                style="color: var(--color-warn)"
-                                >warning</mat-icon
-                            >
-                        }
-                        @if (requiresAttention(element)) {
-                            <mat-icon
-                                data-cy="chart-sync-problem-icon"
-                                [matTooltip]="
-                                    'This chart requires attention because the 
dataset schema changed.'
-                                        | translate
-                                "
-                                style="color: var(--color-warn)"
-                                >sync_problem</mat-icon
-                            >
-                        }
-                        <div fxLayout="column" fxLayoutAlign="start start">
-                            <span class="text-sm">{{ element.name }}</span>
+                        <div
+                            fxLayout="row"
+                            fxLayoutAlign="start center"
+                            fxLayoutGap="5px"
+                        >
+                            @if (element.showLegacyWarning) {
+                                <mat-icon
+                                    [matTooltip]="
+                                        'This chart is outdated and must be 
migrated.'
+                                            | translate
+                                    "
+                                    style="color: var(--color-warn)"
+                                    >warning</mat-icon
+                                >
+                            }
+                            @if (element.showRequiresAttentionWarning) {
+                                <mat-icon
+                                    data-cy="chart-sync-problem-icon"
+                                    [matTooltip]="
+                                        'This chart requires attention because 
the dataset schema changed.'
+                                            | translate
+                                    "
+                                    style="color: var(--color-warn)"
+                                    >sync_problem</mat-icon
+                                >
+                            }
+                            <div fxLayout="column" fxLayoutAlign="start start">
+                                <span class="text-sm">{{ element.name }}</span>
+                            </div>
                         </div>
-                    </div>
-                </td>
-            </ng-container>
+                    </td>
+                </ng-container>
 
-            <ng-container matColumnDef="chartType">
-                <th mat-header-cell mat-sort-header *matHeaderCellDef>
-                    {{ 'Type' | translate }}
-                </th>
-                <td mat-cell *matCellDef="let element">
-                    <div
-                        fxLayout="row"
-                        fxLayoutAlign="start center"
-                        class="text-sm"
-                    >
-                        <mat-icon [matTooltip]="getChartTypeName(element)">{{
-                            getChartTypeIcon(element)
-                        }}</mat-icon>
-                    </div>
-                </td>
-            </ng-container>
-            <ng-container matColumnDef="lastModified">
-                <th mat-header-cell mat-sort-header *matHeaderCellDef>
-                    {{ 'Last modified' | translate }}
-                </th>
-                <td mat-cell *matCellDef="let element">
-                    @if (element.lastModifiedEpochMs !== null) {
-                        <div>
-                            {{ formatDate(element.lastModifiedEpochMs) }}
+                <ng-container matColumnDef="chartType">
+                    <th mat-header-cell mat-sort-header *matHeaderCellDef>
+                        {{ 'Type' | translate }}
+                    </th>
+                    <td mat-cell *matCellDef="let element">
+                        <div
+                            fxLayout="row"
+                            fxLayoutAlign="start center"
+                            class="text-sm"
+                        >
+                            <mat-icon [matTooltip]="element.chartTypeName">{{
+                                element.chartTypeIcon
+                            }}</mat-icon>
                         </div>
-                    } @else {
-                        <div>–</div>
-                    }
-                </td>
-            </ng-container>
+                    </td>
+                </ng-container>
+                <ng-container matColumnDef="lastModified">
+                    <th mat-header-cell mat-sort-header *matHeaderCellDef>
+                        {{ 'Last modified' | translate }}
+                    </th>
+                    <td mat-cell *matCellDef="let element">
+                        <div>{{ element.lastModifiedLabel }}</div>
+                    </td>
+                </ng-container>
 
-            <ng-container matColumnDef="createdAt">
-                <th mat-header-cell mat-sort-header *matHeaderCellDef>
-                    {{ 'Created' | translate }}
-                </th>
-                <td mat-cell *matCellDef="let element">
-                    @if (element.createdAtEpochMs !== null) {
-                        <div>
-                            {{ formatDate(element.createdAtEpochMs) }}
-                        </div>
-                    } @else {
-                        <div>–</div>
-                    }
-                </td>
-            </ng-container>
+                <ng-container matColumnDef="createdAt">
+                    <th mat-header-cell mat-sort-header *matHeaderCellDef>
+                        {{ 'Created' | translate }}
+                    </th>
+                    <td mat-cell *matCellDef="let element">
+                        <div>{{ element.createdAtLabel }}</div>
+                    </td>
+                </ng-container>
 
-            <ng-template spTableActions let-element>
-                <button
-                    mat-menu-item
-                    [attr.data-cy]="
-                        'show-data-view-' + element.name.replaceAll(' ', '')
-                    "
-                    (click)="openChart(element, false)"
-                >
-                    <mat-icon>visibility</mat-icon>
-                    <span>{{ 'Show' | translate }}</span>
-                </button>
-                @if (hasDataExplorerWritePrivileges) {
+                <ng-template spTableActions let-element>
                     <button
                         mat-menu-item
-                        [attr.data-cy]="
-                            'edit-data-view-' + element.name.replaceAll(' ', 
'')
-                        "
-                        (click)="openChart(element, true)"
+                        [attr.data-cy]="element.showDataCyId"
+                        (click)="openChart(element, false)"
                     >
-                        <mat-icon>edit</mat-icon>
-                        <span>{{ 'Edit chart' | translate }}</span>
-                    </button>
-                }
-                @if (hasDataExplorerWritePrivileges) {
-                    <button
-                        [attr.data-cy]="
-                            'open-manage-permissions-' +
-                            element.name.replaceAll(' ', '')
-                        "
-                        mat-menu-item
-                        (click)="showManageDialog(element)"
-                    >
-                        <mat-icon>settings</mat-icon>
-                        <span>{{ 'Manage' | translate }}</span>
-                    </button>
-                }
-                @if (hasDataExplorerWritePrivileges) {
-                    <button mat-menu-item (click)="cloneChart(element)">
-                        <mat-icon>flip_to_front</mat-icon>
-                        <span>{{ 'Clone chart' | translate }}</span>
+                        <mat-icon>visibility</mat-icon>
+                        <span>{{ 'Show' | translate }}</span>
                     </button>
-                }
-                @if (hasDataExplorerWritePrivileges) {
-                    <button
-                        mat-menu-item
-                        [matTooltip]=""
-                        [attr.data-cy]="'delete-data-view-' + element.name"
-                        (click)="deleteChart(element)"
-                    >
-                        <mat-icon>delete</mat-icon>
-                        <span>{{ 'Delete chart' | translate }}</span>
-                    </button>
-                }
-            </ng-template>
-        </sp-table>
-    </div>
+                    @if (hasDataExplorerWritePrivileges) {
+                        <button
+                            mat-menu-item
+                            [attr.data-cy]="element.editDataCyId"
+                            (click)="openChart(element, true)"
+                        >
+                            <mat-icon>edit</mat-icon>
+                            <span>{{ 'Edit chart' | translate }}</span>
+                        </button>
+                    }
+                    @if (hasDataExplorerWritePrivileges) {
+                        <button
+                            [attr.data-cy]="element.manageDataCyId"
+                            mat-menu-item
+                            (click)="showManageDialog(element)"
+                        >
+                            <mat-icon>settings</mat-icon>
+                            <span>{{ 'Manage' | translate }}</span>
+                        </button>
+                    }
+                    @if (hasDataExplorerWritePrivileges) {
+                        <button mat-menu-item (click)="cloneChart(element)">
+                            <mat-icon>flip_to_front</mat-icon>
+                            <span>{{ 'Clone chart' | translate }}</span>
+                        </button>
+                    }
+                    @if (hasDataExplorerWritePrivileges) {
+                        <button
+                            mat-menu-item
+                            [matTooltip]=""
+                            [attr.data-cy]="element.deleteDataCyId"
+                            (click)="deleteChart(element)"
+                        >
+                            <mat-icon>delete</mat-icon>
+                            <span>{{ 'Delete chart' | translate }}</span>
+                        </button>
+                    }
+                </ng-template>
+            </sp-table>
+        </div>
+    }
 </div>
diff --git 
a/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.ts
 
b/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.ts
index 0968a4a169..4b4ff52f91 100644
--- 
a/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.ts
+++ 
b/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.ts
@@ -65,12 +65,26 @@ import { MatMenuItem } from '@angular/material/menu';
 import { MatIcon } from '@angular/material/icon';
 import { MatTooltip } from '@angular/material/tooltip';
 import { ChartRegistry } from 
'../../../../chart-shared/registry/chart-registry.service';
+import { MatProgressSpinner } from '@angular/material/progress-spinner';
 
 type ManageableChart = DataExplorerWidgetModel & {
     name: string;
     description: string;
 };
 
+type ChartOverviewRow = ChartSummaryDto & {
+    chartTypeIcon: string;
+    chartTypeName: string;
+    createdAtLabel: string;
+    lastModifiedLabel: string;
+    showLegacyWarning: boolean;
+    showRequiresAttentionWarning: boolean;
+    showDataCyId: string;
+    editDataCyId: string;
+    manageDataCyId: string;
+    deleteDataCyId: string;
+};
+
 @Component({
     selector: 'sp-data-explorer-overview-table',
     templateUrl: './chart-overview-table.component.html',
@@ -93,6 +107,7 @@ type ManageableChart = DataExplorerWidgetModel & {
         MatMenuItem,
         MatIcon,
         MatTooltip,
+        MatProgressSpinner,
         TranslatePipe,
     ],
 })
@@ -101,9 +116,14 @@ export class ChartOverviewTableComponent implements 
OnInit, OnDestroy {
     hasDataExplorerWritePrivileges: boolean;
 
     @ViewChild(MatSort)
-    sort: MatSort;
+    set sort(sort: MatSort | undefined) {
+        this._sort = sort;
+        if (sort) {
+            this.dataSource.sort = sort;
+        }
+    }
 
-    dataSource = new MatTableDataSource<ChartSummaryDto>();
+    dataSource = new MatTableDataSource<ChartOverviewRow>();
     displayedColumns: string[] = [
         'name',
         'chartType',
@@ -116,8 +136,13 @@ export class ChartOverviewTableComponent implements 
OnInit, OnDestroy {
         resourceLinkType: 'chart',
         resourceIdKey: 'elementId',
     };
-    charts: ChartSummaryDto[] = [];
-    filteredCharts: ChartSummaryDto[] = [];
+    readonly nameSearchConfig = {
+        enabled: true,
+        placeholder: 'Search charts',
+    };
+    isLoading = false;
+    charts: ChartOverviewRow[] = [];
+    filteredCharts: ChartOverviewRow[] = [];
 
     private dataViewService = inject(ChartService);
     private dialog = inject(MatDialog);
@@ -130,6 +155,7 @@ export class ChartOverviewTableComponent implements OnInit, 
OnDestroy {
 
     assetFilter$: Subscription;
     currentFilterIds = new Set<string>();
+    private _sort?: MatSort;
     private chartTypeMetadata = new Map<
         string,
         { icon: string; label: string }
@@ -159,11 +185,20 @@ export class ChartOverviewTableComponent implements 
OnInit, OnDestroy {
     }
 
     getCharts(): void {
-        this.dataViewService.getChartSummary().subscribe(chartSummary => {
-            this.charts = chartSummary.resources.sort((a, b) =>
-                a.name.localeCompare(b.name),
-            );
-            this.applyChartFilters(this.currentFilterIds);
+        this.isLoading = true;
+        this.dataViewService.getChartSummary().subscribe({
+            next: chartSummary => {
+                this.charts = chartSummary.resources
+                    .map(chart => this.toChartOverviewRow(chart))
+                    .sort((a, b) => a.name.localeCompare(b.name));
+                this.applyChartFilters(this.currentFilterIds);
+            },
+            complete: () => {
+                this.isLoading = false;
+            },
+            error: () => {
+                this.isLoading = false;
+            },
         });
     }
 
@@ -281,30 +316,12 @@ export class ChartOverviewTableComponent implements 
OnInit, OnDestroy {
                 elementIds.has(a.elementId),
             );
         }
-        this.dataSource.sort = this.sort;
+        if (this._sort) {
+            this.dataSource.sort = this._sort;
+        }
         this.dataSource.data = this.filteredCharts;
     }
 
-    getChartTypeIcon(chart: ChartSummaryDto): string {
-        return this.getChartTypeMetadata(chart.widgetType).icon;
-    }
-
-    getChartTypeName(chart: ChartSummaryDto): string {
-        return this.getChartTypeMetadata(chart.widgetType).label;
-    }
-
-    formatDate(timestamp?: number): string {
-        return this.dateFormatService.formatDate(timestamp);
-    }
-
-    isLegacyMultiSourceChart(chart: ChartSummaryDto): boolean {
-        return chart.multiSourceChart;
-    }
-
-    requiresAttention(chart: ChartSummaryDto): boolean {
-        return chart?.healthStatus === 'REQUIRES_ATTENTION';
-    }
-
     private withChart(
         chartSummary: ChartSummaryDto,
         callback: (chart: DataExplorerWidgetModel) => void,
@@ -333,4 +350,32 @@ export class ChartOverviewTableComponent implements 
OnInit, OnDestroy {
         this.chartTypeMetadata.set(widgetType, metadata);
         return metadata;
     }
+
+    private toChartOverviewRow(chart: ChartSummaryDto): ChartOverviewRow {
+        const typeMetadata = this.getChartTypeMetadata(chart.widgetType);
+        const sanitizedName = chart.name.replaceAll(' ', '');
+
+        return {
+            ...chart,
+            chartTypeIcon: typeMetadata.icon,
+            chartTypeName: typeMetadata.label,
+            createdAtLabel:
+                chart.createdAtEpochMs !== null
+                    ? this.dateFormatService.formatDate(chart.createdAtEpochMs)
+                    : '–',
+            lastModifiedLabel:
+                chart.lastModifiedEpochMs !== null
+                    ? this.dateFormatService.formatDate(
+                          chart.lastModifiedEpochMs,
+                      )
+                    : '–',
+            showLegacyWarning: !!chart.multiSourceChart,
+            showRequiresAttentionWarning:
+                chart.healthStatus === 'REQUIRES_ATTENTION',
+            showDataCyId: `show-data-view-${sanitizedName}`,
+            editDataCyId: `edit-data-view-${sanitizedName}`,
+            manageDataCyId: `open-manage-permissions-${sanitizedName}`,
+            deleteDataCyId: `delete-data-view-${chart.name}`,
+        };
+    }
 }
diff --git 
a/ui/src/app/chart/components/chart-overview/chart-overview.component.scss 
b/ui/src/app/chart/components/chart-overview/chart-overview.component.scss
index 9103beb12d..06c5911910 100644
--- a/ui/src/app/chart/components/chart-overview/chart-overview.component.scss
+++ b/ui/src/app/chart/components/chart-overview/chart-overview.component.scss
@@ -35,3 +35,16 @@
 .mr-10 {
     margin-right: 10px;
 }
+
+.chart-overview-loading {
+    display: flex;
+    flex: 1 1 auto;
+    align-items: center;
+    justify-content: center;
+    text-align: center;
+}
+
+.chart-overview-loading-text {
+    font-size: var(--font-size-sm);
+    color: var(--color-paragraph);
+}
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.html
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.html
index 37166e0a2e..e1f0fd1508 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.html
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.html
@@ -27,7 +27,7 @@
                 data-cy="designer-panel-data-config"
                 [label]="'Charts' | translate"
             >
-                <div class="scroll-tab-content">
+                <div class="scroll-tab-content chart-selection-tab-content">
                     <sp-chart-selection
                         (addChartEmitter)="addChartEmitter.emit($event)"
                         fxFlex="100"
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.scss
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.scss
index e280dd74d5..1ef662e462 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.scss
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.scss
@@ -15,3 +15,8 @@
  * limitations under the License.
  *
  */
+
+.chart-selection-tab-content {
+    height: 100%;
+    overflow-y: hidden;
+}
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 56576d9cf0..29eddbab1d 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
@@ -17,48 +17,27 @@
   -->
 
 <div
-    fxLayout="row"
-    fxLayoutAlign="space-between center"
-    fxLayoutGap="12px"
     class="data-view-preview-outer"
-    [attr.data-cy]="'add-data-view-btn-' + chart.name.replaceAll(' ', '')"
+    [attr.data-cy]="dataCyId"
+    [attr.title]="chart.name"
     (click)="addChart()"
 >
-    <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"
-        >
+    <div class="chart-preview-header">
+        <div class="chart-preview-icon-shell" [attr.title]="widgetTypeLabel">
             <mat-icon>{{ widgetTypeIcon }}</mat-icon>
         </div>
 
-        <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 class="chart-preview-copy">
+            <div class="chart-preview-title-row">
+                <h5 class="chart-preview-title">{{ chart.name }}</h5>
             </div>
 
-            <div
-                fxLayout="row"
-                fxLayoutAlign="start center"
-                fxLayoutGap="12px"
-                class="chart-preview-meta"
-            >
+            <div class="chart-preview-meta">
                 @if (chart.datasetName) {
-                    <span class="chart-preview-meta-item 
chart-preview-dataset">
+                    <span
+                        class="chart-preview-meta-item chart-preview-dataset"
+                        [attr.title]="chart.datasetName"
+                    >
                         {{ chart.datasetName }}
                     </span>
                 }
@@ -66,26 +45,18 @@
         </div>
     </div>
 
-    <div
-        fxLayout="row"
-        fxLayoutAlign="end center"
-        fxLayoutGap="4px"
-        class="chart-preview-actions"
-    >
+    <div class="chart-preview-actions">
         <button
             mat-icon-button
             type="button"
-            [matTooltip]="'Preview chart' | translate"
+            [attr.aria-label]="'Preview chart' | translate"
+            [attr.title]="'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"
-        >
+        <div class="chart-preview-add-indicator">
             <mat-icon>add</mat-icon>
         </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 5282cd1fc1..3d7a82ba85 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,6 +17,10 @@
  */
 
 .data-view-preview-outer {
+    display: flex;
+    flex-direction: column;
+    align-items: stretch;
+    gap: 8px;
     min-height: 72px;
     border: 1px solid color-mix(in srgb, var(--color-bg-3) 68%, transparent);
     border-radius: 14px;
@@ -41,7 +45,6 @@
     );
     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;
 }
 
@@ -50,6 +53,9 @@ mat-icon {
 }
 
 .chart-preview-icon-shell {
+    display: flex;
+    align-items: center;
+    justify-content: center;
     width: 40px;
     min-width: 40px;
     height: 40px;
@@ -63,11 +69,23 @@ mat-icon {
 }
 
 .chart-preview-main {
+    display: contents;
+}
+
+.chart-preview-header {
+    display: flex;
+    align-items: center;
+    gap: 12px;
     min-width: 0;
+    overflow: hidden;
 }
 
 .chart-preview-copy {
+    display: flex;
+    flex: 1 1 auto;
+    flex-direction: column;
     min-width: 0;
+    overflow: hidden;
 }
 
 .chart-preview-title-row {
@@ -88,13 +106,16 @@ mat-icon {
 
 .chart-preview-meta {
     min-width: 0;
+    display: block;
     width: 100%;
+    overflow: hidden;
     margin-top: 2px;
     color: var(--color-paragraph);
     font-size: var(--font-size-sm);
 }
 
 .chart-preview-meta-item {
+    display: block;
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
@@ -113,10 +134,19 @@ mat-icon {
 }
 
 .chart-preview-actions {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    gap: 4px;
     flex-shrink: 0;
+    width: 100%;
+    margin-left: 0;
 }
 
 .chart-preview-add-indicator {
+    display: flex;
+    align-items: center;
+    justify-content: center;
     width: 28px;
     height: 28px;
     border-radius: 50%;
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 153a49c417..4ca3306b26 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
@@ -17,6 +17,7 @@
  */
 
 import {
+    ChangeDetectionStrategy,
     Component,
     EventEmitter,
     Input,
@@ -26,15 +27,8 @@ import {
 } from '@angular/core';
 import { ChartSummaryDto } from '@streampipes/platform-services';
 import { ChartRegistry } from 
'../../../../../../chart-shared/registry/chart-registry.service';
-import {
-    FlexDirective,
-    LayoutAlignDirective,
-    LayoutDirective,
-    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';
 
@@ -42,16 +36,8 @@ import { TranslatePipe } from '@ngx-translate/core';
     selector: 'sp-chart-preview',
     templateUrl: './chart-preview.component.html',
     styleUrls: ['./chart-preview.component.scss'],
-    imports: [
-        LayoutDirective,
-        FlexDirective,
-        LayoutGapDirective,
-        LayoutAlignDirective,
-        MatIcon,
-        MatIconButton,
-        MatTooltip,
-        TranslatePipe,
-    ],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+    imports: [MatIcon, MatIconButton, TranslatePipe],
 })
 export class ChartPreviewComponent implements OnInit {
     private widgetRegistryService = inject(ChartRegistry);
@@ -62,6 +48,7 @@ export class ChartPreviewComponent implements OnInit {
 
     widgetTypeLabel = '';
     widgetTypeIcon = 'insert_chart';
+    dataCyId = '';
 
     @Output()
     addChartEmitter: EventEmitter<string> = new EventEmitter<string>();
@@ -72,6 +59,7 @@ export class ChartPreviewComponent implements OnInit {
         );
         this.widgetTypeLabel = template?.label ?? this.chart.widgetType;
         this.widgetTypeIcon = template?.icon ?? 'insert_chart';
+        this.dataCyId = `add-data-view-btn-${this.chart.name.replaceAll(' ', 
'')}`;
     }
 
     addChart(): void {
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 d77aeb90f0..535eed1f08 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
@@ -99,21 +99,44 @@
         </mat-form-field>
     </div>
 
-    @if (charts.length > 0 && filteredCharts.length > 0) {
+    @if (isRefreshing) {
         <div
-            fxFlexFill
+            fxFlex="100"
             fxLayout="column"
-            fxLayoutGap="8px"
-            class="chart-selection-results"
+            fxLayoutAlign="center center"
+            fxLayoutGap="12px"
+            class="chart-selection-loading"
+        >
+            <mat-progress-spinner
+                mode="indeterminate"
+                diameter="36"
+            ></mat-progress-spinner>
+            <span class="chart-selection-loading-text">{{
+                'Loading charts...' | translate
+            }}</span>
+        </div>
+    } @else if (charts.length > 0 && filteredCharts.length > 0) {
+        <cdk-virtual-scroll-viewport
+            fxFlexFill
+            class="chart-selection-results chart-selection-viewport"
+            [itemSize]="chartItemSize"
+            [minBufferPx]="chartItemSize * 4"
+            [maxBufferPx]="chartItemSize * 8"
         >
-            @for (chart of filteredCharts; track chart.elementId) {
+            <div
+                *cdkVirtualFor="
+                    let chart of filteredCharts;
+                    trackBy: trackByChartId
+                "
+                class="chart-selection-item"
+            >
                 <sp-chart-preview
                     [chart]="chart"
                     (addChartEmitter)="addChartEmitter.emit($event)"
                 >
                 </sp-chart-preview>
-            }
-        </div>
+            </div>
+        </cdk-virtual-scroll-viewport>
     } @else if (charts.length === 0) {
         <div fxLayoutAlign="center center" fxLayout="column" fxFlex="100">
             <span class="no-widget-hint">{{
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 445d919e70..6af04c100e 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
@@ -17,7 +17,12 @@
  */
 
 .chart-selection-shell {
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
     height: 100%;
+    min-height: 0;
+    overflow: hidden;
     padding: 6px 6px 8px;
     background: linear-gradient(
         180deg,
@@ -27,6 +32,7 @@
 }
 
 .chart-selection-toolbar {
+    flex: 0 0 auto;
     position: sticky;
     top: 0;
     z-index: 2;
@@ -64,7 +70,37 @@
 }
 
 .chart-selection-results {
-    padding-top: 2px;
+    flex: 1 1 0;
+    min-height: 0;
+    padding-top: 0;
+}
+
+.chart-selection-viewport {
+    box-sizing: border-box;
+    width: 100%;
+    overflow-y: auto;
+    overflow-x: hidden;
+}
+
+.chart-selection-item {
+    box-sizing: border-box;
+    width: 100%;
+    height: 132px;
+    padding: 0 0 6px;
+}
+
+.chart-selection-viewport ::ng-deep .cdk-virtual-scroll-content-wrapper {
+    width: 100%;
+    max-width: 100%;
+}
+
+.chart-selection-loading {
+    text-align: center;
+}
+
+.chart-selection-loading-text {
+    font-size: var(--font-size-sm);
+    color: var(--color-paragraph);
 }
 
 .chart-selection-empty {
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 9059f2b978..ebba633d40 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
@@ -16,7 +16,15 @@
  *
  */
 
-import { Component, EventEmitter, inject, OnInit, Output } from 
'@angular/core';
+import {
+    ChangeDetectionStrategy,
+    ChangeDetectorRef,
+    Component,
+    EventEmitter,
+    inject,
+    OnInit,
+    Output,
+} from '@angular/core';
 import { ChartService, ChartSummaryDto } from '@streampipes/platform-services';
 import { AuthService } from '../../../../../services/auth.service';
 import { UserPrivilege } from '../../../../../core/auth/user-privilege.enum';
@@ -39,12 +47,19 @@ import {
     MatSuffix,
 } from '@angular/material/form-field';
 import { MatInput } from '@angular/material/input';
+import { MatProgressSpinner } from '@angular/material/progress-spinner';
 import { MatTooltip } from '@angular/material/tooltip';
+import {
+    CdkFixedSizeVirtualScroll,
+    CdkVirtualForOf,
+    CdkVirtualScrollViewport,
+} from '@angular/cdk/scrolling';
 
 @Component({
     selector: 'sp-chart-selection',
     templateUrl: './chart-selection.component.html',
     styleUrls: ['./chart-selection.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
     imports: [
         FlexDirective,
         FlexFillDirective,
@@ -59,8 +74,12 @@ import { MatTooltip } from '@angular/material/tooltip';
         MatPrefix,
         MatSuffix,
         MatInput,
+        MatProgressSpinner,
         TranslatePipe,
         MatTooltip,
+        CdkVirtualScrollViewport,
+        CdkFixedSizeVirtualScroll,
+        CdkVirtualForOf,
     ],
 })
 export class ChartSelectionComponent implements OnInit {
@@ -68,6 +87,7 @@ export class ChartSelectionComponent implements OnInit {
     private authService = inject(AuthService);
     private chartRegistryService = inject(ChartRegistry);
     private chartRoutingService = inject(ChartRoutingService);
+    private cdr = inject(ChangeDetectorRef);
 
     @Output()
     addChartEmitter: EventEmitter<string> = new EventEmitter();
@@ -76,6 +96,7 @@ export class ChartSelectionComponent implements OnInit {
     filteredCharts: ChartSummaryDto[] = [];
     searchTerm = '';
     isRefreshing = false;
+    readonly chartItemSize = 132;
 
     hasChartWritePrivileges: boolean = false;
 
@@ -93,18 +114,22 @@ export class ChartSelectionComponent implements OnInit {
 
     refreshCharts(): void {
         this.isRefreshing = true;
+        this.cdr.markForCheck();
         this.dataViewService.getChartSummary().subscribe({
             next: chartSummary => {
                 this.charts = chartSummary.resources.sort((a, b) =>
                     a.name.localeCompare(b.name),
                 );
                 this.applySearch();
+                this.cdr.markForCheck();
             },
             complete: () => {
                 this.isRefreshing = false;
+                this.cdr.markForCheck();
             },
             error: () => {
                 this.isRefreshing = false;
+                this.cdr.markForCheck();
             },
         });
     }
@@ -112,17 +137,23 @@ export class ChartSelectionComponent implements OnInit {
     onSearchTermChanged(value: string): void {
         this.searchTerm = value;
         this.applySearch();
+        this.cdr.markForCheck();
     }
 
     clearSearch(): void {
         this.searchTerm = '';
         this.applySearch();
+        this.cdr.markForCheck();
     }
 
     hasActiveSearch(): boolean {
         return this.searchTerm.trim().length > 0;
     }
 
+    trackByChartId(index: number, chart: ChartSummaryDto): string {
+        return chart.elementId;
+    }
+
     private applySearch(): void {
         const query = this.searchTerm.trim().toLowerCase();
         if (!query) {
diff --git 
a/ui/src/app/dashboard/components/panel/dashboard-panel.component.html 
b/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
index 86eda57b85..d60106f42e 100644
--- a/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
+++ b/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
@@ -75,17 +75,23 @@
                         position="end"
                         class="designer-panel"
                     >
-                        <div fxLayout="column" fxFlex="100">
+                        <div
+                            fxLayout="column"
+                            fxFlex="100"
+                            style="min-height: 0; overflow: hidden"
+                        >
                             @if (editMode) {
                                 <div
                                     fxFlex="100"
                                     class="designer-panel-content"
                                     fxLayout="column"
+                                    style="min-height: 0"
                                 >
                                     <div
                                         fxFlex="100"
                                         fxLayout="column"
                                         class="no-overflow"
+                                        style="min-height: 0"
                                     >
                                         <mat-tab-group
                                             color="accent"
@@ -99,7 +105,9 @@
                                                 
data-cy="designer-panel-data-config"
                                                 [label]="'Charts' | translate"
                                             >
-                                                <div 
class="scroll-tab-content">
+                                                <div
+                                                    class="scroll-tab-content 
chart-selection-tab-content"
+                                                >
                                                     <sp-chart-selection
                                                         (addChartEmitter)="
                                                             
addChartToDashboard(
diff --git 
a/ui/src/app/dashboard/components/panel/dashboard-panel.component.scss 
b/ui/src/app/dashboard/components/panel/dashboard-panel.component.scss
index 3708772e94..2655e51d21 100644
--- a/ui/src/app/dashboard/components/panel/dashboard-panel.component.scss
+++ b/ui/src/app/dashboard/components/panel/dashboard-panel.component.scss
@@ -45,6 +45,27 @@
 
 .designer-panel {
     width: 350px;
+    overflow: hidden;
+}
+
+.chart-selection-tab-content {
+    height: 100%;
+    overflow-y: hidden;
+}
+
+.designer-panel ::ng-deep .mat-drawer-inner-container {
+    overflow: hidden;
+}
+
+.designer-panel ::ng-deep .mat-mdc-tab-body-wrapper,
+.designer-panel ::ng-deep .mat-mdc-tab-body,
+.designer-panel ::ng-deep .mat-mdc-tab-body-content {
+    height: 100%;
+    min-height: 0;
+}
+
+.designer-panel ::ng-deep .mat-mdc-tab-body-content {
+    overflow: hidden;
 }
 
 .panel-toggle-button {


Reply via email to