Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,38 @@ <h2 class="mat-h2 table-name">{{ displayName }}</h2>
</ng-container>

<ng-container [matColumnDef]="column" *ngFor="let column of tableData.displayedDataColumns">
<mat-header-cell *matHeaderCellDef mat-sort-header [disabled]="!isSortable(column)"> {{ tableData.dataNormalizedColumns[column] }} </mat-header-cell>
<mat-header-cell *matHeaderCellDef class="sort-header-cell" [class.sort-header-active]="sort && sort.active === column">
<div class="sort-header-content">
<span>{{ tableData.dataNormalizedColumns[column] }}</span>
<button *ngIf="isSortable(column)"
mat-icon-button
type="button"
class="sort-header-button"
[matMenuTriggerFor]="columnSortMenu"
(click)="$event.stopPropagation()"
[matTooltip]="getSortTooltip(column)">
<mat-icon [class.sort-icon-sync]="!(sort && sort.active === column)">{{ getSortIcon(column) }}</mat-icon>
</button>
<mat-menu #columnSortMenu="matMenu" class="sort-menu">
<button mat-menu-item
[class.sort-menu-item-active]="sort && sort.active === column && sort.direction === 'asc'"
(click)="applySort(column, 'asc')">
<div class="sort-menu-item-content">
<span>Sort ascending (A-Z)</span>
</div>
<mat-icon *ngIf="sort && sort.active === column && sort.direction === 'asc'" class="sort-menu-item-check">close</mat-icon>
</button>
<button mat-menu-item
[class.sort-menu-item-active]="sort && sort.active === column && sort.direction === 'desc'"
(click)="applySort(column, 'desc')">
<div class="sort-menu-item-content">
<span>Sort descending (Z-A)</span>
</div>
<mat-icon *ngIf="sort && sort.active === column && sort.direction === 'desc'" class="sort-menu-item-check">close</mat-icon>
</button>
</mat-menu>
</div>
</mat-header-cell>
<mat-cell *matCellDef="let element; let i = index" [attr.data-label]="tableData.dataNormalizedColumns[column]" class="db-table-cell" data-hj-suppress>
<div class="table-cell-content">
<ng-container *ngIf="isForeignKey(column); else contentCell">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Loading