Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 8 additions & 2 deletions client/src/app/domain/definitions/meeting-settings-defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,17 @@ export const meetingSettingsDefaults: Record<string, any> = {
motions_amendments_prefix: '-Ä',
motions_amendments_text_mode: 'paragraph',
motions_supporters_min_amount: 0,
poll_default_live_voting_enabled: false,
motions_enable_origin_motion_display: false,
motions_origin_motion_toggle_default: false,
assignment_poll_default_method: 'selection',
assignment_poll_add_candidates_to_list_of_speakers: false,
poll_enable_max_votes_per_option: false,
poll_default_live_voting_enabled: false,
poll_default_allow_invalid: false,
poll_default_allow_vote_split: false,
poll_projection_name_order_first: 'last_name',
poll_projection_max_columns: 6,
topic_poll_default_method: 'selection',
assignment_poll_default_method: 'selection',
users_allow_self_set_present: true,
users_enable_presence_view: false,
users_enable_vote_weight: false,
Expand Down
1 change: 1 addition & 0 deletions client/src/app/domain/models/meetings/meeting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ export class Meeting extends BaseModel<Meeting> {
`default_projector_countdown_ids`,
`default_projector_assignment_poll_ids`,
`default_projector_motion_poll_ids`,
`default_projector_topic_poll_ids`,
`default_group_id`,
`admin_group_id`,
`anonymous_group_id`,
Expand Down
8 changes: 4 additions & 4 deletions client/src/app/domain/models/projector/projection-default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const PROJECTIONDEFAULT = {
projectorCountdown: `countdown`,
assignmentPoll: `assignment_poll`,
motionPoll: `motion_poll`,
poll: `poll`
topicPoll: `topic_poll`
} as const;

export type ProjectiondefaultKey = keyof typeof PROJECTIONDEFAULT;
Expand All @@ -33,9 +33,9 @@ export const PROJECTIONDEFAULT_VERBOSE: Record<ProjectiondefaultKey, string> = {
mediafile: _(`Files`),
projectorMessage: _(`Messages`),
projectorCountdown: _(`Timers`),
assignmentPoll: _(`Ballots`),
motionPoll: _(`Motion votes`),
poll: _(`Polls`)
assignmentPoll: _(`Elections`),
motionPoll: _(`Motion polls`),
topicPoll: _(`Topic polls`)
};

export const PROJECTIONDEFAULTS = Object.values(PROJECTIONDEFAULT) as ProjectiondefaultValue[];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,43 @@
import { Component, inject } from '@angular/core';
import { Component, computed, effect, inject, input } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { PollState } from '@app/domain/models/poll';

import { ViewPoll } from '../../../pages/polls';

@Component({ template: `` })
export abstract class PollFormBaseComponent {
public form: UntypedFormGroup;

public data = input.required<Partial<ViewPoll>>();

public pollStarted = computed<boolean>(() => {
return this.data().state !== PollState.Created;
});

protected fb = inject(UntypedFormBuilder);

public constructor() {
this.initForm();

effect(this.onDataUpdated.bind(this));
}

public abstract initForm(): void;
protected abstract initForm(): void;
protected abstract getPatchedFormData(data: Partial<ViewPoll>): Record<string, unknown>;
public abstract getSerialzedForm(): Record<string, unknown>;

private onDataUpdated(): void {
if (!this.data() || !this.form) {
return;
}

const patch = this.getPatchedFormData(this.data());
for (const field of Object.keys(patch)) {
if (!this.form.get(field)?.pristine) {
delete patch[field];
}
}

this.form.patchValue(patch);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,45 @@ export class PollEditResultComponent implements OnInit {

public ngOnInit(): void {
const result = this.pollData().result ? JSON.parse(this.pollData().result) : {};
const builtOptions = this.options().map(opt => ({
value: {
yes: isNaN(+result[opt.key]) ? null : (result[opt.key] ?? null),
no: null,
abstain: null
},
majority: result[opt.key] === VOTE_MAJORITY,
majority_value: result[opt.key] === VOTE_MAJORITY ? YES_KEY : null
}));
const builtOptions =
this.configType() === 'approval'
? [
{
value: {
yes: this.parseResultAmount(result?.yes),
no: this.parseResultAmount(result?.no),
abstain: this.parseResultAmount(result?.abstain)
},
majority: null,
majority_value: this.getResultOptionMajorityKey(result)
}
]
: this.options().map(opt => {
const rOpt = result[opt.key] ?? {};
const majorityValue = this.getResultOptionMajorityKey(rOpt);

if (this.configType() === 'rating_approval') {
return {
value: {
yes: this.parseResultAmount(rOpt.yes),
no: this.parseResultAmount(rOpt.no),
abstain: this.parseResultAmount(rOpt.abstain)
},
majority: null,
majority_value: majorityValue
};
}

return {
value: {
yes: this.parseResultAmount(rOpt),
no: null,
abstain: null
},
majority: rOpt === VOTE_MAJORITY,
majority_value: majorityValue
};
});

this.model.set({
options: builtOptions,
Expand Down Expand Up @@ -197,4 +227,29 @@ export class PollEditResultComponent implements OnInit {
...(this.showNota() ? { nota: m.nota } : {})
};
}

private parseResultAmount(amount: any): number | null {
if (isNaN(+amount)) {
return null;
}

const parsedAmount = +amount;
if (parsedAmount === VOTE_UNDOCUMENTED || parsedAmount === VOTE_MAJORITY) {
return null;
}

return amount ?? null;
}

private getResultOptionMajorityKey(rOpt: any): SingleVoteOptionKey {
if (rOpt?.yes === VOTE_MAJORITY) {
return YES_KEY;
} else if (rOpt?.no === VOTE_MAJORITY) {
return NO_KEY;
} else if (rOpt?.abstain === VOTE_MAJORITY) {
return ABSTAIN_KEY;
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<form [formGroup]="form">
<div class="margin-bottom-15 info-grid">
<mat-form-field>
<mat-label>{{ 'Election method' | translate }}</mat-label>
<mat-select formControlName="allow_abstain">
<mat-option [value]="false">{{ 'Yes/No' | translate }}</mat-option>
<mat-option [value]="true">{{ 'Yes/No/Abstain' | translate }}</mat-option>
</mat-select>
</mat-form-field>
</div>
@if (!pollStarted()) {
<div class="margin-bottom-15 info-grid">
<mat-form-field>
<mat-label>{{ 'Election method' | translate }}</mat-label>
<mat-select formControlName="allow_abstain">
<mat-option [value]="false">{{ 'Yes/No' | translate }}</mat-option>
<mat-option [value]="true">{{ 'Yes/No/Abstain' | translate }}</mat-option>
</mat-select>
</mat-form-field>
</div>
}

<mat-form-field>
<mat-label>{{ '100% base' | translate }}</mat-label>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, effect, input } from '@angular/core';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatFormFieldModule } from '@angular/material/form-field';
Expand Down Expand Up @@ -27,29 +27,23 @@ export class PollFormApprovalComponent extends PollFormBaseComponent {
[`disabled`, _('Disabled (no percents)')]
];

public readonly data = input<Partial<ViewPoll>>({});

public initForm(): void {
protected initForm(): void {
this.form = this.fb.group({
onehundred_percent_base: [`valid`],
allow_abstain: [false]
});
}

protected getPatchedFormData(data: Partial<ViewPoll>): Record<string, unknown> {
const patch: Record<string, any> = {};
if (data.config?.allow_abstain !== undefined) patch[`allow_abstain`] = data.config.allow_abstain;
if (data.config?.onehundred_percent_base !== undefined)
patch[`onehundred_percent_base`] = data.config.onehundred_percent_base;

effect(this.updateData.bind(this));
return patch;
}

public getSerialzedForm(): Record<string, unknown> {
return this.form.value;
}

private updateData(): void {
const data = this.data();
if (data && this.form) {
const patch: Record<string, any> = {};
if (data.config?.allow_abstain !== undefined) patch[`allow_abstain`] = data.config.allow_abstain;
if (data.config?.onehundred_percent_base !== undefined)
patch[`onehundred_percent_base`] = data.config.onehundred_percent_base;
this.form.patchValue(patch);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
<form [formGroup]="form">
<div class="poll-preview-meta-info-form">
<mat-form-field>
<mat-label>{{ 'Election method' | translate }}</mat-label>
<mat-select formControlName="allow_abstain">
<mat-option [value]="false">{{ 'Yes/No' | translate }}</mat-option>
<mat-option [value]="true">{{ 'Yes/No/Abstain' | translate }}</mat-option>
</mat-select>
</mat-form-field>
<div class="margin-bottom-15 info-grid">
@if (!pollStarted()) {
<mat-form-field>
<mat-label>{{ 'Min amount of votes' | translate }}</mat-label>
<input formControlName="min_options_amount" matInput required type="number" />
<mat-label>{{ 'Election method' | translate }}</mat-label>
<mat-select formControlName="allow_abstain">
<mat-option [value]="false">{{ 'Yes/No' | translate }}</mat-option>
<mat-option [value]="true">{{ 'Yes/No/Abstain' | translate }}</mat-option>
</mat-select>
</mat-form-field>
<div class="margin-bottom-15 info-grid">
<mat-form-field>
<mat-label>{{ 'Min amount of votes' | translate }}</mat-label>
<input formControlName="min_options_amount" matInput required type="number" />
</mat-form-field>

<mat-form-field>
<mat-label>{{ 'Max amount of votes' | translate }}</mat-label>
<input formControlName="max_options_amount" matInput required type="number" />
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'Max amount of votes' | translate }}</mat-label>
<input formControlName="max_options_amount" matInput required type="number" />
</mat-form-field>

<mat-form-field>
<mat-label>{{ 'Max amount of yes votes' | translate }}</mat-label>
<input formControlName="max_yes_amount" matInput required type="number" />
</mat-form-field>
</div>
<mat-form-field>
<mat-label>{{ 'Max amount of yes votes' | translate }}</mat-label>
<input formControlName="max_yes_amount" matInput required type="number" />
</mat-form-field>
</div>
}

<mat-form-field>
<mat-label>{{ '100% base' | translate }}</mat-label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,12 @@ export class PollFormRatingApprovalComponent extends PollFormBaseComponent {
[`disabled`, _('Disabled (no percents)')]
];

public data = input<Partial<ViewPoll>>();
public optionAmount = input<number>(null);

public initForm(): void {
protected initForm(): void {
this.form = this.fb.group({
onehundred_percent_base: [this.data().config.onehundred_percent_base],
allow_abstain: [this.data().config.allow_abstain],
onehundred_percent_base: [`valid`],
allow_abstain: [false],
max_yes_amount: [1, [Validators.required, Validators.min(1)]],
max_options_amount: [1, [Validators.required, Validators.min(1)]],
min_options_amount: [1, [Validators.required, Validators.min(0), this.minOptionsAmountValidator()]]
Expand All @@ -50,6 +49,22 @@ export class PollFormRatingApprovalComponent extends PollFormBaseComponent {
effect(this.onOptionAmountUpdate.bind(this));
}

protected getPatchedFormData(data: Partial<ViewPoll>): Record<string, unknown> {
const patch: Record<string, unknown> = {};
for (const field of [
`onehundred_percent_base`,
`allow_abstain`,
`max_yes_amount`,
`max_options_amount`,
`min_options_amount`
]) {
if (data && data[field] !== undefined) patch[field] = data[field];
else if (data && data.config[field] !== undefined) patch[field] = data.config[field];
}

return patch;
}

public getSerialzedForm(): Record<string, unknown> {
return this.form.value;
}
Expand All @@ -74,8 +89,13 @@ export class PollFormRatingApprovalComponent extends PollFormBaseComponent {
if (optionAmount) {
maxCtrl?.setValidators([Validators.required, Validators.min(1), Validators.max(optionAmount)]);
maxYesCtrl?.setValidators([Validators.required, Validators.min(1), Validators.max(optionAmount)]);
maxCtrl?.setValue(optionAmount, { emitEvent: false });
maxYesCtrl.setValue(optionAmount, { emitEvent: false });
if (maxCtrl?.pristine) {
maxCtrl?.setValue(optionAmount, { emitEvent: false });
}

if (maxYesCtrl?.pristine) {
maxYesCtrl.setValue(optionAmount, { emitEvent: false });
}
} else {
maxCtrl?.setValidators([Validators.required, Validators.min(1)]);
maxYesCtrl?.setValidators([Validators.required, Validators.min(1)]);
Expand Down
Loading
Loading