Skip to content

Table list ordering#1617

Merged
lyubov-voloshko merged 15 commits into
mainfrom
table-list-ordering
Feb 20, 2026
Merged

Table list ordering#1617
lyubov-voloshko merged 15 commits into
mainfrom
table-list-ordering

Conversation

@lyubov-voloshko

Copy link
Copy Markdown
Collaborator

No description provided.

Copilot AI review requested due to automatic review settings February 19, 2026 16:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces table list ordering functionality, allowing users to reorder tables within folders using drag-and-drop. The changes refactor the table data structure from a flat list to a hierarchical folder-based system with ordering support.

Changes:

  • Moves table folder management methods from ConnectionsService to TablesService, introducing a new v2 API endpoint for fetching table folders
  • Adds CDK drag-drop functionality to enable table reordering within folders, with order persistence via UI settings for "All Tables" and connection settings for custom folders
  • Refactors components to use tableFolders (hierarchical structure) instead of separate tables and folders inputs, simplifying data flow

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
frontend/src/app/services/tables.service.ts Adds fetchTablesFolders and updateTablesFolders methods to handle table folder operations
frontend/src/app/services/connections.service.ts Removes getTablesFolders and updateTablesFolders methods, consolidating folder operations in TablesService
frontend/src/app/models/ui-settings.ts Adds allTablesOrder property to persist table order for the "All Tables" folder
frontend/src/app/models/table.ts Adds initials and icon properties to TableProperties for improved UI display
frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts Refactors to use tableFolders input, implements CDK drag-drop for table reordering, adds commented-out legacy code
frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html Integrates CDK drag-drop directives for reordering, maintains native drag-drop for folder operations
frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css Adds styling for drag-drop indicators and animations
frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts Refactors to use single tableFolders input, adds computed properties for table organization
frontend/src/app/components/dashboard/db-table-view/db-table-view.component.html Simplifies table switcher dropdown to use folder categories from tableFolders
frontend/src/app/components/dashboard/dashboard.component.ts Refactors getData from async/await to reactive subscribe pattern, introduces tableFolders data structure
frontend/src/app/components/dashboard/dashboard.component.html Updates component bindings to pass tableFolders instead of separate tables/folders
frontend/src/app/app.component.html Updates copyright year to 2026

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +159 to +161
[draggable]="true"
(dragstart)="onTableDragStart($event, table)"
(dragend)="onTableDragEnd($event)"

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential conflict between two drag-and-drop mechanisms. The table link has both CDK drag-drop attributes (cdkDrag on the parent div at line 150) and native HTML5 drag-and-drop attributes (draggable="true" with dragstart/dragend handlers at lines 159-161). These two mechanisms serve different purposes: CDK drag-drop is for reordering within a folder, while HTML5 drag-drop is for moving tables between folders. However, having the draggable attribute on the child anchor element while cdkDrag is on the parent could cause unexpected behavior or conflicts. Consider either removing the draggable attribute from the anchor when reordering is enabled, or moving the native drag handlers to a separate element that's only visible when reordering is disabled.

Suggested change
[draggable]="true"
(dragstart)="onTableDragStart($event, table)"
(dragend)="onTableDragEnd($event)"
[draggable]="!isReorderingEnabled(folder)"
(dragstart)="!isReorderingEnabled(folder) ? onTableDragStart($event, table) : null"
(dragend)="!isReorderingEnabled(folder) ? onTableDragEnd($event) : null"

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +157
const expandedFolders = this.uiSettings?.tableFoldersExpanded || ['0'];
if (expandedFolders && expandedFolders.length > 0) {
this.folders.forEach(folder => {
folder.expanded = expandedFolders.includes(folder.id);
});
}

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential bug with folder ID comparison. The "All Tables" folder has id: null (line 144), but the default value for expandedFolders is ['0'] (line 152). The comparison at line 155 uses expandedFolders.includes(folder.id), which will never match for the "All Tables" folder since ['0'].includes(null) is false. This means the "All Tables" folder's expanded state set at line 146 will be overridden to false (unless the user has saved settings). Consider using a consistent ID for the "All Tables" folder, such as null consistently throughout, or use a specific string ID like '0' and update line 144 to use that string.

