diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts index a0350ee6b1..9aa0f69646 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts @@ -176,6 +176,8 @@ export class GfAccountDetailDialogComponent implements OnInit { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.initialize(); + + this.refreshUser(); }); } @@ -195,6 +197,8 @@ export class GfAccountDetailDialogComponent implements OnInit { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.initialize(); + + this.refreshUser(); }); } @@ -413,4 +417,11 @@ export class GfAccountDetailDialogComponent implements OnInit { this.fetchChart(); this.fetchPortfolioHoldings(); } + + private refreshUser() { + this.userService + .get(true) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(); + } } diff --git a/apps/client/src/app/pages/accounts/account-dialog-host/account-dialog-host.component.ts b/apps/client/src/app/pages/accounts/account-dialog-host/account-dialog-host.component.ts index 8cac553561..7d473a106c 100644 --- a/apps/client/src/app/pages/accounts/account-dialog-host/account-dialog-host.component.ts +++ b/apps/client/src/app/pages/accounts/account-dialog-host/account-dialog-host.component.ts @@ -24,8 +24,14 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Observable, of } from 'rxjs'; -import { map, switchMap } from 'rxjs/operators'; +import { Observable, of, Subject } from 'rxjs'; +import { + distinctUntilChanged, + map, + switchMap, + takeUntil, + tap +} from 'rxjs/operators'; import { GfCreateOrUpdateAccountDialogComponent } from '../create-or-update-account-dialog/create-or-update-account-dialog.component'; import { CreateOrUpdateAccountDialogParams } from '../create-or-update-account-dialog/interfaces/interfaces'; @@ -37,12 +43,16 @@ import { AccountDialogMode } from './types/account-dialog-mode.type'; template: '' }) export class GfAccountDialogHostComponent implements OnDestroy, OnInit { - private dialogRef: MatDialogRef; + private dialogRef: MatDialogRef< + GfAccountDetailDialogComponent | GfCreateOrUpdateAccountDialogComponent + >; private readonly deviceType = computed(() => { return this.deviceDetectorService.deviceInfo().deviceType; }); + private readonly dialogClosed = new Subject(); + private readonly dataService = inject(DataService); private readonly destroyRef = inject(DestroyRef); private readonly deviceDetectorService = inject(DeviceDetectorService); @@ -56,20 +66,31 @@ export class GfAccountDialogHostComponent implements OnDestroy, OnInit { public ngOnInit() { const mode = this.route.snapshot.data.mode as AccountDialogMode; - const accountId = this.route.snapshot.paramMap.get('accountId'); - const account$: Observable = - mode === 'update' && accountId - ? this.dataService.fetchAccount(accountId) - : of(undefined); - - this.userService - .get() + // The router reuses this component when only the account id changes, so + // the parameters are observed instead of read from the snapshot once + this.route.paramMap .pipe( - switchMap((user) => { - return account$.pipe( - map((account) => { - return { account, user }; + map((paramMap) => { + return paramMap.get('accountId'); + }), + distinctUntilChanged(), + tap(() => { + this.closeDialog(); + }), + switchMap((accountId) => { + const account$: Observable = + mode === 'update' && accountId + ? this.dataService.fetchAccount(accountId) + : of(undefined); + + return this.userService.get().pipe( + switchMap((user) => { + return account$.pipe( + map((account) => { + return { account, accountId, user }; + }) + ); }) ); }), @@ -79,15 +100,18 @@ export class GfAccountDialogHostComponent implements OnDestroy, OnInit { error: () => { this.navigateBack(); }, - next: ({ account, user }) => { + next: ({ account, accountId, user }) => { if (mode === 'detail') { - this.openAccountDetailDialog({ user }); + this.openAccountDetailDialog({ accountId, user }); return; } if (mode === 'update') { - if (!account) { + if ( + !account || + !hasPermission(user?.permissions, permissions.updateAccount) + ) { this.navigateBack(); return; @@ -151,15 +175,29 @@ export class GfAccountDialogHostComponent implements OnDestroy, OnInit { // be closed explicitly when leaving the route (for example via the browser // navigation) this.dialogRef?.close(); + + this.dialogClosed.complete(); + } + + private closeDialog() { + // Tear down the subscription of the dialog which is about to be replaced, + // so that its result is not mistaken for the user closing it + this.dialogClosed.next(); + + this.dialogRef?.close(); } private navigateBack() { void this.router.navigate(internalRoutes.accounts.routerLink); } - private openAccountDetailDialog({ user }: { user: User }) { - const accountId = this.route.snapshot.paramMap.get('accountId'); - + private openAccountDetailDialog({ + accountId, + user + }: { + accountId: string | null; + user: User; + }) { if (!accountId) { this.navigateBack(); @@ -191,18 +229,12 @@ export class GfAccountDialogHostComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntilDestroyed(this.destroyRef)) + .pipe(takeUntil(this.dialogClosed), takeUntilDestroyed(this.destroyRef)) .subscribe((result) => { if (result?.isNavigating) { return; } - // Deliberately not bound to the destroy reference: navigating back - // destroys this component and the refreshed user is what makes the - // accounts page reload its data, which may have been changed in the - // dialog (for example the cash balances) - this.userService.get(true).subscribe(); - this.navigateBack(); }); } @@ -233,7 +265,7 @@ export class GfAccountDialogHostComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntilDestroyed(this.destroyRef)) + .pipe(takeUntil(this.dialogClosed), takeUntilDestroyed(this.destroyRef)) .subscribe((result) => { if (!result) { this.navigateBack(); diff --git a/apps/client/src/app/pages/accounts/accounts-page.component.ts b/apps/client/src/app/pages/accounts/accounts-page.component.ts index 489d46b598..0f21ef55ef 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -48,6 +48,8 @@ export class GfAccountsPageComponent implements OnInit { protected totalValueInBaseCurrency = 0; protected user: User; + private isInitialFetch = true; + private readonly deviceType = computed( () => this.deviceDetectorService.deviceInfo().deviceType ); @@ -120,8 +122,6 @@ export class GfAccountsPageComponent implements OnInit { .get(true) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); - - this.fetchAccounts(); }); } @@ -147,12 +147,18 @@ export class GfAccountsPageComponent implements OnInit { this.totalBalanceInBaseCurrency = totalBalanceInBaseCurrency; this.totalValueInBaseCurrency = totalValueInBaseCurrency; - if (this.hasPermissionToCreateAccount && this.accounts?.length <= 0) { + if ( + this.accounts?.length <= 0 && + this.hasPermissionToCreateAccount && + this.isInitialFetch + ) { void this.router.navigate( internalRoutes.accounts.subRoutes.create.routerLink ); } + this.isInitialFetch = false; + this.changeDetectorRef.markForCheck(); } ); 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 56e4357082..b6e0d846c7 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.html +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -307,15 +307,17 @@ - - - - View Details... - - + @if (hasPermissionToOpenDetails()) { + + + + View Details... + + + }