From 427338911ae597f2f79701b079d89594b755cc16 Mon Sep 17 00:00:00 2001 From: mister-ideas <36502095+mister-ideas@users.noreply.github.com> Date: Mon, 19 Oct 2026 03:34:06 +0200 Subject: [PATCH 1/3] Feature/add sorting to holdings table of home overview --- CHANGELOG.md | 1 + .../ui/src/lib/holdings-table/holdings-table.component.html | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e35621f0b..a4989f0cd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the holdings table of the home overview by adding the sorting by name and performance - Improved the usability of the activities page by eliminating the reload on canceling the import activities dialog - Improved the accessibility of the _Buy me a coffee_ button on the about page - Improved the accessibility of the entity logo component diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.html b/libs/ui/src/lib/holdings-table/holdings-table.component.html index 025be8a723..5d586d8911 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.html +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -210,11 +210,7 @@ - + Date: Mon, 19 Oct 2026 13:27:35 +0200 Subject: [PATCH 2/3] Feature/sort the holdings table by performance in descending order by default --- CHANGELOG.md | 1 + .../lib/holdings-table/holdings-table.component.html | 4 ++-- .../lib/holdings-table/holdings-table.component.ts | 11 ++--------- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4989f0cd1..01202969ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Improved the holdings table of the home overview by adding the sorting by name and performance +- Changed the default sorting of the holdings table to the performance in descending order - Improved the usability of the activities page by eliminating the reload on canceling the import activities dialog - Improved the accessibility of the _Buy me a coffee_ button on the about page - Improved the accessibility of the entity logo component diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.html b/libs/ui/src/lib/holdings-table/holdings-table.component.html index 5d586d8911..efd6f38763 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.html +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -5,8 +5,8 @@ matSort [class.simple]="mode() === 'simple'" [dataSource]="dataSource" - [matSortActive]="sortActive()" - [matSortDirection]="sortDirection()" + [matSortActive]="sortActive" + [matSortDirection]="sortDirection" > diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.ts b/libs/ui/src/lib/holdings-table/holdings-table.component.ts index 3181cc1347..70ce2cb1e9 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.ts +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.ts @@ -101,15 +101,8 @@ export class GfHoldingsTableComponent { return !this.holdings(); }); - protected readonly sortActive = computed(() => { - return this.mode() === 'default' - ? 'allocationInPercentage' - : 'assetProfile.name'; - }); - - protected readonly sortDirection = computed(() => { - return this.mode() === 'default' ? 'desc' : 'asc'; - }); + protected readonly sortActive = 'netPerformancePercentWithCurrencyEffect'; + protected readonly sortDirection: SortDirection = 'desc'; public constructor() { this.dataSource.sortingDataAccessor = (holding, path) => { From 223b65e0eb60d4c6f8d37684e1602c5a62721994 Mon Sep 17 00:00:00 2001 From: mister-ideas <36502095+mister-ideas@users.noreply.github.com> Date: Mon, 19 Oct 2026 13:48:12 +0200 Subject: [PATCH 3/3] Feature/persist the sorting of the holdings and accounts tables on the device --- CHANGELOG.md | 1 + .../accounts-table.component.html | 1 + .../accounts-table.component.ts | 2 + .../holdings-table.component.html | 1 + .../holdings-table.component.ts | 2 + libs/ui/src/lib/persist-sort/index.ts | 1 + .../persist-sort.directive.spec.ts | 70 +++++++++++++++++++ .../persist-sort/persist-sort.directive.ts | 57 +++++++++++++++ 8 files changed, 135 insertions(+) create mode 100644 libs/ui/src/lib/persist-sort/index.ts create mode 100644 libs/ui/src/lib/persist-sort/persist-sort.directive.spec.ts create mode 100644 libs/ui/src/lib/persist-sort/persist-sort.directive.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 01202969ae..c4e75fe19e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the holdings table of the home overview by adding the sorting by name and performance - Changed the default sorting of the holdings table to the performance in descending order +- Persisted the sorting of the holdings and accounts tables on the device - Improved the usability of the activities page by eliminating the reload on canceling the import activities dialog - Improved the accessibility of the _Buy me a coffee_ button on the about page - Improved the accessibility of the entity logo component diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.html b/libs/ui/src/lib/accounts-table/accounts-table.component.html index 43a6a429b4..00b544bf4e 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.html +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -20,6 +20,7 @@ matSortActive="name" matSortDirection="asc" [dataSource]="dataSource" + [gfPersistSort]="'accounts'" > diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.ts b/libs/ui/src/lib/holdings-table/holdings-table.component.ts index 70ce2cb1e9..d6131c1f07 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.ts +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.ts @@ -27,6 +27,7 @@ import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { GfEntityLogoComponent } from '../entity-logo/entity-logo.component'; +import { GfPersistSortDirective } from '../persist-sort/persist-sort.directive'; import { GfValueComponent } from '../value/value.component'; import { HoldingsTableItem } from './interfaces/interfaces'; @@ -34,6 +35,7 @@ import { HoldingsTableItem } from './interfaces/interfaces'; changeDetection: ChangeDetectionStrategy.OnPush, imports: [ GfEntityLogoComponent, + GfPersistSortDirective, GfValueComponent, MatButtonModule, MatDialogModule, diff --git a/libs/ui/src/lib/persist-sort/index.ts b/libs/ui/src/lib/persist-sort/index.ts new file mode 100644 index 0000000000..22d5db3391 --- /dev/null +++ b/libs/ui/src/lib/persist-sort/index.ts @@ -0,0 +1 @@ +export * from './persist-sort.directive'; diff --git a/libs/ui/src/lib/persist-sort/persist-sort.directive.spec.ts b/libs/ui/src/lib/persist-sort/persist-sort.directive.spec.ts new file mode 100644 index 0000000000..7fa7a7f811 --- /dev/null +++ b/libs/ui/src/lib/persist-sort/persist-sort.directive.spec.ts @@ -0,0 +1,70 @@ +import { Component } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatSort, MatSortModule } from '@angular/material/sort'; + +import { GfPersistSortDirective } from './persist-sort.directive'; + +@Component({ + imports: [GfPersistSortDirective, MatSortModule], + template: `
` +}) +class TestHostComponent {} + +describe('GfPersistSortDirective', () => { + let fixture: ComponentFixture; + + const create = () => { + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + + return fixture.debugElement.children[0].injector.get(MatSort); + }; + + beforeEach(() => { + localStorage.clear(); + TestBed.configureTestingModule({ imports: [TestHostComponent] }); + }); + + it('keeps the default sorting without a stored value', () => { + const sort = create(); + + expect(sort.active).toBe('name'); + expect(sort.direction).toBe('asc'); + }); + + it('stores the sorting on change', () => { + const sort = create(); + + sort.sort({ id: 'value', start: 'desc', disableClear: false }); + + expect(JSON.parse(localStorage.getItem('gf-sort-test') ?? '{}')).toEqual({ + active: 'value', + direction: 'desc' + }); + }); + + it('restores a stored sorting', () => { + localStorage.setItem( + 'gf-sort-test', + JSON.stringify({ active: 'value', direction: 'desc' }) + ); + + const sort = create(); + + expect(sort.active).toBe('value'); + expect(sort.direction).toBe('desc'); + }); + + it('ignores an invalid stored sorting', () => { + localStorage.setItem('gf-sort-test', '{"active":"value","direction":"x"}'); + + const sort = create(); + + expect(sort.active).toBe('name'); + }); +}); diff --git a/libs/ui/src/lib/persist-sort/persist-sort.directive.ts b/libs/ui/src/lib/persist-sort/persist-sort.directive.ts new file mode 100644 index 0000000000..d7e70f81ae --- /dev/null +++ b/libs/ui/src/lib/persist-sort/persist-sort.directive.ts @@ -0,0 +1,57 @@ +import { DestroyRef, Directive, inject, input, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { MatSort, SortDirection } from '@angular/material/sort'; + +const STORAGE_KEY_PREFIX = 'gf-sort-'; + +/** + * Remembers the sorting of a `matSort` table on the device and restores it + * on the next visit. + */ +@Directive({ + selector: '[gfPersistSort]' +}) +export class GfPersistSortDirective implements OnInit { + public readonly gfPersistSort = input.required(); + + private readonly destroyRef = inject(DestroyRef); + private readonly matSort = inject(MatSort, { self: true }); + + public ngOnInit() { + const storageKey = `${STORAGE_KEY_PREFIX}${this.gfPersistSort()}`; + const storedSort = this.readStoredSort(storageKey); + + if (storedSort) { + this.matSort.active = storedSort.active; + this.matSort.direction = storedSort.direction; + } + + this.matSort.sortChange + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(({ active, direction }) => { + try { + globalThis.localStorage?.setItem( + storageKey, + JSON.stringify({ active, direction }) + ); + } catch {} + }); + } + + private readStoredSort(aStorageKey: string) { + try { + const item = globalThis.localStorage?.getItem(aStorageKey); + const { active, direction } = JSON.parse(item ?? 'null') ?? {}; + + if ( + typeof active === 'string' && + active.length > 0 && + (direction === 'asc' || direction === 'desc') + ) { + return { active, direction: direction as SortDirection }; + } + } catch {} + + return undefined; + } +}