Copilot uses AI. Check for mistakes.
}

updateTablesFolders(connectionID: string, tablesFolders: any) {
return this._http.put(`/table-categories/${connectionID}`, tablesFolders).pipe(

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API endpoint version inconsistency. The fetchTablesFolders method uses /table-categories/v2/${connectionID} (line 85), while updateTablesFolders uses /table-categories/${connectionID} (line 98). This inconsistency could indicate that one endpoint should use the v2 API while the other uses the original API, or it could be a bug. Verify that both endpoints are correct and intentional. If both should use v2, update the PUT endpoint to match.

Suggested change
return this._http.put(`/table-categories/${connectionID}`, tablesFolders).pipe(
return this._http.put(`/table-categories/v2/${connectionID}`, tablesFolders).pipe(

Copilot uses AI. Check for mistakes.
Comment on lines 610 to 630
// if (expandedFolders && expandedFolders.length > 0) {
// this.folders.forEach(folder => {
// folder.expanded = expandedFolders.includes(folder.id);
// });
// }

// const allTablesFolder: Folder = {
// id: '0',
// name: 'All Tables',
// expanded: expandedFolders && expandedFolders.length === 0 ? false : expandedFolders.includes('0'),
// tableIds: this.tables.map(table => table.table)
// };
// this.folders.unshift(allTablesFolder);
// },
// error: (error) => {
// console.error('Error fetching folders from connection settings:', error);
// this.folders = [];
// }
// });
// }

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented-out code. This large block of commented code (the old loadFolders method) should be deleted rather than left in the codebase. Commented code creates confusion and maintenance burden. If this code needs to be referenced in the future, it can be retrieved from version control history.

Suggested change
// if (expandedFolders && expandedFolders.length > 0) {
// this.folders.forEach(folder => {
// folder.expanded = expandedFolders.includes(folder.id);
// });
// }
// const allTablesFolder: Folder = {
// id: '0',
// name: 'All Tables',
// expanded: expandedFolders && expandedFolders.length === 0 ? false : expandedFolders.includes('0'),
// tableIds: this.tables.map(table => table.table)
// };
// this.folders.unshift(allTablesFolder);
// },
// error: (error) => {
// console.error('Error fetching folders from connection settings:', error);
// this.folders = [];
// }
// });
// }

Copilot uses AI. Check for mistakes.
Comment on lines +164 to +220
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;

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();
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.allTables[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();
}
});
}
});

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for the fetchTablesFolders API call. The previous async/await implementation had a try-catch block that handled errors by setting isServerError and serverError properties. Without error handling, if the API call fails, the application may be left in an inconsistent state without informing the user. Add error handling using the catchError operator or a second callback in the subscribe method.

Copilot uses AI. Check for mistakes.
Comment on lines 638 to +641
category_color: folder.iconColor,
tables: folder.tableIds
}));
this._connectionsService.updateTablesFolders(this.connectionID, this.tableCategories).subscribe({
this._tablesService.updateTablesFolders(this.connectionID, this.tableCategories).subscribe({

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent filter logic. The filter on line 634 uses folder.name !== 'All Tables' to exclude the "All Tables" folder, but this is inconsistent with the rest of the code which uses folder.id === null or folder.id !== null to identify the "All Tables" folder (see lines 106, 118, 132, 146, 666). This inconsistency could cause issues if the folder name is changed or localized. The filter should be changed to use folder.id !== null for consistency.

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +128
this.foundTables = this.tableFolders
.find((folder: any) => folder.category_id === null)?.tables
.map((table: TableProperties) => {
return {
table: table.table,
display_name: table.display_name,
normalizedTableName: normalizeTableName(table.table),
permissions: table.permissions
};
});

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential null pointer exception. If tableFolders is null or doesn't have a category with category_id === null, the optional chaining will return undefined, and calling .map() on undefined will throw an error. This can occur during component initialization before data is loaded. Add a null/undefined check before calling map().

Copilot uses AI. Check for mistakes.
@lyubov-voloshko lyubov-voloshko merged commit 6ae0562 into main Feb 20, 2026
13 of 15 checks passed
@lyubov-voloshko lyubov-voloshko deleted the table-list-ordering branch February 20, 2026 13:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants