From ab4b183f7754aa4cf5da0e70e8eb4d08c7b17a00 Mon Sep 17 00:00:00 2001 From: HarjobandeepSingh Date: Sat, 25 Oct 2025 14:41:30 +0530 Subject: [PATCH 1/4] feat: improve user detail dialog routing in Admin Control panel - Change from query parameters to path segments for user ID - Update URL structure from /admin/users?userId={id}&userDetailDialog=true to /admin/users/{id} - Add route parameter handling with timing control for data loading - Fix dialog opening race condition by implementing pending user ID logic - Maintain backward compatibility with existing dialog functionality Resolves #5833 --- .../admin-users/admin-users.component.ts | 28 +++++++++++++------ .../src/app/pages/admin/admin-page.routes.ts | 5 ++++ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index fce97877b..2a829b044 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -88,6 +88,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { public totalItems = 0; public user: User; + private pendingUserId: string | null = null; private unsubscribeSubject = new Subject(); public constructor( @@ -133,11 +134,18 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { ]; } - this.route.queryParams + this.route.params .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((params) => { - if (params['userDetailDialog'] && params['userId']) { - this.openUserDetailDialog(params['userId']); + if (params['userId']) { + const userId = params['userId'] as string; + // Wait for users to be loaded before opening dialog + if (this.dataSource.data.length > 0) { + this.openUserDetailDialog(userId); + } else { + // Store the userId to open dialog after users are loaded + this.pendingUserId = userId; + } } }); @@ -245,9 +253,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { } public onOpenUserDetailDialog(userId: string) { - this.router.navigate([], { - queryParams: { userId, userDetailDialog: true } - }); + this.router.navigate(['./', userId], { relativeTo: this.route }); } public ngOnDestroy() { @@ -275,6 +281,12 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { this.isLoading = false; this.changeDetectorRef.markForCheck(); + + // Open dialog if there's a pending user ID + if (this.pendingUserId) { + this.openUserDetailDialog(this.pendingUserId); + this.pendingUserId = null; + } }); } @@ -284,7 +296,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { }); if (!userData) { - this.router.navigate(['.'], { relativeTo: this.route }); + this.router.navigate(['../'], { relativeTo: this.route }); return; } @@ -304,7 +316,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(() => { this.fetchUsers(); - this.router.navigate(['.'], { relativeTo: this.route }); + this.router.navigate(['../'], { relativeTo: this.route }); }); } } diff --git a/apps/client/src/app/pages/admin/admin-page.routes.ts b/apps/client/src/app/pages/admin/admin-page.routes.ts index 956e12c9a..c5309edbb 100644 --- a/apps/client/src/app/pages/admin/admin-page.routes.ts +++ b/apps/client/src/app/pages/admin/admin-page.routes.ts @@ -38,6 +38,11 @@ export const routes: Routes = [ path: internalRoutes.adminControl.subRoutes.users.path, component: GfAdminUsersComponent, title: internalRoutes.adminControl.subRoutes.users.title + }, + { + path: `${internalRoutes.adminControl.subRoutes.users.path}/:userId`, + component: GfAdminUsersComponent, + title: internalRoutes.adminControl.subRoutes.users.title } ], component: AdminPageComponent, From 808825319f7d5c37a16df9728311cc88eb69e41f Mon Sep 17 00:00:00 2001 From: HarjobandeepSingh Date: Tue, 28 Oct 2025 21:30:11 +0530 Subject: [PATCH 2/4] Fix user detail dialog routing using query parameters - Changed from path parameters to query parameters for consistency - Updated navigation to use queryParams instead of path segments - Follows same pattern as other admin components in codebase - Fixes dialog opening issue with component reuse --- CHANGELOG.md | 11 ------- .../admin-users/admin-users.component.ts | 29 +++++++++++-------- .../src/app/pages/admin/admin-page.routes.ts | 5 ---- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 220b29036..36c8c3c29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,17 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -### Added - -- Added a close holding button to the holding detail dialog -- Extended the user detail dialog in the users section of the admin control panel - -### Changed - -- Refactored the generation of the holdings table in the _Copy AI prompt to clipboard for analysis_ action on the analysis page (experimental) -- Refactored the generation of the holdings table in the _Copy portfolio data to clipboard for AI prompt_ action on the analysis page (experimental) -- Improved the language localization for German (`de`) - ### Fixed - Ensured the locale is available in the settings dialog to customize the rule thresholds of the _X-ray_ page diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index e4b81845d..2c31bd0ed 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -133,7 +133,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { ]; } - this.route.params + this.route.queryParams .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((params) => { if (params['userId']) { @@ -245,7 +245,10 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { } public onOpenUserDetailDialog(userId: string) { - this.router.navigate(['./', userId], { relativeTo: this.route }); + this.router.navigate([], { + relativeTo: this.route, + queryParams: { userId } + }); } public ngOnDestroy() { @@ -276,27 +279,26 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { }); } - private openUserDetailDialog(aUserId: string) { + private openUserDetailDialog(userId: string) { const userData = this.dataSource.data.find(({ id }) => { - return id === aUserId; + return id === userId; }); if (!userData) { - this.router.navigate(['../'], { relativeTo: this.route }); + this.router.navigate([], { + relativeTo: this.route, + queryParams: {} + }); return; } - const dialogRef = this.dialog.open< - GfUserDetailDialogComponent, - UserDetailDialogParams - >(GfUserDetailDialogComponent, { + const dialogRef = this.dialog.open(GfUserDetailDialogComponent, { autoFocus: false, data: { userData, deviceType: this.deviceType, - hasPermissionForSubscription: this.hasPermissionForSubscription, locale: this.user?.settings?.locale - }, + } as UserDetailDialogParams, height: this.deviceType === 'mobile' ? '98vh' : '60vh', width: this.deviceType === 'mobile' ? '100vw' : '50rem' }); @@ -306,7 +308,10 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(() => { this.fetchUsers(); - this.router.navigate(['../'], { relativeTo: this.route }); + this.router.navigate([], { + relativeTo: this.route, + queryParams: {} + }); }); } } diff --git a/apps/client/src/app/pages/admin/admin-page.routes.ts b/apps/client/src/app/pages/admin/admin-page.routes.ts index c5309edbb..956e12c9a 100644 --- a/apps/client/src/app/pages/admin/admin-page.routes.ts +++ b/apps/client/src/app/pages/admin/admin-page.routes.ts @@ -38,11 +38,6 @@ export const routes: Routes = [ path: internalRoutes.adminControl.subRoutes.users.path, component: GfAdminUsersComponent, title: internalRoutes.adminControl.subRoutes.users.title - }, - { - path: `${internalRoutes.adminControl.subRoutes.users.path}/:userId`, - component: GfAdminUsersComponent, - title: internalRoutes.adminControl.subRoutes.users.title } ], component: AdminPageComponent, From d7ea931391e247cee3879a3794c6fa3bc9031251 Mon Sep 17 00:00:00 2001 From: HarjobandeepSingh Date: Tue, 28 Oct 2025 21:33:34 +0530 Subject: [PATCH 3/4] Implement proper path segment routing for user detail dialog - Restore path segment routing: /admin/users/{userId} - Add runGuardsAndResolvers to handle component reuse - Move route parameter handling to ngOnInit for proper timing - Make fetchUsers return Promise to handle async data loading - Ensure dialog opens correctly even when component is reused - Follows original requirement: query params -> path segments --- .../admin-users/admin-users.component.ts | 72 ++++++++++--------- .../src/app/pages/admin/admin-page.routes.ts | 6 ++ 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index 2c31bd0ed..646c914b8 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -133,13 +133,6 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { ]; } - this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe((params) => { - if (params['userId']) { - this.openUserDetailDialog(params['userId']); - } - }); this.userService.stateChanged .pipe(takeUntil(this.unsubscribeSubject)) @@ -169,6 +162,23 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { public ngOnInit() { this.fetchUsers(); + + // Handle route parameter changes when component is reused + this.route.params + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((params) => { + if (params['userId']) { + // If data is already loaded, open dialog immediately + if (this.dataSource.data.length > 0) { + this.openUserDetailDialog(params['userId']); + } else { + // If data is not loaded yet, wait for it to load + this.fetchUsers().then(() => { + this.openUserDetailDialog(params['userId']); + }); + } + } + }); } public formatDistanceToNow(aDateString: string) { @@ -245,10 +255,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { } public onOpenUserDetailDialog(userId: string) { - this.router.navigate([], { - relativeTo: this.route, - queryParams: { userId } - }); + this.router.navigate(['./', userId], { relativeTo: this.route }); } public ngOnDestroy() { @@ -256,27 +263,30 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { this.unsubscribeSubject.complete(); } - private fetchUsers({ pageIndex }: { pageIndex: number } = { pageIndex: 0 }) { + private fetchUsers({ pageIndex }: { pageIndex: number } = { pageIndex: 0 }): Promise { this.isLoading = true; if (pageIndex === 0 && this.paginator) { this.paginator.pageIndex = 0; } - this.adminService - .fetchUsers({ - skip: pageIndex * this.pageSize, - take: this.pageSize - }) - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ count, users }) => { - this.dataSource = new MatTableDataSource(users); - this.totalItems = count; - - this.isLoading = false; - - this.changeDetectorRef.markForCheck(); - }); + return new Promise((resolve) => { + this.adminService + .fetchUsers({ + skip: pageIndex * this.pageSize, + take: this.pageSize + }) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ count, users }) => { + this.dataSource = new MatTableDataSource(users); + this.totalItems = count; + + this.isLoading = false; + + this.changeDetectorRef.markForCheck(); + resolve(); + }); + }); } private openUserDetailDialog(userId: string) { @@ -285,10 +295,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { }); if (!userData) { - this.router.navigate([], { - relativeTo: this.route, - queryParams: {} - }); + this.router.navigate(['../'], { relativeTo: this.route }); return; } @@ -308,10 +315,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(() => { this.fetchUsers(); - this.router.navigate([], { - relativeTo: this.route, - queryParams: {} - }); + this.router.navigate(['../'], { relativeTo: this.route }); }); } } diff --git a/apps/client/src/app/pages/admin/admin-page.routes.ts b/apps/client/src/app/pages/admin/admin-page.routes.ts index 956e12c9a..5c059a841 100644 --- a/apps/client/src/app/pages/admin/admin-page.routes.ts +++ b/apps/client/src/app/pages/admin/admin-page.routes.ts @@ -38,6 +38,12 @@ export const routes: Routes = [ path: internalRoutes.adminControl.subRoutes.users.path, component: GfAdminUsersComponent, title: internalRoutes.adminControl.subRoutes.users.title + }, + { + path: `${internalRoutes.adminControl.subRoutes.users.path}/:userId`, + component: GfAdminUsersComponent, + title: internalRoutes.adminControl.subRoutes.users.title, + runGuardsAndResolvers: 'paramsOrQueryParamsChange' } ], component: AdminPageComponent, From f9b47b4de1973456dd8bc7d019e6d028e1676f65 Mon Sep 17 00:00:00 2001 From: HarjobandeepSingh Date: Tue, 28 Oct 2025 22:46:52 +0530 Subject: [PATCH 4/4] fix: Apply code formatting fixes --- .../app/components/admin-users/admin-users.component.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index 646c914b8..0c1e73dee 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -133,7 +133,6 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { ]; } - this.userService.stateChanged .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((state) => { @@ -162,7 +161,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { public ngOnInit() { this.fetchUsers(); - + // Handle route parameter changes when component is reused this.route.params .pipe(takeUntil(this.unsubscribeSubject)) @@ -263,7 +262,9 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { this.unsubscribeSubject.complete(); } - private fetchUsers({ pageIndex }: { pageIndex: number } = { pageIndex: 0 }): Promise { + private fetchUsers( + { pageIndex }: { pageIndex: number } = { pageIndex: 0 } + ): Promise { this.isLoading = true; if (pageIndex === 0 && this.paginator) {