From 17a5742d070a9a36d471e27d29c2701db39b43a9 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Thu, 30 Apr 2026 17:13:00 +0200 Subject: [PATCH 01/11] Add option to account button --- .../account-button.component.html | 4 ++ .../account-button.component.ts | 13 +++++ .../participant-list.component.html | 9 +--- ...articipant-list-info-dialog.component.html | 54 +++++-------------- .../participant-list-info-dialog.service.ts | 33 ++++++++++-- 5 files changed, 60 insertions(+), 53 deletions(-) diff --git a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.html b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.html index 378314685ac..f4a2f85409d 100644 --- a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.html +++ b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.html @@ -94,6 +94,10 @@ } } + } diff --git a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts index e6752513462..e0c2ed20d39 100644 --- a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts +++ b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts @@ -9,6 +9,7 @@ import { allAvailableTranslations, availableTranslations } from 'src/app/domain/ import { getOmlVerboseName } from 'src/app/domain/definitions/organization-permission'; import { largeDialogSettings } from 'src/app/infrastructure/utils/dialog-settings'; import { mediumDialogSettings } from 'src/app/infrastructure/utils/dialog-settings'; +import { ParticipantListInfoDialogService } from 'src/app/site/pages/meetings/pages/participants/pages/participant-list/modules/participant-list-info-dialog'; import { ActiveMeetingIdService } from 'src/app/site/pages/meetings/services/active-meeting-id.service'; import { MeetingSettingsService } from 'src/app/site/pages/meetings/services/meeting-settings.service'; import { ViewUser } from 'src/app/site/pages/meetings/view-models/view-user'; @@ -88,6 +89,7 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { private userRepo: UserControllerService, private authService: AuthService, private dialog: MatDialog, + private ParticipantListDialog: ParticipantListInfoDialogService, private router: Router, private theme: ThemeService, private meetingSettingsService: MeetingSettingsService, @@ -226,4 +228,15 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { } return stringForUserPresent.replace(`{}`, this.user.short_name); } + + public async openDialogEditInfo(user: ViewUser) { + await this.ParticipantListDialog.open({ + id: user.id, + name: user.getName(), + number: user.number(), + structure_level_ids: this.user.structure_level_ids(), + vote_delegations_from_ids: this.user.vote_delegations_from_meeting_user_ids(), + vote_delegated_to_id: this.user.vote_delegated_to_meeting_user_id() + }); + } } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html index 34658da893d..62eea91151b 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html @@ -341,14 +341,7 @@

Participants

record_voice_over Contributions - + + @if (activeMeetingSetting) { + + } } diff --git a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts index e0c2ed20d39..4f395cb916d 100644 --- a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts +++ b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts @@ -7,6 +7,7 @@ import { Observable, Subscription } from 'rxjs'; import { Id } from 'src/app/domain/definitions/key-types'; import { allAvailableTranslations, availableTranslations } from 'src/app/domain/definitions/languages'; import { getOmlVerboseName } from 'src/app/domain/definitions/organization-permission'; +import { Permission } from 'src/app/domain/definitions/permission'; import { largeDialogSettings } from 'src/app/infrastructure/utils/dialog-settings'; import { mediumDialogSettings } from 'src/app/infrastructure/utils/dialog-settings'; import { ParticipantListInfoDialogService } from 'src/app/site/pages/meetings/pages/participants/pages/participant-list/modules/participant-list-info-dialog'; @@ -71,6 +72,7 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { public username = ``; public isLoggedIn = false; + private _voteDelegationEnabled = false; public show1337 = -20; @@ -78,6 +80,13 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { return this.activeMeetingIdService.meetingId; } + public get activeMeetingSetting(): boolean { + this.meetingSettingsService + .get(`users_enable_vote_delegations`) + .subscribe(enabled => (this._voteDelegationEnabled = enabled)); + return this._voteDelegationEnabled; + } + private _isAllowedSelfSetPresent = false; private _languageTrigger: MatMenuTrigger | undefined = undefined; private clickCounter = 0; @@ -89,7 +98,7 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { private userRepo: UserControllerService, private authService: AuthService, private dialog: MatDialog, - private ParticipantListDialog: ParticipantListInfoDialogService, + private participantListDialog: ParticipantListInfoDialogService, private router: Router, private theme: ThemeService, private meetingSettingsService: MeetingSettingsService, @@ -229,8 +238,30 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { return stringForUserPresent.replace(`{}`, this.user.short_name); } - public async openDialogEditInfo(user: ViewUser) { - await this.ParticipantListDialog.open({ + public canEditOwnDelegation(user: ViewUser): boolean { + if ( + this.operator.hasPerms(Permission.userCanEditOwnDelegation) && + !this.operator.hasPerms(Permission.userCanManage) && + !this.operator.hasPerms(Permission.userCanUpdate) + ) { + return this.operator.operatorId === user.id; + } else if ( + this.operator.hasPerms(Permission.userCanManage) || + this.operator.hasPerms(Permission.userCanUpdate) + ) { + return true; + } else { + return false; + } + } + + public async openDialogEditInfo(user: ViewUser): Promise { + console.log( + this.user, + this.user.vote_delegations_from_meeting_user_ids(), + this.user.vote_delegated_to_meeting_user_id() + ); + await this.participantListDialog.open({ id: user.id, name: user.getName(), number: user.number(), @@ -238,5 +269,6 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { vote_delegations_from_ids: this.user.vote_delegations_from_meeting_user_ids(), vote_delegated_to_id: this.user.vote_delegated_to_meeting_user_id() }); + // *osPerms="permission.userCanUpdate; or: canEditOwnDelegation(user)" } } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/modules/participant-list-info-dialog/components/participant-list-info-dialog/participant-list-info-dialog.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/modules/participant-list-info-dialog/components/participant-list-info-dialog/participant-list-info-dialog.component.html index 99440774b88..6312addd51d 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/modules/participant-list-info-dialog/components/participant-list-info-dialog/participant-list-info-dialog.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/modules/participant-list-info-dialog/components/participant-list-info-dialog/participant-list-info-dialog.component.html @@ -17,7 +17,7 @@

{{ 'Voting right delegated to (proxy)' | translate }} ([]); private _currentUser: ViewUser | null = null; private _voteDelegationEnabled = false; + private participantSubscriptionConfig: SubscribeToConfig; + public userList = this.participantRepo.getViewModelListObservable(); + private modelRequest = inject(ModelRequestService); public constructor( @Inject(MAT_DIALOG_DATA) public readonly infoDialog: InfoDialog, @@ -60,25 +67,33 @@ export class ParticipantListInfoDialogComponent extends BaseUiComponent implemen private groupRepo: GroupControllerService, private structureLevelRepo: StructureLevelControllerService, private meetingSettings: MeetingSettingsService, - private operator: OperatorService + private activeMeetingIdService: ActiveMeetingIdService, + private operator: OperatorService, + protected userRepo: UserRepositoryService = inject(UserRepositoryService) ) { super(); } public ngOnInit(): void { this.userSortService.initSorting(); + this.participantSubscriptionConfig = getParticipantListSubscriptionConfig( + this.activeMeetingIdService.meetingId + ); + this.modelRequest.subscribeTo({ + ...this.participantSubscriptionConfig, + subscriptionName: 'participant_minimal_delegation_dialog:subscription' + }); + console.log(this.userRepo); this._currentUser = this.participantRepo.getViewModel(this.infoDialog.id); this.structureLevelObservable = this.structureLevelRepo.getViewModelListObservable(); this.subscriptions.push( - this.participantRepo - .getSortedViewModelListObservable(this.userSortService.repositorySortingKey) - .subscribe(participants => - this._otherParticipantsSubject.next( - participants - .filter(participant => participant.id !== this._currentUser.id) - .map(participant => participant.getMeetingUser()) - ) - ), + this.userList.subscribe(participants => + this._otherParticipantsSubject.next( + participants + .filter(participant => participant.id !== this._currentUser.id) + .map(participant => participant.getMeetingUser()) + ) + ), this.meetingSettings .get(`users_enable_vote_delegations`) .subscribe(enabled => (this._voteDelegationEnabled = enabled)) @@ -87,6 +102,7 @@ export class ParticipantListInfoDialogComponent extends BaseUiComponent implemen public override ngOnDestroy(): void { this.userSortService.exitSortService(); + this.modelRequest.closeSubscription('participant_minimal_delegation_dialog:subscription'); super.ngOnDestroy(); } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/modules/participant-list-info-dialog/services/participant-list-info-dialog.service.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/modules/participant-list-info-dialog/services/participant-list-info-dialog.service.ts index 9b6489e3760..b9d291a6067 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/modules/participant-list-info-dialog/services/participant-list-info-dialog.service.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/modules/participant-list-info-dialog/services/participant-list-info-dialog.service.ts @@ -69,14 +69,10 @@ export class ParticipantListInfoDialogService extends BaseDialogService< } }); const result = await firstValueFrom(dialogRef.afterClosed()); - console.log(data.id, result, result.id, data); - if (result) { if (data instanceof ViewUser) { - console.log('it is'); this.update(result, data); } else { - console.log('something else'); this.create(result); } } From b0d7c97bf8ea6ca77bdd79dee7a317ff50219621 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Wed, 6 May 2026 11:44:37 +0200 Subject: [PATCH 03/11] Removed current user from selectable, update user data, able to remove delegation --- .../account-button.component.html | 2 +- .../account-button.component.ts | 7 +--- .../participant-list.component.ts | 11 +---- ...articipant-list-info-dialog.component.html | 11 ++--- .../participant-list-info-dialog.component.ts | 40 +++++++------------ .../participant-list-info-dialog.service.ts | 20 +++------- 6 files changed, 30 insertions(+), 61 deletions(-) diff --git a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.html b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.html index 22b81d3140a..3668d5ec33c 100644 --- a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.html +++ b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.html @@ -94,7 +94,7 @@ } } - @if (activeMeetingSetting) { + @if (activeMeetingSetting && canEditOwnDelegation(user)) { diff --git a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts index dbe5e65ba5d..a9cbc03aec8 100644 --- a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts +++ b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts @@ -8,6 +8,12 @@ import { getOmlVerboseName } from '@app/domain/definitions/organization-permissi import { Permission } from '@app/domain/definitions/permission'; import { largeDialogSettings } from '@app/infrastructure/utils/dialog-settings'; import { mediumDialogSettings } from '@app/infrastructure/utils/dialog-settings'; +import { + afterDialogClosed, + ParticipantListInfoDialogService +} from '@app/site/pages/meetings/pages/participants/pages/participant-list/modules/participant-list-info-dialog'; +import { ParticipantControllerService } from '@app/site/pages/meetings/pages/participants/services/common/participant-controller.service'; +import { ActiveMeetingService } from '@app/site/pages/meetings/services/active-meeting.service'; import { ActiveMeetingIdService } from '@app/site/pages/meetings/services/active-meeting-id.service'; import { MeetingSettingsService } from '@app/site/pages/meetings/services/meeting-settings.service'; import { ViewUser } from '@app/site/pages/meetings/view-models/view-user'; @@ -16,6 +22,7 @@ import { OperatorService } from '@app/site/services/operator.service'; import { ThemeService } from '@app/site/services/theme.service'; import { UserControllerService } from '@app/site/services/user-controller.service'; import { BaseUiComponent } from '@app/ui/base/base-ui-component'; +import { PromptService } from '@app/ui/modules/prompt-dialog/services/prompt.service'; import { ChessDialogComponent } from '@app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/components/chess-dialog/chess-dialog.component'; import { ChessChallengeService } from '@app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/services/chess-challenge.service'; import { TranslateService } from '@ngx-translate/core'; @@ -95,12 +102,17 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { private translate = inject(TranslateService); private operator = inject(OperatorService); private userRepo = inject(UserControllerService); + private participantRepo = inject(ParticipantControllerService); private authService = inject(AuthService); private theme = inject(ThemeService); + private activeMeeting = inject(ActiveMeetingService); private meetingSettingsService = inject(MeetingSettingsService); private activeMeetingIdService = inject(ActiveMeetingIdService); private dialog = inject(MatDialog); private router = inject(Router); + protected participantListDialog = inject(ParticipantListInfoDialogService); + private infoDialog = inject(ParticipantListInfoDialogService); + private prompt = inject(PromptService); public constructor(chessChallengeService: ChessChallengeService) { super(); @@ -253,15 +265,23 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { } } - public async openDialogEditInfo(user: ViewUser): Promise { - await this.participantListDialog.open({ - user, + public async openEditInfo(user: ViewUser): Promise { + const dialogRef = await this.infoDialog.open({ id: user.id, name: user.getName(), number: user.number(), - structure_level_ids: this.user.structure_level_ids(), - vote_delegations_from_ids: this.user.vote_delegations_from_meeting_user_ids(), - vote_delegated_to_id: this.user.vote_delegated_to_meeting_user_id() + group_ids: user.group_ids(), + structure_level_ids: user.structure_level_ids(), + vote_delegations_from_ids: user.vote_delegations_from_meeting_user_ids(), + vote_delegated_to_id: user.vote_delegated_to_meeting_user_id() }); + afterDialogClosed( + dialogRef, + user, + this.operator, + this.activeMeeting.meeting, + this.participantRepo, + this.prompt + ); } } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.ts index 7134aa5711c..a55359ef7b6 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.ts @@ -10,7 +10,6 @@ import { mediumDialogSettings } from '@app/infrastructure/utils/dialog-settings' import { OsFilterOption } from '@app/site/base/base-filter.service'; import { BaseMeetingListViewComponent } from '@app/site/pages/meetings/base/base-meeting-list-view.component'; import { ParticipantControllerService } from '@app/site/pages/meetings/pages/participants/services/common/participant-controller.service/participant-controller.service'; -import { ViewMeeting } from '@app/site/pages/meetings/view-models/view-meeting'; import { ViewUser } from '@app/site/pages/meetings/view-models/view-user'; import { OrganizationSettingsService } from '@app/site/pages/organization/services/organization-settings.service'; import { OperatorService } from '@app/site/services/operator.service'; @@ -28,21 +27,17 @@ import { ParticipantPdfExportService } from '../../../../export/participant-pdf- import { GroupControllerService, ViewGroup } from '../../../../modules'; import { StructureLevelControllerService } from '../../../structure-levels/services/structure-level-controller.service'; import { ViewStructureLevel } from '../../../structure-levels/view-models'; -import { ParticipantListInfoDialogService } from '../../modules/participant-list-info-dialog'; +import { + afterDialogClosed, + areGroupsDiminished, + ParticipantListInfoDialogService +} from '../../modules/participant-list-info-dialog'; import { ParticipantListFilterService } from '../../services/participant-list-filter/participant-list-filter.service'; import { ParticipantListSortService } from '../../services/participant-list-sort/participant-list-sort.service'; import { ParticipantSwitchDialogComponent } from '../participant-switch-dialog/participant-switch-dialog.component'; const PARTICIPANTS_LIST_STORAGE_INDEX = `participants`; -export function areGroupsDiminished(oldGroupIds: number[], newGroupIds: number[], activeMeeting: ViewMeeting): boolean { - return ( - oldGroupIds - .filter(group => group !== activeMeeting.default_group_id) - .some(id => !(newGroupIds ?? []).includes(id)) && !newGroupIds.includes(activeMeeting.admin_group_id) - ); -} - @Component({ selector: `os-participant-list`, templateUrl: `./participant-list.component.html`, @@ -348,7 +343,8 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent { - if (result) { - if (!result.group_ids?.length) { - result.group_ids = [this.activeMeeting!.default_group_id]; - } - if (result.vote_delegated_to_id === 0) { - result.vote_delegated_to_id = null; - } - if ( - !( - user.id === this.operator.operatorId && - areGroupsDiminished(this.operator.user.group_ids(), result.group_ids, this.activeMeeting) - ) || - (await this.prompt.open(this.selfGroupRemovalDialogTitle, this.selfGroupRemovalDialogContent)) - ) { - if ( - this.operator.hasPerms(Permission.userCanEditOwnDelegation) && - !this.operator.hasPerms(Permission.userCanManage) && - !this.operator.hasPerms(Permission.userCanUpdate) && - user.id === this.operator.operatorId - ) { - this.repo.updateSelfDelegation(result, user); - } else { - this.repo.update(result, user).resolve(); - } - } - } + id: user.id, + name: user.short_name, + group_ids: user.group_ids(), + number: user.number(), + structure_level_ids: user.structure_level_ids(), + vote_delegations_from_ids: user.vote_delegations_from_meeting_user_ids(), + vote_delegated_to_id: user.vote_delegated_to_meeting_user_id() }); + afterDialogClosed(dialogRef, user, this.operator, this.activeMeeting, this.repo, this.prompt); } public getOtherUsersObservable(user: ViewUser): Observable { @@ -725,3 +699,4 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent {{ 'Edit your own delegation' | translate }}

- - {{ 'Voting right received from (principals)' | translate }} - - @if (setting?.helpText) { - {{ setting.helpText | translate }} - } - - - {{ 'Voting right delegated to (proxy)' | translate }} - - + @if (!canOnlyEditOwnDelegation) { + + {{ 'Groups' | translate }} + + @for (group of groupsObservable | async; track group) { + + {{ group.getTitle() }} + + } + + + } + @if (showVoteDelegations) { + + {{ 'Voting right received from (principals)' | translate }} + + + + {{ 'Voting right delegated to (proxy)' | translate }} + + + } + @if (!canOnlyEditOwnDelegation) { + + {{ 'Structure level' | translate }} + + + + {{ 'Participant number' | translate }} + + + } - } @else if (hasActiveMeeting) { - @if (isPresent) { + @if (hasActiveMeeting) { + @if (isAllowedSelfSetPresent && hasActiveMeeting) { + + } @else if (isPresent) {
done {{ 'Present' | translate }} @@ -93,13 +93,13 @@
} } + @if (isVoteDelegationEnabled && canEditOwnDelegation()) { + + } - @if (activeMeetingSetting && canEditOwnDelegation(user)) { - - } } diff --git a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts index a9cbc03aec8..1228f195903 100644 --- a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts +++ b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts @@ -10,6 +10,7 @@ import { largeDialogSettings } from '@app/infrastructure/utils/dialog-settings'; import { mediumDialogSettings } from '@app/infrastructure/utils/dialog-settings'; import { afterDialogClosed, + OperatorInfo, ParticipantListInfoDialogService } from '@app/site/pages/meetings/pages/participants/pages/participant-list/modules/participant-list-info-dialog'; import { ParticipantControllerService } from '@app/site/pages/meetings/pages/participants/services/common/participant-controller.service'; @@ -87,10 +88,7 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { return this.activeMeetingIdService.meetingId; } - public get activeMeetingSetting(): boolean { - this.meetingSettingsService - .get(`users_enable_vote_delegations`) - .subscribe(enabled => (this._voteDelegationEnabled = enabled)); + protected get isVoteDelegationEnabled(): boolean { return this._voteDelegationEnabled; } @@ -117,6 +115,9 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { public constructor(chessChallengeService: ChessChallengeService) { super(); chessChallengeService.startListening(); + this.meetingSettingsService + .get(`users_enable_vote_delegations`) + .subscribe(enabled => (this._voteDelegationEnabled = enabled)); } public ngOnInit(): void { @@ -245,16 +246,15 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { } else { stringForUserPresent = this.translate.instant(`Your account is not in this meeting.`); } - return stringForUserPresent.replace(`{}`, this.user.short_name); + return this.user.short_name + ': ' + stringForUserPresent; } - public canEditOwnDelegation(user: ViewUser): boolean { + public canEditOwnDelegation(): boolean { if ( this.operator.hasPerms(Permission.userCanEditOwnDelegation) && - !this.operator.hasPerms(Permission.userCanManage) && - !this.operator.hasPerms(Permission.userCanUpdate) + this.activeMeeting.meeting.user_ids.includes(this.operator.operatorId) ) { - return this.operator.operatorId === user.id; + return true; } else if ( this.operator.hasPerms(Permission.userCanManage) || this.operator.hasPerms(Permission.userCanUpdate) @@ -275,13 +275,13 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { vote_delegations_from_ids: user.vote_delegations_from_meeting_user_ids(), vote_delegated_to_id: user.vote_delegated_to_meeting_user_id() }); - afterDialogClosed( - dialogRef, - user, - this.operator, - this.activeMeeting.meeting, - this.participantRepo, - this.prompt - ); + const operatorInfo: OperatorInfo = { + operatorId: this.operator.operatorId, + operatorGroupIds: this.operator.user.group_ids(), + canManage: this.operator.hasPerms(Permission.userCanManage), + changeOwnDel: this.operator.hasPerms(Permission.userCanEditOwnDelegation), + canUpdate: this.operator.hasPerms(Permission.userCanUpdate) + }; + afterDialogClosed(dialogRef, user, operatorInfo, this.activeMeeting.meeting, this.participantRepo, this.prompt); } } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-edit/participant-detail-edit.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-edit/participant-detail-edit.component.ts index ca1342aaa9a..f58d512d73c 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-edit/participant-detail-edit.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-edit/participant-detail-edit.component.ts @@ -28,7 +28,7 @@ import { getParticipantMinimalSubscriptionConfig, PARTICIPANT_DETAIL_SUBSCRIPTION } from '../../../../participants.subscription'; -import { areGroupsDiminished } from '../../../participant-list/components/participant-list/participant-list.component'; +import { areGroupsDiminished } from '../../../participant-list/modules/participant-list-info-dialog'; import { ParticipantListSortService } from '../../../participant-list/services/participant-list-sort/participant-list-sort.service'; import { StructureLevelControllerService } from '../../../structure-levels/services/structure-level-controller.service'; import { ViewStructureLevel } from '../../../structure-levels/view-models'; diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html index eb8abfaf2c0..bc3296db9ca 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html @@ -259,6 +259,7 @@

Participants

isHidden: !canSeeItemMenu() && viewport.isMobileSubject " class="cell-slot" + [hidden]="!hasOptions()" > - + + @if (isVoteDelegationEnabled && canEditOwnDelegation()) { + + } } } diff --git a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts index 1228f195903..652b2dcd9d2 100644 --- a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts +++ b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts @@ -8,11 +8,7 @@ import { getOmlVerboseName } from '@app/domain/definitions/organization-permissi import { Permission } from '@app/domain/definitions/permission'; import { largeDialogSettings } from '@app/infrastructure/utils/dialog-settings'; import { mediumDialogSettings } from '@app/infrastructure/utils/dialog-settings'; -import { - afterDialogClosed, - OperatorInfo, - ParticipantListInfoDialogService -} from '@app/site/pages/meetings/pages/participants/pages/participant-list/modules/participant-list-info-dialog'; +import { ParticipantListInfoDialogService } from '@app/site/pages/meetings/pages/participants/pages/participant-list/modules/participant-list-info-dialog'; import { ParticipantControllerService } from '@app/site/pages/meetings/pages/participants/services/common/participant-controller.service'; import { ActiveMeetingService } from '@app/site/pages/meetings/services/active-meeting.service'; import { ActiveMeetingIdService } from '@app/site/pages/meetings/services/active-meeting-id.service'; @@ -26,7 +22,7 @@ import { BaseUiComponent } from '@app/ui/base/base-ui-component'; import { PromptService } from '@app/ui/modules/prompt-dialog/services/prompt.service'; import { ChessDialogComponent } from '@app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/components/chess-dialog/chess-dialog.component'; import { ChessChallengeService } from '@app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/services/chess-challenge.service'; -import { TranslateService } from '@ngx-translate/core'; +import { _, TranslateService } from '@ngx-translate/core'; import { Observable, Subscription } from 'rxjs'; import { AccountDialogMainComponent } from '../account-dialog-main/account-dialog-main.component'; @@ -250,19 +246,10 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { } public canEditOwnDelegation(): boolean { - if ( - this.operator.hasPerms(Permission.userCanEditOwnDelegation) && + return this.operator.hasPerms(Permission.userCanEditOwnDelegation) && this.activeMeeting.meeting.user_ids.includes(this.operator.operatorId) - ) { - return true; - } else if ( - this.operator.hasPerms(Permission.userCanManage) || - this.operator.hasPerms(Permission.userCanUpdate) - ) { - return true; - } else { - return false; - } + ? true + : false; } public async openEditInfo(user: ViewUser): Promise { @@ -272,16 +259,45 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { number: user.number(), group_ids: user.group_ids(), structure_level_ids: user.structure_level_ids(), - vote_delegations_from_ids: user.vote_delegations_from_meeting_user_ids(), + vote_delegations_from_ids: user.vote_delegations_from_meeting_user_ids() || [], vote_delegated_to_id: user.vote_delegated_to_meeting_user_id() }); - const operatorInfo: OperatorInfo = { - operatorId: this.operator.operatorId, - operatorGroupIds: this.operator.user.group_ids(), - canManage: this.operator.hasPerms(Permission.userCanManage), - changeOwnDel: this.operator.hasPerms(Permission.userCanEditOwnDelegation), - canUpdate: this.operator.hasPerms(Permission.userCanUpdate) - }; - afterDialogClosed(dialogRef, user, operatorInfo, this.activeMeeting.meeting, this.participantRepo, this.prompt); + const selfGroupRemovalDialogTitle = _(`This action will remove you from one or more groups.`); + const selfGroupRemovalDialogContent = _( + `This may diminish your ability to do things in this meeting and you may not be able to revert it by yourself. Are you sure you want to do this?` + ); + + dialogRef.afterClosed().subscribe(async result => { + if (result) { + if (!result.group_ids?.length) { + result.group_ids = [this.activeMeeting.meeting!.default_group_id]; + } + if (!this.participantRepo.getViewModel(result.vote_delegated_to_id)) { + result.vote_delegated_to_id = null; + } + if ( + !( + user.id === this.operator.operatorId && + this.infoDialog.areGroupsDiminished( + this.operator.user.group_ids(), + result.group_ids, + this.activeMeeting.meeting + ) + ) || + (await this.prompt.open(selfGroupRemovalDialogTitle, selfGroupRemovalDialogContent)) + ) { + if ( + this.operator.hasPerms(Permission.userCanEditOwnDelegation) && + !this.operator.hasPerms(Permission.userCanManage) && + !this.operator.hasPerms(Permission.userCanUpdate) && + user.id === this.operator.operatorId + ) { + this.participantRepo.updateSelfDelegation(result, user); + } else { + this.participantRepo.update(result, user).resolve(); + } + } + } + }); } } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-edit/participant-detail-edit.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-edit/participant-detail-edit.component.ts index f58d512d73c..e85d6a028de 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-edit/participant-detail-edit.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/components/participant-detail-edit/participant-detail-edit.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit, ViewChild } from '@angular/core'; import { Validators } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { Id } from '@app/domain/definitions/key-types'; @@ -18,6 +18,7 @@ import { CommitteeControllerService } from '@app/site/pages/organization/pages/c import { OrganizationSettingsService } from '@app/site/pages/organization/services/organization-settings.service'; import { OperatorService } from '@app/site/services/operator.service'; import { UserService } from '@app/site/services/user.service'; +import { UserControllerService } from '@app/site/services/user-controller.service'; import { PromptService } from '@app/ui/modules/prompt-dialog'; import { _ } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core'; @@ -28,10 +29,11 @@ import { getParticipantMinimalSubscriptionConfig, PARTICIPANT_DETAIL_SUBSCRIPTION } from '../../../../participants.subscription'; -import { areGroupsDiminished } from '../../../participant-list/modules/participant-list-info-dialog'; +import { ParticipantListInfoDialogService } from '../../../participant-list/modules/participant-list-info-dialog/services'; import { ParticipantListSortService } from '../../../participant-list/services/participant-list-sort/participant-list-sort.service'; import { StructureLevelControllerService } from '../../../structure-levels/services/structure-level-controller.service'; import { ViewStructureLevel } from '../../../structure-levels/view-models'; + @Component({ selector: `os-participant-detail-edit`, templateUrl: `./participant-detail-edit.component.html`, @@ -41,6 +43,10 @@ import { ViewStructureLevel } from '../../../structure-levels/view-models'; }) export class ParticipantDetailEditComponent extends BaseMeetingComponent implements OnInit { @ViewChild(UserDetailViewComponent) + private infoDialog = inject(ParticipantListInfoDialogService); + + private userRepo = inject(UserControllerService); + private userDetailView; public participantSubscriptionConfig = getParticipantMinimalSubscriptionConfig(this.activeMeetingId); @@ -299,7 +305,7 @@ export class ParticipantDetailEditComponent extends BaseMeetingComponent impleme public updateByValueChange(event: any): void { this.personalInfoFormValue = event; - if (this.userDetailView.personalInfoForm.get(`locked_out`).disabled !== this.lockoutCheckboxDisabled) { + if (this.userDetailView.personalInfoForm?.get(`locked_out`).disabled !== this.lockoutCheckboxDisabled) { if (this.lockoutCheckboxDisabled) { this.userDetailView.personalInfoForm.get(`locked_out`).disable(); } else { @@ -334,12 +340,16 @@ export class ParticipantDetailEditComponent extends BaseMeetingComponent impleme } const title = _(`This action will remove you from one or more groups.`); const content = _( - `This may diminish your ability to do things in this meeting and you may not be able to revert it by youself. Are you sure you want to do this?` + `This may diminish your ability to do things in this meeting and you may not be able to revert it by yourself. Are you sure you want to do this?` ); if ( !( this.user.id === this.operator.operatorId && - areGroupsDiminished(this.operator.user.group_ids(), payload.group_ids, this.activeMeeting) + this.infoDialog.areGroupsDiminished( + this.operator.user.group_ids(), + payload.group_ids, + this.activeMeeting + ) ) || (await this.promptService.open(title, content)) ) { diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html index bc3296db9ca..c82adeffc4f 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.html @@ -259,7 +259,6 @@

Participants

isHidden: !canSeeItemMenu() && viewport.isMobileSubject " class="cell-slot" - [hidden]="!hasOptions()" >