diff --git a/frontend/angular.json b/frontend/angular.json index 740de4c9a..985ac1ccf 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -137,8 +137,7 @@ "builder": "@angular-devkit/build-angular:dev-server", "options": { "buildTarget": "dissendium-v0:build", - "host": "127.0.0.1", - "proxyConfig": "src/proxy.conf.json" + "host": "127.0.0.1" }, "configurations": { "production": { diff --git a/frontend/src/app/components/dashboard/dashboard.component.ts b/frontend/src/app/components/dashboard/dashboard.component.ts index d6b7551c6..d9e61e497 100644 --- a/frontend/src/app/components/dashboard/dashboard.component.ts +++ b/frontend/src/app/components/dashboard/dashboard.component.ts @@ -1,7 +1,13 @@ +import { Angulartics2, Angulartics2Module } from 'angulartics2'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ConnectionSettingsUI, UiSettings } from 'src/app/models/ui-settings'; +import { CustomEvent, TableProperties } from 'src/app/models/table'; +import { map } from 'rxjs/operators'; + +import { AlertComponent } from '../ui-components/alert/alert.component'; 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'; import { MatIconModule } from '@angular/material/icon'; @@ -9,14 +15,10 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, ParamMap, Router, RouterModule } from '@angular/router'; import JsonURL from '@jsonurl/jsonurl'; -import { Angulartics2, Angulartics2Module } from 'angulartics2'; import { omitBy } from 'lodash'; -import { first, 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'; import { CompanyService } from 'src/app/services/company.service'; import { ConnectionsService } from 'src/app/services/connections.service'; @@ -27,7 +29,6 @@ import { UiSettingsService } from 'src/app/services/ui-settings.service'; import { environment } from 'src/environments/environment'; import { normalizeTableName } from '../../lib/normalize'; import { PlaceholderTableViewComponent } from '../skeletons/placeholder-table-view/placeholder-table-view.component'; -import { AlertComponent } from '../ui-components/alert/alert.component'; import { BannerComponent } from '../ui-components/banner/banner.component'; import { ContentLoaderComponent } from '../ui-components/content-loader/content-loader.component'; import { DbActionLinkDialogComponent } from './db-table-view/db-action-link-dialog/db-action-link-dialog.component'; @@ -76,14 +77,12 @@ export class DashboardComponent implements OnInit, OnDestroy { public currentPage: number = 1; public shownTableTitles: boolean = true; public connectionID: string; - // public isTestConnection: boolean = false; public filters: object = {}; public comparators: object; public pageIndex: number; public pageSize: number; public sortColumn: string; public sortOrder: 'ASC' | 'DESC'; - public loading: boolean = true; public isServerError: boolean = false; public serverError: ServerError; @@ -131,10 +130,8 @@ export class DashboardComponent implements OnInit, OnDestroy { } ngOnInit() { - this.connectionID = this._connections.currentConnectionID; - // this.isTestConnection = this._connections.currentConnection.isTestConnection; - this.dataSource = new TablesDataSource(this._tables, this._connections, this._uiSettings, this._tableRow); - + this.connectionID = this._connections.currentConnectionID; + this.dataSource = new TablesDataSource(this._tables, this._connections, this._tableRow); this._tableState.cast.subscribe((row) => { this.selectedRow = row; }); @@ -384,7 +381,6 @@ export class DashboardComponent implements OnInit, OnDestroy { this.uiSettings = settings?.connections[this.connectionID]; this.shownTableTitles = settings?.connections[this.connectionID]?.shownTableTitles ?? true; - const shownColumns = this.uiSettings?.tables[this.selectedTableName]?.shownColumns; this.dataSource.fetchRows({ connectionID: this.connectionID, tableName: this.selectedTableName, @@ -393,8 +389,7 @@ export class DashboardComponent implements OnInit, OnDestroy { sortColumn: this.sortColumn, sortOrder: this.sortOrder, filters: this.filters, - search, - shownColumns, + search }); }); } diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-settings/db-table-settings.component.html b/frontend/src/app/components/dashboard/db-table-view/db-table-settings/db-table-settings.component.html index 6e3f7e20e..143892f00 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-settings/db-table-settings.component.html +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-settings/db-table-settings.component.html @@ -62,7 +62,7 @@

General settings

Table view

- + -
+ Searchable columns @@ -115,7 +115,7 @@

Table view

Choose the columns Rocketadmin scans when using the Search bar.
-
+ Sortable columns @@ -189,7 +189,7 @@

"Edit ro

diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-settings/db-table-settings.component.spec.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-settings/db-table-settings.component.spec.ts index a0f6de627..504a1135c 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-settings/db-table-settings.component.spec.ts +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-settings/db-table-settings.component.spec.ts @@ -84,18 +84,14 @@ describe('DbTableSettingsComponent', () => { icon: "", search_fields: [], excluded_fields: [], - list_fields: [], // identification_fields: [], // list_per_page: null, - ordering: TableOrdering.Ascending, - ordering_field: "", identity_column: "", readonly_fields: [], sortable_by: [], autocomplete_columns: [ "FirstName" ], - columns_view: [], sensitive_fields: [], connection_id: "63f804e4-8588-4957-8d7f-655e2309fef7", allow_csv_export: true, diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-settings/db-table-settings.component.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-settings/db-table-settings.component.ts index bb582fbe9..e407d2686 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-settings/db-table-settings.component.ts +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-settings/db-table-settings.component.ts @@ -61,7 +61,7 @@ export class DbTableSettingsComponent implements OnInit { public loading: boolean = true; public fields: string[]; public fields_to_exclude: string[]; - public orderChanged: boolean = false; + // public orderChanged: boolean = false; public iconChanged: boolean = false; public listFieldsOrder: string[]; public tableSettingsInitial: TableSettings = { @@ -73,12 +73,8 @@ export class DbTableSettingsComponent implements OnInit { identity_column: '', search_fields: [], excluded_fields: [], - list_fields: [], - ordering: TableOrdering.Ascending, - ordering_field: '', readonly_fields: [], sortable_by: [], - columns_view: [], sensitive_fields: [], allow_csv_export: true, allow_csv_import: true, @@ -161,17 +157,17 @@ export class DbTableSettingsComponent implements OnInit { this.iconChanged = true; } - drop(event: CdkDragDrop) { - moveItemInArray(this.listFieldsOrder, event.previousIndex, event.currentIndex); - this.tableSettings.list_fields = [...this.listFieldsOrder]; - this.orderChanged = true; - } + // drop(event: CdkDragDrop) { + // // moveItemInArray(this.listFieldsOrder, event.previousIndex, event.currentIndex); + // // this.tableSettings.list_fields = [...this.listFieldsOrder]; + // // this.orderChanged = true; + // } - resetColumnsOrder() { - this.tableSettings.list_fields = []; - this.listFieldsOrder = [...this.fields]; - this.orderChanged = true; - } + // resetColumnsOrder() { + // // this.tableSettings.list_fields = []; + // this.listFieldsOrder = [...this.fields]; + // // this.orderChanged = true; + // } updateSettings() { this.submitting = true; @@ -183,11 +179,7 @@ export class DbTableSettingsComponent implements OnInit { for (const [key, value] of Object.entries(this.tableSettings)) { if (key !== 'connection_id' && key !== 'table_name' && key !== 'ordering') { if (Array.isArray(value)) { - if (key === 'list_fields') { - updatedSettings[key] = this.orderChanged; - } else { - updatedSettings[key] = value.length > 0; - } + updatedSettings[key] = value.length > 0 } else { updatedSettings[key] = Boolean(value); } 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..cecd8ab02 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,95 @@ 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-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-content_active { + background-color: rgba(255, 255, 255, 0.06); + } +} + +.sort-menu__lock-icon { + font-size: 16px; + width: 16px; + height: 16px; +} + .db-table-cell-checkbox { display: flex; align-items: center; @@ -551,3 +640,85 @@ tr.mat-row:hover { padding: 8px 0; width: 100%; } + +.hidden { + display: none; +} + +/* Columns Menu Drag and Drop Styles */ +.columns-list { + max-height: 400px; + overflow-y: auto; + padding: 8px 0; +} + +.draggable-column-item { + cursor: move; + display: flex !important; + align-items: center; + justify-content: flex-start; + gap: 0px; + padding: 8px 24px 8px 8px !important; + min-height: 48px !important; + transition: background-color 0.2s ease; +} + +.draggable-column-item .drag-handle { + color: #9e9e9e; + cursor: grab; + font-size: 20px; + width: 20px; + height: 20px; + margin-right: 0px; + flex-shrink: 0; +} + +.draggable-column-item ::ng-deep .mat-mdc-checkbox { + align-items: center !important; +} + +.draggable-column-item ::ng-deep .mat-mdc-checkbox-label { + text-align: left; + line-height: 1.5; +} + +.draggable-column-item ::ng-deep .mdc-form-field { + align-items: center !important; +} + +.draggable-column-item:active .drag-handle { + cursor: grabbing; +} + +.draggable-column-item:hover .drag-handle { + color: #616161; +} + +@media (prefers-color-scheme: dark) { + .draggable-column-item .drag-handle { + color: #757575; + } + + .draggable-column-item:hover .drag-handle { + color: #bdbdbd; + } +} + +.cdk-drag-preview.draggable-column-item { + box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12); + opacity: 0.9; +} + +.cdk-drag-placeholder { + opacity: 0.3; +} + +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.columns-list.cdk-drop-list-dragging .draggable-column-item:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} 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..dc26d987a 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 @@ -104,18 +104,24 @@

{{ displayName }}

{{ tableData.columns.length }}) - - + +
+ +
+ + + + + + + +
diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.spec.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.spec.ts index 3fa3698a6..27eff3fc3 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.spec.ts +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.spec.ts @@ -91,7 +91,7 @@ describe('DbTableViewComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(DbTableViewComponent); component = fixture.componentInstance; - component.table = new TablesDataSource({} as any, {} as any, {} as any, {} as any); + component.table = new TablesDataSource({} as any, {} as any, {} as any); component.selection = new SelectionModel(true, []); component.filterComparators = mockFilterComparators; fixture.autoDetectChanges(); 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..78a99ef43 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 @@ -1,9 +1,18 @@ +import * as JSON5 from 'json5'; + +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; + +import { AccessLevel } from 'src/app/models/user'; +import { Angulartics2OnModule } from 'angulartics2'; +import { CommonModule } from '@angular/common'; +import { ConnectionsService } from 'src/app/services/connections.service'; +import { DbTableImportDialogComponent } from './db-table-import-dialog/db-table-import-dialog.component'; +import { DragDropModule, CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { DynamicModule } from 'ng-dynamic-component'; +import JsonURL from "@jsonurl/jsonurl"; import { ClipboardModule } from '@angular/cdk/clipboard'; import { SelectionModel } from '@angular/cdk/collections'; -import { DragDropModule } from '@angular/cdk/drag-drop'; -import { CommonModule } from '@angular/common'; -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ChangeDetectorRef } from '@angular/core'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; @@ -14,15 +23,12 @@ import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; 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 { TablesService } from 'src/app/services/tables.service'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; -import JsonURL from '@jsonurl/jsonurl'; -import { Angulartics2OnModule } from 'angulartics2'; -import * as JSON5 from 'json5'; -import { DynamicModule } from 'ng-dynamic-component'; import { merge } from 'rxjs'; import { tap } from 'rxjs/operators'; import { formatFieldValue } from 'src/app/lib/format-field-value'; @@ -35,18 +41,17 @@ import { TableRow, Widget, } from 'src/app/models/table'; -import { AccessLevel } from 'src/app/models/user'; -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'; import { ForeignKeyDisplayComponent } from '../../ui-components/table-display-fields/foreign-key/foreign-key.component'; import { DbTableExportDialogComponent } from './db-table-export-dialog/db-table-export-dialog.component'; -import { DbTableImportDialogComponent } from './db-table-import-dialog/db-table-import-dialog.component'; import { SavedFiltersPanelComponent } from './saved-filters-panel/saved-filters-panel.component'; +import { MatSelectModule } from '@angular/material/select'; interface Column { title: string; @@ -60,36 +65,38 @@ export interface Folder { } @Component({ - selector: 'app-db-table-view', - templateUrl: './db-table-view.component.html', - styleUrls: ['./db-table-view.component.css'], - imports: [ - CommonModule, - FormsModule, - RouterModule, - MatTableModule, - MatPaginatorModule, - MatSortModule, - MatButtonModule, - MatIconModule, - MatCheckboxModule, - MatChipsModule, - MatDialogModule, - MatFormFieldModule, - ReactiveFormsModule, - MatInputModule, - MatAutocompleteModule, - MatSelectModule, - MatMenuModule, - MatTooltipModule, - ClipboardModule, - DragDropModule, - Angulartics2OnModule, - PlaceholderTableDataComponent, - DynamicModule, - ForeignKeyDisplayComponent, - SavedFiltersPanelComponent, - ], + selector: 'app-db-table-view', + standalone: true, + templateUrl: './db-table-view.component.html', + styleUrls: ['./db-table-view.component.css'], + imports: [ + CommonModule, + FormsModule, + RouterModule, + MatTableModule, + MatPaginatorModule, + MatSortModule, + MatButtonModule, + MatIconModule, + MatCheckboxModule, + MatChipsModule, + MatDialogModule, + MatFormFieldModule, + MatSelectModule, + ReactiveFormsModule, + MatInputModule, + MatAutocompleteModule, + MatMenuModule, + MatTooltipModule, + MatDividerModule, + ClipboardModule, + DragDropModule, + Angulartics2OnModule, + PlaceholderTableDataComponent, + DynamicModule, + ForeignKeyDisplayComponent, + SavedFiltersPanelComponent + ] }) export class DbTableViewComponent implements OnInit { @Input() name: string; @@ -150,21 +157,34 @@ 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 _tables: TablesService, private route: ActivatedRoute, public router: Router, public dialog: MatDialog, + private cdr: ChangeDetectorRef, ) {} ngAfterViewInit() { 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( @@ -235,17 +255,60 @@ export class DbTableViewComponent implements OnInit { }); } - ngOnChanges(changes: SimpleChanges) { - if (changes.name?.currentValue && this.paginator) { - this.paginator.pageIndex = 0; - this.searchString = ''; - } - } + // 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; } + applySort(column: string, direction: 'asc' | 'desc') { + // If clicking on already selected sort - clear it + if (this.sort.active === column && this.sort.direction === 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; + 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); } @@ -564,6 +627,87 @@ export class DbTableViewComponent implements OnInit { this.applyFilter.emit($event); } + get sortedColumns() { + if (!this.tableData || !this.tableData.columns) { + return []; + } + // Sort columns: visible (selected=true) first, then hidden (selected=false) + return [...this.tableData.columns].sort((a, b) => { + if (a.selected === b.selected) return 0; + return a.selected ? -1 : 1; + }); + } + + onColumnVisibilityChange() { + this.tableData.changleColumnList(this.connectionID, this.name); + this.cdr.detectChanges(); + this._tables.updatePersonalTableViewSettings(this.connectionID, this.name, { + columns_view: this.tableData.displayedDataColumns + }).subscribe({ + next: () => { + console.log('Personal table view settings updated with custom ordering'); + }, + error: (error) => { + console.error('Error updating personal table view settings:', error); + } + }); + } + + onColumnsMenuDrop(event: CdkDragDrop) { + if (event.previousIndex === event.currentIndex) { + return; + } + + // The drag indices are based on sortedColumns (visible first, then hidden) + // We need to map these to the actual indices in this.tableData.columns + const sorted = this.sortedColumns; + const draggedColumn = sorted[event.previousIndex]; + const targetColumn = sorted[event.currentIndex]; + + // Find actual indices in the original columns array + const actualPreviousIndex = this.tableData.columns.findIndex(col => col.title === draggedColumn.title); + const actualCurrentIndex = this.tableData.columns.findIndex(col => col.title === targetColumn.title); + + if (actualPreviousIndex === -1 || actualCurrentIndex === -1) { + return; + } + + // Reorder columns array in the menu + moveItemInArray(this.tableData.columns, actualPreviousIndex, actualCurrentIndex); + + // Update dataColumns array + this.tableData.dataColumns = this.tableData.columns.map(column => column.title); + + // Update displayedDataColumns to match the new order (only visible columns) + const newDisplayedOrder = this.tableData.columns + .filter(col => col.selected) + .map(col => col.title); + + this.tableData.displayedDataColumns = newDisplayedOrder; + + // Update full displayed columns list - THIS UPDATES THE TABLE IMMEDIATELY + if (this.tableData.keyAttributes && this.tableData.keyAttributes.length) { + this.tableData.displayedColumns = ['select', ...newDisplayedOrder, 'actions']; + } else { + this.tableData.displayedColumns = [...newDisplayedOrder]; + } + + // Force Angular to detect changes and re-render the table immediately + this.cdr.detectChanges(); + + this._tables.updatePersonalTableViewSettings(this.connectionID, this.name, { + list_fields: this.tableData.columns.map(col => col.title) + }).subscribe({ + next: () => { + console.log('Personal table view settings updated with custom ordering'); + }, + error: (error) => { + console.error('Error updating personal table view settings:', error); + } + }); + + console.log('Columns reordered in menu - table updated:', newDisplayedOrder); + } exportData() { const convertToCSVValue = (value: any): string => { // Handle null and undefined diff --git a/frontend/src/app/components/dashboard/db-tables-data-source.ts b/frontend/src/app/components/dashboard/db-tables-data-source.ts index 1f87ebc31..71cea898f 100644 --- a/frontend/src/app/components/dashboard/db-tables-data-source.ts +++ b/frontend/src/app/components/dashboard/db-tables-data-source.ts @@ -11,9 +11,7 @@ import { ConnectionsService } from 'src/app/services/connections.service'; import { DataSource } from '@angular/cdk/table'; import { MatPaginator } from '@angular/material/paginator'; import { TableRowService } from 'src/app/services/table-row.service'; -// import { MatSort } from '@angular/material/sort'; import { TablesService } from 'src/app/services/tables.service'; -import { UiSettingsService } from 'src/app/services/ui-settings.service'; import { filter } from "lodash"; import { formatFieldValue } from 'src/app/lib/format-field-value'; import { getTableTypes } from 'src/app/lib/setup-table-row-structure'; @@ -35,8 +33,7 @@ interface RowsParams { filters?: object, comparators?: object, search?: string, - isTablePageSwitched?: boolean, - shownColumns?: string[] + isTablePageSwitched?: boolean } export class TablesDataSource implements DataSource { @@ -89,7 +86,6 @@ export class TablesDataSource implements DataSource { constructor( private _tables: TablesService, private _connections: ConnectionsService, - private _uiSettings: UiSettingsService, private _tableRow: TableRowService, ) {} @@ -123,8 +119,7 @@ export class TablesDataSource implements DataSource { sortOrder, filters, comparators, search, - isTablePageSwitched, - shownColumns + isTablePageSwitched }: RowsParams) { this.loadingSubject.next(true); this.alert_primaryKeysInfo = null; @@ -215,8 +210,8 @@ export class TablesDataSource implements DataSource { this.tableTypes = getTableTypes(res.structure, this.foreignKeysList); let orderedColumns: TableField[]; - if (res.list_fields.length) { - orderedColumns = res.structure.sort((fieldA: TableField, fieldB: TableField) => res.list_fields.indexOf(fieldA.column_name) - res.list_fields.indexOf(fieldB.column_name)); + if (res.table_settings.list_fields.length) { + orderedColumns = res.structure.sort((fieldA: TableField, fieldB: TableField) => res.table_settings.list_fields.indexOf(fieldA.column_name) - res.table_settings.list_fields.indexOf(fieldB.column_name)); } else { orderedColumns = [...res.structure]; }; @@ -224,17 +219,11 @@ export class TablesDataSource implements DataSource { if (isTablePageSwitched === undefined) this.columns = orderedColumns .filter (item => item.isExcluded === false) .map((item, index) => { - if (shownColumns?.length) { + if (res.table_settings.columns_view && res.table_settings.columns_view.length !== 0) { return { title: item.column_name, normalizedTitle: this.widgets[item.column_name]?.name || normalizeFieldName(item.column_name), - selected: shownColumns.includes(item.column_name) - } - } else if (res.columns_view && res.columns_view.length !== 0) { - return { - title: item.column_name, - normalizedTitle: this.widgets[item.column_name]?.name || normalizeFieldName(item.column_name), - selected: res.columns_view.includes(item.column_name) + selected: res.table_settings.columns_view.includes(item.column_name) } } else { if (index < 6) { @@ -326,8 +315,6 @@ export class TablesDataSource implements DataSource { } else { this.displayedColumns = [...this.displayedDataColumns]; }; - - this._uiSettings.updateTableSetting(connectionId, tableName, 'shownColumns', this.displayedDataColumns); } getQueryParams(row, action) { diff --git a/frontend/src/app/models/table.ts b/frontend/src/app/models/table.ts index ab5c6233b..251d59d37 100644 --- a/frontend/src/app/models/table.ts +++ b/frontend/src/app/models/table.ts @@ -21,24 +21,32 @@ export enum TableOrdering { export interface TableSettings { connection_id: string, table_name: string, + icon: string, display_name: string, autocomplete_columns: string[], identity_column: string, search_fields: string[], excluded_fields: string[], - list_fields: string[], - ordering: TableOrdering, - ordering_field: string, readonly_fields: string[], sortable_by: string[], - columns_view: string[], sensitive_fields: string[], allow_csv_export: boolean, allow_csv_import: boolean, can_delete: boolean, } +export interface PersonalTableViewSettings { + ordering?: TableOrdering, + ordering_field?: string, + list_per_page?: number, + list_fields?: string[], + columns_view?: string[], + original_names?: boolean, + + // sortable_by: string[], +} + export interface TableRow { connectionID: string, tableName: string, diff --git a/frontend/src/app/models/ui-settings.ts b/frontend/src/app/models/ui-settings.ts index 90c103377..e011be8bb 100644 --- a/frontend/src/app/models/ui-settings.ts +++ b/frontend/src/app/models/ui-settings.ts @@ -4,7 +4,11 @@ export interface GlobalSettingsUI { } export interface TableSettingsUI { - shownColumns: string[]; + shownColumns?: string[]; + defaultSort?: { + column: string; + direction: 'asc' | 'desc'; + }; } export interface ConnectionSettingsUI { diff --git a/frontend/src/app/services/tables.service.ts b/frontend/src/app/services/tables.service.ts index 54c8666a3..9ce4c38a7 100644 --- a/frontend/src/app/services/tables.service.ts +++ b/frontend/src/app/services/tables.service.ts @@ -1,6 +1,6 @@ import { AlertActionType, AlertType } from '../models/alert'; import { BehaviorSubject, EMPTY, throwError } from 'rxjs'; -import { Rule, TableSettings, Widget } from '../models/table'; +import { PersonalTableViewSettings, Rule, TableSettings, Widget } from '../models/table'; import { HttpClient, } from '@angular/common/http'; import { NavigationEnd, Router } from '@angular/router'; import { catchError, filter, map } from 'rxjs/operators'; @@ -147,29 +147,6 @@ export class TablesService { ); } - fetchTableSettings(connectionID: string, tableName: string) { - return this._http.get('/settings', { - params: { - connectionId: connectionID, - tableName - } - }) - .pipe( - map(res => res), - catchError((err) => { - console.log(err); - this._notifications.showAlert(AlertType.Error, {abstract: err.error.message, details: err.error.originalMessage}, [ - { - type: AlertActionType.Button, - caption: 'Dismiss', - action: (_id: number) => this._notifications.dismissAlert() - } - ]); - return EMPTY; - }) - ); - } - exportTableCSV({ connectionID, tableName, @@ -230,6 +207,29 @@ export class TablesService { ); } + fetchTableSettings(connectionID: string, tableName: string) { + return this._http.get('/settings', { + params: { + connectionId: connectionID, + tableName + } + }) + .pipe( + map(res => res), + catchError((err) => { + console.log(err); + this._notifications.showAlert(AlertType.Error, {abstract: err.error.message, details: err.error.originalMessage}, [ + { + type: AlertActionType.Button, + caption: 'Dismiss', + action: (id: number) => this._notifications.dismissAlert() + } + ]); + return EMPTY; + }) + ); + } + updateTableSettings(isSettingsExist: boolean, connectionID: string, tableName: string, settings: TableSettings) { let method: string; if (isSettingsExist) { @@ -287,6 +287,53 @@ export class TablesService { ); } + // fetchPersonalTableViewSettings(connectionID: string, tableName: string) { + // return this._http.get(`/settings/personal/${connectionID}`, { + // params: { + // tableName + // } + // }) + // .pipe( + // map(res => res), + // catchError((err) => { + // console.log(err); + // this._notifications.showAlert(AlertType.Error, {abstract: err.error.message, details: err.error.originalMessage}, [ + // { + // type: AlertActionType.Button, + // caption: 'Dismiss', + // action: (id: number) => this._notifications.dismissAlert() + // } + // ]); + // return EMPTY; + // }) + // ); + // } + + updatePersonalTableViewSettings(connectionID: string, tableName: string, settings: PersonalTableViewSettings) { + return this._http.put(`/settings/personal/${connectionID}`, settings, { + params: { + tableName + } + }) + .pipe( + map(() => { + this.tables.next('settings'); + // this._notifications.showSuccessSnackbar('Table settings has been updated.') + }), + catchError((err) => { + console.log(err); + this._notifications.showAlert(AlertType.Error, {abstract: err.error.message, details: err.error.originalMessage}, [ + { + type: AlertActionType.Button, + caption: 'Dismiss', + action: (id: number) => this._notifications.dismissAlert() + } + ]); + return EMPTY; + }) + ); + } + fetchTableWidgets(connectionID: string, tableName: string) { return this._http.get(`/widgets/${connectionID}`, { params: { diff --git a/frontend/src/app/services/ui-settings.service.ts b/frontend/src/app/services/ui-settings.service.ts index ddd578e13..a1f59e70f 100644 --- a/frontend/src/app/services/ui-settings.service.ts +++ b/frontend/src/app/services/ui-settings.service.ts @@ -52,9 +52,9 @@ export class UiSettingsService { 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: [] }; - } + // 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(); }