From 5ec3c5f07a0c7fd91d1a4a7cd8ccfe66e0863c9a Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Tue, 13 Jan 2026 15:58:46 +0200 Subject: [PATCH 1/2] feat: add column sorting functionality with default sort option - Add sortable header with dropdown menu for ascending/descending sort - Show sort icon on column header hover, change to active icon when sorted - Add "Set as default" option to persist sort preference across page reloads - Default sort takes priority over URL params on page reload - Clicking selected sort option toggles it off (reverts to default if set) - Style active sort option with gray background highlight Co-Authored-By: Claude Opus 4.5 --- .../db-table-view/db-table-view.component.css | 80 +++++++++++++++++++ .../db-table-view.component.html | 33 +++++++- .../db-table-view/db-table-view.component.ts | 61 +++++++++++++- frontend/src/app/models/ui-settings.ts | 4 + 4 files changed, 176 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css index 4a4673591..9a0671828 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css @@ -427,6 +427,86 @@ th.mat-header-cell, td.mat-cell { text-align: left; } +/* Sortable header styles */ +.sortable-header__content { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + gap: 4px; +} + +.sortable-header__text { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.sortable-header__button { + opacity: 0; + width: 24px; + height: 24px; + line-height: 24px; + flex-shrink: 0; + transition: opacity 0.2s ease; +} + +.sortable-header:hover .sortable-header__button, +.sortable-header_active .sortable-header__button { + opacity: 1; +} + +.sortable-header__icon { + font-size: 18px; + width: 18px; + height: 18px; + color: rgba(0, 0, 0, 0.54); +} + +.sortable-header_active .sortable-header__icon { + color: var(--color-primaryPalette-500); +} + +@media (prefers-color-scheme: dark) { + .sortable-header__icon { + color: rgba(255, 255, 255, 0.54); + } + + .sortable-header_active .sortable-header__icon { + color: var(--color-primaryPalette-300); + } +} + +/* Sort menu styles */ +.sort-menu__item { + display: flex; + align-items: center; + gap: 8px; +} + +.sort-menu__item mat-icon { + font-size: 18px; + width: 18px; + height: 18px; +} + +.sort-menu__item_active { + background-color: rgba(0, 0, 0, 0.06) !important; +} + +@media (prefers-color-scheme: dark) { + .sort-menu__item_active { + background-color: rgba(255, 255, 255, 0.1) !important; + } +} + +.sort-menu__lock-icon { + font-size: 16px; + width: 16px; + height: 16px; +} + .db-table-cell-checkbox { display: flex; align-items: center; diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.html b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.html index 48798d9b4..c2bd02bc6 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.html +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.html @@ -235,7 +235,38 @@

{{ displayName }}

- {{ tableData.dataNormalizedColumns[column] }} + +
+ {{ tableData.dataNormalizedColumns[column] }} + +
+ + + + + + +
diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts index fef902f3b..c4bae9e61 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts @@ -17,6 +17,7 @@ import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; import { MatSelectModule } from '@angular/material/select'; import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatTableModule } from '@angular/material/table'; +import { MatDividerModule } from '@angular/material/divider'; import { MatTooltipModule } from '@angular/material/tooltip'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import JsonURL from '@jsonurl/jsonurl'; @@ -40,6 +41,7 @@ import { ConnectionsService } from 'src/app/services/connections.service'; import { NotificationsService } from 'src/app/services/notifications.service'; import { TableRowService } from 'src/app/services/table-row.service'; import { TableStateService } from 'src/app/services/table-state.service'; +import { UiSettingsService } from 'src/app/services/ui-settings.service'; import { tableDisplayTypes, UIwidgets } from '../../../consts/table-display-types'; import { normalizeTableName } from '../../../lib/normalize'; import { PlaceholderTableDataComponent } from '../../skeletons/placeholder-table-data/placeholder-table-data.component'; @@ -81,6 +83,7 @@ export interface Folder { MatAutocompleteModule, MatSelectModule, MatMenuModule, + MatDividerModule, MatTooltipModule, ClipboardModule, DragDropModule, @@ -150,11 +153,14 @@ export class DbTableViewComponent implements OnInit { @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; + public defaultSort: { column: string; direction: 'asc' | 'desc' } | null = null; + constructor( private _tableState: TableStateService, private _notifications: NotificationsService, private _tableRow: TableRowService, private _connections: ConnectionsService, + private _uiSettings: UiSettingsService, private route: ActivatedRoute, public router: Router, public dialog: MatDialog, @@ -164,7 +170,15 @@ export class DbTableViewComponent implements OnInit { this.tableData.paginator = this.paginator; this.tableData.sort = this.sort; - // this.sort.sortChange.subscribe(() => { this.paginator.pageIndex = 0 }); + + // Load default sort from settings + this.loadDefaultSort(); + + // Initialize sort - default sort takes priority + if (this.defaultSort) { + this.sort.active = this.defaultSort.column; + this.sort.direction = this.defaultSort.direction; + } merge(this.sort.sortChange, this.paginator.page) .pipe( @@ -246,6 +260,51 @@ export class DbTableViewComponent implements OnInit { return this.tableData.sortByColumns.includes(column) || !this.tableData.sortByColumns.length; } + applySort(column: string, direction: 'asc' | 'desc') { + // If clicking on already selected sort - revert to default or clear + if (this.sort.active === column && this.sort.direction === direction) { + if (this.defaultSort) { + // Revert to default sort + this.sort.active = this.defaultSort.column; + this.sort.direction = this.defaultSort.direction; + this.sort.sortChange.emit({ active: this.defaultSort.column, direction: this.defaultSort.direction }); + } else { + // Clear sort + this.sort.active = ''; + this.sort.direction = ''; + this.sort.sortChange.emit({ active: '', direction: '' }); + } + } else { + this.sort.active = column; + this.sort.direction = direction; + this.sort.sortChange.emit({ active: column, direction: direction }); + } + } + + loadDefaultSort() { + const tableSettings = this._uiSettings.settings?.connections?.[this.connectionID]?.tables?.[this.name]; + if (tableSettings?.defaultSort) { + this.defaultSort = tableSettings.defaultSort; + } + } + + toggleDefaultSort(column: string) { + if (this.isDefaultSort(column)) { + // Remove default sort + this.defaultSort = null; + this._uiSettings.updateTableSetting(this.connectionID, this.name, 'defaultSort', null); + } else { + // Set current sort as default + const direction = this.sort.active === column ? this.sort.direction : 'asc'; + this.defaultSort = { column, direction: direction as 'asc' | 'desc' }; + this._uiSettings.updateTableSetting(this.connectionID, this.name, 'defaultSort', this.defaultSort); + } + } + + isDefaultSort(column: string): boolean { + return this.defaultSort?.column === column; + } + isForeignKey(column: string) { return this.tableData.foreignKeysList.includes(column); } diff --git a/frontend/src/app/models/ui-settings.ts b/frontend/src/app/models/ui-settings.ts index 90c103377..4fd8a9c44 100644 --- a/frontend/src/app/models/ui-settings.ts +++ b/frontend/src/app/models/ui-settings.ts @@ -5,6 +5,10 @@ export interface GlobalSettingsUI { export interface TableSettingsUI { shownColumns: string[]; + defaultSort?: { + column: string; + direction: 'asc' | 'desc'; + }; } export interface ConnectionSettingsUI { From 92b2fa29f1408733d00041d3c0016caab0314b1a Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Tue, 13 Jan 2026 16:05:37 +0200 Subject: [PATCH 2/2] fix: improve column sorting UX - Style active sort option with rounded highlight background - Auto-remove default sort when user deselects sorting on default column - Disable "Set as default" button until sort order is selected - Add tooltip hint when default button is disabled Co-Authored-By: Claude Opus 4.5 --- .../db-table-view/db-table-view.component.css | 17 +++++++++---- .../db-table-view.component.html | 24 ++++++++++++------- .../db-table-view/db-table-view.component.ts | 20 +++++++--------- 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css index 9a0671828..5b5bf7c66 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css @@ -491,13 +491,22 @@ th.mat-header-cell, td.mat-cell { height: 18px; } -.sort-menu__item_active { - background-color: rgba(0, 0, 0, 0.06) !important; +.sort-menu__item-content { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 10px; + border-radius: 4px; + margin: -4px -10px; +} + +.sort-menu__item-content_active { + background-color: rgba(0, 0, 0, 0.04); } @media (prefers-color-scheme: dark) { - .sort-menu__item_active { - background-color: rgba(255, 255, 255, 0.1) !important; + .sort-menu__item-content_active { + background-color: rgba(255, 255, 255, 0.06); } } diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.html b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.html index c2bd02bc6..589112f48 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.html +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.html @@ -249,19 +249,25 @@

{{ displayName }}

- diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts index c4bae9e61..281c8878e 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts @@ -261,19 +261,17 @@ export class DbTableViewComponent implements OnInit { } applySort(column: string, direction: 'asc' | 'desc') { - // If clicking on already selected sort - revert to default or clear + // If clicking on already selected sort - clear it if (this.sort.active === column && this.sort.direction === direction) { - if (this.defaultSort) { - // Revert to default sort - this.sort.active = this.defaultSort.column; - this.sort.direction = this.defaultSort.direction; - this.sort.sortChange.emit({ active: this.defaultSort.column, direction: this.defaultSort.direction }); - } else { - // Clear sort - this.sort.active = ''; - this.sort.direction = ''; - this.sort.sortChange.emit({ active: '', direction: '' }); + // If this column was the default, remove the default too + if (this.defaultSort?.column === column) { + this.defaultSort = null; + this._uiSettings.updateTableSetting(this.connectionID, this.name, 'defaultSort', null); } + // Clear sort + this.sort.active = ''; + this.sort.direction = ''; + this.sort.sortChange.emit({ active: '', direction: '' }); } else { this.sort.active = column; this.sort.direction = direction;