From 0ec295184394589aa9aab92b090ee4a8e6efa467 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Mon, 22 Dec 2025 16:52:28 +0200 Subject: [PATCH 1/3] Implement sortable columns in dashboard table view with visual indicators and tooltip support --- .../db-table-view/db-table-view.component.css | 172 ++++++++++++++++++ .../db-table-view.component.html | 33 +++- .../db-table-view/db-table-view.component.ts | 74 ++++++++ 3 files changed, 278 insertions(+), 1 deletion(-) 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..4ee518e23 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,178 @@ th.mat-header-cell, td.mat-cell { text-align: left; } +.sort-header-cell { + padding: 0 !important; +} + +.sort-header-cell:hover .sort-header-button, +.sort-header-cell.sort-header-active .sort-header-button, +.sort-header-button[aria-expanded="true"], +.sort-header-button:focus { + opacity: 1; + visibility: visible; +} + +.sort-header-content { + display: flex; + align-items: center; + width: 100%; + padding: 0 16px 0 0; + text-align: left; +} + +.sort-header-button { + width: 32px; + height: 32px; + --mdc-icon-button-icon-size: 18px; + margin-left: 4px; + opacity: 0; + visibility: hidden; + transition: opacity 0.2s, visibility 0.2s; +} + +.sort-header-button .mat-icon { + font-size: 18px; + width: 18px; + height: 18px; +} + +.sort-header-button .mat-icon.sort-icon-sync { + transform: rotate(90deg); + color: rgba(0, 0, 0, 0.54); +} + +@media (prefers-color-scheme: dark) { + .sort-header-button .mat-icon.sort-icon-sync { + color: rgba(255, 255, 255, 0.54); + } +} + +.sort-menu { + min-width: 280px; +} + +.sort-menu .mat-mdc-menu-item { + display: flex; + align-items: center; + justify-content: space-between; + flex-direction: row; +} + +.sort-menu-item-content { + display: flex; + align-items: center; + gap: 12px; +} + +.sort-menu .mat-mdc-menu-item.sort-menu-item-active { + background-color: rgba(0, 0, 0, 0.04); +} + +@media (prefers-color-scheme: dark) { + .sort-menu .mat-mdc-menu-item.sort-menu-item-active { + background-color: rgba(255, 255, 255, 0.04); + } +} + +.sort-menu-item-check { + font-size: 16px; + width: 16px; + height: 16px; + margin-left: 24px; + margin-right: 0; + opacity: 0.6; + order: 2; +} + +.sort-menu-item-content { + order: 1; +} + +.sort-icon-container { + position: relative; + width: 28px; + height: 16px; + display: inline-flex; + align-items: center; + justify-content: flex-start; + gap: 0; +} + +.sort-icon-custom { + position: relative; + width: 14px; + height: 14px; + flex-shrink: 0; +} + +.sort-icon-custom::before, +.sort-icon-custom::after { + content: ''; + position: absolute; + left: 0; + height: 2px; + background-color: currentColor; + border-radius: 1px; +} + +/* A-Z: короткая, длинная, короткая */ +.sort-icon-asc .sort-icon-custom { + background-color: currentColor; + width: 6px; + height: 2px; + border-radius: 1px; + top: 0; +} + +.sort-icon-asc .sort-icon-custom::before { + top: 5px; + width: 12px; +} + +.sort-icon-asc .sort-icon-custom::after { + top: 10px; + width: 6px; +} + +/* Z-A: короткая, длинная, короткая */ +.sort-icon-desc .sort-icon-custom { + background-color: currentColor; + width: 6px; + height: 2px; + border-radius: 1px; + top: 0; +} + +.sort-icon-desc .sort-icon-custom::before { + top: 5px; + width: 12px; +} + +.sort-icon-desc .sort-icon-custom::after { + top: 10px; + width: 6px; +} + +.sort-icon-arrow { + font-size: 16px; + width: 16px; + height: 16px; + line-height: 16px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.sort-icon-arrow { + transform: rotate(180deg); +} + +.sort-icon-arrow-down { + transform: rotate(180deg); +} + .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 5dd7b8391..2d8b0f03c 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 84f8f9c01..166fa97c8 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 @@ -241,6 +241,80 @@ export class DbTableViewComponent implements OnInit { return this.tableData.sortByColumns.includes(column) || !this.tableData.sortByColumns.length; } + getSortIcon(column: string): string { + if (this.sort && this.sort.active === column) { + return this.sort.direction === 'asc' ? 'arrow_upward' : 'arrow_downward'; + } + return 'sync_alt'; + } + + getSortTooltip(column: string): string { + if (this.sort && this.sort.active === column) { + return this.sort.direction === 'asc' + ? 'Sort ascending (A-Z)' + : 'Sort descending (Z-A)'; + } + return 'Sort column'; + } + + applySort(column: string, direction: 'asc' | 'desc') { + if (!this.sort || !this.paginator) return; + + // Если колонка уже отсортирована в том же направлении, отменяем сортировку + if (this.sort.active === column && this.sort.direction === direction) { + this.clearSort(); + return; + } + + // Применяем сортировку программно через MatSort API + this.sort.sort({ + id: column, + start: direction, + disableClear: true + }); + + // Триггерим событие сортировки вручную, чтобы обновить URL и загрузить данные + const filters = JsonURL.stringify(this.activeFilters); + const saved_filter = this.route.snapshot.queryParams.saved_filter; + const dynamic_column = this.route.snapshot.queryParams.dynamic_column; + + this.router.navigate([`/dashboard/${this.connectionID}/${this.name}`], { + queryParams: { + filters, + saved_filter, + dynamic_column, + sort_active: column, + sort_direction: direction.toUpperCase(), + page_index: this.paginator.pageIndex, + page_size: this.paginator.pageSize + } + }); + this.loadRowsPage(); + } + + clearSort() { + if (!this.sort || !this.paginator) return; + + // Очищаем сортировку, вызывая sort с пустым id + this.sort.sort({ id: '', start: 'asc', disableClear: false }); + + const filters = JsonURL.stringify(this.activeFilters); + const saved_filter = this.route.snapshot.queryParams.saved_filter; + const dynamic_column = this.route.snapshot.queryParams.dynamic_column; + + // Навигация без параметров сортировки (они будут удалены из URL) + this.router.navigate([`/dashboard/${this.connectionID}/${this.name}`], { + queryParams: { + filters, + saved_filter, + dynamic_column, + page_index: this.paginator.pageIndex, + page_size: this.paginator.pageSize + } + }); + this.loadRowsPage(); + } + isForeignKey(column: string) { return this.tableData.foreignKeysList.includes(column); } From ec2c0dd707b6f0a3c6cae7fcbbbdd0cc61b55571 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Mon, 5 Jan 2026 13:58:30 +0200 Subject: [PATCH 2/3] Add custom SVG icons for sorting (ascending, descending, default) and update sort menu UI --- frontend/src/app/app.component.ts | 3 + .../db-table-view/db-table-view.component.css | 78 ++++++++++----- .../db-table-view.component.html | 12 ++- .../saved-filters-dialog.component.css | 95 +++++++++++++++++++ .../saved-filters-dialog.component.ts | 36 +++++++ .../saved-filters-panel.component.css | 16 +++- .../saved-filters-panel.component.html | 34 ++----- .../saved-filters-panel.component.ts | 32 +++++++ frontend/src/assets/icons/sort-ascending.svg | 3 + frontend/src/assets/icons/sort-descending.svg | 3 + frontend/src/assets/icons/sorting.svg | 11 +++ 11 files changed, 266 insertions(+), 57 deletions(-) create mode 100644 frontend/src/assets/icons/sort-ascending.svg create mode 100644 frontend/src/assets/icons/sort-descending.svg create mode 100644 frontend/src/assets/icons/sorting.svg diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 8a9a78ccb..e328a52a5 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -114,6 +114,9 @@ export class AppComponent { this.matIconRegistry.addSvgIcon("github", this.domSanitizer.bypassSecurityTrustResourceUrl("/assets/icons/github.svg")); this.matIconRegistry.addSvgIcon("google", this.domSanitizer.bypassSecurityTrustResourceUrl("/assets/icons/google.svg")); this.matIconRegistry.addSvgIcon("ai_rocket", this.domSanitizer.bypassSecurityTrustResourceUrl("/assets/icons/ai-rocket.svg")); + this.matIconRegistry.addSvgIcon("sort_ascending", this.domSanitizer.bypassSecurityTrustResourceUrl("/assets/icons/sort-ascending.svg")); + this.matIconRegistry.addSvgIcon("sort_descending", this.domSanitizer.bypassSecurityTrustResourceUrl("/assets/icons/sort-descending.svg")); + this.matIconRegistry.addSvgIcon("sorting", this.domSanitizer.bypassSecurityTrustResourceUrl("/assets/icons/sorting.svg")); angulartics2Amplitude.startTracking(); } 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 4ee518e23..77275918b 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 @@ -463,15 +463,10 @@ th.mat-header-cell, td.mat-cell { height: 18px; } -.sort-header-button .mat-icon.sort-icon-sync { - transform: rotate(90deg); - color: rgba(0, 0, 0, 0.54); -} - -@media (prefers-color-scheme: dark) { - .sort-header-button .mat-icon.sort-icon-sync { - color: rgba(255, 255, 255, 0.54); - } +.sort-header-icon { + font-size: 18px; + width: 18px; + height: 18px; } .sort-menu { @@ -488,31 +483,68 @@ th.mat-header-cell, td.mat-cell { .sort-menu-item-content { display: flex; align-items: center; - gap: 12px; + gap: 6px; } -.sort-menu .mat-mdc-menu-item.sort-menu-item-active { - background-color: rgba(0, 0, 0, 0.04); +.sort-menu-icon { + font-size: 16px; + width: 16px; + height: 16px; } -@media (prefers-color-scheme: dark) { - .sort-menu .mat-mdc-menu-item.sort-menu-item-active { - background-color: rgba(255, 255, 255, 0.04); - } +.sort-menu-icon-container { + display: flex; + align-items: center; + gap: 4px; + position: relative; + width: 28px; + height: 16px; } -.sort-menu-item-check { +.sort-menu-icon-lines { + display: flex; + flex-direction: column; + gap: 3px; + align-items: flex-start; + width: 12px; + height: 14px; +} + +.sort-menu-line { + display: inline-block; + height: 2px; + background-color: currentColor; + border-radius: 1px; +} + +.sort-menu-line-1 { + width: 6px; +} + +.sort-menu-line-2 { + width: 8px; +} + +.sort-menu-line-3 { + width: 12px; +} + + +.sort-menu-icon-arrow { font-size: 16px; width: 16px; height: 16px; - margin-left: 24px; - margin-right: 0; - opacity: 0.6; - order: 2; + line-height: 16px; } -.sort-menu-item-content { - order: 1; +.sort-menu .mat-mdc-menu-item.sort-menu-item-active { + background-color: rgba(0, 0, 0, 0.04); +} + +@media (prefers-color-scheme: dark) { + .sort-menu .mat-mdc-menu-item.sort-menu-item-active { + background-color: rgba(255, 255, 255, 0.04); + } } .sort-icon-container { 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 2d8b0f03c..1e61c7232 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 @@ -245,24 +245,26 @@

{{ displayName }}

[matMenuTriggerFor]="columnSortMenu" (click)="$event.stopPropagation()" [matTooltip]="getSortTooltip(column)"> - {{ getSortIcon(column) }} + + +
diff --git a/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-dialog/saved-filters-dialog.component.css b/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-dialog/saved-filters-dialog.component.css index a27ec9c1e..4c86c836e 100644 --- a/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-dialog/saved-filters-dialog.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-dialog/saved-filters-dialog.component.css @@ -109,3 +109,98 @@ font-style: italic; margin: 16px 0; } + +.comparator-icon { + font-size: 18px; + width: 18px; + height: 18px; + margin-right: 8px; + vertical-align: middle; +} + +::ng-deep .mat-mdc-select-panel .mat-mdc-option { + display: flex; + align-items: center; +} + +.comparator-select-field { + width: auto; + min-width: 140px; + max-width: none; +} + +.comparator-select-field ::ng-deep .mat-mdc-form-field-infix { + width: 100% !important; + min-width: fit-content; + padding-right: 40px; + padding-left: 0; + position: relative; +} + +.comparator-select-field ::ng-deep .mat-mdc-select-trigger { + width: 100%; + min-width: fit-content; + display: flex; + justify-content: space-between; + align-items: center; + position: relative; +} + +.comparator-select-field ::ng-deep .mat-mdc-select-value-text { + width: auto; + min-width: fit-content; + flex: 1; +} + +.comparator-select-field ::ng-deep .mat-mdc-select-arrow-wrapper { + position: absolute; + right: 12px; + top: 50%; + transform: translateY(-50%); + pointer-events: none; +} + +.comparator-select-field ::ng-deep .mat-mdc-select-panel { + min-width: max-content !important; + width: max-content !important; +} + +.comparator-text { + grid-column: 3; + font-size: 14px; + font-weight: 400; + line-height: 20px; + padding: 8px 0; + align-self: center; + color: rgba(0, 0, 0, 0.87); +} + +@media (prefers-color-scheme: dark) { + .comparator-text { + color: rgba(255, 255, 255, 0.87); + } +} + +.conditions-error-message { + grid-column: 1 / span 6; + color: #f44336; + font-size: 12px; + margin: 8px 0 0 0; + padding-left: 16px; +} + +/* Add more spacing for multiline textarea inputs (more than 2 rows) */ +.filters-content ::ng-deep .filter-line mat-form-field:has(textarea[rows]):not(:has(textarea[rows="1"])):not(:has(textarea[rows="2"])), +.filters-content ::ng-deep .filter-line mat-form-field:has(textarea.long-textarea), +.filters-content ::ng-deep .filter-line mat-form-field:has(textarea.form-textarea) { + margin-top: 24px !important; + margin-bottom: 24px !important; +} + +/* Add more spacing for foreign key fields */ +.filters-content ::ng-deep .filter-line .foreign-key, +.filters-content ::ng-deep .filter-line app-edit-foreign-key, +.filters-content ::ng-deep .foreign-key { + margin-top: 24px !important; + margin-bottom: 24px !important; +} diff --git a/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-dialog/saved-filters-dialog.component.ts b/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-dialog/saved-filters-dialog.component.ts index 1c658f9cf..03533dbc5 100644 --- a/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-dialog/saved-filters-dialog.component.ts +++ b/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-dialog/saved-filters-dialog.component.ts @@ -212,6 +212,38 @@ export class SavedFiltersDialogComponent implements OnInit { } } + getOperatorIcon(operator: string): string { + const iconMap: { [key: string]: string } = { + 'startswith': 'play_arrow', + 'endswith': 'play_arrow', + 'eq': 'drag_handle', + 'contains': 'search', + 'icontains': 'block', + 'empty': 'space_bar', + 'gt': 'keyboard_arrow_right', + 'lt': 'keyboard_arrow_left', + 'gte': 'keyboard_double_arrow_right', + 'lte': 'keyboard_double_arrow_left' + }; + return iconMap[operator] || 'drag_handle'; + } + + getOperatorText(operator: string): string { + const textMap: { [key: string]: string } = { + 'startswith': 'starts with', + 'endswith': 'ends with', + 'eq': 'equal', + 'contains': 'contains', + 'icontains': 'not contains', + 'empty': 'is empty', + 'gt': 'greater than', + 'lt': 'less than', + 'gte': 'greater than or equal', + 'lte': 'less than or equal' + }; + return textMap[operator] || 'equal'; + } + removeFilter(field) { delete this.tableRowFieldsShown[field]; delete this.tableRowFieldsComparator[field]; @@ -230,6 +262,10 @@ export class SavedFiltersDialogComponent implements OnInit { this.dynamicColumn = null; } else { this.dynamicColumn = field; + // Ensure comparator is set (default to 'eq' if not set) + if (!this.tableRowFieldsComparator[field]) { + this.tableRowFieldsComparator[field] = 'eq'; + } } } diff --git a/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.css b/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.css index 4e50b5293..e35898227 100644 --- a/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.css @@ -80,7 +80,7 @@ } .column-name { - margin-top: -8px; + margin-top: 0; } @media (prefers-color-scheme: light) { @@ -98,6 +98,20 @@ margin-left: 8px; } +.comparator-text { + font-size: 14px; + font-weight: 400; + line-height: 20px; + margin-left: 8px; + color: rgba(0, 0, 0, 0.87); +} + +@media (prefers-color-scheme: dark) { + .comparator-text { + color: rgba(255, 255, 255, 0.87); + } +} + .dynamic-column-editor ::ng-deep .foreign-key { display: block; min-width: 280px; diff --git a/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.html b/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.html index 3ea37f474..e82b65126 100644 --- a/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.html +++ b/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.html @@ -44,35 +44,13 @@
- where - - {{ savedFilterMap[selectedFilterSetId]?.dynamicColumn.column }} - + + + {{ savedFilterMap[selectedFilterSetId]?.dynamicColumn.column }} + + {{ getOperatorText(savedFilterMap[selectedFilterSetId]?.dynamicColumn.operator || 'eq') }} + - - - starts with - ends with - equal - contains - not contains - is empty - - - - - - equal - greater than - less than - greater than or equal - less than or equal - -
diff --git a/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.ts b/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.ts index 2423f9224..b4b3036f3 100644 --- a/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.ts +++ b/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.ts @@ -371,6 +371,38 @@ export class SavedFiltersPanelComponent implements OnInit, OnDestroy { this.selectedFilter = entry; } + getOperatorIcon(operator: string): string { + const iconMap: { [key: string]: string } = { + 'startswith': 'play_arrow', + 'endswith': 'play_arrow', + 'eq': 'drag_handle', + 'contains': 'search', + 'icontains': 'block', + 'empty': 'space_bar', + 'gt': 'keyboard_arrow_right', + 'lt': 'keyboard_arrow_left', + 'gte': 'keyboard_double_arrow_right', + 'lte': 'keyboard_double_arrow_left' + }; + return iconMap[operator] || 'drag_handle'; + } + + getOperatorText(operator: string): string { + const textMap: { [key: string]: string } = { + 'startswith': 'starts with', + 'endswith': 'ends with', + 'eq': 'equal', + 'contains': 'contains', + 'icontains': 'not contains', + 'empty': 'is empty', + 'gt': 'greater than', + 'lt': 'less than', + 'gte': 'greater than or equal', + 'lte': 'less than or equal' + }; + return textMap[operator] || 'equal'; + } + getFilter(activeFilter: {column: string, operator: string, value: any}) { const displayedName = normalizeTableName(activeFilter.column); const comparator = activeFilter.operator; diff --git a/frontend/src/assets/icons/sort-ascending.svg b/frontend/src/assets/icons/sort-ascending.svg new file mode 100644 index 000000000..0e7446a36 --- /dev/null +++ b/frontend/src/assets/icons/sort-ascending.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/icons/sort-descending.svg b/frontend/src/assets/icons/sort-descending.svg new file mode 100644 index 000000000..7fc67db8e --- /dev/null +++ b/frontend/src/assets/icons/sort-descending.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/icons/sorting.svg b/frontend/src/assets/icons/sorting.svg new file mode 100644 index 000000000..3e81159ff --- /dev/null +++ b/frontend/src/assets/icons/sorting.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + From dc3dae7241a45b967e0590142d78f791e67de423 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Tue, 6 Jan 2026 15:32:35 +0200 Subject: [PATCH 3/3] sorting in AddSaveOrderingByDefaultInTableSettingsEntity --- .../create-table-settings.ds.ts | 6 + .../found-table-settings.ds.ts | 6 + .../table-settings.controller.ts | 8 + .../table-settings/table-settings.entity.ts | 6 + .../utils/build-found-table-settings-ds.ts | 4 + .../utils/build-new-table-settings-entity.ts | 4 + ...eOrderingByDefaultInTableSettingsEntity.ts | 14 ++ ...ngByDefaultColumnsInTableSettingsEntity.ts | 14 ++ .../db-table-settings.component.ts | 1 + .../db-table-view/db-table-view.component.css | 82 +++++++- .../db-table-view.component.html | 4 +- .../db-table-view/db-table-view.component.ts | 187 +++++++++++++++++- frontend/src/app/models/table.ts | 2 + 13 files changed, 330 insertions(+), 8 deletions(-) create mode 100644 backend/src/migrations/1767703855307-AddSaveOrderingByDefaultInTableSettingsEntity.ts create mode 100644 backend/src/migrations/1767704529763-AddSaveOrderingByDefaultColumnsInTableSettingsEntity.ts diff --git a/backend/src/entities/table-settings/application/data-structures/create-table-settings.ds.ts b/backend/src/entities/table-settings/application/data-structures/create-table-settings.ds.ts index 54cc254af..71ec333a6 100644 --- a/backend/src/entities/table-settings/application/data-structures/create-table-settings.ds.ts +++ b/backend/src/entities/table-settings/application/data-structures/create-table-settings.ds.ts @@ -81,4 +81,10 @@ export class CreateTableSettingsDs { @ApiProperty() allow_csv_import: boolean; + + @ApiProperty() + save_ordering_by_default: boolean; + + @ApiProperty({ required: false }) + save_ordering_by_default_columns?: { [columnName: string]: boolean }; } diff --git a/backend/src/entities/table-settings/application/data-structures/found-table-settings.ds.ts b/backend/src/entities/table-settings/application/data-structures/found-table-settings.ds.ts index bfa89efff..3da748cee 100644 --- a/backend/src/entities/table-settings/application/data-structures/found-table-settings.ds.ts +++ b/backend/src/entities/table-settings/application/data-structures/found-table-settings.ds.ts @@ -82,4 +82,10 @@ export class FoundTableSettingsDs { @ApiProperty() allow_csv_import: boolean; + + @ApiProperty() + save_ordering_by_default: boolean; + + @ApiProperty({ required: false }) + save_ordering_by_default_columns?: { [columnName: string]: boolean }; } diff --git a/backend/src/entities/table-settings/table-settings.controller.ts b/backend/src/entities/table-settings/table-settings.controller.ts index 95471b3d7..1a82d7fa8 100644 --- a/backend/src/entities/table-settings/table-settings.controller.ts +++ b/backend/src/entities/table-settings/table-settings.controller.ts @@ -120,6 +120,8 @@ export class TableSettingsController { @Body('icon') icon: string, @Body('allow_csv_export') allow_csv_export: boolean, @Body('allow_csv_import') allow_csv_import: boolean, + @Body('save_ordering_by_default') save_ordering_by_default: boolean, + @Body('save_ordering_by_default_columns') save_ordering_by_default_columns: { [columnName: string]: boolean }, @UserId() userId: string, @MasterPassword() masterPwd: string, ): Promise { @@ -150,6 +152,8 @@ export class TableSettingsController { icon: icon, allow_csv_export: allow_csv_export, allow_csv_import: allow_csv_import, + save_ordering_by_default: save_ordering_by_default, + save_ordering_by_default_columns: save_ordering_by_default_columns, }; const errors = this.validateParameters(inputData); @@ -200,6 +204,8 @@ export class TableSettingsController { @Body('icon') icon: string, @Body('allow_csv_export') allow_csv_export: boolean, @Body('allow_csv_import') allow_csv_import: boolean, + @Body('save_ordering_by_default') save_ordering_by_default: boolean, + @Body('save_ordering_by_default_columns') save_ordering_by_default_columns: { [columnName: string]: boolean }, @UserId() userId: string, @MasterPassword() masterPwd: string, ): Promise { @@ -229,6 +235,8 @@ export class TableSettingsController { icon: icon, allow_csv_export: allow_csv_export, allow_csv_import: allow_csv_import, + save_ordering_by_default: save_ordering_by_default, + save_ordering_by_default_columns: save_ordering_by_default_columns, }; const errors = this.validateParameters(inputData); diff --git a/backend/src/entities/table-settings/table-settings.entity.ts b/backend/src/entities/table-settings/table-settings.entity.ts index d0147a3ca..8b63d64c9 100644 --- a/backend/src/entities/table-settings/table-settings.entity.ts +++ b/backend/src/entities/table-settings/table-settings.entity.ts @@ -73,6 +73,12 @@ export class TableSettingsEntity { @Column({ default: true, type: 'boolean' }) allow_csv_import: boolean; + @Column({ default: false, type: 'boolean' }) + save_ordering_by_default: boolean; + + @Column('jsonb', { default: null, nullable: true }) + save_ordering_by_default_columns: { [columnName: string]: boolean } | null; + @Column('varchar', { array: true, default: null }) sensitive_fields: string[]; diff --git a/backend/src/entities/table-settings/utils/build-found-table-settings-ds.ts b/backend/src/entities/table-settings/utils/build-found-table-settings-ds.ts index ae8ffb2eb..4fc1cee3f 100644 --- a/backend/src/entities/table-settings/utils/build-found-table-settings-ds.ts +++ b/backend/src/entities/table-settings/utils/build-found-table-settings-ds.ts @@ -29,6 +29,8 @@ export function buildFoundTableSettingsDs(tableSettings: TableSettingsEntity): F icon, allow_csv_export, allow_csv_import, + save_ordering_by_default, + save_ordering_by_default_columns, } = tableSettings; let connection_id = tableSettings.connection_id as unknown; if (connection_id instanceof ConnectionEntity) { @@ -61,5 +63,7 @@ export function buildFoundTableSettingsDs(tableSettings: TableSettingsEntity): F icon: icon, allow_csv_export: allow_csv_export, allow_csv_import: allow_csv_import, + save_ordering_by_default: save_ordering_by_default, + save_ordering_by_default_columns: save_ordering_by_default_columns || undefined, }; } diff --git a/backend/src/entities/table-settings/utils/build-new-table-settings-entity.ts b/backend/src/entities/table-settings/utils/build-new-table-settings-entity.ts index 1fdef0450..ad1072e93 100644 --- a/backend/src/entities/table-settings/utils/build-new-table-settings-entity.ts +++ b/backend/src/entities/table-settings/utils/build-new-table-settings-entity.ts @@ -32,6 +32,8 @@ export function buildNewTableSettingsEntity( icon, allow_csv_export, allow_csv_import, + save_ordering_by_default, + save_ordering_by_default_columns, } = settings; newSettings.connection_id = connection; newSettings.display_name = display_name; @@ -58,5 +60,7 @@ export function buildNewTableSettingsEntity( newSettings.icon = icon; newSettings.allow_csv_export = allow_csv_export; newSettings.allow_csv_import = allow_csv_import; + newSettings.save_ordering_by_default = save_ordering_by_default; + newSettings.save_ordering_by_default_columns = save_ordering_by_default_columns || null; return newSettings; } diff --git a/backend/src/migrations/1767703855307-AddSaveOrderingByDefaultInTableSettingsEntity.ts b/backend/src/migrations/1767703855307-AddSaveOrderingByDefaultInTableSettingsEntity.ts new file mode 100644 index 000000000..006107814 --- /dev/null +++ b/backend/src/migrations/1767703855307-AddSaveOrderingByDefaultInTableSettingsEntity.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSaveOrderingByDefaultInTableSettingsEntity1767703855307 implements MigrationInterface { + name = 'AddSaveOrderingByDefaultInTableSettingsEntity1767703855307'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tableSettings" ADD "save_ordering_by_default" boolean NOT NULL DEFAULT false`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tableSettings" DROP COLUMN "save_ordering_by_default"`); + } +} + diff --git a/backend/src/migrations/1767704529763-AddSaveOrderingByDefaultColumnsInTableSettingsEntity.ts b/backend/src/migrations/1767704529763-AddSaveOrderingByDefaultColumnsInTableSettingsEntity.ts new file mode 100644 index 000000000..ef838c393 --- /dev/null +++ b/backend/src/migrations/1767704529763-AddSaveOrderingByDefaultColumnsInTableSettingsEntity.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSaveOrderingByDefaultColumnsInTableSettingsEntity1767704529763 implements MigrationInterface { + name = 'AddSaveOrderingByDefaultColumnsInTableSettingsEntity1767704529763'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tableSettings" ADD "save_ordering_by_default_columns" jsonb DEFAULT NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tableSettings" DROP COLUMN "save_ordering_by_default_columns"`); + } +} + 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..bc682fd8a 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 @@ -83,6 +83,7 @@ export class DbTableSettingsComponent implements OnInit { allow_csv_export: true, allow_csv_import: true, can_delete: true, + save_ordering_by_default: false, } public tableSettings: TableSettings = null; public defaultIcons = ['favorite', 'star', 'done', 'arrow_forward', 'key', 'lock', 'visibility', 'language', 'notifications', 'schedule']; 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 77275918b..58d1d067d 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 @@ -486,6 +486,48 @@ th.mat-header-cell, td.mat-cell { gap: 6px; } +.sort-menu-item-content.sort-menu-item-active-content { + background-color: rgba(0, 0, 0, 0.08); + padding: 4px 8px; + margin: -4px -8px; + border-radius: 4px; +} + +@media (prefers-color-scheme: dark) { + .sort-menu-item-content.sort-menu-item-active-content { + background-color: rgba(255, 255, 255, 0.08); + } +} + +:host ::ng-deep .sort-menu .mat-mdc-menu-item.sort-menu-item-active .sort-menu-item-content, +:host ::ng-deep .sort-menu button.mat-mdc-menu-item.sort-menu-item-active .sort-menu-item-content { + background-color: rgba(0, 0, 0, 0.08) !important; + padding: 4px 8px !important; + margin: -4px -8px !important; + border-radius: 4px !important; + display: inline-flex !important; +} + +:host ::ng-deep .sort-menu .mat-mdc-menu-item.sort-menu-item-active .sort-menu-item-content span, +:host ::ng-deep .sort-menu button.mat-mdc-menu-item.sort-menu-item-active .sort-menu-item-content span { + background-color: rgba(0, 0, 0, 0.08) !important; + padding: 2px 6px !important; + border-radius: 4px !important; + display: inline-block !important; +} + +@media (prefers-color-scheme: dark) { + :host ::ng-deep .sort-menu .mat-mdc-menu-item.sort-menu-item-active .sort-menu-item-content, + :host ::ng-deep .sort-menu button.mat-mdc-menu-item.sort-menu-item-active .sort-menu-item-content { + background-color: rgba(255, 255, 255, 0.08) !important; + } + + :host ::ng-deep .sort-menu .mat-mdc-menu-item.sort-menu-item-active .sort-menu-item-content span, + :host ::ng-deep .sort-menu button.mat-mdc-menu-item.sort-menu-item-active .sort-menu-item-content span { + background-color: rgba(255, 255, 255, 0.08) !important; + } +} + .sort-menu-icon { font-size: 16px; width: 16px; @@ -537,13 +579,45 @@ th.mat-header-cell, td.mat-cell { line-height: 16px; } -.sort-menu .mat-mdc-menu-item.sort-menu-item-active { - background-color: rgba(0, 0, 0, 0.04); +:host ::ng-deep .sort-menu button.mat-mdc-menu-item.sort-menu-item-active, +:host ::ng-deep .sort-menu .mat-mdc-menu-item.sort-menu-item-active { + background-color: rgba(0, 0, 0, 0.08) !important; +} + +:host ::ng-deep .sort-menu button.mat-mdc-menu-item.sort-menu-item-active:hover, +:host ::ng-deep .sort-menu .mat-mdc-menu-item.sort-menu-item-active:hover { + background-color: rgba(0, 0, 0, 0.12) !important; +} + +:host ::ng-deep .sort-menu button.mat-mdc-menu-item.sort-menu-item-active:focus, +:host ::ng-deep .sort-menu .mat-mdc-menu-item.sort-menu-item-active:focus { + background-color: rgba(0, 0, 0, 0.08) !important; +} + +:host ::ng-deep .sort-menu button.mat-mdc-menu-item.sort-menu-item-active:focus-visible, +:host ::ng-deep .sort-menu .mat-mdc-menu-item.sort-menu-item-active:focus-visible { + background-color: rgba(0, 0, 0, 0.08) !important; } @media (prefers-color-scheme: dark) { - .sort-menu .mat-mdc-menu-item.sort-menu-item-active { - background-color: rgba(255, 255, 255, 0.04); + :host ::ng-deep .sort-menu button.mat-mdc-menu-item.sort-menu-item-active, + :host ::ng-deep .sort-menu .mat-mdc-menu-item.sort-menu-item-active { + background-color: rgba(255, 255, 255, 0.08) !important; + } + + :host ::ng-deep .sort-menu button.mat-mdc-menu-item.sort-menu-item-active:hover, + :host ::ng-deep .sort-menu .mat-mdc-menu-item.sort-menu-item-active:hover { + background-color: rgba(255, 255, 255, 0.12) !important; + } + + :host ::ng-deep .sort-menu button.mat-mdc-menu-item.sort-menu-item-active:focus, + :host ::ng-deep .sort-menu .mat-mdc-menu-item.sort-menu-item-active:focus { + background-color: rgba(255, 255, 255, 0.08) !important; + } + + :host ::ng-deep .sort-menu button.mat-mdc-menu-item.sort-menu-item-active:focus-visible, + :host ::ng-deep .sort-menu .mat-mdc-menu-item.sort-menu-item-active:focus-visible { + background-color: rgba(255, 255, 255, 0.08) !important; } } 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 1e61c7232..bc8541711 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 @@ -253,7 +253,7 @@

{{ displayName }}