Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
17a5742
Add option to account button
Apr 30, 2026
3e3d1df
Permissions updated and advances in data display
May 4, 2026
d19ff28
Merge remote-tracking branch 'origin' into 5967-change-own-delegation…
May 5, 2026
b0d7c97
Removed current user from selectable, update user data, able to remov…
May 6, 2026
ca26d29
Added helptext
May 6, 2026
aec54ab
Angular 22
Jul 27, 2026
9ecebc0
Solve conflicts
Jul 27, 2026
cf544c5
Merge remote-tracking branch 'origin' into 5967-change-own-delegation…
Jul 27, 2026
ff7543b
Merge remote-tracking branch 'origin' into 5967-change-own-delegation…
Jul 28, 2026
77edf34
Fixed permissions and restablished dialog fields
Jul 28, 2026
6df3696
Merge remote-tracking branch 'origin' into 5967-change-own-delegation…
Jul 31, 2026
b88cec4
Merge remote-tracking branch 'origin' into 5967-change-own-delegation…
Aug 3, 2026
b815f63
Code cleanup and improved logic
Aug 3, 2026
e72488f
Merge remote-tracking branch 'origin' into 5967-change-own-delegation…
Aug 5, 2026
4f55913
Minor bugs fixed, refactor and cleanup
Aug 5, 2026
927b2a6
Solved merging conflicts
Aug 28, 2026
6e8a679
Merge remote-tracking branch 'origin' into 5967-change-own-delegation…
Aug 31, 2026
f5a6d8c
Fix vote delegation on new participants in new meeting
Aug 31, 2026
b894a46
Merge remote branch
Sep 24, 2026
5aeb4e9
Fix error on removing delegated vote
Sep 25, 2026
7e95fb7
Merge remote-tracking branch 'origin' into 5967-change-own-delegation…
Oct 7, 2026
c03ecf1
Fix minor issues
Oct 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,21 @@
@if (user && isLoggedIn) {
<div>
<!-- present toggle -->
@if (isAllowedSelfSetPresent && hasActiveMeeting) {
<button
mat-menu-item
role="menuitemcheckbox"
[aria-checked]="isPresent"
[ngClass]="{ active: isPresent }"
(click)="toggleOperatorPresence()"
>
<mat-icon class="menu-icon" [color]="isPresent ? 'accent' : ''">
{{ isPresent ? 'check_box' : 'check_box_outline_blank' }}
</mat-icon>
<span class="menu-text">{{ 'Present' | translate }}</span>
</button>
} @else if (hasActiveMeeting) {
@if (isPresent) {
@if (hasActiveMeeting) {
@if (isAllowedSelfSetPresent) {
<button
mat-menu-item
role="menuitemcheckbox"
[aria-checked]="isPresent"
[ngClass]="{ active: isPresent }"
(click)="toggleOperatorPresence()"
>
<mat-icon class="menu-icon" [color]="isPresent ? 'accent' : ''">
{{ isPresent ? 'check_box' : 'check_box_outline_blank' }}
</mat-icon>
<span class="menu-text">{{ 'Present' | translate }}</span>
</button>
} @else if (isPresent) {
<div mat-menu-item>
<mat-icon class="menu-icon">done</mat-icon>
<span class="menu-text">{{ 'Present' | translate }}</span>
Expand All @@ -90,6 +90,13 @@
<span class="menu-text">{{ 'Not present' | translate }}</span>
</div>
}

@if (isVoteDelegationEnabled && canEditOwnDelegation()) {
<button mat-menu-item (click)="openEditInfo(user)">
<mat-icon>edit</mat-icon>
<span>{{ 'Change your delegation' | translate }}</span>
</button>
}
}
</div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -71,13 +76,18 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit {

public username = ``;
public isLoggedIn = false;
private _voteDelegationEnabled = false;

public show1337 = -20;

private get activeMeetingId(): Id | null {
return this.activeMeetingIdService.meetingId;
}

protected get isVoteDelegationEnabled(): boolean {
return this._voteDelegationEnabled;
}

private _isAllowedSelfSetPresent = false;
private _languageTrigger: MatMenuTrigger | undefined = undefined;
private clickCounter = 0;
Expand All @@ -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();
Expand All @@ -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
}
Expand Down Expand Up @@ -226,4 +244,60 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit {
}
return this.user.short_name + ': ' + stringForUserPresent;
}

public canEditOwnDelegation(): boolean {
Comment thread
Elblinator marked this conversation as resolved.
return (
this.operator.hasPerms(Permission.userCanEditOwnDelegation) &&
this.activeMeeting.meeting.user_ids.includes(this.operator.operatorId)
);
}

public async openEditInfo(user: ViewUser): Promise<void> {
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();
}
}
}
});
}
}
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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`,
Expand All @@ -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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

private userDetailView;

public participantSubscriptionConfig = getParticipantMinimalSubscriptionConfig(this.activeMeetingId);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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))
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,14 +369,6 @@ <h1 class="mock-h2">{{ 'Participants' | translate }}</h1>
<mat-icon>record_voice_over</mat-icon>
<span>{{ 'Contributions' | translate }}</span>
</button>
<button
*osPerms="permission.userCanUpdate; or: canEditOwnDelegation(operator.user)"
mat-menu-item
(click)="openEditInfo(operator.user)"
>
<mat-icon>edit</mat-icon>
<span>{{ 'Change your delegation' | translate }}</span>
</button>
<button mat-menu-item (click)="pdfExportUserList()">
<mat-icon>picture_as_pdf</mat-icon>
<span>{{ 'List of participants (PDF)' | translate }}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -36,14 +35,6 @@ import { ParticipantSwitchDialogComponent } from '../participant-switch-dialog/p

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`,
Expand Down Expand Up @@ -350,7 +341,7 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent<ViewU
/**
* This function opens the dialog,
* where the user can quick change the groups,
* the gender and the participant number.
* the participant number and the delegations.
*
* @param user is an instance of ViewUser. This is the given user, who will be modified.
*/
Expand All @@ -371,21 +362,29 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent<ViewU
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!.default_group_id];
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 &&
areGroupsDiminished(this.operator.user.group_ids(), result.group_ids, this.activeMeeting)
this.infoDialog.areGroupsDiminished(
this.operator.user.group_ids(),
result.group_ids,
this.activeMeeting
)
) ||
(await this.prompt.open(this.selfGroupRemovalDialogTitle, this.selfGroupRemovalDialogContent))
(await this.prompt.open(selfGroupRemovalDialogTitle, selfGroupRemovalDialogContent))
) {
if (
this.operator.hasPerms(Permission.userCanEditOwnDelegation) &&
Expand Down Expand Up @@ -462,7 +461,7 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent<ViewU
user =>
!(
user.id === this.operator.operatorId &&
areGroupsDiminished(
this.infoDialog.areGroupsDiminished(
this.operator.user.group_ids(),
this.operator.user.group_ids().filter(id => !chosenGroupIds.includes(id)),
this.activeMeeting
Expand Down Expand Up @@ -636,8 +635,9 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent<ViewU

public canSeeItemMenu(): boolean {
return (
this.operator.hasPerms(Permission.userCanUpdate) ||
this.operator.hasPerms(Permission.userCanEditOwnDelegation)
this.operator.hasPermsInMeeting(this.activeMeetingId, ...[this.permission.userCanUpdate]) &&
(this.operator.hasPerms(Permission.userCanUpdate) ||
this.operator.hasPerms(Permission.userCanEditOwnDelegation))
);
}

Expand Down
Loading
Loading