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 2732bd3c29..46b5f737db 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 @@ -60,21 +60,21 @@ @if (user && isLoggedIn) {
- @if (isAllowedSelfSetPresent && hasActiveMeeting) { - - } @else if (hasActiveMeeting) { - @if (isPresent) { + @if (hasActiveMeeting) { + @if (isAllowedSelfSetPresent) { + + } @else if (isPresent) {
done {{ 'Present' | translate }} @@ -90,6 +90,13 @@ {{ 'Not present' | translate }}
} + + @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 026710e578..d1bc7384ff 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 @@ -5,8 +5,12 @@ import { Router } from '@angular/router'; import { Id } from '@app/domain/definitions/key-types'; import { allAvailableTranslations, availableTranslations } from '@app/domain/definitions/languages'; import { getOmlVerboseName } from '@app/domain/definitions/organization-permission'; +import { Permission } from '@app/domain/definitions/permission'; import { largeDialogSettings } from '@app/infrastructure/utils/dialog-settings'; import { mediumDialogSettings } from '@app/infrastructure/utils/dialog-settings'; +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'; import { MeetingSettingsService } from '@app/site/pages/meetings/services/meeting-settings.service'; import { ViewUser } from '@app/site/pages/meetings/view-models/view-user'; @@ -15,9 +19,10 @@ 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'; +import { _, TranslateService } from '@ngx-translate/core'; import { Observable, Subscription } from 'rxjs'; import { AccountDialogMainComponent } from '../account-dialog-main/account-dialog-main.component'; @@ -71,6 +76,7 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { public username = ``; public isLoggedIn = false; + private _voteDelegationEnabled = false; public show1337 = -20; @@ -78,6 +84,10 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { return this.activeMeetingIdService.meetingId; } + protected get isVoteDelegationEnabled(): boolean { + return this._voteDelegationEnabled; + } + private _isAllowedSelfSetPresent = false; private _languageTrigger: MatMenuTrigger | undefined = undefined; private clickCounter = 0; @@ -86,12 +96,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(); @@ -104,6 +119,9 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { this.meetingSettingsService .get(`users_allow_self_set_present`) .subscribe(allowed => (this._isAllowedSelfSetPresent = allowed)); + this.meetingSettingsService + .get(`users_enable_vote_delegations`) + .subscribe(enabled => (this._voteDelegationEnabled = enabled)); this.onOperatorUpdate(); // initially trigger the update manually to set initial values } @@ -226,4 +244,60 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { } return this.user.short_name + ': ' + stringForUserPresent; } + + public canEditOwnDelegation(): boolean { + return ( + this.operator.hasPerms(Permission.userCanEditOwnDelegation) && + this.activeMeeting.meeting.user_ids.includes(this.operator.operatorId) + ); + } + + public async openEditInfo(user: ViewUser): Promise { + const dialogRef = await this.infoDialog.open({ + id: user.id, + name: user.getName(), + 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_delegated_to_id: user.vote_delegated_to_meeting_user_id() + }); + 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 (result.vote_delegated_to_id === 0) { + 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 989762264e..a4c504d075 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'; @@ -28,10 +28,11 @@ import { getParticipantMinimalSubscriptionConfig, PARTICIPANT_DETAIL_SUBSCRIPTION } from '../../../../participants.subscription'; -import { areGroupsDiminished } from '../../../participant-list/components/participant-list/participant-list.component'; +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/view-structure-level'; + @Component({ selector: `os-participant-detail-edit`, templateUrl: `./participant-detail-edit.component.html`, @@ -41,6 +42,8 @@ import { ViewStructureLevel } from '../../../structure-levels/view-models/view-s }) export class ParticipantDetailEditComponent extends BaseMeetingComponent implements OnInit { @ViewChild(UserDetailViewComponent) + private infoDialog = inject(ParticipantListInfoDialogService); + private userDetailView; public participantSubscriptionConfig = getParticipantMinimalSubscriptionConfig(this.activeMeetingId); @@ -299,7 +302,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 +337,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 135d80a5f7..67372064d4 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 @@ -369,14 +369,6 @@

{{ 'Participants' | translate }}

record_voice_over {{ 'Contributions' | translate }} -