From 37af117a172911fc43059bfe5e75f090a2db711d Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Thu, 19 Feb 2026 09:37:00 +0000 Subject: [PATCH 01/11] folders: update logic in new endpoint --- .../dashboard/dashboard.component.html | 4 +- .../dashboard/dashboard.component.ts | 191 ++++++++++-------- .../db-tables-list.component.html | 16 +- .../db-tables-list.component.ts | 153 +++++++------- .../src/app/services/connections.service.ts | 29 --- frontend/src/app/services/tables.service.ts | 30 +++ 6 files changed, 227 insertions(+), 196 deletions(-) diff --git a/frontend/src/app/components/dashboard/dashboard.component.html b/frontend/src/app/components/dashboard/dashboard.component.html index 05765acc6..b10892311 100644 --- a/frontend/src/app/components/dashboard/dashboard.component.html +++ b/frontend/src/app/components/dashboard/dashboard.component.html @@ -70,7 +70,7 @@

Rocketadmin can not find any tables

Rocketadmin can not find any tables [isTestConnection]="currentConnectionIsTest" [accessLevel]="currentConnectionAccessLevel" [tables]="tablesList" - [folders]="tableFolders" + [folders]="tableFoldersForView" (openFilters)="openTableFilters($event)" (removeFilter)="removeFilter($event)" (resetAllFilters)="clearAllFilters()" diff --git a/frontend/src/app/components/dashboard/dashboard.component.ts b/frontend/src/app/components/dashboard/dashboard.component.ts index 3d3572b42..12293a27b 100644 --- a/frontend/src/app/components/dashboard/dashboard.component.ts +++ b/frontend/src/app/components/dashboard/dashboard.component.ts @@ -98,7 +98,8 @@ export class DashboardComponent implements OnInit, OnDestroy { public isAIpanelOpened: boolean = false; public uiSettings: ConnectionSettingsUI; - public tableFolders: Folder[] = []; + public tableFolders: any[] = []; + public tableFoldersForView: Folder[] = []; constructor( private _connections: ConnectionsService, @@ -149,84 +150,100 @@ export class DashboardComponent implements OnInit, OnDestroy { console.log('getData from ngOnInit'); }); - this.loadTableFolders(); + // this.loadTableFolders(); } ngOnDestroy() { this._tableState.clearSelection(); } - async getData() { + getData() { console.log('getData'); - let tables; - try { - tables = await this.getTables(); - } catch (err) { - this.loading = false; - this.isServerError = true; - this.title.setTitle(`Dashboard | ${this._company.companyTabTitle || 'Rocketadmin'}`); - - if (err instanceof HttpErrorResponse) { - this.serverError = { abstract: err.error?.message || err.message, details: err.error?.originalMessage }; - } else { - throw err; - } - } - - if (tables && tables.length === 0) { - this.noTablesError = true; - this.loading = false; - this.title.setTitle(`No tables | ${this._company.companyTabTitle || 'Rocketadmin'}`); - } else if (tables) { - this.formatTableNames(tables); - this.route.paramMap - .pipe( - map((params: ParamMap) => { - let tableName = params.get('table-name'); - if (tableName) { - this.selectedTableName = tableName; - this.setTable(tableName); - console.log('setTable from getData paramMap'); - this.title.setTitle( - `${this.selectedTableDisplayName} table | ${this._company.companyTabTitle || 'Rocketadmin'}`, - ); - this.selection.clear(); - } else { - if (this.defaultTableToOpen) { - tableName = this.defaultTableToOpen; + // let tables; + // try { + // tables = await this.getTables(); + // } catch (err) { + // this.loading = false; + // this.isServerError = true; + // this.title.setTitle(`Dashboard | ${this._company.companyTabTitle || 'Rocketadmin'}`); + + // if (err instanceof HttpErrorResponse) { + // this.serverError = { abstract: err.error?.message || err.message, details: err.error?.originalMessage }; + // } else { + // throw err; + // } + // } + + this._tables.fetchTablesFolders(this.connectionID).subscribe((res) => { + console.log('getTables folders') + console.log(res); + + const tables = res.find((item) => item.category_id === null)?.tables || []; + + this.tableFolders = res; + this.tableFoldersForView = res + .filter((item) => item.category_id !== null) + .map((item) => ({ + id: item.category_id, + name: item.category_name, + tableIds: item.tables || [], + })); + + if (tables && tables.length === 0) { + this.noTablesError = true; + this.loading = false; + this.title.setTitle(`No tables | ${this._company.companyTabTitle || 'Rocketadmin'}`); + } else if (tables) { + this.formatTableNames(tables); + this.route.paramMap + .pipe( + map((params: ParamMap) => { + let tableName = params.get('table-name'); + if (tableName) { + this.selectedTableName = tableName; + this.setTable(tableName); + console.log('setTable from getData paramMap'); + this.title.setTitle( + `${this.selectedTableDisplayName} table | ${this._company.companyTabTitle || 'Rocketadmin'}`, + ); + this.selection.clear(); } else { - tableName = this.tablesList[0].table; + if (this.defaultTableToOpen) { + tableName = this.defaultTableToOpen; + } else { + tableName = this.tablesList[0].table; + } + this.router.navigate([`/dashboard/${this.connectionID}/${tableName}`], { replaceUrl: true }); + this.selectedTableName = tableName; } - this.router.navigate([`/dashboard/${this.connectionID}/${tableName}`], { replaceUrl: true }); - this.selectedTableName = tableName; - } - }), - ) - .subscribe(); - this._tableRow.cast.subscribe((arg) => { - if (arg === 'delete row' && this.selectedTableName) { - this.setTable(this.selectedTableName); - console.log('setTable from getData _tableRow cast'); - this.selection.clear(); - } - }); - this._tables.cast.subscribe((arg) => { - if ((arg === 'delete rows' || arg === 'import') && this.selectedTableName) { - this.setTable(this.selectedTableName); - console.log('setTable from getData _tables cast'); - this.selection.clear(); - } - if (arg === 'activate actions') { - this.selection.clear(); - } - }); - } + }), + ) + .subscribe(); + this._tableRow.cast.subscribe((arg) => { + if (arg === 'delete row' && this.selectedTableName) { + this.setTable(this.selectedTableName); + console.log('setTable from getData _tableRow cast'); + this.selection.clear(); + } + }); + this._tables.cast.subscribe((arg) => { + if ((arg === 'delete rows' || arg === 'import') && this.selectedTableName) { + this.setTable(this.selectedTableName); + console.log('setTable from getData _tables cast'); + this.selection.clear(); + } + if (arg === 'activate actions') { + this.selection.clear(); + } + }); + } + }); } - getTables() { - console.log('getTables'); - return this._tables.fetchTables(this.connectionID).toPromise(); - } + // getTables() { + // console.log('getTables'); + // return this._tables.fetchTables(this.connectionID).toPromise(); + // } formatTableNames(tables: TableProperties[]) { this.tablesList = tables.map((tableItem: TableProperties) => { @@ -438,23 +455,23 @@ export class DashboardComponent implements OnInit, OnDestroy { this._uiSettings.updateConnectionSetting(this.connectionID, 'shownTableTitles', this.shownTableTitles); } - private loadTableFolders() { - this._connections.getTablesFolders(this.connectionID).subscribe({ - next: (categories: TableCategory[]) => { - if (categories && categories.length > 0) { - this.tableFolders = categories.map((cat) => ({ - id: cat.category_id, - name: cat.category_name, - tableIds: cat.tables, - })); - } else { - this.tableFolders = []; - } - }, - error: (error) => { - console.error('Error fetching table folders:', error); - this.tableFolders = []; - }, - }); - } + // private loadTableFolders() { + // this._connections.getTablesFolders(this.connectionID).subscribe({ + // next: (categories: TableCategory[]) => { + // if (categories && categories.length > 0) { + // this.tableFolders = categories.map((cat) => ({ + // id: cat.category_id, + // name: cat.category_name, + // tableIds: cat.tables, + // })); + // } else { + // this.tableFolders = []; + // } + // }, + // error: (error) => { + // console.error('Error fetching table folders:', error); + // this.tableFolders = []; + // }, + // }); + // } } diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index f3b4b6a1c..9063f747a 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -1,8 +1,8 @@ - + -
+
@@ -24,9 +24,9 @@ [matTooltip]="folder.name" matTooltipPosition="right" (click)="onCollapsedFolderClick(folder)"> - dehaze - folder
@@ -95,7 +95,7 @@
@@ -103,10 +103,10 @@ [class.expanded]="folder.expanded" (click)="toggleFolder(folder.id)"> - dehaze - folder @@ -115,7 +115,7 @@
-
+ class="selected-tables-list" + cdkDropList + [cdkDropListDisabled]="!isReorderingEnabled(folder)" + (cdkDropListDropped)="onTableReorder($event, folder)">
+ cdkDrag + [cdkDragDisabled]="!isReorderingEnabled(folder)"> + drag_indicator {{getTableName(table)}} diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index d35b8b773..2f9288435 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -1,4 +1,5 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { CdkDragDrop, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop'; import { DbFolderDeleteDialogComponent, DbFolderDeleteDialogData } from './db-folder-delete-dialog/db-folder-delete-dialog.component'; import { DbFolderEditDialogComponent, DbFolderEditDialogData } from './db-folder-edit-dialog/db-folder-edit-dialog.component'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; @@ -47,6 +48,7 @@ interface Category { imports: [ CommonModule, FormsModule, + DragDropModule, MatButtonModule, MatCheckboxModule, MatIconModule, @@ -121,13 +123,28 @@ export class DbTablesListComponent implements OnInit, OnChanges { permissions: table.permissions }; }); - this.folders = this.tableFolders.map(category => ({ - id: category.category_id, - name: category.category_name, - expanded: category.category_id === null ? true : false, - tableIds: category.tables.map((table: TableProperties) => table.table), - iconColor: category.category_color - })); + this.folders = this.tableFolders.map(category => { + let tableIds = category.tables.map((table: TableProperties) => table.table); + // Restore saved order for "All Tables" folder from UI settings + if (category.category_id === null) { + const savedOrder: string[] = this.uiSettings?.allTablesOrder; + if (savedOrder?.length) { + const savedSet = new Set(savedOrder); + const currentSet = new Set(tableIds); + // Reorder: saved tables first (in saved order), then any new tables not in saved order + const ordered = savedOrder.filter(id => currentSet.has(id)); + const newTables = tableIds.filter(id => !savedSet.has(id)); + tableIds = [...ordered, ...newTables]; + } + } + return { + id: category.category_id, + name: category.category_name, + expanded: category.category_id === null ? true : false, + tableIds, + iconColor: category.category_color + }; + }); const expandedFolders = this.uiSettings?.tableFoldersExpanded || ['0']; if (expandedFolders && expandedFolders.length > 0) { @@ -309,7 +326,11 @@ export class DbTablesListComponent implements OnInit, OnChanges { } getFolderTables(folder: Folder): TableProperties[] { - const folderTables = this.allTables.filter(table => folder.tableIds.includes(table.table)); + // Preserve folder.tableIds order by mapping through tableIds + const allTablesMap = new Map(this.allTables.map(t => [t.table, t])); + const folderTables = folder.tableIds + .map(tableId => allTablesMap.get(tableId)) + .filter((table): table is TableProperties => !!table); // If there's a search term, filter the collection tables too if (this.substringToSearch && this.substringToSearch.trim() !== '') { @@ -599,6 +620,26 @@ export class DbTablesListComponent implements OnInit, OnChanges { } } + // Table reordering within folders + isReorderingEnabled(folder: Folder): boolean { + return this.accessLevel === 'edit' + && !this.collapsed + && (!this.substringToSearch || this.substringToSearch.trim() === ''); + } + + onTableReorder(event: CdkDragDrop, folder: Folder) { + if (event.previousIndex === event.currentIndex) { + return; + } + moveItemInArray(folder.tableIds, event.previousIndex, event.currentIndex); + if (folder.id === null) { + // "All Tables" is dynamically generated, persist order in UI settings + this._uiSettingsService.updateConnectionSetting(this.connectionID, 'allTablesOrder', folder.tableIds); + } else { + this.saveFolders(); + } + } + // Drag and drop methods onTableDragStart(event: DragEvent, table: TableProperties) { this.draggedTable = table; From 59efb310bcb0cf7e6205f15e26ff5e74251f918c Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Thu, 19 Feb 2026 10:48:22 +0000 Subject: [PATCH 03/11] tables list: insert folder in the position user drops it --- .../db-tables-list.component.ts | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index 2f9288435..0089de2f9 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -557,6 +557,21 @@ export class DbTablesListComponent implements OnInit, OnChanges { return Date.now().toString(); } + private getDropIndex(event: DragEvent, folder: Folder): number { + const folderEl = (event.currentTarget as HTMLElement); + const tableItems = folderEl.querySelectorAll('.selected-table-item'); + const y = event.clientY; + + for (let i = 0; i < tableItems.length; i++) { + const rect = tableItems[i].getBoundingClientRect(); + const midY = rect.top + rect.height / 2; + if (y < midY) { + return i; + } + } + return folder.tableIds.length; + } + // private loadFolders() { // this._connectionsService.getTablesFolders(this.connectionID).subscribe({ // next: (categories: TableCategory[]) => { @@ -682,13 +697,13 @@ export class DbTablesListComponent implements OnInit, OnChanges { // Table already exists in this folder, do nothing (no notification) return; } else { - // Simply add table to the target folder (don't remove from other folders) - folder.tableIds.push(this.draggedTable.table); + // Determine insertion index based on drop position + const insertIndex = this.getDropIndex(event, folder); + folder.tableIds.splice(insertIndex, 0, this.draggedTable.table); // Remove empty flag when adding tables via drag and drop if (folder.isEmpty) { folder.isEmpty = false; } - console.log('onFolderDrop'); this.saveFolders(); } } From f8d2e5a6c59f8660dd49863ba3b1f26e2a2e0fb8 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Thu, 19 Feb 2026 15:32:43 +0000 Subject: [PATCH 04/11] refactor and fix table name formatting --- .../dashboard/dashboard.component.html | 3 +- .../dashboard/dashboard.component.ts | 97 ++++++------------- .../db-table-view.component.html | 19 ++-- .../db-table-view/db-table-view.component.ts | 50 ++++------ frontend/src/app/models/table.ts | 2 + frontend/src/app/models/ui-settings.ts | 1 + 6 files changed, 60 insertions(+), 112 deletions(-) diff --git a/frontend/src/app/components/dashboard/dashboard.component.html b/frontend/src/app/components/dashboard/dashboard.component.html index b10892311..57606293c 100644 --- a/frontend/src/app/components/dashboard/dashboard.component.html +++ b/frontend/src/app/components/dashboard/dashboard.component.html @@ -98,8 +98,7 @@

Rocketadmin can not find any tables

[connectionID]="connectionID" [isTestConnection]="currentConnectionIsTest" [accessLevel]="currentConnectionAccessLevel" - [tables]="tablesList" - [folders]="tableFoldersForView" + [tableFolders]="tableFolders" (openFilters)="openTableFilters($event)" (removeFilter)="removeFilter($event)" (resetAllFilters)="clearAllFilters()" diff --git a/frontend/src/app/components/dashboard/dashboard.component.ts b/frontend/src/app/components/dashboard/dashboard.component.ts index 12293a27b..fed14ec8a 100644 --- a/frontend/src/app/components/dashboard/dashboard.component.ts +++ b/frontend/src/app/components/dashboard/dashboard.component.ts @@ -36,7 +36,7 @@ import { BbBulkActionConfirmationDialogComponent } from './db-table-view/db-bulk import { DbTableAiPanelComponent } from './db-table-view/db-table-ai-panel/db-table-ai-panel.component'; import { DbTableFiltersDialogComponent } from './db-table-view/db-table-filters-dialog/db-table-filters-dialog.component'; import { DbTableRowViewComponent } from './db-table-view/db-table-row-view/db-table-row-view.component'; -import { DbTableViewComponent, Folder } from './db-table-view/db-table-view.component'; +import { DbTableViewComponent } from './db-table-view/db-table-view.component'; import { TablesDataSource } from './db-tables-data-source'; import { DbTablesListComponent } from './db-tables-list/db-tables-list.component'; @@ -72,7 +72,9 @@ export class DashboardComponent implements OnInit, OnDestroy { protected posthog = posthog; public isSaas = (environment as any).saas; public user: User = null; - public tablesList: TableProperties[] = null; + get allTables(): TableProperties[] { + return this.tableFolders?.find((cat: any) => cat.category_id === null)?.tables || []; + } public selectedTableName: string; public selectedTableDisplayName: string; public currentPage: number = 1; @@ -99,7 +101,6 @@ export class DashboardComponent implements OnInit, OnDestroy { public uiSettings: ConnectionSettingsUI; public tableFolders: any[] = []; - public tableFoldersForView: Folder[] = []; constructor( private _connections: ConnectionsService, @@ -159,20 +160,6 @@ export class DashboardComponent implements OnInit, OnDestroy { getData() { console.log('getData'); - // let tables; - // try { - // tables = await this.getTables(); - // } catch (err) { - // this.loading = false; - // this.isServerError = true; - // this.title.setTitle(`Dashboard | ${this._company.companyTabTitle || 'Rocketadmin'}`); - - // if (err instanceof HttpErrorResponse) { - // this.serverError = { abstract: err.error?.message || err.message, details: err.error?.originalMessage }; - // } else { - // throw err; - // } - // } this._tables.fetchTablesFolders(this.connectionID).subscribe((res) => { console.log('getTables folders') @@ -181,20 +168,13 @@ export class DashboardComponent implements OnInit, OnDestroy { const tables = res.find((item) => item.category_id === null)?.tables || []; this.tableFolders = res; - this.tableFoldersForView = res - .filter((item) => item.category_id !== null) - .map((item) => ({ - id: item.category_id, - name: item.category_name, - tableIds: item.tables || [], - })); if (tables && tables.length === 0) { this.noTablesError = true; this.loading = false; this.title.setTitle(`No tables | ${this._company.companyTabTitle || 'Rocketadmin'}`); } else if (tables) { - this.formatTableNames(tables); + this.formatTableNames(); this.route.paramMap .pipe( map((params: ParamMap) => { @@ -211,7 +191,7 @@ export class DashboardComponent implements OnInit, OnDestroy { if (this.defaultTableToOpen) { tableName = this.defaultTableToOpen; } else { - tableName = this.tablesList[0].table; + tableName = this.allTables[0]?.table; } this.router.navigate([`/dashboard/${this.connectionID}/${tableName}`], { replaceUrl: true }); this.selectedTableName = tableName; @@ -240,29 +220,30 @@ export class DashboardComponent implements OnInit, OnDestroy { }); } - // getTables() { - // console.log('getTables'); - // return this._tables.fetchTables(this.connectionID).toPromise(); - // } - - formatTableNames(tables: TableProperties[]) { - this.tablesList = tables.map((tableItem: TableProperties) => { - let normalizedTableName; - if (tableItem.display_name) { - normalizedTableName = tableItem.display_name; - } else { - normalizedTableName = normalizeTableName(tableItem.table); + formatTableNames() { + // Format table names inside tableFolders so all components receive formatted tables + this.tableFolders = this.tableFolders.map(category => ({ + ...category, + tables: category.tables.map((tableItem: TableProperties) => this.formatTable(tableItem)), + })); + } + + private formatTable(tableItem: TableProperties): TableProperties { + let normalizedTableName; + if (tableItem.display_name) { + normalizedTableName = tableItem.display_name; + } else { + normalizedTableName = normalizeTableName(tableItem.table); + } + const words = normalizedTableName.split(' '); + const initials = words.reduce((result, word) => { + if (word.length > 0) { + return result + word[0].toUpperCase(); } - const words = normalizedTableName.split(' '); - const initials = words.reduce((result, word) => { - if (word.length > 0) { - return result + word[0].toUpperCase(); - } - return result; - }, ''); + return result; + }, ''); - return { ...tableItem, normalizedTableName, initials: initials.slice(0, 2) }; - }); + return { ...tableItem, normalizedTableName, initials: initials.slice(0, 2) }; } setTable(tableName: string) { @@ -279,7 +260,7 @@ export class DashboardComponent implements OnInit, OnDestroy { this.getRows(search); console.log('getRows from setTable'); - const selectedTableProperties = this.tablesList.find((table: any) => table.table === this.selectedTableName); + const selectedTableProperties = this.allTables.find((table: any) => table.table === this.selectedTableName); if (selectedTableProperties) { this.selectedTableDisplayName = selectedTableProperties.display_name || normalizeTableName(selectedTableProperties.table); @@ -454,24 +435,4 @@ export class DashboardComponent implements OnInit, OnDestroy { this.shownTableTitles = !this.shownTableTitles; this._uiSettings.updateConnectionSetting(this.connectionID, 'shownTableTitles', this.shownTableTitles); } - - // private loadTableFolders() { - // this._connections.getTablesFolders(this.connectionID).subscribe({ - // next: (categories: TableCategory[]) => { - // if (categories && categories.length > 0) { - // this.tableFolders = categories.map((cat) => ({ - // id: cat.category_id, - // name: cat.category_name, - // tableIds: cat.tables, - // })); - // } else { - // this.tableFolders = []; - // } - // }, - // error: (error) => { - // console.error('Error fetching table folders:', error); - // this.tableFolders = []; - // }, - // }); - // } } 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 f0210b7d2..299cb757b 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 @@ -5,23 +5,18 @@

{{ displayName }}

Table - - + + - {{table.normalizedTableName}} - - - - - {{table.normalizedTableName}} + {{table.normalizedTableName || table.display_name || table.table}} - - - {{table.normalizedTableName}} + + + {{table.normalizedTableName || table.display_name || table.table}} - + 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 d1c292331..4a81fc688 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 @@ -69,10 +69,11 @@ interface Column { selected: boolean; } -export interface Folder { - id: string; - name: string; - tableIds: string[]; +interface TableCategory { + category_id: string; + category_name: string; + category_color?: string; + tables: TableProperties[]; } @Component({ @@ -121,8 +122,7 @@ export class DbTableViewComponent implements OnInit, OnChanges { @Input() activeFilters: object; @Input() filterComparators: object; @Input() selection: SelectionModel; - @Input() tables: TableProperties[]; - @Input() folders: Folder[] = []; + @Input() tableFolders: TableCategory[] = []; @Output() openFilters = new EventEmitter(); @Output() openPage = new EventEmitter(); @@ -139,7 +139,6 @@ export class DbTableViewComponent implements OnInit, OnChanges { // public tablesSwitchControl = new FormControl(''); public tableData: any; - public filteredTables: TableProperties[]; // public selection: any; public columns: Column[]; public displayedColumns: string[] = []; @@ -293,19 +292,24 @@ export class DbTableViewComponent implements OnInit, OnChanges { }); } - onInput(searchValue: string) { - this.filteredTables = this.filterTables(searchValue); + get allTables(): TableProperties[] { + return this.tableFolders?.find((cat) => cat.category_id === null)?.tables || []; } - onInputFocus() { - this.filteredTables = this.tables; + get tableFoldersForSelect(): TableCategory[] { + if (!this.tableFolders) return []; + return this.tableFolders.filter((cat) => cat.category_id !== null); } - private filterTables(searchValue: string): any[] { - const filterValue = searchValue.toLowerCase(); - return this.tables.filter((table) => table.normalizedTableName.toLowerCase().includes(filterValue)); + get uncategorizedTables(): TableProperties[] { + const tablesInFolders = new Set(); + this.tableFoldersForSelect.forEach((folder) => { + (folder.tables || []).forEach((t) => tablesInFolders.add(t.table)); + }); + return this.allTables.filter((t) => !tablesInFolders.has(t.table)); } + loadRowsPage() { this.tableRelatedRecords = null; this.tableData.fetchRows({ @@ -321,13 +325,6 @@ export class DbTableViewComponent implements OnInit, OnChanges { }); } - // ngOnChanges(changes: SimpleChanges) { - // if (changes.name?.currentValue && this.paginator) { - // this.paginator.pageIndex = 0; - // this.searchString = ''; - // } - // } - isSortable(column: string) { return this.tableData.sortByColumns.includes(column) || !this.tableData.sortByColumns.length; } @@ -692,17 +689,10 @@ export class DbTableViewComponent implements OnInit, OnChanges { } } - getFolderTables(folder: Folder): TableProperties[] { - return this.tables.filter((table) => folder.tableIds.includes(table.table)); + getFolderTables(folder: TableCategory): TableProperties[] { + return folder.tables || []; } - getUncategorizedTables(): TableProperties[] { - const categorizedTableIds = new Set(); - this.folders.forEach((folder) => { - folder.tableIds.forEach((id) => categorizedTableIds.add(id)); - }); - return this.tables.filter((table) => !categorizedTableIds.has(table.table)); - } onFilterSelected($event) { console.log('table view fiers filterSelected:', $event); diff --git a/frontend/src/app/models/table.ts b/frontend/src/app/models/table.ts index 3dfb071da..248f193f0 100644 --- a/frontend/src/app/models/table.ts +++ b/frontend/src/app/models/table.ts @@ -10,6 +10,8 @@ export interface TableProperties { table: string, display_name?: string, normalizedTableName?: string, + initials?: string, + icon?: string, permissions: TablePermissions, } diff --git a/frontend/src/app/models/ui-settings.ts b/frontend/src/app/models/ui-settings.ts index 40a36187e..98870c70a 100644 --- a/frontend/src/app/models/ui-settings.ts +++ b/frontend/src/app/models/ui-settings.ts @@ -17,6 +17,7 @@ export interface ConnectionSettingsUI { tables: { [tableName: string]: TableSettingsUI }; tableFoldersExpanded?: string[]; dashboardsSidebarCollapsed?: boolean; + allTablesOrder?: string[]; } export interface UiSettings { From dead6b3b97b90d14b84e47c0978e8a9aad9ad732 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Thu, 19 Feb 2026 15:48:23 +0000 Subject: [PATCH 05/11] tables list: restore folders expanded state after clear Search --- .../db-tables-list.component.ts | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index 0089de2f9..5be11b02c 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -93,6 +93,9 @@ export class DbTablesListComponent implements OnInit, OnChanges { private preservedFolderStates: { [key: string]: boolean } = {}; private preservedActiveFolder: string | null = null; + // Search state preservation + private preSearchFolderStates: { [key: string]: boolean } | null = null; + // Folder icon colors public folderIconColors = [ { name: 'Default', value: '#212121' }, @@ -171,11 +174,24 @@ export class DbTablesListComponent implements OnInit, OnChanges { searchSubstring() { if (!this.substringToSearch || this.substringToSearch.trim() === '') { this.foundTables = this.allTables; - // Collapse all folders when search is cleared + // Restore folder expanded states from before the search + if (this.preSearchFolderStates) { + this.folders.forEach(folder => { + if (Object.hasOwn(this.preSearchFolderStates, folder.id)) { + folder.expanded = this.preSearchFolderStates[folder.id]; + } + }); + this.preSearchFolderStates = null; + } + return; + } + + // Save folder states before the first search modification + if (!this.preSearchFolderStates) { + this.preSearchFolderStates = {}; this.folders.forEach(folder => { - folder.expanded = false; + this.preSearchFolderStates[folder.id] = folder.expanded; }); - return; } const searchTerm = this.substringToSearch.toLowerCase(); From 812f28c596c71a325dd628976751b412c8de1361 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Thu, 19 Feb 2026 15:50:58 +0000 Subject: [PATCH 06/11] footer: update year --- frontend/src/app/app.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 481c5b81a..ffb82749f 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -244,7 +244,7 @@ From 7410af9be93bd08757a50357ad8d0426a32762bf Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Thu, 19 Feb 2026 16:09:50 +0000 Subject: [PATCH 07/11] fix unit tests --- .../components/dashboard/dashboard.component.spec.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/dashboard/dashboard.component.spec.ts b/frontend/src/app/components/dashboard/dashboard.component.spec.ts index 1de8d670b..f8a0b4634 100644 --- a/frontend/src/app/components/dashboard/dashboard.component.spec.ts +++ b/frontend/src/app/components/dashboard/dashboard.component.spec.ts @@ -75,6 +75,7 @@ describe('DashboardComponent', () => { fakeTablesService = { fetchTables: vi.fn().mockReturnValue(of(fakeTables)), + fetchTablesFolders: vi.fn().mockReturnValue(of([{ category_id: null, tables: fakeTables }])), }; await TestBed.configureTestingModule({ @@ -121,9 +122,10 @@ describe('DashboardComponent', () => { expect(component.currentConnectionAccessLevel).toEqual('readonly'); }); - it('should call getTables', async () => { - fakeTablesService.fetchTables.mockReturnValue(of(fakeTables)); - const tables = await component.getTables(); - expect(tables).toEqual(fakeTables); + it('should call getData and populate tables', () => { + fakeTablesService.fetchTablesFolders.mockReturnValue(of([{ category_id: null, tables: fakeTables }])); + component.getData(); + expect(component.allTables.length).toEqual(fakeTables.length); + expect(component.allTables.map(t => t.table)).toEqual(fakeTables.map(t => t.table)); }); }); From 3315cf9b4e59317601533f08be493beca6548498 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Fri, 20 Feb 2026 08:35:15 +0000 Subject: [PATCH 08/11] tables list: show All tables from from server respond --- .../db-tables-list.component.ts | 22 ++----------------- frontend/src/app/models/ui-settings.ts | 1 - 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index 5be11b02c..31a71eaff 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -127,19 +127,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { }; }); this.folders = this.tableFolders.map(category => { - let tableIds = category.tables.map((table: TableProperties) => table.table); - // Restore saved order for "All Tables" folder from UI settings - if (category.category_id === null) { - const savedOrder: string[] = this.uiSettings?.allTablesOrder; - if (savedOrder?.length) { - const savedSet = new Set(savedOrder); - const currentSet = new Set(tableIds); - // Reorder: saved tables first (in saved order), then any new tables not in saved order - const ordered = savedOrder.filter(id => currentSet.has(id)); - const newTables = tableIds.filter(id => !savedSet.has(id)); - tableIds = [...ordered, ...newTables]; - } - } + const tableIds = category.tables.map((table: TableProperties) => table.table); return { id: category.category_id, name: category.category_name, @@ -631,7 +619,6 @@ export class DbTablesListComponent implements OnInit, OnChanges { private saveFolders() { try { this.tableCategories = this.folders - .filter(folder => folder.name !== 'All Tables') // Exclude "All Tables" from saving .map(folder => ({ category_id: folder.id, category_name: folder.name, @@ -663,12 +650,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { return; } moveItemInArray(folder.tableIds, event.previousIndex, event.currentIndex); - if (folder.id === null) { - // "All Tables" is dynamically generated, persist order in UI settings - this._uiSettingsService.updateConnectionSetting(this.connectionID, 'allTablesOrder', folder.tableIds); - } else { - this.saveFolders(); - } + this.saveFolders(); } // Drag and drop methods diff --git a/frontend/src/app/models/ui-settings.ts b/frontend/src/app/models/ui-settings.ts index 98870c70a..40a36187e 100644 --- a/frontend/src/app/models/ui-settings.ts +++ b/frontend/src/app/models/ui-settings.ts @@ -17,7 +17,6 @@ export interface ConnectionSettingsUI { tables: { [tableName: string]: TableSettingsUI }; tableFoldersExpanded?: string[]; dashboardsSidebarCollapsed?: boolean; - allTablesOrder?: string[]; } export interface UiSettings { From 596a7f96fdf2ba358319424396ef95c8eab7b42f Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Fri, 20 Feb 2026 09:35:32 +0000 Subject: [PATCH 09/11] tables list: update id of All tables folder --- .../src/app/components/dashboard/dashboard.component.ts | 4 ++-- .../dashboard/db-tables-list/db-tables-list.component.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/components/dashboard/dashboard.component.ts b/frontend/src/app/components/dashboard/dashboard.component.ts index fed14ec8a..70713fbde 100644 --- a/frontend/src/app/components/dashboard/dashboard.component.ts +++ b/frontend/src/app/components/dashboard/dashboard.component.ts @@ -73,7 +73,7 @@ export class DashboardComponent implements OnInit, OnDestroy { public isSaas = (environment as any).saas; public user: User = null; get allTables(): TableProperties[] { - return this.tableFolders?.find((cat: any) => cat.category_id === null)?.tables || []; + return this.tableFolders?.find((cat: any) => cat.category_id === 'all-tables-kitten')?.tables || []; } public selectedTableName: string; public selectedTableDisplayName: string; @@ -165,7 +165,7 @@ export class DashboardComponent implements OnInit, OnDestroy { console.log('getTables folders') console.log(res); - const tables = res.find((item) => item.category_id === null)?.tables || []; + const tables = res.find((item) => item.category_id === 'all-tables-kitten')?.tables || []; this.tableFolders = res; diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index 31a71eaff..054a11ad5 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -117,7 +117,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { ngOnInit() { this.foundTables = this.tableFolders - .find((folder: any) => folder.category_id === null)?.tables + .find((folder: any) => folder.category_id === 'all-tables-kitten')?.tables .map((table: TableProperties) => { return { table: table.table, @@ -131,7 +131,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { return { id: category.category_id, name: category.category_name, - expanded: category.category_id === null ? true : false, + expanded: category.category_id === 'all-tables-kitten' ? true : false, tableIds, iconColor: category.category_color }; @@ -416,7 +416,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { get allTables(): TableProperties[] { return this.tableFolders - ?.find((folder: any) => folder.category_id === null)?.tables || []; + ?.find((folder: any) => folder.category_id === 'all-tables-kitten')?.tables || []; } isTableInFolder(table: TableProperties): boolean { From cbd1b0dd71ca5aa5d41f9575876aa70972cc7c16 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Fri, 20 Feb 2026 09:48:55 +0000 Subject: [PATCH 10/11] tables list: update All tables id in html and fix folder menu button --- .../dashboard/dashboard.component.ts | 4 +- .../db-tables-list.component.css | 21 +----- .../db-tables-list.component.html | 12 +-- .../db-tables-list.component.ts | 75 ------------------- 4 files changed, 11 insertions(+), 101 deletions(-) diff --git a/frontend/src/app/components/dashboard/dashboard.component.ts b/frontend/src/app/components/dashboard/dashboard.component.ts index 70713fbde..9194f9868 100644 --- a/frontend/src/app/components/dashboard/dashboard.component.ts +++ b/frontend/src/app/components/dashboard/dashboard.component.ts @@ -1,6 +1,5 @@ import { SelectionModel } from '@angular/cdk/collections'; import { CommonModule } from '@angular/common'; -import { HttpErrorResponse } from '@angular/common/http'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; @@ -12,10 +11,9 @@ import JsonURL from '@jsonurl/jsonurl'; import { Angulartics2, Angulartics2Module } from 'angulartics2'; import { omitBy } from 'lodash-es'; import posthog from 'posthog-js'; -import { first, map } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { getComparatorsFromUrl } from 'src/app/lib/parse-filter-params'; import { ServerError } from 'src/app/models/alert'; -import { TableCategory } from 'src/app/models/connection'; import { CustomEvent, TableProperties } from 'src/app/models/table'; import { ConnectionSettingsUI, UiSettings } from 'src/app/models/ui-settings'; import { User } from 'src/app/models/user'; diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index 41220505d..56225fb98 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -689,26 +689,13 @@ } .more-folder-button { - width: 16px; - height: 16px; - line-height: 16px; - border: none; - border-radius: 0; - background-color: transparent !important; - color: #bdbdbd !important; - margin-left: 4px; -} - -.more-folder-button:hover { - border: none !important; - background-color: rgba(0, 0, 0, 0.02) !important; - color: #9e9e9e !important; + line-height: 18px; } .more-folder-button mat-icon { - font-size: 12px; - width: 12px; - height: 12px; + font-size: 18px; + width: 18px; + height: 18px; } .folder-name { diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index 3fa73c20c..161541582 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -24,9 +24,9 @@ [matTooltip]="folder.name" matTooltipPosition="right" (click)="onCollapsedFolderClick(folder)"> - dehaze - folder
@@ -95,7 +95,7 @@
@@ -103,10 +103,10 @@ [class.expanded]="folder.expanded" (click)="toggleFolder(folder.id)"> - dehaze - folder @@ -115,7 +115,7 @@
-