Merge pull request #4075 from MaximilianKrambach/multiSelect-tweaks
Multiselect dialogs with multiple choice options
This commit is contained in:
commit
256f4e75a3
@ -27,17 +27,32 @@ export class ChoiceService extends OpenSlidesComponent {
|
||||
* Opens the dialog. Returns the chosen value after the user accepts.
|
||||
* @param title The title to display in the dialog
|
||||
* @param choices The available choices
|
||||
* @param multiSelect turn on the option to select multiple entries.
|
||||
* The answer.items will then be an array.
|
||||
* @param actions optional strings for buttons replacing the regular confirmation.
|
||||
* The answer.action will reflect the button selected
|
||||
* @param clearChoice A string for an extra, visually slightly separated
|
||||
* choice for 'explicitly set an empty selection'. The answer's action may
|
||||
* have this string's value
|
||||
* @returns an answer {@link ChoiceAnswer}
|
||||
*/
|
||||
public async open(
|
||||
title: string,
|
||||
choices: ChoiceDialogOptions,
|
||||
multiSelect: boolean = false
|
||||
multiSelect: boolean = false,
|
||||
actions?: string[],
|
||||
clearChoice?: string
|
||||
): Promise<ChoiceAnswer> {
|
||||
const dialogRef = this.dialog.open(ChoiceDialogComponent, {
|
||||
minWidth: '250px',
|
||||
maxHeight: '90vh',
|
||||
data: { title: title, choices: choices, multiSelect: multiSelect }
|
||||
data: {
|
||||
title: title,
|
||||
choices: choices,
|
||||
multiSelect: multiSelect,
|
||||
actionButtons: actions,
|
||||
clearChoice: clearChoice
|
||||
}
|
||||
});
|
||||
return dialogRef.afterClosed().toPromise();
|
||||
}
|
||||
|
@ -4,29 +4,41 @@
|
||||
<mat-radio-group
|
||||
#radio
|
||||
name="choice"
|
||||
*ngIf="!data.multiSelect"
|
||||
*ngIf="!data.multiSelect && data.choices"
|
||||
class="choice-radio-group"
|
||||
[(ngModel)]="selectedChoice"
|
||||
>
|
||||
<mat-radio-button class="choice-button" *ngFor="let choice of data.choices" [value]="choice.id">
|
||||
{{ getChoiceTitle(choice) | translate }}
|
||||
</mat-radio-button>
|
||||
|
||||
<mat-divider *ngIf="data.clearChoice"></mat-divider>
|
||||
|
||||
<mat-radio-button *ngIf="data.clearChoice" [value]="null">
|
||||
{{ data.clearChoice | translate }}
|
||||
</mat-radio-button>
|
||||
|
||||
</mat-radio-group>
|
||||
|
||||
<mat-list *ngIf="data.multiSelect">
|
||||
<mat-list *ngIf="data.multiSelect && data.choices">
|
||||
<mat-list-item *ngFor="let choice of data.choices">
|
||||
<mat-checkbox [checked]="isChosen(choice)" (change)="toggleChoice(choice)">
|
||||
{{ getChoiceTitle(choice) | translate }}
|
||||
</mat-checkbox>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
|
||||
<span *ngIf="!data.choices.length" translate>No choices available</span>
|
||||
</div>
|
||||
<mat-dialog-actions>
|
||||
<div *ngIf="data.actionButtons">
|
||||
<button *ngFor="let button of data.actionButtons" mat-button (click)="closeDialog(true, button)">
|
||||
<span translate>{{ button }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="!data.actionButtons">
|
||||
<button *ngIf="!data.multiSelect || data.choices.length" mat-button (click)="closeDialog(true)">
|
||||
<span translate>Ok</span>
|
||||
</button>
|
||||
</div>
|
||||
<button mat-button (click)="closeDialog(false)"><span translate>Cancel</span></button>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
|
@ -15,3 +15,7 @@ mat-radio-group {
|
||||
.scrollmenu-outer {
|
||||
max-height: inherit;
|
||||
}
|
||||
mat-divider {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
@ -29,19 +29,33 @@ interface ChoiceDialogData {
|
||||
choices: ChoiceDialogOptions;
|
||||
|
||||
/**
|
||||
* Select, if this should be a multiselect choice
|
||||
* Select if this should be a multiselect choice
|
||||
*/
|
||||
multiSelect: boolean;
|
||||
|
||||
/**
|
||||
* Additional action buttons which will add their value to the
|
||||
* {@link closeDialog} feedback if chosen
|
||||
*/
|
||||
actionButtons?: string[];
|
||||
|
||||
/**
|
||||
* An optional string for 'explicitly select none of the options'. Only
|
||||
* displayed in the single-select variation
|
||||
*/
|
||||
clearChoice?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* undefined is returned, if the dialog is closed. If a choice is submitted,
|
||||
* it might be a number oder an array of numbers for multiselect.
|
||||
* it will be an array of numbers and optionally an action string for multichoice
|
||||
* dialogs
|
||||
*/
|
||||
export type ChoiceAnswer = undefined | number | number[];
|
||||
export type ChoiceAnswer = undefined | { action?: string; items: number | number[]};
|
||||
|
||||
/**
|
||||
* A dialog with choice fields.
|
||||
*
|
||||
*/
|
||||
@Component({
|
||||
selector: 'os-choice-dialog',
|
||||
@ -81,9 +95,15 @@ export class ChoiceDialogComponent {
|
||||
/**
|
||||
* Closes the dialog with the selected choices
|
||||
*/
|
||||
public closeDialog(ok: boolean): void {
|
||||
public closeDialog(ok: boolean, action?: string): void {
|
||||
if (!this.data.multiSelect && this.selectedChoice === null) {
|
||||
action = this.data.clearChoice;
|
||||
}
|
||||
if (ok) {
|
||||
this.dialogRef.close(this.data.multiSelect ? this.selectedMultiChoices : this.selectedChoice);
|
||||
this.dialogRef.close({
|
||||
action: action ? action : null,
|
||||
items: this.data.multiSelect ? this.selectedMultiChoices : this.selectedChoice
|
||||
});
|
||||
} else {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
@ -12,9 +12,6 @@
|
||||
<span>{{ selectedRows.length }} </span><span translate>selected</span>
|
||||
</div>
|
||||
|
||||
<div class="extra-controls-slot on-transition-fade" *ngIf="isMultiSelect">
|
||||
<button mat-icon-button (click)="deleteSelected()"><mat-icon>delete</mat-icon></button>
|
||||
</div>
|
||||
</os-head-bar>
|
||||
|
||||
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
|
||||
@ -110,12 +107,6 @@
|
||||
</div>
|
||||
|
||||
<div *ngIf="isMultiSelect">
|
||||
<!-- Exit multi select -->
|
||||
<button mat-menu-item (click)="toggleMultiSelect()">
|
||||
<mat-icon>library_add</mat-icon>
|
||||
<span translate>Exit multiselect</span>
|
||||
</button>
|
||||
|
||||
<!-- Select all -->
|
||||
<button mat-menu-item (click)="selectAll()">
|
||||
<mat-icon>done_all</mat-icon>
|
||||
|
@ -13,10 +13,6 @@
|
||||
<button mat-icon-button (click)="toggleMultiSelect()"><mat-icon>arrow_back</mat-icon></button>
|
||||
<span>{{ selectedRows.length }} </span><span translate>selected</span>
|
||||
</div>
|
||||
|
||||
<div class="extra-controls-slot on-transition-fade" *ngIf="isMultiSelect">
|
||||
<button mat-icon-button (click)="deleteSelected()"><mat-icon>delete</mat-icon></button>
|
||||
</div>
|
||||
</os-head-bar>
|
||||
|
||||
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
|
||||
@ -40,10 +36,6 @@
|
||||
<mat-chip color="primary" selected>{{ assignment.phase }}</mat-chip>
|
||||
</mat-chip-list>
|
||||
</mat-cell>
|
||||
<button mat-menu-item (click)="toggleMultiSelect()">
|
||||
<mat-icon>library_add</mat-icon>
|
||||
<span translate>Exit multiselect</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="selectAll()">
|
||||
<mat-icon>done_all</mat-icon>
|
||||
<span translate>Select all</span>
|
||||
@ -87,10 +79,6 @@
|
||||
</div>
|
||||
|
||||
<div *ngIf="isMultiSelect">
|
||||
<button mat-menu-item (click)="toggleMultiSelect()">
|
||||
<mat-icon>library_add</mat-icon>
|
||||
<span translate>Exit multiselect</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="selectAll()">
|
||||
<mat-icon>done_all</mat-icon>
|
||||
<span translate>Select all</span>
|
||||
|
@ -46,9 +46,6 @@
|
||||
<span>{{ selectedRows.length }} </span><span translate>selected</span>
|
||||
</div>
|
||||
|
||||
<div class="extra-controls-slot on-transition-fade" *ngIf="isMultiSelect">
|
||||
<button mat-icon-button (click)="deleteSelected()"><mat-icon>delete</mat-icon></button>
|
||||
</div>
|
||||
</os-head-bar>
|
||||
|
||||
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
|
||||
@ -160,10 +157,6 @@
|
||||
</div>
|
||||
<div *ngIf="isMultiSelect">
|
||||
<mat-divider></mat-divider>
|
||||
<button mat-menu-item (click)="toggleMultiSelect()">
|
||||
<mat-icon>library_add</mat-icon>
|
||||
<span translate>Exit multiselect</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="selectAll()">
|
||||
<mat-icon>done_all</mat-icon>
|
||||
<span translate>Select all</span>
|
||||
|
@ -152,12 +152,12 @@
|
||||
<mat-icon>speaker_notes</mat-icon>
|
||||
<span translate>Comment fields</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="csvExportMotionList()">
|
||||
<mat-icon>archive</mat-icon>
|
||||
<span translate>Export as CSV</span>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="isMultiSelect">
|
||||
<button mat-menu-item (click)="toggleMultiSelect()">
|
||||
<mat-icon>library_add</mat-icon>
|
||||
<span translate>Exit multiselect</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="selectAll()">
|
||||
<mat-icon>done_all</mat-icon>
|
||||
<span translate>Select all</span>
|
||||
@ -168,53 +168,47 @@
|
||||
</button>
|
||||
<div *osPerms="'motions.can_manage'">
|
||||
<mat-divider></mat-divider>
|
||||
<button mat-menu-item (click)="multiselectWrapper(multiselectService.setStateOfMultiple(selectedRows))">
|
||||
<mat-icon>label</mat-icon>
|
||||
<span translate>Set status</span>
|
||||
</button>
|
||||
<button *ngIf="recomendationEnabled" mat-menu-item
|
||||
(click)="multiselectWrapper(multiselectService.setRecommendation(selectedRows))">
|
||||
<mat-icon>report</mat-icon>
|
||||
<!-- TODO: better icon -->
|
||||
<span translate>Set recommendation</span>
|
||||
</button>
|
||||
<button mat-menu-item *ngIf="categories.length"
|
||||
(click)="multiselectWrapper(multiselectService.setCategory(selectedRows))">
|
||||
<mat-icon>device_hub</mat-icon>
|
||||
<!-- TODO: icon -->
|
||||
<span translate>Set category</span>
|
||||
</button>
|
||||
<button mat-menu-item *ngIf="motionBlocks.length"
|
||||
(click)="multiselectWrapper(multiselectService.setMotionBlock(selectedRows))">
|
||||
<mat-icon>widgets</mat-icon>
|
||||
<!-- TODO: icon -->
|
||||
<span translate>Set motion block</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="multiselectWrapper(multiselectService.changeSubmitters(selectedRows))">
|
||||
<mat-icon>person_add</mat-icon>
|
||||
<!-- TODO: icon -->
|
||||
<span translate>Add/remove submitters</span>
|
||||
</button>
|
||||
<button mat-menu-item *ngIf="tags.length"
|
||||
(click)="multiselectWrapper(multiselectService.changeTags(selectedRows))">
|
||||
<mat-icon>bookmarks</mat-icon>
|
||||
<!-- TODO: icon -->
|
||||
<span translate>Add/remove tags</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="multiselectWrapper(multiselectService.moveToItem(selectedRows))">
|
||||
<!-- TODO: Not implemented yet -->
|
||||
<mat-icon>sort</mat-icon>
|
||||
<span translate>Move to agenda item</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="multiselectWrapper(multiselectService.setStateOfMultiple(selectedRows))">
|
||||
<mat-icon>label</mat-icon>
|
||||
<span translate>Set status</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="multiselectWrapper(multiselectService.setRecommendation(selectedRows))">
|
||||
<mat-icon>report</mat-icon>
|
||||
<!-- TODO: better icon -->
|
||||
<span translate>Set recommendation</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="multiselectWrapper(multiselectService.setCategory(selectedRows))">
|
||||
<mat-icon>device_hub</mat-icon>
|
||||
<!-- TODO: icon -->
|
||||
<span translate>Set category</span>
|
||||
</button>
|
||||
<mat-divider></mat-divider>
|
||||
<button mat-menu-item (click)="multiselectWrapper(multiselectService.addSubmitters(selectedRows))">
|
||||
<mat-icon>person_add</mat-icon>
|
||||
<!-- TODO: icon -->
|
||||
<span translate>Add submitters</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="multiselectWrapper(multiselectService.removeSubmitters(selectedRows))">
|
||||
<mat-icon>person_outline</mat-icon>
|
||||
<!-- TODO: icon -->
|
||||
<span translate>remove submitters</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="multiselectWrapper(multiselectService.addTags(selectedRows))">
|
||||
<mat-icon>bookmarks</mat-icon>
|
||||
<!-- TODO: icon -->
|
||||
<span translate>Add tags</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="multiselectWrapper(multiselectService.removeTags(selectedRows))">
|
||||
<mat-icon>bookmark_border</mat-icon>
|
||||
<!-- TODO: icon -->
|
||||
<span translate>Remove tags</span>
|
||||
</button>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<button mat-menu-item (click)="csvExportMotionList(); toggleMultiSelect()">
|
||||
<mat-icon>archive</mat-icon>
|
||||
<span translate>Export as CSV</span>
|
||||
</button>
|
||||
<mat-divider></mat-divider>
|
||||
<button
|
||||
mat-menu-item
|
||||
class="red-warning-text"
|
||||
|
@ -11,6 +11,14 @@ import { MotionRepositoryService } from '../../services/motion-repository.servic
|
||||
import { ViewMotion } from '../../models/view-motion';
|
||||
import { WorkflowState } from '../../../../shared/models/motions/workflow-state';
|
||||
import { MotionMultiselectService } from '../../services/motion-multiselect.service';
|
||||
import { TagRepositoryService } from 'app/site/tags/services/tag-repository.service';
|
||||
import { MotionBlockRepositoryService } from '../../services/motion-block-repository.service';
|
||||
import { CategoryRepositoryService } from '../../services/category-repository.service';
|
||||
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
import { ViewWorkflow } from '../../models/view-workflow';
|
||||
import { ViewCategory } from '../../models/view-category';
|
||||
import { ViewMotionBlock } from '../../models/view-motion-block';
|
||||
import { WorkflowRepositoryService } from '../../services/workflow-repository.service';
|
||||
|
||||
/**
|
||||
* Component that displays all the motions in a Table using DataSource.
|
||||
@ -39,6 +47,13 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
* @TODO replace by direct access to config variable, once it's available from the templates
|
||||
*/
|
||||
public statutesEnabled: boolean;
|
||||
public recomendationEnabled: boolean;
|
||||
|
||||
|
||||
public tags: ViewTag[] = [];
|
||||
public workflows: ViewWorkflow[] = [];
|
||||
public categories: ViewCategory[] = [];
|
||||
public motionBlocks: ViewMotionBlock[] = [];
|
||||
|
||||
/**
|
||||
* Constructor implements title and translation Module.
|
||||
@ -50,13 +65,11 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
* @param route Current route
|
||||
* @param configService The configuration provider
|
||||
* @param repo Motion Repository
|
||||
* @param promptService
|
||||
* @param motionCsvExport
|
||||
* @param workflowRepo Workflow Repository
|
||||
* @param tagRepo Tag Repository
|
||||
* @param motionBlockRepo
|
||||
* @param categoryRepo
|
||||
* @param userRepo
|
||||
* @param tagRepo
|
||||
* @param choiceService
|
||||
* @param motionCsvExport
|
||||
* @param multiselectService Service for the multiSelect actions
|
||||
*/
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
@ -66,6 +79,10 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
private route: ActivatedRoute,
|
||||
private configService: ConfigService,
|
||||
private repo: MotionRepositoryService,
|
||||
private tagRepo: TagRepositoryService,
|
||||
private motionBlockRepo: MotionBlockRepositoryService,
|
||||
private categoryRepo: CategoryRepositoryService,
|
||||
private workflowRepo: WorkflowRepositoryService,
|
||||
private motionCsvExport: MotionCsvExportService,
|
||||
public multiselectService: MotionMultiselectService
|
||||
) {
|
||||
@ -84,7 +101,6 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
super.setTitle('Motions');
|
||||
this.initTable();
|
||||
this.repo.getViewModelListObservable().subscribe(newMotions => {
|
||||
this.checkSelection();
|
||||
// TODO: This is for testing purposes. Can be removed with #3963
|
||||
this.dataSource.data = newMotions.sort((a, b) => {
|
||||
if (a.callListWeight !== b.callListWeight) {
|
||||
@ -93,8 +109,14 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
return a.id - b.id;
|
||||
}
|
||||
});
|
||||
this.checkSelection();
|
||||
});
|
||||
this.configService.get('motions_statutes_enabled').subscribe(enabled => (this.statutesEnabled = enabled));
|
||||
this.configService.get('motions_recommendations_by').subscribe(id => (this.recomendationEnabled = !!id));
|
||||
this.motionBlockRepo.getViewModelListObservable().subscribe(mBs => this.motionBlocks = mBs);
|
||||
this.categoryRepo.getViewModelListObservable().subscribe(cats => this.categories = cats);
|
||||
this.tagRepo.getViewModelListObservable().subscribe(tags => this.tags = tags);
|
||||
this.workflowRepo.getViewModelListObservable().subscribe(wfs => this.workflows = wfs);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -178,7 +200,11 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
*
|
||||
* @param multiselectPromise The promise returned by multiselect actions.
|
||||
*/
|
||||
public multiselectWrapper(multiselectPromise: Promise<void>): void {
|
||||
multiselectPromise.then(() => this.toggleMultiSelect(), this.raiseError);
|
||||
public async multiselectWrapper(multiselectPromise: Promise<void>): Promise<void> {
|
||||
try {
|
||||
await multiselectPromise;
|
||||
} catch (e) {
|
||||
this.raiseError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import { HttpService } from 'app/core/services/http.service';
|
||||
import { AgendaRepositoryService } from 'app/site/agenda/services/agenda-repository.service';
|
||||
import { Displayable } from 'app/shared/models/base/displayable';
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
import { MotionBlockRepositoryService } from './motion-block-repository.service';
|
||||
|
||||
/**
|
||||
* Contains all multiselect actions for the motion list view.
|
||||
@ -33,6 +34,9 @@ export class MotionMultiselectService {
|
||||
* @param workflowRepo
|
||||
* @param categoryRepo
|
||||
* @param tagRepo
|
||||
* @param agendaRepo
|
||||
* @param motionBlockRepo
|
||||
* @param httpService
|
||||
*/
|
||||
public constructor(
|
||||
private repo: MotionRepositoryService,
|
||||
@ -44,6 +48,7 @@ export class MotionMultiselectService {
|
||||
private categoryRepo: CategoryRepositoryService,
|
||||
private tagRepo: TagRepositoryService,
|
||||
private agendaRepo: AgendaRepositoryService,
|
||||
private motionBlockRepo: MotionBlockRepositoryService,
|
||||
private httpService: HttpService
|
||||
) {}
|
||||
|
||||
@ -71,7 +76,7 @@ export class MotionMultiselectService {
|
||||
if (selectedChoice) {
|
||||
const requestData = {
|
||||
items: motions.map(motion => motion.agenda_item_id),
|
||||
parent_id: selectedChoice as number
|
||||
parent_id: selectedChoice.items as number
|
||||
};
|
||||
await this.httpService.post('/rest/agenda/item/assign', requestData);
|
||||
}
|
||||
@ -84,14 +89,15 @@ export class MotionMultiselectService {
|
||||
*/
|
||||
public async setStateOfMultiple(motions: ViewMotion[]): Promise<void> {
|
||||
const title = this.translate.instant('This will set the state of all selected motions to:');
|
||||
const choices = this.workflowRepo.getAllWorkflowStates().map(workflowState => ({
|
||||
const choices = this.workflowRepo.getWorkflowStatesForMotions(motions)
|
||||
.map(workflowState => ({
|
||||
id: workflowState.id,
|
||||
label: workflowState.name
|
||||
}));
|
||||
const selectedChoice = await this.choiceService.open(title, choices);
|
||||
if (selectedChoice) {
|
||||
for (const motion of motions) {
|
||||
await this.repo.setState(motion, selectedChoice as number);
|
||||
await this.repo.setState(motion, selectedChoice.items as number);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,18 +110,19 @@ export class MotionMultiselectService {
|
||||
public async setRecommendation(motions: ViewMotion[]): Promise<void> {
|
||||
const title = this.translate.instant('This will set the recommendation for all selected motions to:');
|
||||
const choices = this.workflowRepo
|
||||
.getAllWorkflowStates()
|
||||
.getWorkflowStatesForMotions(motions)
|
||||
.filter(workflowState => !!workflowState.recommendation_label)
|
||||
.map(workflowState => ({
|
||||
id: workflowState.id,
|
||||
label: workflowState.recommendation_label
|
||||
}));
|
||||
choices.push({ id: 0, label: 'Delete recommendation' });
|
||||
const selectedChoice = await this.choiceService.open(title, choices);
|
||||
if (typeof selectedChoice === 'number') {
|
||||
const clearChoice = 'Delete recommendation';
|
||||
const selectedChoice = await this.choiceService.open(title, choices, false,
|
||||
null, clearChoice);
|
||||
if (selectedChoice) {
|
||||
const requestData = motions.map(motion => ({
|
||||
id: motion.id,
|
||||
recommendation: selectedChoice
|
||||
recommendation: selectedChoice.action ? 0 : selectedChoice.items as number
|
||||
}));
|
||||
await this.httpService.post('/rest/motions/motion/manage_multiple_recommendation', {
|
||||
motions: requestData
|
||||
@ -130,25 +137,31 @@ export class MotionMultiselectService {
|
||||
*/
|
||||
public async setCategory(motions: ViewMotion[]): Promise<void> {
|
||||
const title = this.translate.instant('This will set the category of all selected motions to:');
|
||||
const selectedChoice = await this.choiceService.open(title, this.categoryRepo.getViewModelList());
|
||||
const clearChoice = 'No category';
|
||||
const selectedChoice = await this.choiceService.open(title, this.categoryRepo.getViewModelList(),
|
||||
false, null, clearChoice);
|
||||
if (selectedChoice) {
|
||||
for (const motion of motions) {
|
||||
await this.repo.update({ category_id: selectedChoice as number }, motion);
|
||||
await this.repo.update(
|
||||
{category_id: selectedChoice.action ? 0 : selectedChoice.items as number },
|
||||
motion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a dialog and adds the selected submitters for all given motions.
|
||||
* Opens a dialog and adds or removes the selected submitters for all given motions.
|
||||
*
|
||||
* @param motions The motions to add the sumbitters to
|
||||
* @param motions The motions to add/remove the sumbitters to
|
||||
*/
|
||||
public async addSubmitters(motions: ViewMotion[]): Promise<void> {
|
||||
const title = this.translate.instant('This will add the following submitters of all selected motions:');
|
||||
const selectedChoice = await this.choiceService.open(title, this.userRepo.getViewModelList(), true);
|
||||
if (selectedChoice) {
|
||||
public async changeSubmitters(motions: ViewMotion[]): Promise<void> {
|
||||
const title = this.translate.instant('This will add or remove the following submitters for all selected motions:');
|
||||
const choices = ['Add', 'Remove'];
|
||||
const selectedChoice = await this.choiceService.open(title,
|
||||
this.userRepo.getViewModelList(), true, choices);
|
||||
if (selectedChoice && selectedChoice.action === choices[0]) {
|
||||
const requestData = motions.map(motion => {
|
||||
let submitterIds = [...motion.submitters_id, ...(selectedChoice as number[])];
|
||||
let submitterIds = [...motion.submitters_id, ...(selectedChoice.items as number[])];
|
||||
submitterIds = submitterIds.filter((id, index, self) => self.indexOf(id) === index); // remove duplicates
|
||||
return {
|
||||
id: motion.id,
|
||||
@ -156,20 +169,9 @@ export class MotionMultiselectService {
|
||||
};
|
||||
});
|
||||
await this.httpService.post('/rest/motions/motion/manage_multiple_submitters', { motions: requestData });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a dialog and removes the selected submitters for all given motions.
|
||||
*
|
||||
* @param motions The motions to remove the submitters from
|
||||
*/
|
||||
public async removeSubmitters(motions: ViewMotion[]): Promise<void> {
|
||||
const title = this.translate.instant('This will remove the following submitters from all selected motions:');
|
||||
const selectedChoice = await this.choiceService.open(title, this.userRepo.getViewModelList(), true);
|
||||
if (selectedChoice) {
|
||||
} else if (selectedChoice && selectedChoice.action === choices[1]) {
|
||||
const requestData = motions.map(motion => {
|
||||
const submitterIdsToRemove = selectedChoice as number[];
|
||||
const submitterIdsToRemove = selectedChoice.items as number[];
|
||||
const submitterIds = motion.submitters_id.filter(id => !submitterIdsToRemove.includes(id));
|
||||
return {
|
||||
id: motion.id,
|
||||
@ -181,16 +183,18 @@ export class MotionMultiselectService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a dialog and adds the selected tags for all given motions.
|
||||
* Opens a dialog and adds/removes the selected tags for all given motions.
|
||||
*
|
||||
* @param motions The motions to add the tags to
|
||||
*/
|
||||
public async addTags(motions: ViewMotion[]): Promise<void> {
|
||||
const title = this.translate.instant('This will add the following tags to all selected motions:');
|
||||
const selectedChoice = await this.choiceService.open(title, this.tagRepo.getViewModelList(), true);
|
||||
if (selectedChoice) {
|
||||
public async changeTags(motions: ViewMotion[]): Promise<void> {
|
||||
const title = this.translate.instant('This will add or remove the following tags for all selected motions:');
|
||||
const choices = ['Add', 'Remove', 'Clear tags'];
|
||||
const selectedChoice = await this.choiceService.open(title, this.tagRepo.getViewModelList(), true,
|
||||
choices);
|
||||
if (selectedChoice && selectedChoice.action === choices[0]) {
|
||||
const requestData = motions.map(motion => {
|
||||
let tagIds = [...motion.tags_id, ...(selectedChoice as number[])];
|
||||
let tagIds = [...motion.tags_id, ...(selectedChoice.items as number[])];
|
||||
tagIds = tagIds.filter((id, index, self) => self.indexOf(id) === index); // remove duplicates
|
||||
return {
|
||||
id: motion.id,
|
||||
@ -198,20 +202,9 @@ export class MotionMultiselectService {
|
||||
};
|
||||
});
|
||||
await this.httpService.post('/rest/motions/motion/manage_multiple_tags', { motions: requestData });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a dialog and removes the selected tags for all given motions.
|
||||
*
|
||||
* @param motions The motions to remove the tags from
|
||||
*/
|
||||
public async removeTags(motions: ViewMotion[]): Promise<void> {
|
||||
const title = this.translate.instant('This will remove the following tags from all selected motions:');
|
||||
const selectedChoice = await this.choiceService.open(title, this.tagRepo.getViewModelList(), true);
|
||||
if (selectedChoice) {
|
||||
} else if (selectedChoice && selectedChoice.action === choices[1]) {
|
||||
const requestData = motions.map(motion => {
|
||||
const tagIdsToRemove = selectedChoice as number[];
|
||||
const tagIdsToRemove = selectedChoice.items as number[];
|
||||
const tagIds = motion.tags_id.filter(id => !tagIdsToRemove.includes(id));
|
||||
return {
|
||||
id: motion.id,
|
||||
@ -219,6 +212,33 @@ export class MotionMultiselectService {
|
||||
};
|
||||
});
|
||||
await this.httpService.post('/rest/motions/motion/manage_multiple_tags', { motions: requestData });
|
||||
} else if (selectedChoice && selectedChoice.action === choices[2]) {
|
||||
const requestData = motions.map(motion => {
|
||||
return {
|
||||
id: motion.id,
|
||||
tags: []
|
||||
};
|
||||
});
|
||||
await this.httpService.post('/rest/motions/motion/manage_multiple_tags', { motions: requestData });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Opens a dialog and changes the motionBlock for all given motions.
|
||||
*
|
||||
* @param motions The motions for which to change the motionBlock
|
||||
*/
|
||||
public async setMotionBlock(motions: ViewMotion[]): Promise<void> {
|
||||
const title = this.translate.instant('This will change the motion Block for all selected motions:');
|
||||
const clearChoice = 'Clear motion block';
|
||||
const selectedChoice = await this.choiceService.open(title, this.motionBlockRepo.getViewModelList(),
|
||||
false, null, clearChoice);
|
||||
if (selectedChoice) {
|
||||
for (const motion of motions) {
|
||||
const blockId = selectedChoice.action ? null : selectedChoice.items as number;
|
||||
await this.repo.update({motion_block_id: blockId}, motion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { BaseRepository } from '../../base/base-repository';
|
||||
import { Identifiable } from '../../../shared/models/base/identifiable';
|
||||
import { CollectionStringModelMapperService } from '../../../core/services/collectionStringModelMapper.service';
|
||||
import { WorkflowState } from 'app/shared/models/motions/workflow-state';
|
||||
import { ViewMotion } from '../models/view-motion';
|
||||
|
||||
/**
|
||||
* Repository Services for Categories
|
||||
@ -70,4 +71,18 @@ export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Work
|
||||
});
|
||||
return states;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all workflowStates that cover the list of viewMotions given
|
||||
* @param motions
|
||||
*/
|
||||
public getWorkflowStatesForMotions(motions: ViewMotion[]): WorkflowState[] {
|
||||
let states: WorkflowState[] = [];
|
||||
const workflowIds = motions.map(motion => motion.workflow_id).filter((value, index, self) => self.indexOf(value) === index);
|
||||
workflowIds.forEach(id => {
|
||||
const workflow = this.getViewModel(id);
|
||||
states = states.concat(workflow.states);
|
||||
});
|
||||
return states;
|
||||
}
|
||||
}
|
||||
|
@ -12,10 +12,6 @@
|
||||
<button mat-icon-button (click)="toggleMultiSelect()"><mat-icon>arrow_back</mat-icon></button>
|
||||
<span>{{ selectedRows.length }} </span><span translate>selected</span>
|
||||
</div>
|
||||
|
||||
<div class="extra-controls-slot on-transition-fade" *ngIf="isMultiSelect">
|
||||
<button mat-icon-button (click)="deleteSelected()"><mat-icon>delete</mat-icon></button>
|
||||
</div>
|
||||
</os-head-bar>
|
||||
|
||||
<div class="custom-table-header on-transition-fade">
|
||||
@ -100,10 +96,6 @@
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="isMultiSelect">
|
||||
<button mat-menu-item (click)="toggleMultiSelect()">
|
||||
<mat-icon>library_add</mat-icon>
|
||||
<span translate>Exit multiselect</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="selectAll()">
|
||||
<mat-icon>done_all</mat-icon>
|
||||
<span translate>Select all</span>
|
||||
@ -114,42 +106,23 @@
|
||||
</button>
|
||||
<div *osPerms="'users.can_manage'">
|
||||
<mat-divider></mat-divider>
|
||||
<button mat-menu-item (click)="setGroupSelected(true)">
|
||||
<button mat-menu-item (click)="setGroupSelected()">
|
||||
<mat-icon>people</mat-icon>
|
||||
<span translate>Add groups</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="setGroupSelected(false)">
|
||||
<mat-icon>people_outline</mat-icon>
|
||||
<span translate>Remove groups</span>
|
||||
<span translate>Add/remove groups</span>
|
||||
</button>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<button mat-menu-item (click)="setActiveSelected(true)">
|
||||
<button mat-menu-item (click)="setActiveSelected()">
|
||||
<mat-icon>add_circle</mat-icon>
|
||||
<span translate>Is active</span>
|
||||
<span translate>Set/unset active</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="setActiveSelected(false)">
|
||||
<mat-icon>remove_circle</mat-icon>
|
||||
<span translate>Is inactive</span>
|
||||
</button>
|
||||
<mat-divider></mat-divider>
|
||||
<button mat-menu-item (click)="setPresentSelected(true)">
|
||||
|
||||
<button mat-menu-item (click)="setPresentSelected()">
|
||||
<mat-icon>check_box</mat-icon>
|
||||
<span translate>Is present</span>
|
||||
<span translate>Set/unset presence</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="setPresentSelected(false)">
|
||||
<mat-icon>check_box_outline_blank</mat-icon>
|
||||
<span translate>Is not present</span>
|
||||
</button>
|
||||
<mat-divider></mat-divider>
|
||||
<button mat-menu-item (click)="setCommitteeSelected(true)">
|
||||
<button mat-menu-item (click)="setCommitteeSelected()">
|
||||
<mat-icon>account_balance</mat-icon>
|
||||
<span translate>Is committee</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="setCommitteeSelected(false)">
|
||||
<mat-icon>account_balance</mat-icon>
|
||||
<span translate>Is no committee</span>
|
||||
<span translate>Set/unset committee</span>
|
||||
</button>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
|
@ -131,23 +131,20 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
||||
* Opens a dialog and sets the group(s) for all selected users.
|
||||
* SelectedRows is only filled with data in multiSelect mode
|
||||
*/
|
||||
public async setGroupSelected(add: boolean): Promise<void> {
|
||||
let content: string;
|
||||
if (add) {
|
||||
content = this.translate.instant('This will add the following groups to all selected users:');
|
||||
} else {
|
||||
content = this.translate.instant('This will remove the following groups from all selected users:');
|
||||
}
|
||||
const selectedChoice = await this.choiceService.open(content, this.groupRepo.getViewModelList(), true);
|
||||
public async setGroupSelected(): Promise<void> {
|
||||
const content = this.translate.instant('This will add or remove the following groups for all selected users:');
|
||||
const choices = ['Add group(s)', 'Remove group(s)'];
|
||||
const selectedChoice = await this.choiceService.open(content,
|
||||
this.groupRepo.getViewModelList(), true, choices);
|
||||
if (selectedChoice) {
|
||||
for (const user of this.selectedRows) {
|
||||
const newGroups = [...user.groups_id];
|
||||
(selectedChoice as number[]).forEach(newChoice => {
|
||||
(selectedChoice.items as number[]).forEach(newChoice => {
|
||||
const idx = newGroups.indexOf(newChoice);
|
||||
if (idx < 0 && add) {
|
||||
if (idx < 0 && selectedChoice.action === choices[0]) {
|
||||
newGroups.push(newChoice);
|
||||
} else if (idx >= 0 && !add) {
|
||||
newGroups.slice(idx, 1);
|
||||
} else if (idx >= 0 && selectedChoice.action === choices[1]) {
|
||||
newGroups.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
await this.repo.update({ groups_id: newGroups }, user);
|
||||
@ -159,29 +156,48 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
||||
* Handler for bulk setting/unsetting the 'active' attribute.
|
||||
* Uses selectedRows defined via multiSelect mode.
|
||||
*/
|
||||
public async setActiveSelected(active: boolean): Promise<void> {
|
||||
public async setActiveSelected(): Promise<void> {
|
||||
const content = this.translate.instant('Set the active status for the selected users');
|
||||
const options = ['Active', 'Not active'];
|
||||
const selectedChoice = await this.choiceService.open(content, null, false, options);
|
||||
if (selectedChoice) {
|
||||
const active = selectedChoice.action === options[0];
|
||||
for (const user of this.selectedRows) {
|
||||
await this.repo.update({ is_active: active }, user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for bulk setting/unsetting the 'is present' attribute.
|
||||
* Uses selectedRows defined via multiSelect mode.
|
||||
*/
|
||||
public async setPresentSelected(present: boolean): Promise<void> {
|
||||
public async setPresentSelected(): Promise<void> {
|
||||
const content = this.translate.instant('Set the presence status for the selected users');
|
||||
const options = ['Present', 'Not present'];
|
||||
const selectedChoice = await this.choiceService.open(content, null, false, options);
|
||||
if (selectedChoice) {
|
||||
const present = selectedChoice.action === options[0];
|
||||
for (const user of this.selectedRows) {
|
||||
await this.repo.update({ is_present: present }, user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for bulk setting/unsetting the 'is committee' attribute.
|
||||
* Uses selectedRows defined via multiSelect mode.
|
||||
*/
|
||||
public async setCommitteeSelected(is_committee: boolean): Promise<void> {
|
||||
public async setCommitteeSelected(): Promise<void> {
|
||||
const content = this.translate.instant(
|
||||
'Sets/unsets the committee status for the selected users');
|
||||
const options = ['Is committee', 'Is not committee'];
|
||||
const selectedChoice = await this.choiceService.open(content, null, false, options);
|
||||
if (selectedChoice) {
|
||||
const committee = selectedChoice.action === options[0];
|
||||
for (const user of this.selectedRows) {
|
||||
await this.repo.update({ is_committee: is_committee }, user);
|
||||
await this.repo.update({ is_committee: committee }, user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,7 +210,7 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for bulk resetting passwords. Needs multiSelect mode.
|
||||
* Handler for bulk setting new passwords. Needs multiSelect mode.
|
||||
*/
|
||||
public async resetPasswordsSelected(): Promise<void> {
|
||||
for (const user of this.selectedRows) {
|
||||
|
@ -111,7 +111,8 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the password and sets the password without checking for the old one
|
||||
* Updates the password and sets the password without checking for the old one.
|
||||
* Also resets the 'default password' to the newly created one.
|
||||
*
|
||||
* @param user The user to update
|
||||
* @param password The password to set
|
||||
@ -119,6 +120,7 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
|
||||
public async resetPassword(user: ViewUser, password: string): Promise<void> {
|
||||
const path = `/rest/users/user/${user.id}/reset_password/`;
|
||||
await this.httpService.post(path, { password: password });
|
||||
await this.update({default_password: password}, user);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user