From f00b1ef4e38acf0b7d64847f8d5241f145839093 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Fri, 6 Feb 2026 15:25:13 +0200 Subject: [PATCH 01/11] feat: redesign Dashboards section UX with card layouts and sidebar navigation - Add sidebar navigation for Dashboards section with Dashboards/Widgets tabs - Convert dashboards list from table to responsive card grid with widget count - Convert widgets list to card grid with chart type mini previews - Add ChartMiniPreviewComponent with SVG visualizations for bar, line, pie, doughnut, polar area charts - Add "Create new query" option inside dropdown in Add Chart dialog - Backend: include widgets in dashboards list response for accurate counts - Fix sidebar state persistence with localStorage - Fix sidebar animation flash on navigation - Auto-refresh dashboard cards when widgets are added/deleted - Various styling improvements for dark mode support Co-Authored-By: Claude Opus 4.5 --- .../dashboard-custom-repository-extension.ts | 9 +- .../chart-edit/chart-edit.component.css | 34 ++- .../chart-edit/chart-edit.component.html | 35 ++- .../charts/chart-edit/chart-edit.component.ts | 2 + .../charts-list/charts-list.component.css | 218 ++++++++++------ .../charts-list/charts-list.component.html | 194 +++++++-------- .../charts-list/charts-list.component.ts | 23 +- .../db-table-widgets.component.ts | 2 - .../chart-mini-preview.component.css | 85 +++++++ .../chart-mini-preview.component.html | 57 +++++ .../chart-mini-preview.component.ts | 17 ++ .../dashboards-list.component.css | 234 +++++++++++------- .../dashboards-list.component.html | 218 ++++++++-------- .../dashboards-list.component.ts | 7 +- .../dashboards-sidebar.component.css | 98 ++++++++ .../dashboards-sidebar.component.html | 42 ++++ .../dashboards-sidebar.component.ts | 52 ++++ .../widget-edit-dialog.component.css | 58 +++++ .../widget-edit-dialog.component.html | 63 +++-- .../widget-edit-dialog.component.ts | 23 +- .../src/app/services/dashboards.service.ts | 2 + 21 files changed, 1035 insertions(+), 438 deletions(-) create mode 100644 frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.css create mode 100644 frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.html create mode 100644 frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.ts create mode 100644 frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.css create mode 100644 frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.html create mode 100644 frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.ts diff --git a/backend/src/entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.ts b/backend/src/entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.ts index a133e168f..86333f937 100644 --- a/backend/src/entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.ts +++ b/backend/src/entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.ts @@ -19,10 +19,11 @@ export const dashboardCustomRepositoryExtension = { }, async findAllDashboardsByConnectionId(connectionId: string): Promise { - return await this.find({ - where: { connection_id: connectionId }, - order: { created_at: 'DESC' }, - }); + const qb = this.createQueryBuilder('dashboard') + .leftJoinAndSelect('dashboard.widgets', 'widgets') + .where('dashboard.connection_id = :connectionId', { connectionId }) + .orderBy('dashboard.created_at', 'DESC'); + return await qb.getMany(); }, async saveDashboard(dashboard: DashboardEntity): Promise { diff --git a/frontend/src/app/components/charts/chart-edit/chart-edit.component.css b/frontend/src/app/components/charts/chart-edit/chart-edit.component.css index d776500ba..4c2da1fa2 100644 --- a/frontend/src/app/components/charts/chart-edit/chart-edit.component.css +++ b/frontend/src/app/components/charts/chart-edit/chart-edit.component.css @@ -1,12 +1,26 @@ +.chart-edit-layout { + display: flex; + height: calc(100vh - 64px); +} + +.chart-edit-main { + flex: 1; + overflow-y: auto; +} + .chart-edit-page { - margin: 2em auto; - padding: 0 clamp(100px, 10vw, 200px); + display: flex; + flex-direction: column; + min-height: 100%; + padding: 0 clamp(40px, 5vw, 100px); max-width: 1400px; + margin: 3em auto; } -@media (width <= 900px) { +@media (width <= 600px) { .chart-edit-page { padding: 0 16px; + margin: 1.5em auto; } } @@ -14,7 +28,8 @@ display: flex; align-items: center; gap: 16px; - margin-bottom: 24px; + margin-bottom: 32px; + flex-shrink: 0; } .chart-edit-header h1 { @@ -31,6 +46,9 @@ display: flex; flex-direction: column; gap: 24px; + flex: 1; + overflow-y: auto; + padding-bottom: 24px; } .query-details { @@ -210,13 +228,19 @@ display: flex; justify-content: flex-end; gap: 16px; - padding-top: 16px; + padding: 16px 0; border-top: 1px solid rgba(0, 0, 0, 0.12); + background-color: #fff; + flex-shrink: 0; + position: sticky; + bottom: 0; + margin-top: auto; } @media (prefers-color-scheme: dark) { .actions { border-top-color: rgba(255, 255, 255, 0.12); + background-color: #1e1e1e; } } diff --git a/frontend/src/app/components/charts/chart-edit/chart-edit.component.html b/frontend/src/app/components/charts/chart-edit/chart-edit.component.html index a8005ae4b..8e6aeb119 100644 --- a/frontend/src/app/components/charts/chart-edit/chart-edit.component.html +++ b/frontend/src/app/components/charts/chart-edit/chart-edit.component.html @@ -1,6 +1,13 @@ - +
+ + -
+
+ + +
+
-
- - +
+ + +
diff --git a/frontend/src/app/components/charts/chart-edit/chart-edit.component.ts b/frontend/src/app/components/charts/chart-edit/chart-edit.component.ts index 03329e70d..0b01a4d7d 100644 --- a/frontend/src/app/components/charts/chart-edit/chart-edit.component.ts +++ b/frontend/src/app/components/charts/chart-edit/chart-edit.component.ts @@ -19,6 +19,7 @@ import { ChartType, TestQueryResult } from 'src/app/models/saved-query'; import { ConnectionsService } from 'src/app/services/connections.service'; import { SavedQueriesService } from 'src/app/services/saved-queries.service'; import { UiSettingsService } from 'src/app/services/ui-settings.service'; +import { DashboardsSidebarComponent } from '../../dashboards/dashboards-sidebar/dashboards-sidebar.component'; import { AlertComponent } from '../../ui-components/alert/alert.component'; import { ChartPreviewComponent } from '../chart-preview/chart-preview.component'; @@ -41,6 +42,7 @@ import { ChartPreviewComponent } from '../chart-preview/chart-preview.component' CodeEditorModule, ChartPreviewComponent, AlertComponent, + DashboardsSidebarComponent, ], }) export class ChartEditComponent implements OnInit { diff --git a/frontend/src/app/components/charts/charts-list/charts-list.component.css b/frontend/src/app/components/charts/charts-list/charts-list.component.css index ec54d827d..9194be609 100644 --- a/frontend/src/app/components/charts/charts-list/charts-list.component.css +++ b/frontend/src/app/components/charts/charts-list/charts-list.component.css @@ -1,11 +1,23 @@ +.charts-layout { + display: flex; + height: calc(100vh - 64px); +} + +.charts-main { + flex: 1; + overflow-y: auto; +} + .charts-page { margin: 3em auto; - padding: 0 clamp(200px, 20vw, 300px); + padding: 0 clamp(40px, 5vw, 100px); + max-width: 1400px; } @media (width <= 600px) { .charts-page { - padding: 0 9vw; + padding: 0 16px; + margin: 1.5em auto; } } @@ -50,45 +62,143 @@ } } -.charts-table { +/* Cards Grid */ +.widgets-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 24px; +} + +@media (width <= 600px) { + .widgets-grid { + grid-template-columns: 1fr; + gap: 16px; + } +} + +/* Widget Card */ +.widget-card { + display: flex; + flex-direction: column; + overflow: hidden; + transition: box-shadow 0.2s ease, transform 0.2s ease; +} + +.widget-card:hover { + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12); + transform: translateY(-2px); +} + +@media (prefers-color-scheme: dark) { + .widget-card:hover { + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); + } +} + +/* Preview Area */ +.widget-preview { + display: flex; + align-items: center; + justify-content: center; + height: 140px; + background-color: #f5f5f5; + border-bottom: 1px solid rgba(0, 0, 0, 0.08); + text-decoration: none; + overflow: hidden; + padding: 16px; +} + +@media (prefers-color-scheme: dark) { + .widget-preview { + background-color: #2a2a2a; + border-bottom-color: rgba(255, 255, 255, 0.08); + } +} + +.widget-preview app-chart-mini-preview { width: 100%; - margin-bottom: 16px; + height: 100%; } -.charts-cell_name { - max-width: 250px; +/* Card Content */ +.widget-card-content { + padding: 16px; + flex: 1; + display: flex; + flex-direction: column; } -.name-text { +.widget-card-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 8px; + margin-bottom: 2px; +} + +.widget-name { + font-size: 16px; font-weight: 500; + color: inherit; + text-decoration: none; + line-height: 1.4; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; } -.description-text { - color: rgba(0, 0, 0, 0.87); +.widget-name:hover { + color: #673ab7; } @media (prefers-color-scheme: dark) { - .description-text { - color: rgba(255, 255, 255, 0.87); + .widget-name:hover { + color: #b388ff; } } -.no-description { - color: rgba(0, 0, 0, 0.38); - font-style: italic; +.widget-menu-button { + flex-shrink: 0; + margin: -8px -8px 0 0; +} + +.widget-description-text { + font-size: 12px; + color: rgba(0, 0, 0, 0.45); + margin: 0 0 12px 0; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; } @media (prefers-color-scheme: dark) { - .no-description { - color: rgba(255, 255, 255, 0.38); + .widget-description-text { + color: rgba(255, 255, 255, 0.45); } } -.charts-cell_actions { - text-align: right; +.widget-meta { + display: flex; + align-items: center; + justify-content: flex-end; + margin-top: auto; + font-size: 12px; + color: rgba(0, 0, 0, 0.5); +} + +@media (prefers-color-scheme: dark) { + .widget-meta { + color: rgba(255, 255, 255, 0.5); + } } -.no-charts { +/* Empty State */ +.no-widgets { display: flex; flex-direction: column; align-items: center; @@ -100,12 +210,12 @@ } @media (prefers-color-scheme: dark) { - .no-charts { + .no-widgets { background-color: rgba(255, 255, 255, 0.05); } } -.no-charts-icon { +.no-widgets-icon { font-size: 64px; width: 64px; height: 64px; @@ -114,29 +224,29 @@ } @media (prefers-color-scheme: dark) { - .no-charts-icon { + .no-widgets-icon { color: rgba(255, 255, 255, 0.3); } } -.no-charts h3 { +.no-widgets h3 { margin: 0 0 8px 0; color: rgba(0, 0, 0, 0.87); } @media (prefers-color-scheme: dark) { - .no-charts h3 { + .no-widgets h3 { color: rgba(255, 255, 255, 0.87); } } -.no-charts p { +.no-widgets p { margin: 0 0 16px 0; color: rgba(0, 0, 0, 0.54); } @media (prefers-color-scheme: dark) { - .no-charts p { + .no-widgets p { color: rgba(255, 255, 255, 0.54); } } @@ -150,59 +260,3 @@ color: #ef5350; } } - -/* Responsive table styles */ -@media (width <= 600px) { - .charts-table { - display: grid; - grid-template-columns: auto 1fr; - max-width: 100%; - } - - .charts-table-heading { - display: none; - } - - .charts-table ::ng-deep tbody { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / 3; - } - - .charts-row { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / 3; - grid-gap: 12px 28px; - border-bottom-color: var(--mat-table-row-item-outline-color, rgba(0, 0, 0, 0.12)); - border-bottom-width: var(--mat-table-row-item-outline-width, 1px); - border-bottom-style: solid; - height: auto; - padding: 20px 0; - } - - .charts-cell { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / 3; - border-bottom: none; - } - - .charts-cell::before { - content: attr(data-label); - display: inline-block; - font-weight: bold; - white-space: nowrap; - } - - .charts-cell_actions { - grid-column: 1 / span 3; - display: flex; - justify-content: flex-end; - border-bottom: none; - } - - .charts-cell_actions::before { - display: none; - } -} diff --git a/frontend/src/app/components/charts/charts-list/charts-list.component.html b/frontend/src/app/components/charts/charts-list/charts-list.component.html index bfcb37cee..7ec2f823d 100644 --- a/frontend/src/app/components/charts/charts-list/charts-list.component.html +++ b/frontend/src/app/components/charts/charts-list/charts-list.component.html @@ -1,111 +1,107 @@ - +
+ + -
-
-

Saved Queries

-

- Create and manage saved SQL queries with chart visualizations. -

-
+
+ -
- - Search queries - - search - +
+
+

Widgets

+

+ Create and manage widgets with chart visualizations for your dashboards. +

+
- - add - Create Query - -
+
+ + Search widgets + + search + - + + add + Create Widget + +
-
- bar_chart -

No saved queries found

-

No queries match your search criteria.

-

Create your first saved query to visualize your data.

- - add - Create Query - -
+ - - - - - - +
+ widgets +

No widgets found

+

No widgets match your search criteria.

+

Create your first widget to visualize your data.

+ + add + Create Widget + +
- - - - - +
+ - - -
- - - - - - - - - - -
Name - {{query.name}} - Description - {{query.description}} - No description - Last Updated - {{query.updated_at | date:'medium'}} - - - - + - edit - Edit + class="widget-preview"> + - - - -
+ + +
+ + {{query.name}} + + + + + edit + Edit + + + + +
+

+ {{query.description}} +

+
+ + {{query.updated_at | date:'mediumDate'}} + +
+
+ +
+
+
diff --git a/frontend/src/app/components/charts/charts-list/charts-list.component.ts b/frontend/src/app/components/charts/charts-list/charts-list.component.ts index 81f0f0e49..f3a5582c5 100644 --- a/frontend/src/app/components/charts/charts-list/charts-list.component.ts +++ b/frontend/src/app/components/charts/charts-list/charts-list.component.ts @@ -3,23 +3,25 @@ import { Component, computed, effect, inject, OnInit, signal } from '@angular/co import { toSignal } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; import { MatDialog } from '@angular/material/dialog'; import { MatDividerModule } from '@angular/material/divider'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; -import { MatTableModule } from '@angular/material/table'; import { MatTooltipModule } from '@angular/material/tooltip'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, RouterModule } from '@angular/router'; import { Angulartics2 } from 'angulartics2'; -import { SavedQuery } from 'src/app/models/saved-query'; +import { ChartType, SavedQuery } from 'src/app/models/saved-query'; import { ConnectionsService } from 'src/app/services/connections.service'; import { SavedQueriesService } from 'src/app/services/saved-queries.service'; +import { ChartMiniPreviewComponent } from '../../dashboards/chart-mini-preview/chart-mini-preview.component'; import { PlaceholderTableDataComponent } from '../../skeletons/placeholder-table-data/placeholder-table-data.component'; import { AlertComponent } from '../../ui-components/alert/alert.component'; import { ChartDeleteDialogComponent } from '../chart-delete-dialog/chart-delete-dialog.component'; +import { DashboardsSidebarComponent } from '../../dashboards/dashboards-sidebar/dashboards-sidebar.component'; @Component({ selector: 'app-charts-list', @@ -29,22 +31,35 @@ import { ChartDeleteDialogComponent } from '../chart-delete-dialog/chart-delete- CommonModule, FormsModule, RouterModule, - MatTableModule, MatButtonModule, + MatCardModule, MatIconModule, MatMenuModule, MatInputModule, MatFormFieldModule, MatTooltipModule, MatDividerModule, + ChartMiniPreviewComponent, PlaceholderTableDataComponent, AlertComponent, + DashboardsSidebarComponent, ], }) export class ChartsListComponent implements OnInit { protected searchQuery = signal(''); protected connectionId = signal(''); - public displayedColumns = ['name', 'description', 'updatedAt', 'actions']; + + private chartTypeIcons: Record = { + bar: 'bar_chart', + line: 'show_chart', + pie: 'pie_chart', + doughnut: 'donut_large', + polarArea: 'radar', + }; + + getChartIcon(chartType: ChartType | null): string { + return chartType ? this.chartTypeIcons[chartType] : 'bar_chart'; + } private _savedQueries = inject(SavedQueriesService); private _connections = inject(ConnectionsService); diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts index 2b58e24fb..7b8143c10 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts @@ -23,7 +23,6 @@ import { BreadcrumbsComponent } from '../../../ui-components/breadcrumbs/breadcr import { CodeEditComponent } from '../../../ui-components/record-edit-fields/code/code.component'; import { ImageEditComponent } from '../../../ui-components/record-edit-fields/image/image.component'; import { LongTextEditComponent } from '../../../ui-components/record-edit-fields/long-text/long-text.component'; -import { MarkdownEditComponent } from '../../../ui-components/record-edit-fields/markdown/markdown.component'; import { PasswordEditComponent } from '../../../ui-components/record-edit-fields/password/password.component'; import { SelectEditComponent } from '../../../ui-components/record-edit-fields/select/select.component'; import { TextEditComponent } from '../../../ui-components/record-edit-fields/text/text.component'; @@ -49,7 +48,6 @@ import { WidgetDeleteDialogComponent } from './widget-delete-dialog/widget-delet PasswordEditComponent, ImageEditComponent, CodeEditComponent, - MarkdownEditComponent, WidgetComponent, TextEditComponent, LongTextEditComponent, diff --git a/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.css b/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.css new file mode 100644 index 000000000..1be3330d7 --- /dev/null +++ b/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.css @@ -0,0 +1,85 @@ +.chart-mini-preview { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.chart-svg { + width: 100%; + height: 100%; + max-width: 120px; + max-height: 80px; +} + +/* Bar Chart */ +.bar { + fill: rgba(99, 102, 241, 0.7); +} + +.bar-1 { fill: rgba(99, 102, 241, 0.8); } +.bar-2 { fill: rgba(168, 85, 247, 0.8); } +.bar-3 { fill: rgba(236, 72, 153, 0.8); } +.bar-4 { fill: rgba(59, 130, 246, 0.8); } +.bar-5 { fill: rgba(34, 197, 94, 0.8); } + +/* Line Chart */ +.line-path { + stroke: rgba(99, 102, 241, 1); + stroke-width: 2.5; + stroke-linecap: round; + stroke-linejoin: round; +} + +.line-area { + fill: rgba(99, 102, 241, 0.15); +} + +.line-dot { + fill: rgba(99, 102, 241, 1); +} + +.gradient-start { + stop-color: rgba(99, 102, 241, 0.3); +} + +.gradient-end { + stop-color: rgba(99, 102, 241, 0); +} + +/* Pie Chart */ +.pie-slice { + stroke: #fff; + stroke-width: 1; +} + +.slice-1 { fill: rgba(99, 102, 241, 0.85); } +.slice-2 { fill: rgba(168, 85, 247, 0.85); } +.slice-3 { fill: rgba(236, 72, 153, 0.85); } +.slice-4 { fill: rgba(251, 146, 60, 0.85); } +.slice-5 { fill: rgba(34, 197, 94, 0.85); } + +/* Doughnut Chart */ +.doughnut-slice { + stroke: #fff; + stroke-width: 1; +} + +/* Polar Area Chart */ +.polar-slice { + stroke: #fff; + stroke-width: 1; +} + +@media (prefers-color-scheme: dark) { + .pie-slice, + .doughnut-slice, + .polar-slice { + stroke: #1e1e1e; + } + + .line-area { + fill: rgba(99, 102, 241, 0.25); + } +} diff --git a/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.html b/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.html new file mode 100644 index 000000000..01e12ab51 --- /dev/null +++ b/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.html @@ -0,0 +1,57 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.ts b/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.ts new file mode 100644 index 000000000..0acaf1852 --- /dev/null +++ b/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.ts @@ -0,0 +1,17 @@ +import { CommonModule } from '@angular/common'; +import { Component, Input } from '@angular/core'; +import { ChartType } from 'src/app/models/saved-query'; + +@Component({ + selector: 'app-chart-mini-preview', + templateUrl: './chart-mini-preview.component.html', + styleUrls: ['./chart-mini-preview.component.css'], + imports: [CommonModule], +}) +export class ChartMiniPreviewComponent { + @Input() chartType: ChartType | null = 'bar'; + + get displayType(): ChartType { + return this.chartType || 'bar'; + } +} diff --git a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.css b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.css index e4878a054..e141f9eff 100644 --- a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.css +++ b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.css @@ -1,11 +1,23 @@ +.dashboards-layout { + display: flex; + height: calc(100vh - 64px); +} + +.dashboards-main { + flex: 1; + overflow-y: auto; +} + .dashboards-page { margin: 3em auto; - padding: 0 clamp(200px, 20vw, 300px); + padding: 0 clamp(40px, 5vw, 100px); + max-width: 1400px; } @media (width <= 600px) { .dashboards-page { - padding: 0 9vw; + padding: 0 16px; + margin: 1.5em auto; } } @@ -50,71 +62,171 @@ } } -.toolbar-actions { +/* Cards Grid */ +.dashboards-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 24px; +} + +@media (width <= 600px) { + .dashboards-grid { + grid-template-columns: 1fr; + gap: 16px; + } +} + +/* Dashboard Card */ +.dashboard-card { + display: flex; + flex-direction: column; + overflow: hidden; + transition: box-shadow 0.2s ease, transform 0.2s ease; +} + +.dashboard-card:hover { + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12); + transform: translateY(-2px); +} + +@media (prefers-color-scheme: dark) { + .dashboard-card:hover { + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); + } +} + +/* Preview Area */ +.dashboard-preview { + display: block; + height: 160px; + background-color: rgba(0, 0, 0, 0.03); + border-bottom: 1px solid rgba(0, 0, 0, 0.08); + text-decoration: none; + overflow: hidden; +} + +@media (prefers-color-scheme: dark) { + .dashboard-preview { + background-color: rgba(255, 255, 255, 0.05); + border-bottom-color: rgba(255, 255, 255, 0.08); + } +} + +.preview-placeholder { display: flex; - gap: 12px; align-items: center; + justify-content: center; + height: 100%; + background-color: #f5f5f5; } -@media (width <= 600px) { - .toolbar-actions { - flex-direction: column; - width: 100%; +.preview-placeholder-icon { + font-size: 56px; + width: 56px; + height: 56px; + color: rgba(0, 0, 0, 0.2); +} + +@media (prefers-color-scheme: dark) { + .preview-placeholder { + background-color: #2a2a2a; } - .toolbar-actions a, - .toolbar-actions button { - width: 100%; + .preview-placeholder-icon { + color: rgba(255, 255, 255, 0.2); } } -.dashboards-table { - width: 100%; - margin-bottom: 16px; +/* Card Content */ +.dashboard-card-content { + padding: 16px; + flex: 1; + display: flex; + flex-direction: column; } -.dashboards-cell_name { - max-width: 250px; +.dashboard-card-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 8px; + margin-bottom: 8px; } -.name-link { - text-decoration: none; +.dashboard-name { + font-size: 16px; + font-weight: 500; color: inherit; + text-decoration: none; + line-height: 1.4; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; } -.name-link:hover .name-text { - text-decoration: underline; +.dashboard-name:hover { + color: #673ab7; } -.name-text { - font-weight: 500; +@media (prefers-color-scheme: dark) { + .dashboard-name:hover { + color: #b388ff; + } } -.description-text { - color: rgba(0, 0, 0, 0.87); +.dashboard-menu-button { + flex-shrink: 0; + margin: -8px -8px 0 0; +} + +.dashboard-description-text { + font-size: 14px; + color: rgba(0, 0, 0, 0.6); + margin: 0 0 12px 0; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; } @media (prefers-color-scheme: dark) { - .description-text { - color: rgba(255, 255, 255, 0.87); + .dashboard-description-text { + color: rgba(255, 255, 255, 0.6); } } -.no-description { - color: rgba(0, 0, 0, 0.38); - font-style: italic; +.dashboard-meta { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: auto; + font-size: 12px; + color: rgba(0, 0, 0, 0.5); } @media (prefers-color-scheme: dark) { - .no-description { - color: rgba(255, 255, 255, 0.38); + .dashboard-meta { + color: rgba(255, 255, 255, 0.5); } } -.dashboards-cell_actions { - text-align: right; +.dashboard-widgets-count { + display: flex; + align-items: center; + gap: 4px; +} + +.meta-icon { + font-size: 16px; + width: 16px; + height: 16px; } +/* Empty State */ .no-dashboards { display: flex; flex-direction: column; @@ -177,59 +289,3 @@ color: #ef5350; } } - -/* Responsive table styles */ -@media (width <= 600px) { - .dashboards-table { - display: grid; - grid-template-columns: auto 1fr; - max-width: 100%; - } - - .dashboards-table-heading { - display: none; - } - - .dashboards-table ::ng-deep tbody { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / 3; - } - - .dashboards-row { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / 3; - grid-gap: 12px 28px; - border-bottom-color: var(--mat-table-row-item-outline-color, rgba(0, 0, 0, 0.12)); - border-bottom-width: var(--mat-table-row-item-outline-width, 1px); - border-bottom-style: solid; - height: auto; - padding: 20px 0; - } - - .dashboards-cell { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / 3; - border-bottom: none; - } - - .dashboards-cell::before { - content: attr(data-label); - display: inline-block; - font-weight: bold; - white-space: nowrap; - } - - .dashboards-cell_actions { - grid-column: 1 / span 3; - display: flex; - justify-content: flex-end; - border-bottom: none; - } - - .dashboards-cell_actions::before { - display: none; - } -} diff --git a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.html b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.html index 1d5f770ef..808fcb1f8 100644 --- a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.html +++ b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.html @@ -1,126 +1,116 @@ - +
+ + -
-
-

Dashboards

-

- Create and manage custom dashboards with charts, tables, and metrics. -

-
+
+ -
- - Search dashboards - - search - +
+
+

Dashboards

+

+ Create and manage custom dashboards with charts, tables, and metrics. +

+
-
- - code - Manage Saved Queries - - -
-
+
+ + Search dashboards + + search + - - -
- dashboard -

No dashboards found

-

No dashboards match your search criteria.

-

Create your first dashboard to visualize your data.

- -
+ +
- - - - - - + - - - - - +
+ dashboard +

No dashboards found

+

No dashboards match your search criteria.

+

Create your first dashboard to visualize your data.

+ +
- - - - - +
+ - - -
- - - - -
Name - - {{dashboard.name}} - - Description - {{dashboard.description}} - No description - Last Updated - {{dashboard.updated_at | date:'medium'}} - - - - + - visibility - View + class="dashboard-preview"> +
+ dashboard +
- - - -
-
+ + +
+ + {{dashboard.name}} + + + + + visibility + View + + + + + +
+

+ {{dashboard.description}} +

+
+ + widgets + {{dashboard.widgets?.length || 0}} widgets + + + {{dashboard.updated_at | date:'mediumDate'}} + +
+
+ +
+
+
diff --git a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts index 38074d176..0ae225eba 100644 --- a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts +++ b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts @@ -3,13 +3,13 @@ import { Component, computed, DestroyRef, effect, inject, OnInit, signal } from import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; import { MatDialog } from '@angular/material/dialog'; import { MatDividerModule } from '@angular/material/divider'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; -import { MatTableModule } from '@angular/material/table'; import { MatTooltipModule } from '@angular/material/tooltip'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, RouterModule } from '@angular/router'; @@ -21,6 +21,7 @@ import { PlaceholderTableDataComponent } from '../../skeletons/placeholder-table import { AlertComponent } from '../../ui-components/alert/alert.component'; import { DashboardDeleteDialogComponent } from '../dashboard-delete-dialog/dashboard-delete-dialog.component'; import { DashboardEditDialogComponent } from '../dashboard-edit-dialog/dashboard-edit-dialog.component'; +import { DashboardsSidebarComponent } from '../dashboards-sidebar/dashboards-sidebar.component'; @Component({ selector: 'app-dashboards-list', @@ -30,8 +31,8 @@ import { DashboardEditDialogComponent } from '../dashboard-edit-dialog/dashboard CommonModule, FormsModule, RouterModule, - MatTableModule, MatButtonModule, + MatCardModule, MatIconModule, MatMenuModule, MatInputModule, @@ -40,12 +41,12 @@ import { DashboardEditDialogComponent } from '../dashboard-edit-dialog/dashboard MatDividerModule, PlaceholderTableDataComponent, AlertComponent, + DashboardsSidebarComponent, ], }) export class DashboardsListComponent implements OnInit { protected searchQuery = signal(''); protected connectionId = signal(''); - public displayedColumns = ['name', 'description', 'updatedAt', 'actions']; private _dashboards = inject(DashboardsService); private _connections = inject(ConnectionsService); diff --git a/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.css b/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.css new file mode 100644 index 000000000..8fab0af64 --- /dev/null +++ b/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.css @@ -0,0 +1,98 @@ +.dashboards-sidebar { + width: 220px; + min-width: 220px; + height: 100%; + border-right: 1px solid rgba(0, 0, 0, 0.12); + display: flex; + flex-direction: column; + background-color: #fafafa; + overflow: hidden; +} + +.dashboards-sidebar_initialized { + transition: width 0.2s ease-in-out, min-width 0.2s ease-in-out; +} + +@media (prefers-color-scheme: dark) { + .dashboards-sidebar { + background-color: #1e1e1e; + border-right-color: rgba(255, 255, 255, 0.12); + } +} + +.dashboards-sidebar_collapsed { + width: 56px; + min-width: 56px; +} + +.sidebar-toggle-button { + margin: 12px; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 4px; +} + +.sidebar-toggle-button_collapsed { + justify-content: center; + margin: 12px 8px; +} + +.sidebar-nav { + padding-top: 0; + overflow: hidden; +} + +.sidebar-nav-item { + margin: 4px 8px; + border-radius: 8px; + overflow: hidden; +} + +.sidebar-nav-item ::ng-deep .mdc-list-item__primary-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.sidebar-nav-item_collapsed { + margin: 4px; +} + +.sidebar-nav-item_collapsed ::ng-deep .mdc-list-item__content { + padding: 0; + display: flex; + justify-content: center; +} + +.active-link { + background-color: rgba(103, 58, 183, 0.12); +} + +@media (prefers-color-scheme: dark) { + .active-link { + background-color: rgba(179, 136, 255, 0.16); + } +} + +.active-link mat-icon { + color: #673ab7; +} + +@media (prefers-color-scheme: dark) { + .active-link mat-icon { + color: #b388ff; + } +} + +/* Mobile styles */ +@media (width <= 768px) { + .dashboards-sidebar { + width: 56px; + min-width: 56px; + } + + .sidebar-toggle-button:not(.sidebar-toggle-button_collapsed) { + display: none; + } +} diff --git a/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.html b/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.html new file mode 100644 index 000000000..1b9bd8136 --- /dev/null +++ b/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.html @@ -0,0 +1,42 @@ +
+ + + + + + + + + dashboard + Dashboards + + + widgets + Widgets + + +
diff --git a/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.ts b/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.ts new file mode 100644 index 000000000..7dc7155aa --- /dev/null +++ b/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.ts @@ -0,0 +1,52 @@ +import { AfterViewInit, Component, input, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { MatListModule } from '@angular/material/list'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { MatTooltipModule } from '@angular/material/tooltip'; + +const SIDEBAR_COLLAPSED_KEY = 'dashboards_sidebar_collapsed'; + +@Component({ + selector: 'app-dashboards-sidebar', + templateUrl: './dashboards-sidebar.component.html', + styleUrls: ['./dashboards-sidebar.component.css'], + imports: [ + CommonModule, + RouterModule, + MatListModule, + MatIconModule, + MatButtonModule, + MatTooltipModule, + ], +}) +export class DashboardsSidebarComponent implements AfterViewInit { + connectionId = input.required(); + activeTab = input<'dashboards' | 'queries'>('dashboards'); + + collapsed = signal(this._loadCollapsedState()); + initialized = signal(false); + + ngAfterViewInit(): void { + // Enable transitions after initial render to prevent animation on page load + setTimeout(() => this.initialized.set(true), 0); + } + + toggleCollapsed(): void { + this.collapsed.update((v) => { + const newValue = !v; + this._saveCollapsedState(newValue); + return newValue; + }); + } + + private _loadCollapsedState(): boolean { + const saved = localStorage.getItem(SIDEBAR_COLLAPSED_KEY); + return saved === 'true'; + } + + private _saveCollapsedState(collapsed: boolean): void { + localStorage.setItem(SIDEBAR_COLLAPSED_KEY, String(collapsed)); + } +} diff --git a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.css b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.css index 2e9f21fc6..b03c1389b 100644 --- a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.css +++ b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.css @@ -14,3 +14,61 @@ .full-width { width: 100%; } + +.no-queries-message { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 24px; + min-width: 300px; +} + +.no-queries-icon { + font-size: 48px; + width: 48px; + height: 48px; + color: rgba(0, 0, 0, 0.26); + margin-bottom: 16px; +} + +@media (prefers-color-scheme: dark) { + .no-queries-icon { + color: rgba(255, 255, 255, 0.3); + } +} + +.no-queries-message h3 { + margin: 0 0 8px 0; + font-size: 18px; + font-weight: 500; +} + +.no-queries-message p { + margin: 0 0 20px 0; + color: rgba(0, 0, 0, 0.6); + font-size: 14px; +} + +@media (prefers-color-scheme: dark) { + .no-queries-message p { + color: rgba(255, 255, 255, 0.6); + } +} + +.create-option { + color: #673ab7; +} + +.create-option mat-icon { + margin-right: 8px; + font-size: 20px; + width: 20px; + height: 20px; +} + +@media (prefers-color-scheme: dark) { + .create-option { + color: #b388ff; + } +} diff --git a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.html b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.html index 2cacb6888..b42baaf0a 100644 --- a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.html +++ b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.html @@ -1,29 +1,50 @@ -

{{ isEdit ? 'Edit Widget' : 'Add Widget' }}

+

{{ isEdit ? 'Edit Widget' : 'Add Chart' }}

-
- - Saved Query - - @for (query of savedQueries(); track query.id) { - {{ query.name }} + @if (savedQueries().length > 0) { + + + Saved Query + + @for (query of savedQueries(); track query.id) { + {{ query.name }} + } + + + add + Create new query + + + Select a saved query to display in this widget + @if (form.get('query_id')?.hasError('required')) { + Please select a saved query } - - Select a saved query to display in this widget - @if (form.get('query_id')?.hasError('required')) { - Please select a saved query - } - -
+ + + } @else { +
+ code +

No saved queries yet

+

Create a saved query first to add it as a chart on this dashboard.

+ + add + Create Query + +
+ }
- + @if (savedQueries().length > 0) { + + } diff --git a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.ts b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.ts index 30f4facac..d50adc11e 100644 --- a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.ts +++ b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.ts @@ -4,7 +4,10 @@ import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angula import { MatButtonModule } from '@angular/material/button'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatIconModule } from '@angular/material/icon'; import { MatSelectModule } from '@angular/material/select'; +import { Router, RouterModule } from '@angular/router'; import { Angulartics2 } from 'angulartics2'; import { DashboardWidget } from 'src/app/models/dashboard'; import { DashboardsService } from 'src/app/services/dashboards.service'; @@ -14,7 +17,17 @@ import { SavedQueriesService } from 'src/app/services/saved-queries.service'; selector: 'app-widget-edit-dialog', templateUrl: './widget-edit-dialog.component.html', styleUrls: ['./widget-edit-dialog.component.css'], - imports: [CommonModule, ReactiveFormsModule, MatDialogModule, MatButtonModule, MatFormFieldModule, MatSelectModule], + imports: [ + CommonModule, + ReactiveFormsModule, + RouterModule, + MatDialogModule, + MatButtonModule, + MatDividerModule, + MatFormFieldModule, + MatIconModule, + MatSelectModule, + ], }) export class WidgetEditDialogComponent implements OnInit { protected submitting = signal(false); @@ -26,9 +39,10 @@ export class WidgetEditDialogComponent implements OnInit { constructor( @Inject(MAT_DIALOG_DATA) public data: { connectionId: string; dashboardId: string; widget: DashboardWidget | null }, - private dialogRef: MatDialogRef, + public dialogRef: MatDialogRef, private _dashboards: DashboardsService, private _savedQueries: SavedQueriesService, + private _router: Router, private fb: FormBuilder, private angulartics2: Angulartics2, ) { @@ -87,4 +101,9 @@ export class WidgetEditDialogComponent implements OnInit { } this.submitting.set(false); } + + navigateToCreateQuery(): void { + this.dialogRef.close(); + this._router.navigate(['/charts', this.data.connectionId, 'new']); + } } diff --git a/frontend/src/app/services/dashboards.service.ts b/frontend/src/app/services/dashboards.service.ts index 74a9e3580..e79768158 100644 --- a/frontend/src/app/services/dashboards.service.ts +++ b/frontend/src/app/services/dashboards.service.ts @@ -149,6 +149,7 @@ export class DashboardsService { this._http.post(`/dashboard/${dashboardId}/widget/${connectionId}`, payload), ); this._notifications.showSuccessSnackbar('Widget created successfully'); + this._dashboardsUpdated.set('updated'); return widget; } catch (err: unknown) { const error = err as { error?: { message?: string } }; @@ -202,6 +203,7 @@ export class DashboardsService { this._http.delete(`/dashboard/${dashboardId}/widget/${widgetId}/${connectionId}`), ); this._notifications.showSuccessSnackbar('Widget deleted successfully'); + this._dashboardsUpdated.set('updated'); return widget; } catch (err: unknown) { const error = err as { error?: { message?: string } }; From 7d188bbb84db3eea7e07e36ae78c306645fbd0c2 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Fri, 6 Feb 2026 15:46:55 +0200 Subject: [PATCH 02/11] feat: convert widgets to table view with chart type previews - Convert widgets list from cards to table with columns: Type, Name, Description, Updated, Actions - Add chart type mini preview icons in first column with tooltip on hover - Show relative time for recent updates (e.g., "2 hours ago") - Simplify bar chart preview to 3 bars - Enlarge pie, doughnut, polar area chart previews - Rename "Edit Saved Query" to "Edit Widget" - Fix spinner alignment in Test Query button - Add top margin to query inputs to prevent label overlap Co-Authored-By: Claude Opus 4.5 --- .../chart-edit/chart-edit.component.css | 7 + .../chart-edit/chart-edit.component.html | 5 +- .../charts-list/charts-list.component.css | 134 ++++++------------ .../charts-list/charts-list.component.html | 113 +++++++++------ .../charts-list/charts-list.component.ts | 30 ++++ .../chart-mini-preview.component.html | 43 +++--- 6 files changed, 173 insertions(+), 159 deletions(-) diff --git a/frontend/src/app/components/charts/chart-edit/chart-edit.component.css b/frontend/src/app/components/charts/chart-edit/chart-edit.component.css index 4c2da1fa2..660f2cef7 100644 --- a/frontend/src/app/components/charts/chart-edit/chart-edit.component.css +++ b/frontend/src/app/components/charts/chart-edit/chart-edit.component.css @@ -54,6 +54,7 @@ .query-details { display: flex; gap: 16px; + margin-top: 8px; } @media (width <= 600px) { @@ -248,3 +249,9 @@ display: inline-block; margin-right: 8px; } + +.test-query-button .button-spinner { + display: inline-block; + vertical-align: middle; + margin-right: 4px; +} diff --git a/frontend/src/app/components/charts/chart-edit/chart-edit.component.html b/frontend/src/app/components/charts/chart-edit/chart-edit.component.html index 8e6aeb119..0e85408c4 100644 --- a/frontend/src/app/components/charts/chart-edit/chart-edit.component.html +++ b/frontend/src/app/components/charts/chart-edit/chart-edit.component.html @@ -12,7 +12,7 @@ -

{{ isEditMode() ? 'Edit Saved Query' : 'Create Saved Query' }}

+

{{ isEditMode() ? 'Edit Widget' : 'Create Widget' }}

@@ -48,9 +48,10 @@

SQL Query

diff --git a/frontend/src/app/components/charts/charts-list/charts-list.component.css b/frontend/src/app/components/charts/charts-list/charts-list.component.css index 9194be609..1f3c8001c 100644 --- a/frontend/src/app/components/charts/charts-list/charts-list.component.css +++ b/frontend/src/app/components/charts/charts-list/charts-list.component.css @@ -62,141 +62,101 @@ } } -/* Cards Grid */ -.widgets-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 24px; -} - -@media (width <= 600px) { - .widgets-grid { - grid-template-columns: 1fr; - gap: 16px; - } +/* Table Styles */ +.widgets-table { + width: 100%; + background: transparent; } -/* Widget Card */ -.widget-card { - display: flex; - flex-direction: column; - overflow: hidden; - transition: box-shadow 0.2s ease, transform 0.2s ease; +.widgets-table tr.mat-mdc-row { + height: 64px; } -.widget-card:hover { - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12); - transform: translateY(-2px); +.widgets-table th.mat-mdc-header-cell { + font-weight: 500; + color: rgba(0, 0, 0, 0.6); + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; } @media (prefers-color-scheme: dark) { - .widget-card:hover { - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); + .widgets-table th.mat-mdc-header-cell { + color: rgba(255, 255, 255, 0.6); } } -/* Preview Area */ -.widget-preview { +/* Preview Cell */ +.preview-cell { + width: 64px; + padding-right: 8px !important; +} + +.preview-link { display: flex; align-items: center; justify-content: center; - height: 140px; - background-color: #f5f5f5; - border-bottom: 1px solid rgba(0, 0, 0, 0.08); + width: 56px; + height: 56px; text-decoration: none; overflow: hidden; - padding: 16px; -} - -@media (prefers-color-scheme: dark) { - .widget-preview { - background-color: #2a2a2a; - border-bottom-color: rgba(255, 255, 255, 0.08); - } } -.widget-preview app-chart-mini-preview { - width: 100%; - height: 100%; +.preview-link app-chart-mini-preview { + width: 48px; + height: 40px; } -/* Card Content */ -.widget-card-content { - padding: 16px; - flex: 1; - display: flex; - flex-direction: column; -} - -.widget-card-header { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 8px; - margin-bottom: 2px; -} - -.widget-name { - font-size: 16px; +/* Name Link */ +.widget-name-link { font-weight: 500; color: inherit; text-decoration: none; - line-height: 1.4; - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; } -.widget-name:hover { +.widget-name-link:hover { color: #673ab7; } @media (prefers-color-scheme: dark) { - .widget-name:hover { + .widget-name-link:hover { color: #b388ff; } } -.widget-menu-button { - flex-shrink: 0; - margin: -8px -8px 0 0; -} - -.widget-description-text { - font-size: 12px; - color: rgba(0, 0, 0, 0.45); - margin: 0 0 12px 0; +/* Description Cell */ +.description-cell { + color: rgba(0, 0, 0, 0.6); + max-width: 300px; overflow: hidden; text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; + white-space: nowrap; } @media (prefers-color-scheme: dark) { - .widget-description-text { - color: rgba(255, 255, 255, 0.45); + .description-cell { + color: rgba(255, 255, 255, 0.6); } } -.widget-meta { - display: flex; - align-items: center; - justify-content: flex-end; - margin-top: auto; - font-size: 12px; +/* Updated Cell */ +.updated-cell { color: rgba(0, 0, 0, 0.5); + white-space: nowrap; } @media (prefers-color-scheme: dark) { - .widget-meta { + .updated-cell { color: rgba(255, 255, 255, 0.5); } } +/* Actions Cell */ +.actions-cell { + width: 48px; + text-align: right; +} + /* Empty State */ .no-widgets { display: flex; diff --git a/frontend/src/app/components/charts/charts-list/charts-list.component.html b/frontend/src/app/components/charts/charts-list/charts-list.component.html index 7ec2f823d..da4e0d2e7 100644 --- a/frontend/src/app/components/charts/charts-list/charts-list.component.html +++ b/frontend/src/app/components/charts/charts-list/charts-list.component.html @@ -50,58 +50,77 @@

No widgets found

-
- + + + + + + - - - - + + + + + - - - + + + + + + + + + + + + + + + + + +
Type + + + + Name + + {{query.name}} + + Description + {{query.description || '—'}} + Updated + {{formatUpdatedAt(query.updated_at)}} + + + + - {{query.name}} + attr.data-testid="widget-edit-{{i}}-menu-item"> + edit + Edit - - - - edit - Edit - - - - - -

- {{query.description}} -

-
- - {{query.updated_at | date:'mediumDate'}} - -
- - - +
+
diff --git a/frontend/src/app/components/charts/charts-list/charts-list.component.ts b/frontend/src/app/components/charts/charts-list/charts-list.component.ts index f3a5582c5..75dee10d3 100644 --- a/frontend/src/app/components/charts/charts-list/charts-list.component.ts +++ b/frontend/src/app/components/charts/charts-list/charts-list.component.ts @@ -4,6 +4,7 @@ import { toSignal } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; +import { MatTableModule } from '@angular/material/table'; import { MatDialog } from '@angular/material/dialog'; import { MatDividerModule } from '@angular/material/divider'; import { MatFormFieldModule } from '@angular/material/form-field'; @@ -39,6 +40,7 @@ import { DashboardsSidebarComponent } from '../../dashboards/dashboards-sidebar/ MatFormFieldModule, MatTooltipModule, MatDividerModule, + MatTableModule, ChartMiniPreviewComponent, PlaceholderTableDataComponent, AlertComponent, @@ -48,6 +50,7 @@ import { DashboardsSidebarComponent } from '../../dashboards/dashboards-sidebar/ export class ChartsListComponent implements OnInit { protected searchQuery = signal(''); protected connectionId = signal(''); + protected displayedColumns = ['preview', 'name', 'description', 'updated', 'actions']; private chartTypeIcons: Record = { bar: 'bar_chart', @@ -61,6 +64,17 @@ export class ChartsListComponent implements OnInit { return chartType ? this.chartTypeIcons[chartType] : 'bar_chart'; } + getChartTypeName(chartType: ChartType | null): string { + const names: Record = { + bar: 'Bar Chart', + line: 'Line Chart', + pie: 'Pie Chart', + doughnut: 'Doughnut Chart', + polarArea: 'Polar Area Chart', + }; + return chartType ? names[chartType] : 'Bar Chart'; + } + private _savedQueries = inject(SavedQueriesService); private _connections = inject(ConnectionsService); private route = inject(ActivatedRoute); @@ -125,4 +139,20 @@ export class ChartsListComponent implements OnInit { action: 'Charts: delete chart dialog opened', }); } + + formatUpdatedAt(date: string): string { + const now = new Date(); + const updated = new Date(date); + const diffMs = now.getTime() - updated.getTime(); + const diffMins = Math.floor(diffMs / 60000); + const diffHours = Math.floor(diffMs / 3600000); + const diffDays = Math.floor(diffMs / 86400000); + + if (diffMins < 1) return 'Just now'; + if (diffMins < 60) return `${diffMins} min ago`; + if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`; + if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`; + + return updated.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); + } } diff --git a/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.html b/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.html index 01e12ab51..13c0315bc 100644 --- a/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.html +++ b/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.html @@ -1,11 +1,9 @@
- - - - - + + + @@ -26,32 +24,31 @@ - - - - - + + + + + - - - - - + + + + + - - - - - - - - + + + + + + +
From ce951787d9b9856b4516ba47ae728addfc899659 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Fri, 6 Feb 2026 15:57:16 +0200 Subject: [PATCH 03/11] fix: add top padding to dialog inputs to prevent label overlap Co-Authored-By: Claude Opus 4.5 --- .../dashboard-edit-dialog/dashboard-edit-dialog.component.css | 1 + .../widget-edit-dialog/widget-edit-dialog.component.css | 1 + 2 files changed, 2 insertions(+) diff --git a/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.css b/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.css index 7d04bba7b..87944b9f3 100644 --- a/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.css +++ b/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.css @@ -3,6 +3,7 @@ flex-direction: column; gap: 8px; min-width: 400px; + padding-top: 8px; } @media (width <= 600px) { diff --git a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.css b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.css index b03c1389b..5df989bc0 100644 --- a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.css +++ b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.css @@ -3,6 +3,7 @@ flex-direction: column; gap: 8px; min-width: 400px; + padding-top: 8px; } @media (width <= 600px) { From 9d9d9bfc3a55de29128695c192458243fa2e6b61 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Fri, 6 Feb 2026 18:23:32 +0000 Subject: [PATCH 04/11] Dashboards: use uiSettings to store sidebar state --- .../dashboards-sidebar.component.ts | 28 ++++++++++--------- frontend/src/app/models/ui-settings.ts | 1 + .../src/app/services/ui-settings.service.ts | 22 +++++++-------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.ts b/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.ts index 7dc7155aa..762ed7b9e 100644 --- a/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.ts +++ b/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.ts @@ -1,12 +1,11 @@ -import { AfterViewInit, Component, input, signal } from '@angular/core'; +import { AfterViewInit, Component, inject, input, OnInit, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { MatTooltipModule } from '@angular/material/tooltip'; - -const SIDEBAR_COLLAPSED_KEY = 'dashboards_sidebar_collapsed'; +import { UiSettingsService } from '../../../services/ui-settings.service'; @Component({ selector: 'app-dashboards-sidebar', @@ -21,13 +20,19 @@ const SIDEBAR_COLLAPSED_KEY = 'dashboards_sidebar_collapsed'; MatTooltipModule, ], }) -export class DashboardsSidebarComponent implements AfterViewInit { +export class DashboardsSidebarComponent implements OnInit, AfterViewInit { connectionId = input.required(); activeTab = input<'dashboards' | 'queries'>('dashboards'); - collapsed = signal(this._loadCollapsedState()); + collapsed = signal(false); initialized = signal(false); + private _uiSettings = inject(UiSettingsService); + + ngOnInit(): void { + this._loadCollapsedState(); + } + ngAfterViewInit(): void { // Enable transitions after initial render to prevent animation on page load setTimeout(() => this.initialized.set(true), 0); @@ -36,17 +41,14 @@ export class DashboardsSidebarComponent implements AfterViewInit { toggleCollapsed(): void { this.collapsed.update((v) => { const newValue = !v; - this._saveCollapsedState(newValue); + this._uiSettings.updateConnectionSetting(this.connectionId(), 'dashboardsSidebarCollapsed', newValue); return newValue; }); } - private _loadCollapsedState(): boolean { - const saved = localStorage.getItem(SIDEBAR_COLLAPSED_KEY); - return saved === 'true'; - } - - private _saveCollapsedState(collapsed: boolean): void { - localStorage.setItem(SIDEBAR_COLLAPSED_KEY, String(collapsed)); + private _loadCollapsedState(): void { + this._uiSettings.getUiSettings().subscribe((settings) => { + this.collapsed.set(settings?.connections?.[this.connectionId()]?.dashboardsSidebarCollapsed ?? false); + }); } } diff --git a/frontend/src/app/models/ui-settings.ts b/frontend/src/app/models/ui-settings.ts index e011be8bb..a4beffcb9 100644 --- a/frontend/src/app/models/ui-settings.ts +++ b/frontend/src/app/models/ui-settings.ts @@ -15,6 +15,7 @@ export interface ConnectionSettingsUI { shownTableTitles: boolean; tables: { [tableName: string]: TableSettingsUI }; tableFoldersExpanded?: string[]; + dashboardsSidebarCollapsed?: boolean; } export interface UiSettings { diff --git a/frontend/src/app/services/ui-settings.service.ts b/frontend/src/app/services/ui-settings.service.ts index 16b0dffec..2a40fc1bd 100644 --- a/frontend/src/app/services/ui-settings.service.ts +++ b/frontend/src/app/services/ui-settings.service.ts @@ -46,17 +46,17 @@ export class UiSettingsService { this.syncUiSettings().subscribe(); } - updateTableSetting(connectionId: string, tableName: string, key: string, value: any) { - console.log('updateTableSetting') - if (!this.settings.connections[connectionId]) { - this.settings.connections[connectionId] = { shownTableTitles: false, tables: {} }; - } - // if (!this.settings.connections[connectionId].tables[tableName]) { - // this.settings.connections[connectionId].tables[tableName] = { shownColumns: [] }; - // } - this.settings.connections[connectionId].tables[tableName][key] = value; - this.syncUiSettings().subscribe(); - } + updateTableSetting(connectionId: string, tableName: string, key: string, value: any) { + console.log('updateTableSetting') + if (!this.settings.connections[connectionId]) { + this.settings.connections[connectionId] = { shownTableTitles: false, tables: {} }; + } + // if (!this.settings.connections[connectionId].tables[tableName]) { + // this.settings.connections[connectionId].tables[tableName] = { shownColumns: [] }; + // } + this.settings.connections[connectionId].tables[tableName][key] = value; + this.syncUiSettings().subscribe(); + } getUiSettings(): Observable { if (!this.uiSettings) { From 0d9bb6b42d4e7ea37df1fa8ffb9369ccfc117159 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Fri, 6 Feb 2026 18:40:43 +0000 Subject: [PATCH 05/11] Dashboards: show only one Create button on empty state --- .../dashboards-list/dashboards-list.component.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.html b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.html index 808fcb1f8..825122006 100644 --- a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.html +++ b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.html @@ -26,7 +26,7 @@

Dashboards

search - From 9b7ebd3a39e4b628317ae7abcd70ccae508287f6 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Fri, 6 Feb 2026 18:48:20 +0000 Subject: [PATCH 06/11] Dashboards: fix updating cards on add and delete dashboard --- .../dashboards/dashboards-list/dashboards-list.component.ts | 5 ++++- frontend/src/app/services/dashboards.service.ts | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts index 0ae225eba..d82d1b928 100644 --- a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts +++ b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts @@ -89,7 +89,10 @@ export class DashboardsListComponent implements OnInit { // Dashboards update effect effect(() => { const action = this._dashboards.dashboardsUpdated(); - if (action) this._dashboards.refreshDashboards(); + if (action) { + this._dashboards.refreshDashboards(); + this._dashboards.clearDashboardsUpdated(); + } }); } diff --git a/frontend/src/app/services/dashboards.service.ts b/frontend/src/app/services/dashboards.service.ts index e79768158..67a173896 100644 --- a/frontend/src/app/services/dashboards.service.ts +++ b/frontend/src/app/services/dashboards.service.ts @@ -85,6 +85,10 @@ export class DashboardsService { this._dashboardsResource.reload(); } + clearDashboardsUpdated(): void { + this._dashboardsUpdated.set(''); + } + refreshDashboard(): void { this._dashboardResource.reload(); } From a26a888a6cc3ffb0afea577d65897ccabec3ddbe Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Fri, 6 Feb 2026 18:51:08 +0000 Subject: [PATCH 07/11] Dashboards: send create dashboard on enter --- .../dashboard-edit-dialog.component.html | 77 ++++++++++--------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.html b/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.html index dee780ba6..a3955f8ab 100644 --- a/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.html +++ b/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.html @@ -1,41 +1,42 @@ -

{{ isEdit ? 'Edit Dashboard' : 'Create Dashboard' }}

+
+

{{ isEdit ? 'Edit Dashboard' : 'Create Dashboard' }}

- - - - Name - - @if (form.get('name')?.hasError('required')) { - Name is required - } - @if (form.get('name')?.hasError('maxlength')) { - Name must be less than 255 characters - } - + +
+ + Name + + @if (form.get('name')?.hasError('required')) { + Name is required + } + @if (form.get('name')?.hasError('maxlength')) { + Name must be less than 255 characters + } + - - Description - - @if (form.get('description')?.hasError('maxlength')) { - Description must be less than 1000 characters - } - - - + + Description + + @if (form.get('description')?.hasError('maxlength')) { + Description must be less than 1000 characters + } + +
+
- - - - + + + + + From 92a9d2e2d992a2f530ed1301bb3bd6a96a83271e Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Fri, 6 Feb 2026 19:07:55 +0000 Subject: [PATCH 08/11] Dashboard: - hide edit and add buttons on charts empty state; - fix Dashboard list on page load error --- .../dashboard-view.component.html | 34 ++++++++++--------- .../dashboards-list.component.ts | 1 + 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/components/dashboards/dashboard-view/dashboard-view.component.html b/frontend/src/app/components/dashboards/dashboard-view/dashboard-view.component.html index 0496d6e8d..f7245f281 100644 --- a/frontend/src/app/components/dashboards/dashboard-view/dashboard-view.component.html +++ b/frontend/src/app/components/dashboards/dashboard-view/dashboard-view.component.html @@ -13,21 +13,23 @@

{{ dashboard()?.name || 'Loading...' }}

} -
- - Edit Mode - - -
+ @if (gridsterItems().length > 0) { +
+ + Edit Mode + + +
+ } @if (loading()) { @@ -41,7 +43,7 @@

{{ dashboard()?.name || 'Loading...' }}

bar_chart

No charts yet

Add charts to visualize your data from saved queries.

- diff --git a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts index d82d1b928..40cfc1143 100644 --- a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts +++ b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts @@ -99,6 +99,7 @@ export class DashboardsListComponent implements OnInit { ngOnInit(): void { const connId = this.route.snapshot.paramMap.get('connection-id') || ''; this.connectionId.set(connId); + this._dashboards.setActiveDashboard(null); this._dashboards.setActiveConnection(connId); } From fa0e36357cb6b5c960472b57babe6d4f100d2ba4 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Fri, 6 Feb 2026 19:13:00 +0000 Subject: [PATCH 09/11] Dashboard: add chart on enter --- .../widget-edit-dialog.component.html | 95 ++++++++++--------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.html b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.html index b42baaf0a..6f0848085 100644 --- a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.html +++ b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.html @@ -1,50 +1,51 @@ -

{{ isEdit ? 'Edit Widget' : 'Add Chart' }}

+
+

{{ isEdit ? 'Edit Widget' : 'Add Chart' }}

- - @if (savedQueries().length > 0) { - - - Saved Query - - @for (query of savedQueries(); track query.id) { - {{ query.name }} + + @if (savedQueries().length > 0) { +
+ } @else { +
+ code +

No saved queries yet

+

Create a saved query first to add it as a chart on this dashboard.

+ + add + Create Query + +
+ } + - - - @if (savedQueries().length > 0) { - - } - + + + @if (savedQueries().length > 0) { + + } + + From 988c6b818cc825fe0ed0a44e36380e20bdef3ea1 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Fri, 6 Feb 2026 19:17:50 +0000 Subject: [PATCH 10/11] Dashboards: fix widgets empty state --- .../components/charts/charts-list/charts-list.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/charts/charts-list/charts-list.component.html b/frontend/src/app/components/charts/charts-list/charts-list.component.html index da4e0d2e7..3595deba5 100644 --- a/frontend/src/app/components/charts/charts-list/charts-list.component.html +++ b/frontend/src/app/components/charts/charts-list/charts-list.component.html @@ -26,7 +26,7 @@

Widgets

search - @@ -42,7 +42,7 @@

Widgets

No widgets found

No widgets match your search criteria.

Create your first widget to visualize your data.

-
add From 0cc899563dd4314648f50e0aba723a3e16d7c339 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Sun, 8 Feb 2026 21:13:41 +0000 Subject: [PATCH 11/11] fix unit tests --- .../components/charts/chart-edit/chart-edit.component.spec.ts | 1 + .../components/charts/charts-list/charts-list.component.spec.ts | 2 +- .../app/components/charts/charts-list/charts-list.component.ts | 2 +- .../dashboards-list/dashboards-list.component.spec.ts | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/charts/chart-edit/chart-edit.component.spec.ts b/frontend/src/app/components/charts/chart-edit/chart-edit.component.spec.ts index 385b1cc6c..12449a77e 100644 --- a/frontend/src/app/components/charts/chart-edit/chart-edit.component.spec.ts +++ b/frontend/src/app/components/charts/chart-edit/chart-edit.component.spec.ts @@ -74,6 +74,7 @@ describe('ChartEditComponent', () => { mockUiSettingsService = { editorTheme: 'vs-dark', + getUiSettings: vi.fn().mockReturnValue(of({})), }; await TestBed.configureTestingModule({ diff --git a/frontend/src/app/components/charts/charts-list/charts-list.component.spec.ts b/frontend/src/app/components/charts/charts-list/charts-list.component.spec.ts index f42d8e04b..a536165cb 100644 --- a/frontend/src/app/components/charts/charts-list/charts-list.component.spec.ts +++ b/frontend/src/app/components/charts/charts-list/charts-list.component.spec.ts @@ -116,7 +116,7 @@ describe('ChartsListComponent', () => { }); it('should have correct displayed columns', () => { - expect(component.displayedColumns).toEqual(['name', 'description', 'updatedAt', 'actions']); + expect(component.displayedColumns).toEqual(['preview', 'name', 'description', 'updated', 'actions']); }); describe('filteredQueries computed', () => { diff --git a/frontend/src/app/components/charts/charts-list/charts-list.component.ts b/frontend/src/app/components/charts/charts-list/charts-list.component.ts index 75dee10d3..caa969c21 100644 --- a/frontend/src/app/components/charts/charts-list/charts-list.component.ts +++ b/frontend/src/app/components/charts/charts-list/charts-list.component.ts @@ -50,7 +50,7 @@ import { DashboardsSidebarComponent } from '../../dashboards/dashboards-sidebar/ export class ChartsListComponent implements OnInit { protected searchQuery = signal(''); protected connectionId = signal(''); - protected displayedColumns = ['preview', 'name', 'description', 'updated', 'actions']; + displayedColumns = ['preview', 'name', 'description', 'updated', 'actions']; private chartTypeIcons: Record = { bar: 'bar_chart', diff --git a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.spec.ts b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.spec.ts index 4d1d52fc4..596ce1129 100644 --- a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.spec.ts +++ b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.spec.ts @@ -30,6 +30,7 @@ describe('DashboardsListComponent', () => { dashboardsLoading: dashboardsLoadingSignal.asReadonly(), dashboardsUpdated: dashboardsUpdatedSignal.asReadonly(), setActiveConnection: vi.fn(), + setActiveDashboard: vi.fn(), refreshDashboards: vi.fn(), };