Vote delegations on client
Add "your vote was delegated" error Observe operator alterations during vote Add "canVoteFor" getter Adjust poll progress bars
This commit is contained in:
parent
3ac8569712
commit
8c28b03ffc
@ -7,8 +7,8 @@ import { HttpService } from 'app/core/core-services/http.service';
|
|||||||
import { RelationManagerService } from 'app/core/core-services/relation-manager.service';
|
import { RelationManagerService } from 'app/core/core-services/relation-manager.service';
|
||||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||||
import { RelationDefinition } from 'app/core/definitions/relations';
|
import { RelationDefinition } from 'app/core/definitions/relations';
|
||||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
|
||||||
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
||||||
|
import { UserVote } from 'app/shared/models/poll/base-vote';
|
||||||
import { ViewAssignment } from 'app/site/assignments/models/view-assignment';
|
import { ViewAssignment } from 'app/site/assignments/models/view-assignment';
|
||||||
import { ViewAssignmentOption } from 'app/site/assignments/models/view-assignment-option';
|
import { ViewAssignmentOption } from 'app/site/assignments/models/view-assignment-option';
|
||||||
import { AssignmentPollTitleInformation, ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
import { AssignmentPollTitleInformation, ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
||||||
@ -97,7 +97,6 @@ export class AssignmentPollRepositoryService extends BasePollRepositoryService<
|
|||||||
viewModelStoreService: ViewModelStoreService,
|
viewModelStoreService: ViewModelStoreService,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
relationManager: RelationManagerService,
|
relationManager: RelationManagerService,
|
||||||
votingService: VotingService,
|
|
||||||
http: HttpService
|
http: HttpService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
@ -110,7 +109,6 @@ export class AssignmentPollRepositoryService extends BasePollRepositoryService<
|
|||||||
AssignmentPoll,
|
AssignmentPoll,
|
||||||
AssignmentPollRelations,
|
AssignmentPollRelations,
|
||||||
{},
|
{},
|
||||||
votingService,
|
|
||||||
http
|
http
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -123,14 +121,11 @@ export class AssignmentPollRepositoryService extends BasePollRepositoryService<
|
|||||||
return this.translate.instant(plural ? 'Polls' : 'Poll');
|
return this.translate.instant(plural ? 'Polls' : 'Poll');
|
||||||
};
|
};
|
||||||
|
|
||||||
public vote(data: VotingData, poll_id: number): Promise<void> {
|
public vote(data: VotingData, poll_id: number, userId?: number): Promise<void> {
|
||||||
let requestData;
|
const requestData: UserVote = {
|
||||||
if (data.global) {
|
data: data.global ?? data.votes,
|
||||||
requestData = `"${data.global}"`;
|
user_id: userId ?? undefined
|
||||||
} else {
|
};
|
||||||
requestData = data.votes;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.http.post(`/rest/assignments/assignment-poll/${poll_id}/vote/`, requestData);
|
return this.http.post(`/rest/assignments/assignment-poll/${poll_id}/vote/`, requestData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,8 @@ import { HttpService } from 'app/core/core-services/http.service';
|
|||||||
import { RelationManagerService } from 'app/core/core-services/relation-manager.service';
|
import { RelationManagerService } from 'app/core/core-services/relation-manager.service';
|
||||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||||
import { RelationDefinition } from 'app/core/definitions/relations';
|
import { RelationDefinition } from 'app/core/definitions/relations';
|
||||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
|
||||||
import { MotionPoll } from 'app/shared/models/motions/motion-poll';
|
import { MotionPoll } from 'app/shared/models/motions/motion-poll';
|
||||||
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
import { UserVote, VoteValue } from 'app/shared/models/poll/base-vote';
|
||||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||||
import { ViewMotionOption } from 'app/site/motions/models/view-motion-option';
|
import { ViewMotionOption } from 'app/site/motions/models/view-motion-option';
|
||||||
import { MotionPollTitleInformation, ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
import { MotionPollTitleInformation, ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||||
@ -66,7 +65,6 @@ export class MotionPollRepositoryService extends BasePollRepositoryService<
|
|||||||
viewModelStoreService: ViewModelStoreService,
|
viewModelStoreService: ViewModelStoreService,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
relationManager: RelationManagerService,
|
relationManager: RelationManagerService,
|
||||||
votingService: VotingService,
|
|
||||||
http: HttpService
|
http: HttpService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
@ -79,7 +77,6 @@ export class MotionPollRepositoryService extends BasePollRepositoryService<
|
|||||||
MotionPoll,
|
MotionPoll,
|
||||||
MotionPollRelations,
|
MotionPollRelations,
|
||||||
{},
|
{},
|
||||||
votingService,
|
|
||||||
http
|
http
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -92,7 +89,11 @@ export class MotionPollRepositoryService extends BasePollRepositoryService<
|
|||||||
return this.translate.instant(plural ? 'Polls' : 'Poll');
|
return this.translate.instant(plural ? 'Polls' : 'Poll');
|
||||||
};
|
};
|
||||||
|
|
||||||
public vote(vote: VoteValue, poll_id: number): Promise<void> {
|
public vote(vote: VoteValue, poll_id: number, userId?: number): Promise<void> {
|
||||||
return this.http.post(`/rest/motions/motion-poll/${poll_id}/vote/`, JSON.stringify(vote));
|
const requestData: UserVote = {
|
||||||
|
data: vote,
|
||||||
|
user_id: userId ?? undefined
|
||||||
|
};
|
||||||
|
return this.http.post(`/rest/motions/motion-poll/${poll_id}/vote/`, requestData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,18 @@ const UserRelations: RelationDefinition[] = [
|
|||||||
ownIdKey: 'groups_id',
|
ownIdKey: 'groups_id',
|
||||||
ownKey: 'groups',
|
ownKey: 'groups',
|
||||||
foreignViewModel: ViewGroup
|
foreignViewModel: ViewGroup
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'M2O',
|
||||||
|
ownIdKey: 'vote_delegated_to_id',
|
||||||
|
ownKey: 'voteDelegatedTo',
|
||||||
|
foreignViewModel: ViewUser
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'M2M',
|
||||||
|
ownIdKey: 'vote_delegated_from_users_id',
|
||||||
|
ownKey: 'voteDelegationsFrom',
|
||||||
|
foreignViewModel: ViewUser
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -255,6 +267,8 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User, UserTi
|
|||||||
|
|
||||||
public async update(update: Partial<User>, viewModel: ViewUser): Promise<void> {
|
public async update(update: Partial<User>, viewModel: ViewUser): Promise<void> {
|
||||||
this.preventAlterationOnDemoUsers(viewModel);
|
this.preventAlterationOnDemoUsers(viewModel);
|
||||||
|
console.log('update: ', update);
|
||||||
|
|
||||||
return super.update(update, viewModel);
|
return super.update(update, viewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
|
||||||
import { PollState, PollType } from 'app/shared/models/poll/base-poll';
|
import { PollState, PollType } from 'app/shared/models/poll/base-poll';
|
||||||
import { ViewBasePoll } from 'app/site/polls/models/view-base-poll';
|
import { ViewBasePoll } from 'app/site/polls/models/view-base-poll';
|
||||||
|
import { ViewUser } from 'app/site/users/models/view-user';
|
||||||
import { OperatorService } from '../core-services/operator.service';
|
import { OperatorService } from '../core-services/operator.service';
|
||||||
|
|
||||||
export enum VotingError {
|
export enum VotingError {
|
||||||
@ -9,19 +12,21 @@ export enum VotingError {
|
|||||||
POLL_WRONG_TYPE,
|
POLL_WRONG_TYPE,
|
||||||
USER_HAS_NO_PERMISSION,
|
USER_HAS_NO_PERMISSION,
|
||||||
USER_IS_ANONYMOUS,
|
USER_IS_ANONYMOUS,
|
||||||
USER_NOT_PRESENT
|
USER_NOT_PRESENT,
|
||||||
|
USER_HAS_DELEGATED_RIGHT
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: It appears that the only message that makes sense for the user to see it the last one.
|
* TODO: It appears that the only message that makes sense for the user to see it the last one.
|
||||||
*/
|
*/
|
||||||
export const VotingErrorVerbose = {
|
const VotingErrorVerbose = {
|
||||||
1: "You can't vote on this poll right now because it's not in the 'Started' state.",
|
1: _("You can not vote on this poll right now because it is not in the 'Started' state."),
|
||||||
2: "You can't vote on this poll because its type is set to analog voting.",
|
2: _('You can not vote on this poll because its type is set to analog voting.'),
|
||||||
3: "You don't have permission to vote on this poll.",
|
3: _('You do not not have the permission to vote on this poll.'),
|
||||||
4: 'You have to be logged in to be able to vote.',
|
4: _('You have to be logged in to be able to vote.'),
|
||||||
5: 'You have to be present to vote on a poll.',
|
5: _('You have to be present to vote on a poll.'),
|
||||||
6: "You have already voted on this poll. You can't change your vote in a pseudoanonymous poll."
|
6: _('Your right to vote was delegated to another user.'),
|
||||||
|
7: _('You have already voted on this poll. You can not change your vote in a pseudoanonymous poll.')
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@ -33,8 +38,8 @@ export class VotingService {
|
|||||||
/**
|
/**
|
||||||
* checks whether the operator can vote on the given poll
|
* checks whether the operator can vote on the given poll
|
||||||
*/
|
*/
|
||||||
public canVote(poll: ViewBasePoll): boolean {
|
public canVote(poll: ViewBasePoll, user?: ViewUser): boolean {
|
||||||
const error = this.getVotePermissionError(poll);
|
const error = this.getVotePermissionError(poll, user);
|
||||||
return !error;
|
return !error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,12 +47,11 @@ export class VotingService {
|
|||||||
* checks whether the operator can vote on the given poll
|
* checks whether the operator can vote on the given poll
|
||||||
* @returns null if no errors exist (= user can vote) or else a VotingError
|
* @returns null if no errors exist (= user can vote) or else a VotingError
|
||||||
*/
|
*/
|
||||||
public getVotePermissionError(poll: ViewBasePoll): VotingError | void {
|
public getVotePermissionError(poll: ViewBasePoll, user: ViewUser = this.operator.viewUser): VotingError | void {
|
||||||
if (this.operator.isAnonymous) {
|
if (this.operator.isAnonymous) {
|
||||||
return VotingError.USER_IS_ANONYMOUS;
|
return VotingError.USER_IS_ANONYMOUS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = this.operator.user;
|
|
||||||
if (!poll.groups_id.intersect(user.groups_id).length) {
|
if (!poll.groups_id.intersect(user.groups_id).length) {
|
||||||
return VotingError.USER_HAS_NO_PERMISSION;
|
return VotingError.USER_HAS_NO_PERMISSION;
|
||||||
}
|
}
|
||||||
@ -57,13 +61,16 @@ export class VotingService {
|
|||||||
if (poll.state !== PollState.Started) {
|
if (poll.state !== PollState.Started) {
|
||||||
return VotingError.POLL_WRONG_STATE;
|
return VotingError.POLL_WRONG_STATE;
|
||||||
}
|
}
|
||||||
if (!user.is_present) {
|
if (!user.is_present && !this.operator.viewUser.canVoteFor(user)) {
|
||||||
return VotingError.USER_NOT_PRESENT;
|
return VotingError.USER_NOT_PRESENT;
|
||||||
}
|
}
|
||||||
|
if (user.isVoteRightDelegated && this.operator.user.id === user.id) {
|
||||||
|
return VotingError.USER_HAS_DELEGATED_RIGHT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getVotePermissionErrorVerbose(poll: ViewBasePoll): string | void {
|
public getVotePermissionErrorVerbose(poll: ViewBasePoll, user: ViewUser = this.operator.viewUser): string | void {
|
||||||
const error = this.getVotePermissionError(poll);
|
const error = this.getVotePermissionError(poll, user);
|
||||||
if (error) {
|
if (error) {
|
||||||
return VotingErrorVerbose[error];
|
return VotingErrorVerbose[error];
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!multiple && includeNone">
|
<ng-container *ngIf="!multiple && includeNone">
|
||||||
<mat-option>
|
<mat-option [value]="null">
|
||||||
{{ noneTitle | translate }}
|
{{ noneTitle | translate }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
|
@ -56,8 +56,9 @@ export abstract class BasePoll<
|
|||||||
public votescast: number;
|
public votescast: number;
|
||||||
public groups_id: number[];
|
public groups_id: number[];
|
||||||
public majority_method: MajorityMethod;
|
public majority_method: MajorityMethod;
|
||||||
|
public voted_id: number[];
|
||||||
public user_has_voted: boolean;
|
public user_has_voted: boolean;
|
||||||
|
public user_has_voted_for_delegations: number[];
|
||||||
public pollmethod: PM;
|
public pollmethod: PM;
|
||||||
public onehundred_percent_base: PB;
|
public onehundred_percent_base: PB;
|
||||||
|
|
||||||
|
@ -16,6 +16,13 @@ export const GeneralValueVerbose = {
|
|||||||
votesabstain: 'Votes abstain'
|
votesabstain: 'Votes abstain'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface UserVote {
|
||||||
|
// the voting payload is hard to describe.
|
||||||
|
// Can be "VoteValue" or any userID-Number sequence in combination with any VoteValue
|
||||||
|
data: Object;
|
||||||
|
user_id?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class BaseVote<T = any> extends BaseDecimalModel<T> {
|
export abstract class BaseVote<T = any> extends BaseDecimalModel<T> {
|
||||||
public weight: number;
|
public weight: number;
|
||||||
public value: VoteValue;
|
public value: VoteValue;
|
||||||
|
@ -30,6 +30,8 @@ export class User extends BaseDecimalModel<User> {
|
|||||||
public is_present: boolean;
|
public is_present: boolean;
|
||||||
public is_committee: boolean;
|
public is_committee: boolean;
|
||||||
public email?: string;
|
public email?: string;
|
||||||
|
public vote_delegated_to_id: number;
|
||||||
|
public vote_delegated_from_users_id: number[];
|
||||||
public last_email_send?: string; // ISO datetime string
|
public last_email_send?: string; // ISO datetime string
|
||||||
public comment?: string;
|
public comment?: string;
|
||||||
public is_active?: boolean;
|
public is_active?: boolean;
|
||||||
@ -41,6 +43,10 @@ export class User extends BaseDecimalModel<User> {
|
|||||||
return this.vote_weight === 1;
|
return this.vote_weight === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get isVoteRightDelegated(): boolean {
|
||||||
|
return !!this.vote_delegated_to_id;
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(input?: Partial<User>) {
|
public constructor(input?: Partial<User>) {
|
||||||
super(User.COLLECTIONSTRING, input);
|
super(User.COLLECTIONSTRING, input);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,27 @@
|
|||||||
<ng-container *ngIf="poll">
|
<ng-container *ngIf="poll">
|
||||||
<ng-container *ngIf="vmanager.canVote(poll) && !alreadyVoted && !deliveringVote; else cannotVote">
|
<!-- own voting -->
|
||||||
|
<ng-container [ngTemplateOutlet]="votingArea"></ng-container>
|
||||||
|
|
||||||
|
<!-- Delegations -->
|
||||||
|
<ng-container *ngIf="user.is_present">
|
||||||
|
<div class="assignment-vote-delegation" *ngFor="let delegation of delegations">
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
|
||||||
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="votingArea"
|
||||||
|
[ngTemplateOutletContext]="{ delegation: delegation }"
|
||||||
|
></ng-container>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-template #votingArea let-delegation="delegation">
|
||||||
|
<h4 *ngIf="delegation" class="assignment-delegation-title">
|
||||||
|
<span>{{ 'Vote delegation for' | translate }}</span>
|
||||||
|
<span> {{ delegation.getFullName() }}</span>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<ng-container *ngIf="canVote(delegation)">
|
||||||
<!-- Poll hint -->
|
<!-- Poll hint -->
|
||||||
<p *ngIf="pollHint">
|
<p *ngIf="pollHint">
|
||||||
<i>{{ pollHint }}</i>
|
<i>{{ pollHint }}</i>
|
||||||
@ -9,7 +31,7 @@
|
|||||||
<h4 *ngIf="poll.pollmethod === AssignmentPollMethod.Votes && poll.votes_amount > 1">
|
<h4 *ngIf="poll.pollmethod === AssignmentPollMethod.Votes && poll.votes_amount > 1">
|
||||||
{{ 'Available votes' | translate }}:
|
{{ 'Available votes' | translate }}:
|
||||||
|
|
||||||
<b> {{ getVotesAvailable() }}/{{ poll.votes_amount }} </b>
|
<b> {{ getVotesAvailable(delegation) }}/{{ poll.votes_amount }} </b>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<!-- Options and Actions -->
|
<!-- Options and Actions -->
|
||||||
@ -36,14 +58,9 @@
|
|||||||
<button
|
<button
|
||||||
class="vote-button"
|
class="vote-button"
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
(click)="saveSingleVote(option.id, action.vote)"
|
(click)="saveSingleVote(option.id, action.vote, delegation)"
|
||||||
[disabled]="deliveringVote"
|
[disabled]="isDeliveringVote(delegation)"
|
||||||
[ngClass]="
|
[ngClass]="getActionButtonClass(action, option, delegation)"
|
||||||
voteRequestData.votes[option.id] === action.vote ||
|
|
||||||
voteRequestData.votes[option.id] === 1
|
|
||||||
? action.css
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<mat-icon> {{ action.icon }}</mat-icon>
|
<mat-icon> {{ action.icon }}</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -64,9 +81,9 @@
|
|||||||
<button
|
<button
|
||||||
class="vote-button"
|
class="vote-button"
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
(click)="saveGlobalVote('N')"
|
(click)="saveGlobalVote('N', delegation)"
|
||||||
[ngClass]="voteRequestData.global === 'N' ? 'voted-no' : ''"
|
[ngClass]="getGlobalNoClass(delegation)"
|
||||||
[disabled]="deliveringVote"
|
[disabled]="isDeliveringVote(delegation)"
|
||||||
>
|
>
|
||||||
<mat-icon> thumb_down </mat-icon>
|
<mat-icon> thumb_down </mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -79,8 +96,9 @@
|
|||||||
<button
|
<button
|
||||||
class="vote-button"
|
class="vote-button"
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
(click)="saveGlobalVote('A')"
|
(click)="saveGlobalVote('A', delegation)"
|
||||||
[ngClass]="voteRequestData.global === 'A' ? 'voted-abstain' : ''"
|
[ngClass]="getGlobalAbstainClass(delegation)"
|
||||||
|
[disabled]="isDeliveringVote(delegation)"
|
||||||
>
|
>
|
||||||
<mat-icon> trip_origin</mat-icon>
|
<mat-icon> trip_origin</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -92,39 +110,47 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Submit Vote -->
|
<!-- Submit Vote -->
|
||||||
<ng-container [ngTemplateOutlet]="sendNow"></ng-container>
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="sendNow"
|
||||||
|
[ngTemplateOutletContext]="{ delegation: delegation }"
|
||||||
|
></ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Shows the permission error -->
|
<ng-container
|
||||||
<ng-container *ngIf="!vmanager.canVote(poll)">
|
*ngIf="!canVote(delegation)"
|
||||||
<span>{{ vmanager.getVotePermissionErrorVerbose(poll) | translate }}</span>
|
[ngTemplateOutlet]="cannotVote"
|
||||||
</ng-container>
|
[ngTemplateOutletContext]="{ delegation: delegation }"
|
||||||
|
>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #cannotVote>
|
<ng-template #cannotVote let-delegation="delegation">
|
||||||
<div class="centered-button-wrapper">
|
<div class="centered-button-wrapper">
|
||||||
<div *ngIf="!deliveringVote">
|
<!-- Success -->
|
||||||
<mat-icon class="vote-submitted">
|
<div *ngIf="hasAlreadyVoted(delegation) && !isDeliveringVote(delegation)">
|
||||||
check_circle
|
<mat-icon class="vote-submitted"> check_circle </mat-icon>
|
||||||
</mat-icon>
|
|
||||||
<br />
|
<br />
|
||||||
<span>{{ 'Voting successful.' | translate }}</span>
|
<span>{{ 'Voting successful.' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="deliveringVote" class="submit-vote-indicator">
|
<!-- Delivering -->
|
||||||
|
<div *ngIf="isDeliveringVote(delegation)" class="submit-vote-indicator">
|
||||||
<mat-spinner class="small-spinner"></mat-spinner>
|
<mat-spinner class="small-spinner"></mat-spinner>
|
||||||
<br />
|
<br />
|
||||||
<span>{{ 'Delivering vote... Please wait!' | translate }}</span>
|
<span>{{ 'Delivering vote... Please wait!' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Permission error error -->
|
||||||
|
<div *ngIf="!hasAlreadyVoted(delegation) && !isDeliveringVote(delegation)">
|
||||||
|
<span>{{ getVotingError(delegation) | translate }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #sendNow>
|
<ng-template #sendNow let-delegation="delegation">
|
||||||
<div class="centered-button-wrapper">
|
<div class="centered-button-wrapper">
|
||||||
<button mat-flat-button color="accent" (click)="submitVote()">
|
<button mat-flat-button color="accent" (click)="submitVote(delegation)">
|
||||||
<mat-icon>
|
<mat-icon> how_to_vote </mat-icon>
|
||||||
how_to_vote
|
|
||||||
</mat-icon>
|
|
||||||
<span>
|
<span>
|
||||||
{{ 'Submit vote now' | translate }}
|
{{ 'Submit vote now' | translate }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -7,6 +7,14 @@
|
|||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.assignment-vote-delegation {
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
.assignment-delegation-title {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.yn-grid {
|
.yn-grid {
|
||||||
@extend %vote-grid-base;
|
@extend %vote-grid-base;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
|
@ -7,24 +7,17 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import {
|
import {
|
||||||
AssignmentPollRepositoryService,
|
AssignmentPollRepositoryService,
|
||||||
GlobalVote,
|
GlobalVote
|
||||||
VotingData
|
|
||||||
} from 'app/core/repositories/assignments/assignment-poll-repository.service';
|
} from 'app/core/repositories/assignments/assignment-poll-repository.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
import { VotingService } from 'app/core/ui-services/voting.service';
|
||||||
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
|
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
|
||||||
import { PollType } from 'app/shared/models/poll/base-poll';
|
import { PollType } from 'app/shared/models/poll/base-poll';
|
||||||
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
||||||
|
import { ViewAssignmentOption } from 'app/site/assignments/models/view-assignment-option';
|
||||||
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
||||||
import { BasePollVoteComponentDirective } from 'app/site/polls/components/base-poll-vote.component';
|
import { BasePollVoteComponentDirective, VoteOption } from 'app/site/polls/components/base-poll-vote.component';
|
||||||
|
import { ViewUser } from 'app/site/users/models/view-user';
|
||||||
// TODO: Duplicate
|
|
||||||
interface VoteActions {
|
|
||||||
vote: VoteValue;
|
|
||||||
css: string;
|
|
||||||
icon: string;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-assignment-poll-vote',
|
selector: 'os-assignment-poll-vote',
|
||||||
@ -35,37 +28,60 @@ interface VoteActions {
|
|||||||
export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<ViewAssignmentPoll> implements OnInit {
|
export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<ViewAssignmentPoll> implements OnInit {
|
||||||
public AssignmentPollMethod = AssignmentPollMethod;
|
public AssignmentPollMethod = AssignmentPollMethod;
|
||||||
public PollType = PollType;
|
public PollType = PollType;
|
||||||
public voteActions: VoteActions[] = [];
|
public voteActions: VoteOption[] = [];
|
||||||
public voteRequestData: VotingData = {
|
|
||||||
votes: {}
|
public get pollHint(): string {
|
||||||
};
|
return this.poll.assignment.default_poll_description;
|
||||||
public alreadyVoted: boolean;
|
}
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
title: Title,
|
title: Title,
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
matSnackbar: MatSnackBar,
|
matSnackbar: MatSnackBar,
|
||||||
operator: OperatorService,
|
operator: OperatorService,
|
||||||
public vmanager: VotingService,
|
votingService: VotingService,
|
||||||
private pollRepo: AssignmentPollRepositoryService,
|
private pollRepo: AssignmentPollRepositoryService,
|
||||||
private promptService: PromptService,
|
private promptService: PromptService,
|
||||||
private cd: ChangeDetectorRef
|
private cd: ChangeDetectorRef
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar, operator);
|
super(title, translate, matSnackbar, operator, votingService);
|
||||||
|
|
||||||
|
// observe user updates to refresh the view on dynamic changes
|
||||||
|
this.subscriptions.push(
|
||||||
|
operator.getViewUserObservable().subscribe(() => {
|
||||||
|
this.cd.markForCheck();
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
if (this.poll && !this.poll.user_has_voted) {
|
this.createVotingDataObjects();
|
||||||
this.alreadyVoted = false;
|
|
||||||
this.defineVoteOptions();
|
this.defineVoteOptions();
|
||||||
} else {
|
|
||||||
this.alreadyVoted = true;
|
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getActionButtonClass(actions: VoteOption, option: ViewAssignmentOption, user: ViewUser = this.user): string {
|
||||||
|
if (
|
||||||
|
this.voteRequestData[user.id]?.votes[option.id] === actions.vote ||
|
||||||
|
this.voteRequestData[user.id]?.votes[option.id] === 1
|
||||||
|
) {
|
||||||
|
return actions.css;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get pollHint(): string {
|
public getGlobalAbstainClass(user: ViewUser = this.user): string {
|
||||||
return this.poll.assignment.default_poll_description;
|
if (this.voteRequestData[user.id]?.global === 'A') {
|
||||||
|
return 'voted-abstain';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGlobalNoClass(user: ViewUser = this.user): string {
|
||||||
|
if (this.voteRequestData[user.id]?.global === 'N') {
|
||||||
|
return 'voted-no';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private defineVoteOptions(): void {
|
private defineVoteOptions(): void {
|
||||||
@ -76,7 +92,7 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
label: 'Yes'
|
label: 'Yes'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.poll.pollmethod !== AssignmentPollMethod.Votes) {
|
if (this.poll?.pollmethod !== AssignmentPollMethod.Votes) {
|
||||||
this.voteActions.push({
|
this.voteActions.push({
|
||||||
vote: 'N',
|
vote: 'N',
|
||||||
css: 'voted-no',
|
css: 'voted-no',
|
||||||
@ -85,7 +101,7 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.poll.pollmethod === AssignmentPollMethod.YNA) {
|
if (this.poll?.pollmethod === AssignmentPollMethod.YNA) {
|
||||||
this.voteActions.push({
|
this.voteActions.push({
|
||||||
vote: 'A',
|
vote: 'A',
|
||||||
css: 'voted-abstain',
|
css: 'voted-abstain',
|
||||||
@ -95,40 +111,48 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getVotesCount(): number {
|
public getVotesCount(user: ViewUser = this.user): number {
|
||||||
return Object.keys(this.voteRequestData.votes).filter(key => this.voteRequestData.votes[key]).length;
|
if (this.voteRequestData[user.id]) {
|
||||||
|
return Object.keys(this.voteRequestData[user.id].votes).filter(
|
||||||
|
key => this.voteRequestData[user.id].votes[key]
|
||||||
|
).length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getVotesAvailable(): number {
|
public getVotesAvailable(user: ViewUser = this.user): number {
|
||||||
return this.poll.votes_amount - this.getVotesCount();
|
return this.poll.votes_amount - this.getVotesCount(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
private isGlobalOptionSelected(): boolean {
|
private isGlobalOptionSelected(user: ViewUser = this.user): boolean {
|
||||||
return !!this.voteRequestData.global;
|
return !!this.voteRequestData[user.id]?.global;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async submitVote(): Promise<void> {
|
public async submitVote(user: ViewUser = this.user): Promise<void> {
|
||||||
const title = this.translate.instant('Submit selection now?');
|
const title = this.translate.instant('Submit selection now?');
|
||||||
const content = this.translate.instant('Your decision cannot be changed afterwards.');
|
const content = this.translate.instant('Your decision cannot be changed afterwards.');
|
||||||
const confirmed = await this.promptService.open(title, content);
|
const confirmed = await this.promptService.open(title, content);
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
this.deliveringVote = true;
|
this.deliveringVote[user.id] = true;
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
this.pollRepo
|
this.pollRepo
|
||||||
.vote(this.voteRequestData, this.poll.id)
|
.vote(this.voteRequestData[user.id], this.poll.id, user.id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.alreadyVoted = true;
|
this.alreadyVoted[user.id] = true;
|
||||||
})
|
})
|
||||||
.catch(this.raiseError)
|
.catch(this.raiseError)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.deliveringVote = false;
|
this.deliveringVote[user.id] = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveSingleVote(optionId: number, vote: VoteValue): void {
|
public saveSingleVote(optionId: number, vote: VoteValue, user: ViewUser = this.user): void {
|
||||||
if (this.isGlobalOptionSelected()) {
|
if (!this.voteRequestData[user.id]) {
|
||||||
delete this.voteRequestData.global;
|
throw new Error('The user for your voting request does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isGlobalOptionSelected(user)) {
|
||||||
|
delete this.voteRequestData[user.id].global;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.poll.pollmethod === AssignmentPollMethod.Votes) {
|
if (this.poll.pollmethod === AssignmentPollMethod.Votes) {
|
||||||
@ -138,10 +162,10 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
.reduce((o, n) => {
|
.reduce((o, n) => {
|
||||||
o[n] = 0;
|
o[n] = 0;
|
||||||
if (votesAmount === 1) {
|
if (votesAmount === 1) {
|
||||||
if (n === optionId && this.voteRequestData.votes[n] !== 1) {
|
if (n === optionId && this.voteRequestData[user.id].votes[n] !== 1) {
|
||||||
o[n] = 1;
|
o[n] = 1;
|
||||||
}
|
}
|
||||||
} else if ((n === optionId) !== (this.voteRequestData.votes[n] === 1)) {
|
} else if ((n === optionId) !== (this.voteRequestData[user.id].votes[n] === 1)) {
|
||||||
o[n] = 1;
|
o[n] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,11 +175,11 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
// check if you can still vote
|
// check if you can still vote
|
||||||
const countedVotes = Object.keys(tmpVoteRequest).filter(key => tmpVoteRequest[key]).length;
|
const countedVotes = Object.keys(tmpVoteRequest).filter(key => tmpVoteRequest[key]).length;
|
||||||
if (countedVotes <= votesAmount) {
|
if (countedVotes <= votesAmount) {
|
||||||
this.voteRequestData.votes = tmpVoteRequest;
|
this.voteRequestData[user.id].votes = tmpVoteRequest;
|
||||||
|
|
||||||
// if you have no options anymore, try to send
|
// if you have no options anymore, try to send
|
||||||
if (this.getVotesCount() === votesAmount) {
|
if (this.getVotesCount(user) === votesAmount) {
|
||||||
this.submitVote();
|
this.submitVote(user);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.raiseError(
|
this.raiseError(
|
||||||
@ -164,26 +188,29 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// YN/YNA
|
// YN/YNA
|
||||||
if (this.voteRequestData.votes[optionId] && this.voteRequestData.votes[optionId] === vote) {
|
if (
|
||||||
delete this.voteRequestData.votes[optionId];
|
this.voteRequestData[user.id].votes[optionId] &&
|
||||||
|
this.voteRequestData[user.id].votes[optionId] === vote
|
||||||
|
) {
|
||||||
|
delete this.voteRequestData[user.id].votes[optionId];
|
||||||
} else {
|
} else {
|
||||||
this.voteRequestData.votes[optionId] = vote;
|
this.voteRequestData[user.id].votes[optionId] = vote;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if you filled out every option, try to send
|
// if you filled out every option, try to send
|
||||||
if (Object.keys(this.voteRequestData.votes).length === this.poll.options.length) {
|
if (Object.keys(this.voteRequestData[user.id].votes).length === this.poll.options.length) {
|
||||||
this.submitVote();
|
this.submitVote(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveGlobalVote(globalVote: GlobalVote): void {
|
public saveGlobalVote(globalVote: GlobalVote, user: ViewUser = this.user): void {
|
||||||
this.voteRequestData.votes = {};
|
this.voteRequestData[user.id].votes = {};
|
||||||
if (this.voteRequestData.global && this.voteRequestData.global === globalVote) {
|
if (this.voteRequestData[user.id].global && this.voteRequestData[user.id].global === globalVote) {
|
||||||
delete this.voteRequestData.global;
|
delete this.voteRequestData[user.id].global;
|
||||||
} else {
|
} else {
|
||||||
this.voteRequestData.global = globalVote;
|
this.voteRequestData[user.id].global = globalVote;
|
||||||
this.submitVote();
|
this.submitVote(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,8 @@ export class AssignmentPollComponent
|
|||||||
this.descriptionForm = this.formBuilder.group({
|
this.descriptionForm = this.formBuilder.group({
|
||||||
description: this.poll ? this.poll.description : ''
|
description: this.poll ? this.poll.description : ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('the poll: ', this.poll);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,10 +4,10 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div *ngIf="poll.pollClassType === 'motion'">
|
<div *ngIf="poll.pollClassType === 'motion'">
|
||||||
<os-motion-poll-vote [poll]="poll"></os-motion-poll-vote>
|
<os-motion-poll-vote [poll]="poll" *ngIf="poll.canBeVotedFor()"></os-motion-poll-vote>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="poll.pollClassType === 'assignment'">
|
<div *ngIf="poll.pollClassType === 'assignment'">
|
||||||
<os-assignment-poll-vote [poll]="poll"></os-assignment-poll-vote>
|
<os-assignment-poll-vote [poll]="poll" *ngIf="poll.canBeVotedFor()"></os-assignment-poll-vote>
|
||||||
</div>
|
</div>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
@ -1,12 +1,34 @@
|
|||||||
<ng-container *ngIf="poll && !poll.user_has_voted; else userHasVotes">
|
<ng-container *ngIf="poll">
|
||||||
<div *ngIf="vmanager.canVote(poll) && !deliveringVote" class="vote-button-grid">
|
<!-- own voting -->
|
||||||
|
<ng-container [ngTemplateOutlet]="votingArea"></ng-container>
|
||||||
|
|
||||||
|
<!-- Delegations -->
|
||||||
|
<ng-container *ngIf="user.is_present">
|
||||||
|
<div class="motion-vote-delegation" *ngFor="let delegation of delegations">
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
|
||||||
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="votingArea"
|
||||||
|
[ngTemplateOutletContext]="{ delegation: delegation }"
|
||||||
|
></ng-container>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-template #votingArea let-delegation="delegation">
|
||||||
|
<h4 *ngIf="delegation" class="motion-delegation-title">
|
||||||
|
<span>{{ 'Vote delegation for' | translate }}</span>
|
||||||
|
<span> {{ delegation.getFullName() }}</span>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div *ngIf="canVote(delegation)" class="vote-button-grid">
|
||||||
<!-- Voting -->
|
<!-- Voting -->
|
||||||
<div class="vote-button" *ngFor="let option of voteOptions">
|
<div class="vote-button" *ngFor="let option of voteOptions">
|
||||||
<button
|
<button
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
(click)="saveVote(option.vote)"
|
(click)="saveVote(option.vote, delegation)"
|
||||||
[ngClass]="currentVote && currentVote.vote === option.vote ? option.css : ''"
|
[ngClass]="getActionButtonClass(option, delegation)"
|
||||||
[disabled]="deliveringVote"
|
[disabled]="isDeliveringVote(delegation)"
|
||||||
>
|
>
|
||||||
<mat-icon> {{ option.icon }}</mat-icon>
|
<mat-icon> {{ option.icon }}</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -14,21 +36,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="deliveringVote" class="submit-vote-indicator">
|
<ng-container
|
||||||
|
*ngIf="!canVote(delegation)"
|
||||||
|
[ngTemplateOutlet]="cannotVote"
|
||||||
|
[ngTemplateOutletContext]="{ delegation: delegation }"
|
||||||
|
>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Delivering -->
|
||||||
|
<div *ngIf="isDeliveringVote(delegation)" class="submit-vote-indicator">
|
||||||
<mat-spinner class="small-spinner"></mat-spinner>
|
<mat-spinner class="small-spinner"></mat-spinner>
|
||||||
<br />
|
<br />
|
||||||
<span>{{ 'Delivering vote... Please wait!' | translate }}</span>
|
<span>{{ 'Delivering vote... Please wait!' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #userHasVotes>
|
<ng-template #cannotVote let-delegation="delegation">
|
||||||
<div class="user-has-voted">
|
<!-- Success -->
|
||||||
|
<div *ngIf="hasAlreadyVoted(delegation) && !isDeliveringVote(delegation)" class="user-has-voted">
|
||||||
<div>
|
<div>
|
||||||
<mat-icon class="vote-submitted">
|
<mat-icon class="vote-submitted"> check_circle </mat-icon>
|
||||||
check_circle
|
|
||||||
</mat-icon>
|
|
||||||
<br />
|
<br />
|
||||||
<span>{{ 'Voting successful.' | translate }}</span>
|
<span>{{ 'Voting successful.' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Error -->
|
||||||
|
<div *ngIf="!hasAlreadyVoted(delegation) && !isDeliveringVote(delegation)">
|
||||||
|
<span>{{ getVotingError(delegation) | translate }}</span>
|
||||||
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -8,6 +8,14 @@
|
|||||||
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.motion-vote-delegation {
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
.motion-delegation-title {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.submit-vote-indicator {
|
.submit-vote-indicator {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -26,6 +34,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mat-divider-horizontal {
|
||||||
|
position: initial;
|
||||||
|
}
|
||||||
|
|
||||||
.user-has-voted {
|
.user-has-voted {
|
||||||
display: flex;
|
display: flex;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
@ -10,14 +10,8 @@ import { PromptService } from 'app/core/ui-services/prompt.service';
|
|||||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
import { VotingService } from 'app/core/ui-services/voting.service';
|
||||||
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
||||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||||
import { BasePollVoteComponentDirective } from 'app/site/polls/components/base-poll-vote.component';
|
import { BasePollVoteComponentDirective, VoteOption } from 'app/site/polls/components/base-poll-vote.component';
|
||||||
|
import { ViewUser } from 'app/site/users/models/view-user';
|
||||||
interface VoteOption {
|
|
||||||
vote?: VoteValue;
|
|
||||||
css?: string;
|
|
||||||
icon?: string;
|
|
||||||
label?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-motion-poll-vote',
|
selector: 'os-motion-poll-vote',
|
||||||
@ -25,8 +19,7 @@ interface VoteOption {
|
|||||||
styleUrls: ['./motion-poll-vote.component.scss'],
|
styleUrls: ['./motion-poll-vote.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class MotionPollVoteComponent extends BasePollVoteComponentDirective<ViewMotionPoll> {
|
export class MotionPollVoteComponent extends BasePollVoteComponentDirective<ViewMotionPoll> implements OnInit {
|
||||||
public currentVote: VoteOption = {};
|
|
||||||
public voteOptions: VoteOption[] = [
|
public voteOptions: VoteOption[] = [
|
||||||
{
|
{
|
||||||
vote: 'Y',
|
vote: 'Y',
|
||||||
@ -53,30 +46,56 @@ export class MotionPollVoteComponent extends BasePollVoteComponentDirective<View
|
|||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
matSnackbar: MatSnackBar,
|
matSnackbar: MatSnackBar,
|
||||||
operator: OperatorService,
|
operator: OperatorService,
|
||||||
public vmanager: VotingService,
|
public votingService: VotingService,
|
||||||
private pollRepo: MotionPollRepositoryService,
|
private pollRepo: MotionPollRepositoryService,
|
||||||
private promptService: PromptService,
|
private promptService: PromptService,
|
||||||
private cd: ChangeDetectorRef
|
private cd: ChangeDetectorRef
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar, operator);
|
super(title, translate, matSnackbar, operator, votingService);
|
||||||
|
|
||||||
|
// observe user updates to refresh the view on dynamic changes
|
||||||
|
this.subscriptions.push(
|
||||||
|
operator.getViewUserObservable().subscribe(() => {
|
||||||
|
this.cd.markForCheck();
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveVote(vote: VoteValue): Promise<void> {
|
public ngOnInit(): void {
|
||||||
this.currentVote.vote = vote;
|
this.createVotingDataObjects();
|
||||||
|
this.cd.markForCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getActionButtonClass(voteOption: VoteOption, user: ViewUser = this.user): string {
|
||||||
|
if (this.voteRequestData[user.id]?.vote === voteOption.vote) {
|
||||||
|
return voteOption.css;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public async saveVote(vote: VoteValue, user: ViewUser = this.user): Promise<void> {
|
||||||
|
if (this.voteRequestData[user.id]) {
|
||||||
|
this.voteRequestData[user.id].vote = vote;
|
||||||
|
|
||||||
const title = this.translate.instant('Submit selection now?');
|
const title = this.translate.instant('Submit selection now?');
|
||||||
const content = this.translate.instant('Your decision cannot be changed afterwards.');
|
const content = this.translate.instant('Your decision cannot be changed afterwards.');
|
||||||
const confirmed = await this.promptService.open(title, content);
|
const confirmed = await this.promptService.open(title, content);
|
||||||
|
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
this.deliveringVote = true;
|
this.deliveringVote[user.id] = true;
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
|
|
||||||
this.pollRepo
|
this.pollRepo
|
||||||
.vote(vote, this.poll.id)
|
.vote(vote, this.poll.id, user.id)
|
||||||
|
.then(() => {
|
||||||
|
this.alreadyVoted[user.id] = true;
|
||||||
|
})
|
||||||
.catch(this.raiseError)
|
.catch(this.raiseError)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.deliveringVote = false;
|
this.deliveringVote[user.id] = false;
|
||||||
|
this.cd.markForCheck();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
<div *osPerms="'motions.can_manage_polls'; and: poll && poll.isStarted">
|
<div *osPerms="'motions.can_manage_polls'; and: poll && poll.isStarted">
|
||||||
<os-poll-progress [poll]="poll"></os-poll-progress>
|
<os-poll-progress [poll]="poll"></os-poll-progress>
|
||||||
</div>
|
</div>
|
||||||
<os-motion-poll-vote [poll]="poll"></os-motion-poll-vote>
|
<os-motion-poll-vote [poll]="poll" *ngIf="poll.canBeVotedFor()"></os-motion-poll-vote>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Detail link -->
|
<!-- Detail link -->
|
||||||
|
@ -5,11 +5,20 @@ import { Title } from '@angular/platform-browser';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { VotingError } from 'app/core/ui-services/voting.service';
|
import { VotingData } from 'app/core/repositories/assignments/assignment-poll-repository.service';
|
||||||
|
import { VotingError, VotingService } from 'app/core/ui-services/voting.service';
|
||||||
|
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
||||||
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
||||||
import { ViewUser } from 'app/site/users/models/view-user';
|
import { ViewUser } from 'app/site/users/models/view-user';
|
||||||
import { ViewBasePoll } from '../models/view-base-poll';
|
import { ViewBasePoll } from '../models/view-base-poll';
|
||||||
|
|
||||||
|
export interface VoteOption {
|
||||||
|
vote?: VoteValue;
|
||||||
|
css?: string;
|
||||||
|
icon?: string;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export abstract class BasePollVoteComponentDirective<V extends ViewBasePoll> extends BaseViewComponentDirective {
|
export abstract class BasePollVoteComponentDirective<V extends ViewBasePoll> extends BaseViewComponentDirective {
|
||||||
@Input()
|
@Input()
|
||||||
@ -17,22 +26,71 @@ export abstract class BasePollVoteComponentDirective<V extends ViewBasePoll> ext
|
|||||||
|
|
||||||
public votingErrors = VotingError;
|
public votingErrors = VotingError;
|
||||||
|
|
||||||
public deliveringVote = false;
|
protected voteRequestData = {};
|
||||||
|
|
||||||
|
protected alreadyVoted = {};
|
||||||
|
|
||||||
|
protected deliveringVote = {};
|
||||||
|
|
||||||
protected user: ViewUser;
|
protected user: ViewUser;
|
||||||
|
|
||||||
|
protected delegations: ViewUser[];
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
title: Title,
|
title: Title,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
matSnackbar: MatSnackBar,
|
matSnackbar: MatSnackBar,
|
||||||
protected operator: OperatorService
|
operator: OperatorService,
|
||||||
|
protected votingService: VotingService
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar);
|
super(title, translate, matSnackbar);
|
||||||
|
|
||||||
this.subscriptions.push(
|
this.subscriptions.push(
|
||||||
this.operator.getViewUserObservable().subscribe(user => {
|
operator.getViewUserObservable().subscribe(user => {
|
||||||
|
if (user) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
this.delegations = user.voteDelegationsFrom;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected createVotingDataObjects(): void {
|
||||||
|
if (this.user) {
|
||||||
|
this.voteRequestData[this.user.id] = {
|
||||||
|
votes: {}
|
||||||
|
} as VotingData;
|
||||||
|
this.alreadyVoted[this.user.id] = this.poll.user_has_voted;
|
||||||
|
this.deliveringVote[this.user.id] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.delegations) {
|
||||||
|
for (const delegation of this.delegations) {
|
||||||
|
this.voteRequestData[delegation.id] = {
|
||||||
|
votes: {}
|
||||||
|
} as VotingData;
|
||||||
|
this.alreadyVoted[delegation.id] = this.poll.hasVotedId(delegation.id);
|
||||||
|
this.deliveringVote[delegation.id] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public isDeliveringVote(user: ViewUser = this.user): boolean {
|
||||||
|
return this.deliveringVote[user.id] === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasAlreadyVoted(user: ViewUser = this.user): boolean {
|
||||||
|
return this.alreadyVoted[user.id] === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public canVote(user: ViewUser = this.user): boolean {
|
||||||
|
return (
|
||||||
|
this.votingService.canVote(this.poll, user) && !this.isDeliveringVote(user) && !this.hasAlreadyVoted(user)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getVotingError(user: ViewUser = this.user): string | void {
|
||||||
|
console.log('error ', this.votingService.getVotePermissionErrorVerbose(this.poll, user));
|
||||||
|
return this.votingService.getVotePermissionErrorVerbose(this.poll, user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,15 @@ export class PollProgressComponent extends BaseViewComponentDirective implements
|
|||||||
.getViewModelListObservable()
|
.getViewModelListObservable()
|
||||||
.pipe(
|
.pipe(
|
||||||
map(users =>
|
map(users =>
|
||||||
|
/**
|
||||||
|
* Filter the users who would be able to vote:
|
||||||
|
* They are present or have their right to vote delegated
|
||||||
|
* They are in one of the voting groups
|
||||||
|
*/
|
||||||
users.filter(
|
users.filter(
|
||||||
user => user.is_present && this.poll.groups_id.intersect(user.groups_id).length
|
user =>
|
||||||
|
(user.is_present || user.isVoteRightDelegated) &&
|
||||||
|
this.poll.groups_id.intersect(user.groups_id).length
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -114,6 +114,10 @@ export abstract class ViewBasePoll<
|
|||||||
|
|
||||||
public canBeVotedFor: () => boolean;
|
public canBeVotedFor: () => boolean;
|
||||||
|
|
||||||
|
public hasVotedId(userId: number): boolean {
|
||||||
|
return this.user_has_voted_for_delegations?.includes(userId);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract getSlide(): ProjectorElementBuildDeskriptor;
|
public abstract getSlide(): ProjectorElementBuildDeskriptor;
|
||||||
|
|
||||||
public abstract getContentObject(): BaseViewModel;
|
public abstract getContentObject(): BaseViewModel;
|
||||||
|
@ -8,7 +8,6 @@ import { RelationManagerService } from 'app/core/core-services/relation-manager.
|
|||||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||||
import { RelationDefinition } from 'app/core/definitions/relations';
|
import { RelationDefinition } from 'app/core/definitions/relations';
|
||||||
import { BaseRepository, NestedModelDescriptors } from 'app/core/repositories/base-repository';
|
import { BaseRepository, NestedModelDescriptors } from 'app/core/repositories/base-repository';
|
||||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
|
||||||
import { ModelConstructor } from 'app/shared/models/base/base-model';
|
import { ModelConstructor } from 'app/shared/models/base/base-model';
|
||||||
import { BasePoll, PollState } from 'app/shared/models/poll/base-poll';
|
import { BasePoll, PollState } from 'app/shared/models/poll/base-poll';
|
||||||
import { BaseViewModel, TitleInformation } from 'app/site/base/base-view-model';
|
import { BaseViewModel, TitleInformation } from 'app/site/base/base-view-model';
|
||||||
@ -30,7 +29,6 @@ export abstract class BasePollRepositoryService<
|
|||||||
protected baseModelCtor: ModelConstructor<M>,
|
protected baseModelCtor: ModelConstructor<M>,
|
||||||
protected relationDefinitions: RelationDefinition<BaseViewModel>[] = [],
|
protected relationDefinitions: RelationDefinition<BaseViewModel>[] = [],
|
||||||
protected nestedModelDescriptors: NestedModelDescriptors = {},
|
protected nestedModelDescriptors: NestedModelDescriptors = {},
|
||||||
private votingService: VotingService,
|
|
||||||
protected http: HttpService
|
protected http: HttpService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
@ -53,7 +51,7 @@ export abstract class BasePollRepositoryService<
|
|||||||
protected createViewModelWithTitles(model: M): V {
|
protected createViewModelWithTitles(model: M): V {
|
||||||
const viewModel = super.createViewModelWithTitles(model);
|
const viewModel = super.createViewModelWithTitles(model);
|
||||||
Object.defineProperty(viewModel, 'canBeVotedFor', {
|
Object.defineProperty(viewModel, 'canBeVotedFor', {
|
||||||
value: () => this.votingService.canVote(viewModel)
|
value: () => viewModel.isStarted
|
||||||
});
|
});
|
||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
@ -164,6 +164,16 @@
|
|||||||
[inputListValues]="groups"
|
[inputListValues]="groups"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Delegate Vote -->
|
||||||
|
<mat-form-field>
|
||||||
|
<os-search-value-selector
|
||||||
|
formControlName="vote_delegated_from_users_id"
|
||||||
|
[multiple]="true"
|
||||||
|
placeholder="{{ 'Transferred voting rights from' | translate }}"
|
||||||
|
[inputListValues]="users"
|
||||||
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="isAllowed('manage')">
|
<div *ngIf="isAllowed('manage')">
|
||||||
@ -303,6 +313,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="isAllowed('manage')">
|
<div *ngIf="isAllowed('manage')">
|
||||||
|
<!-- Own Vote delegations -->
|
||||||
|
<div *ngIf="user.voteDelegatedTo">
|
||||||
|
<h4>{{ 'Vote right delegated to:' | translate }}</h4>
|
||||||
|
<span>{{ user.voteDelegatedTo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Received Vote delegations -->
|
||||||
|
<div *ngIf="user.voteDelegationsFrom && user.voteDelegationsFrom.length">
|
||||||
|
<h4>{{ 'Vote delegations from' | translate }}</h4>
|
||||||
|
<span>{{ user.voteDelegationsFrom }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Vote weight -->
|
<!-- Vote weight -->
|
||||||
<div *ngIf="user.vote_weight && showVoteWeight">
|
<div *ngIf="user.vote_weight && showVoteWeight">
|
||||||
<h4>{{ 'Vote weight' | translate }}</h4>
|
<h4>{{ 'Vote weight' | translate }}</h4>
|
||||||
|
@ -71,6 +71,8 @@ export class UserDetailComponent extends BaseViewComponentDirective implements O
|
|||||||
*/
|
*/
|
||||||
public readonly groups: BehaviorSubject<ViewGroup[]> = new BehaviorSubject<ViewGroup[]>([]);
|
public readonly groups: BehaviorSubject<ViewGroup[]> = new BehaviorSubject<ViewGroup[]>([]);
|
||||||
|
|
||||||
|
public readonly users: BehaviorSubject<ViewUser[]> = new BehaviorSubject<ViewUser[]>([]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hold the list of genders (sexes) publicly to dynamically iterate in the view
|
* Hold the list of genders (sexes) publicly to dynamically iterate in the view
|
||||||
*/
|
*/
|
||||||
@ -124,6 +126,7 @@ export class UserDetailComponent extends BaseViewComponentDirective implements O
|
|||||||
.subscribe(active => (this.isVoteWeightActive = active));
|
.subscribe(active => (this.isVoteWeightActive = active));
|
||||||
|
|
||||||
this.groupRepo.getViewModelListObservableWithoutDefaultGroup().subscribe(this.groups);
|
this.groupRepo.getViewModelListObservableWithoutDefaultGroup().subscribe(this.groups);
|
||||||
|
this.users = this.repo.getViewModelListBehaviorSubject();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -173,6 +176,7 @@ export class UserDetailComponent extends BaseViewComponentDirective implements O
|
|||||||
vote_weight: [],
|
vote_weight: [],
|
||||||
about_me: [''],
|
about_me: [''],
|
||||||
groups_id: [''],
|
groups_id: [''],
|
||||||
|
vote_delegated_from_users_id: [''],
|
||||||
is_present: [true],
|
is_present: [true],
|
||||||
is_committee: [false],
|
is_committee: [false],
|
||||||
email: ['', Validators.email],
|
email: ['', Validators.email],
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
>
|
>
|
||||||
<div class="groupsCell">
|
<div class="groupsCell">
|
||||||
<div *ngIf="user.groups && user.groups.length">
|
<div *ngIf="user.groups && user.groups.length">
|
||||||
<os-icon-container icon="people">
|
<os-icon-container icon="people" noWrap="true">
|
||||||
<span *ngFor="let group of user.groups; let last = last">
|
<span *ngFor="let group of user.groups; let last = last">
|
||||||
{{ group.getTitle() | translate }}<span *ngIf="!last">,</span>
|
{{ group.getTitle() | translate }}<span *ngIf="!last">,</span>
|
||||||
</span>
|
</span>
|
||||||
@ -74,6 +74,10 @@
|
|||||||
<div *ngIf="user.number" class="spacer-top-5">
|
<div *ngIf="user.number" class="spacer-top-5">
|
||||||
<os-icon-container icon="perm_identity">{{ user.number }}</os-icon-container>
|
<os-icon-container icon="perm_identity">{{ user.number }}</os-icon-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="user.vote_delegated_to_id" class="spacer-top-5">
|
||||||
|
<os-icon-container icon="forward" noWrap="true">{{ user.voteDelegatedTo }}</os-icon-container>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -90,9 +94,7 @@
|
|||||||
</mat-icon>
|
</mat-icon>
|
||||||
|
|
||||||
<!-- Has comment indicator -->
|
<!-- Has comment indicator -->
|
||||||
<mat-icon inline *ngIf="!!user.comment" matTooltip="{{ user.comment }}">
|
<mat-icon inline *ngIf="!!user.comment" matTooltip="{{ user.comment }}"> comment </mat-icon>
|
||||||
comment
|
|
||||||
</mat-icon>
|
|
||||||
|
|
||||||
<os-icon-container *ngIf="user.isSamlUser" icon="device_hub"
|
<os-icon-container *ngIf="user.isSamlUser" icon="device_hub"
|
||||||
><span>{{ 'Is SAML user' | translate }}</span></os-icon-container
|
><span>{{ 'Is SAML user' | translate }}</span></os-icon-container
|
||||||
@ -270,6 +272,14 @@
|
|||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<os-search-value-selector
|
||||||
|
[(ngModel)]="infoDialog.vote_delegated_from_users_id"
|
||||||
|
[multiple]="true"
|
||||||
|
placeholder="{{ 'Transferred voting rights from' | translate }}"
|
||||||
|
[inputListValues]="users"
|
||||||
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select placeholder="{{ 'Gender' | translate }}" [(ngModel)]="infoDialog.gender">
|
<mat-select placeholder="{{ 'Gender' | translate }}" [(ngModel)]="infoDialog.gender">
|
||||||
<mat-option>-</mat-option>
|
<mat-option>-</mat-option>
|
||||||
@ -300,6 +310,7 @@
|
|||||||
color="accent"
|
color="accent"
|
||||||
[mat-dialog-close]="{
|
[mat-dialog-close]="{
|
||||||
groups_id: infoDialog.groups_id,
|
groups_id: infoDialog.groups_id,
|
||||||
|
vote_delegated_from_users_id: infoDialog.vote_delegated_from_users_id,
|
||||||
gender: infoDialog.gender,
|
gender: infoDialog.gender,
|
||||||
number: infoDialog.number,
|
number: infoDialog.number,
|
||||||
structure_level: infoDialog.structure_level
|
structure_level: infoDialog.structure_level
|
||||||
|
@ -7,6 +7,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { PblColumnDefinition } from '@pebula/ngrid';
|
import { PblColumnDefinition } from '@pebula/ngrid';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
import { OperatorService, Permission } from 'app/core/core-services/operator.service';
|
import { OperatorService, Permission } from 'app/core/core-services/operator.service';
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
@ -55,6 +56,11 @@ interface InfoDialog {
|
|||||||
* Structure level for one user.
|
* Structure level for one user.
|
||||||
*/
|
*/
|
||||||
structure_level: string;
|
structure_level: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transfer voting rights
|
||||||
|
*/
|
||||||
|
vote_delegated_from_users_id: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,6 +88,8 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
|
|||||||
*/
|
*/
|
||||||
public groups: ViewGroup[];
|
public groups: ViewGroup[];
|
||||||
|
|
||||||
|
public readonly users: BehaviorSubject<ViewUser[]> = new BehaviorSubject<ViewUser[]>([]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of all genders.
|
* The list of all genders.
|
||||||
*/
|
*/
|
||||||
@ -187,6 +195,7 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
|
|||||||
|
|
||||||
// enable multiSelect for this listView
|
// enable multiSelect for this listView
|
||||||
this.canMultiSelect = true;
|
this.canMultiSelect = true;
|
||||||
|
this.users = this.repo.getViewModelListBehaviorSubject();
|
||||||
config.get<boolean>('users_enable_presence_view').subscribe(state => (this._presenceViewConfigured = state));
|
config.get<boolean>('users_enable_presence_view').subscribe(state => (this._presenceViewConfigured = state));
|
||||||
config.get<boolean>('users_activate_vote_weight').subscribe(active => (this.isVoteWeightActive = active));
|
config.get<boolean>('users_activate_vote_weight').subscribe(active => (this.isVoteWeightActive = active));
|
||||||
config.get<boolean>(this.selfPresentConfStr).subscribe(allowed => (this.allowSelfSetPresent = allowed));
|
config.get<boolean>(this.selfPresentConfStr).subscribe(allowed => (this.allowSelfSetPresent = allowed));
|
||||||
@ -203,9 +212,12 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
|
|||||||
|
|
||||||
// Initialize the groups
|
// Initialize the groups
|
||||||
this.groups = this.groupRepo.getViewModelList().filter(group => group.id !== 1);
|
this.groups = this.groupRepo.getViewModelList().filter(group => group.id !== 1);
|
||||||
|
|
||||||
|
this.subscriptions.push(
|
||||||
this.groupRepo
|
this.groupRepo
|
||||||
.getViewModelListObservable()
|
.getViewModelListObservable()
|
||||||
.subscribe(groups => (this.groups = groups.filter(group => group.id !== 1)));
|
.subscribe(groups => (this.groups = groups.filter(group => group.id !== 1)))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -242,7 +254,8 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
|
|||||||
groups_id: user.groups_id,
|
groups_id: user.groups_id,
|
||||||
gender: user.gender,
|
gender: user.gender,
|
||||||
structure_level: user.structure_level,
|
structure_level: user.structure_level,
|
||||||
number: user.number
|
number: user.number,
|
||||||
|
vote_delegated_from_users_id: user.vote_delegated_from_users_id
|
||||||
};
|
};
|
||||||
|
|
||||||
const dialogRef = this.dialog.open(this.userInfoDialog, infoDialogSettings);
|
const dialogRef = this.dialog.open(this.userInfoDialog, infoDialogSettings);
|
||||||
|
@ -82,9 +82,15 @@ export class ViewUser extends BaseProjectableViewModel<User> implements UserTitl
|
|||||||
getDialogTitle: () => this.getTitle()
|
getDialogTitle: () => this.getTitle()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public canVoteFor(user: ViewUser): boolean {
|
||||||
|
return this.vote_delegated_from_users_id.includes(user.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
interface IUserRelations {
|
interface IUserRelations {
|
||||||
groups: ViewGroup[];
|
groups: ViewGroup[];
|
||||||
|
voteDelegatedTo: ViewUser;
|
||||||
|
voteDelegationsFrom: ViewUser[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewUser extends User, IUserRelations {}
|
export interface ViewUser extends User, IUserRelations {}
|
||||||
|
Loading…
Reference in New Issue
Block a user