From bb9ccd894d20bc875f76ee15b77e52316d2dbd25 Mon Sep 17 00:00:00 2001 From: eduardojvr10 Date: Thu, 18 Jun 2026 09:27:49 -0300 Subject: [PATCH 1/3] Correct implementation to prevent surge substitution --- .../user-create-view-modify.component.ts | 79 +++++++++++-------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/src/app/features/user/pages/user-create-view-modify/user-create-view-modify.component.ts b/src/app/features/user/pages/user-create-view-modify/user-create-view-modify.component.ts index 77a87de07..ad7791344 100644 --- a/src/app/features/user/pages/user-create-view-modify/user-create-view-modify.component.ts +++ b/src/app/features/user/pages/user-create-view-modify/user-create-view-modify.component.ts @@ -29,6 +29,7 @@ import { ToastV2Service } from '../../../../core/services/helper/toast-v2.servic import { I18nService } from '../../../../core/services/helper/i18n.service'; import { V2SideDialogConfigInputType } from '../../../../shared/components-v2/app-side-dialog-v2/models/side-dialog-config.model'; import { LocalizationHelper } from '../../../../core/helperClasses/localization-helper'; +import { IV2BottomDialogConfigButtonType } from '../../../../shared/components-v2/app-bottom-dialog-v2/models/bottom-dialog-config.model'; /** * Component @@ -40,6 +41,7 @@ import { LocalizationHelper } from '../../../../core/helperClasses/localization- export class UserCreateViewModifyComponent extends CreateViewModifyComponent implements OnDestroy { // data private _passwordConfirm: string; + private _originalOutbreakIds: string[] = []; /** * Constructor @@ -96,7 +98,9 @@ export class UserCreateViewModifyComponent extends CreateViewModifyComponent 'LNG_USER_FIELD_LABEL_AVAILABLE_OUTBREAKS_DESCRIPTION', value: { get: () => this.itemData.outbreakIds, - set: (value) => { - // set data + set: (value: string[]) => { this.itemData.outbreakIds = value; - - // update visible active outbreaks (tab.nameToInput.activeOutbreakId as ICreateViewModifyV2TabInputSingleSelect).options = this.getAllowedActiveOutbreaks(); } }, @@ -558,35 +559,49 @@ export class UserCreateViewModifyComponent extends CreateViewModifyComponent { - // show error - finished(err, undefined); - - // finished - return throwError(err); - }) - ).subscribe((outbreak) => { - // display message - this.toastV2Service.success( + // check if any previously saved outbreaks are being removed + const removedOutbreaks = this._originalOutbreakIds.filter( + (id) => !(data.outbreakIds ?? []).includes(id) + ); + + const doSave = () => { + // create / modify + ( type === CreateViewModifyV2ActionType.CREATE ? - 'LNG_PAGE_CREATE_USER_ACTION_CREATE_USER_SUCCESS_MESSAGE' : - 'LNG_PAGE_MODIFY_USER_ACTION_MODIFY_USER_SUCCESS_MESSAGE' - ); + this.userDataService.createUser(data) : + this.userDataService.modifyUser(this.itemData.id, data) + ).pipe( + catchError((err) => { + finished(err, undefined); + return throwError(err); + }) + ).subscribe((outbreak) => { + this.toastV2Service.success( + type === CreateViewModifyV2ActionType.CREATE ? + 'LNG_PAGE_CREATE_USER_ACTION_CREATE_USER_SUCCESS_MESSAGE' : + 'LNG_PAGE_MODIFY_USER_ACTION_MODIFY_USER_SUCCESS_MESSAGE' + ); + finished(undefined, outbreak); + }); + }; - // hide loading & redirect - finished(undefined, outbreak); - }); + if (type === CreateViewModifyV2ActionType.UPDATE && removedOutbreaks.length) { + this.dialogV2Service.showConfirmDialog({ + config: { + title: { get: () => 'LNG_USER_FIELD_LABEL_REMOVE_OUTBREAK_CONFIRM_TITLE' }, + message: { get: () => 'LNG_USER_FIELD_LABEL_REMOVE_OUTBREAK_CONFIRM' } + } + }).subscribe((response) => { + if (response.button.type === IV2BottomDialogConfigButtonType.CANCEL) { + finished(undefined, undefined); + return; + } + doSave(); + }); + return; + } + + doSave(); }; } From a6d586d9902888e202c4076d180cd5e487ab119d Mon Sep 17 00:00:00 2001 From: eduardojvr10 Date: Tue, 23 Jun 2026 13:45:21 -0300 Subject: [PATCH 2/3] =?UTF-8?q?Ajustes=20para=20n=C3=A3o=20sobrescrever=20?= =?UTF-8?q?outbreaks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/services/data/outbreak.data.service.ts | 16 ++++++++++++++++ .../services/resolvers/data/outbreak.resolver.ts | 15 ++++++++++++--- src/app/features/user/user.module.routing.ts | 8 ++++++-- .../app-form-select-multiple-v2.component.html | 4 ++-- .../app-form-select-multiple-v2.component.ts | 4 ++++ 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/app/core/services/data/outbreak.data.service.ts b/src/app/core/services/data/outbreak.data.service.ts index 36ea20598..bd58c008f 100644 --- a/src/app/core/services/data/outbreak.data.service.ts +++ b/src/app/core/services/data/outbreak.data.service.ts @@ -72,6 +72,22 @@ export class OutbreakDataService { ); } + /** + * Retrieve outbreaks (minimal fields) for user management screens, regardless of + * the current user's outbreak access restrictions. Gated server-side by the + * 'user_list' permission. Used to resolve names of outbreaks assigned to other + * users that are outside the current user's own access scope. + */ + getOutbreaksListForUserManagement( + queryBuilder: RequestQueryBuilder = new RequestQueryBuilder() + ): Observable { + const filter = queryBuilder.buildQuery(); + return this.modelHelper.mapObservableListToModel( + this.http.get(`outbreaks/for-user-management?filter=${filter}`), + OutbreakModel + ); + } + /** * Retrieve the list of Outbreaks * @returns {Observable} diff --git a/src/app/core/services/resolvers/data/outbreak.resolver.ts b/src/app/core/services/resolvers/data/outbreak.resolver.ts index c99ebdbe2..7fbdcd8db 100644 --- a/src/app/core/services/resolvers/data/outbreak.resolver.ts +++ b/src/app/core/services/resolvers/data/outbreak.resolver.ts @@ -24,8 +24,16 @@ export class OutbreakDataResolver implements IMapResolverV2 { * Retrieve data */ resolve(route: ActivatedRouteSnapshot): Observable> { + // user management screens need to resolve the names of outbreaks assigned to + // other users even when they are outside the current user's access scope; + // this is gated server-side by the 'user_list' permission + const loadAllForUserManagement = !!route.data?.outbreakLoadAllForUserManagement; + // user doesn't have rights ? - if (!OutbreakModel.canList(this.authDataService.getAuthenticatedUser())) { + if ( + !loadAllForUserManagement && + !OutbreakModel.canList(this.authDataService.getAuthenticatedUser()) + ) { return of({ list: [], map: {}, @@ -51,8 +59,9 @@ export class OutbreakDataResolver implements IMapResolverV2 { .by('name', RequestSortDirection.ASC); // retrieve records - return this.outbreakDataService - .getOutbreaksList(qb) + return (loadAllForUserManagement ? + this.outbreakDataService.getOutbreaksListForUserManagement(qb) : + this.outbreakDataService.getOutbreaksList(qb)) .pipe( map((data) => { // construct map diff --git a/src/app/features/user/user.module.routing.ts b/src/app/features/user/user.module.routing.ts index af1165041..3211fdd68 100644 --- a/src/app/features/user/user.module.routing.ts +++ b/src/app/features/user/user.module.routing.ts @@ -45,7 +45,10 @@ const routes: Routes = [ permissions: [ PERMISSION.USER_LIST ], - outbreakIncludeDeleted: true + outbreakIncludeDeleted: true, + // resolve names of outbreaks assigned to other users even when outside the + // current user's access scope (gated server-side by 'user_list' permission) + outbreakLoadAllForUserManagement: true }, resolve: { createdOn: CreatedOnResolver, @@ -83,7 +86,8 @@ const routes: Routes = [ PERMISSION.USER_VIEW ], action: CreateViewModifyV2Action.VIEW, - outbreakIncludeDeleted: true + outbreakIncludeDeleted: true, + outbreakLoadAllForUserManagement: true } }, // Edit user diff --git a/src/app/shared/forms-v2/components/app-form-select-multiple-v2/app-form-select-multiple-v2.component.html b/src/app/shared/forms-v2/components/app-form-select-multiple-v2/app-form-select-multiple-v2.component.html index c1aa2c013..5097c96ee 100644 --- a/src/app/shared/forms-v2/components/app-form-select-multiple-v2/app-form-select-multiple-v2.component.html +++ b/src/app/shared/forms-v2/components/app-form-select-multiple-v2/app-form-select-multiple-v2.component.html @@ -57,7 +57,7 @@ fxLayout="row wrap" class="gd-form-select-multiple-v2-panel-inner"> + *ngFor="let selectedValue of validSelectedValues; let selectedIndex = index"> + *ngFor="let selectedValue of validSelectedValues; let selectedIndex = index"> !!this.allOptionsMap[v]); + } + // allow disabled options to be selected ? @Input() allowDisabledToBeSelected: boolean = false; From 724b43acf966cadc562e10d1119e031434722c31 Mon Sep 17 00:00:00 2001 From: eduardojvr10 Date: Tue, 23 Jun 2026 13:55:17 -0300 Subject: [PATCH 3/3] Remove modal --- .../outbreak-list/outbreak-list.component.ts | 98 +++++++------------ 1 file changed, 37 insertions(+), 61 deletions(-) diff --git a/src/app/features/outbreak/pages/outbreak-list/outbreak-list.component.ts b/src/app/features/outbreak/pages/outbreak-list/outbreak-list.component.ts index 85fe66a44..2b6a91d3c 100644 --- a/src/app/features/outbreak/pages/outbreak-list/outbreak-list.component.ts +++ b/src/app/features/outbreak/pages/outbreak-list/outbreak-list.component.ts @@ -141,68 +141,44 @@ export class OutbreakListComponent extends ListComponent { - // show confirm dialog - this.dialogV2Service.showConfirmDialog({ - config: { - title: { - get: () => 'LNG_COMMON_LABEL_ACTIVE', - data: () => ({ - name: item.name - }) - }, - message: { - get: () => 'LNG_DIALOG_CONFIRM_MAKE_OUTBREAK_ACTIVE', - data: () => ({ - name: item.name - }) + // show loading + const loading = this.dialogV2Service.showLoadingDialog(); + + // modify outbreak + this.userDataService + .modifyUser( + this.authUser.id, + { + activeOutbreakId: item.id } - } - }).subscribe((response) => { - // canceled ? - if (response.button.type === IV2BottomDialogConfigButtonType.CANCEL) { - // finished - return; - } - - // show loading - const loading = this.dialogV2Service.showLoadingDialog(); - - // modify outbreak - this.userDataService - .modifyUser( - this.authUser.id, - { - activeOutbreakId: item.id - } - ) - .pipe( - catchError((err) => { - this.toastV2Service.error(err); - return throwError(err); - }) - ) - .subscribe(() => { - // reload user data to save the new active outbreak - this.authDataService - .reloadAndPersistAuthUser() - .subscribe((authenticatedUser) => { - this.authUser = authenticatedUser.user; - this.outbreakDataService.checkActiveSelectedOutbreak(); - - // refresh list of top nav outbreaks - TopnavComponent.REFRESH_OUTBREAK_LIST(); - - // success - this.toastV2Service.success('LNG_PAGE_LIST_OUTBREAKS_ACTION_SET_ACTIVE_SUCCESS_MESSAGE'); - - // hide loading - loading.close(); - - // reload data - this.needsRefreshList(true); - }); - }); - }); + ) + .pipe( + catchError((err) => { + this.toastV2Service.error(err); + return throwError(err); + }) + ) + .subscribe(() => { + // reload user data to save the new active outbreak + this.authDataService + .reloadAndPersistAuthUser() + .subscribe((authenticatedUser) => { + this.authUser = authenticatedUser.user; + this.outbreakDataService.checkActiveSelectedOutbreak(); + + // refresh list of top nav outbreaks + TopnavComponent.REFRESH_OUTBREAK_LIST(); + + // success + this.toastV2Service.success('LNG_PAGE_LIST_OUTBREAKS_ACTION_SET_ACTIVE_SUCCESS_MESSAGE'); + + // hide loading + loading.close(); + + // reload data + this.needsRefreshList(true); + }); + }); } }, cssClasses: (item: OutbreakModel): string => {