Browse Source

Migrate account dialogs to dedicated routes

pull/7473/head
Thomas Kaul 1 month ago
parent
commit
bb49ab1447
  1. 11
      apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts
  2. 90
      apps/client/src/app/pages/accounts/account-dialog-host/account-dialog-host.component.ts
  3. 12
      apps/client/src/app/pages/accounts/accounts-page.component.ts
  4. 20
      libs/ui/src/lib/accounts-table/accounts-table.component.html
  5. 5
      libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts
  6. 3
      libs/ui/src/lib/assistant/assistant.component.ts

11
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();
}
}

90
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<unknown>;
private dialogRef: MatDialogRef<
GfAccountDetailDialogComponent | GfCreateOrUpdateAccountDialogComponent
>;
private readonly deviceType = computed(() => {
return this.deviceDetectorService.deviceInfo().deviceType;
});
private readonly dialogClosed = new Subject<void>();
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<AccountResponse | undefined> =
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<AccountResponse | undefined> =
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();

12
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();
}
);

20
libs/ui/src/lib/accounts-table/accounts-table.component.html

@ -307,15 +307,17 @@
<ion-icon name="ellipsis-horizontal" />
</button>
<mat-menu #accountMenu="matMenu" xPosition="before">
<a
mat-menu-item
[routerLink]="accountDialogRouterLinks().get(element.id)?.detail"
>
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="wallet-outline" />
<span><ng-container i18n>View Details</ng-container>...</span>
</span>
</a>
@if (hasPermissionToOpenDetails()) {
<a
mat-menu-item
[routerLink]="accountDialogRouterLinks().get(element.id)?.detail"
>
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="wallet-outline" />
<span><ng-container i18n>View Details</ng-container>...</span>
</span>
</a>
}
<a
mat-menu-item
[routerLink]="accountDialogRouterLinks().get(element.id)?.update"

5
libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts

@ -53,10 +53,7 @@ export class GfAssistantListItemComponent
public ngOnChanges() {
if (this.item?.mode === SearchMode.ACCOUNT) {
this.queryParams = {};
this.routerLink = internalRoutes.accounts.subRoutes.detail.routerLink(
this.item.id
);
this.routerLink = this.item.routerLink;
} else if (this.item?.mode === SearchMode.ASSET_PROFILE) {
this.queryParams = {
assetProfileDialog: true,

3
libs/ui/src/lib/assistant/assistant.component.ts

@ -612,7 +612,8 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
return {
id,
name,
routerLink: internalRoutes.accounts.routerLink,
routerLink:
internalRoutes.accounts.subRoutes.detail.routerLink(id),
mode: SearchMode.ACCOUNT as const
};
});

Loading…
Cancel
Save