diff --git a/client/src/app/core/services/choice.service.ts b/client/src/app/core/services/choice.service.ts index 76fa7f4b6..fcff233de 100644 --- a/client/src/app/core/services/choice.service.ts +++ b/client/src/app/core/services/choice.service.ts @@ -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 { 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(); } diff --git a/client/src/app/shared/components/choice-dialog/choice-dialog.component.html b/client/src/app/shared/components/choice-dialog/choice-dialog.component.html index b3f8cce8b..605622f26 100644 --- a/client/src/app/shared/components/choice-dialog/choice-dialog.component.html +++ b/client/src/app/shared/components/choice-dialog/choice-dialog.component.html @@ -4,29 +4,41 @@ {{ getChoiceTitle(choice) | translate }} + + + + + {{ data.clearChoice | translate }} + + - + {{ getChoiceTitle(choice) | translate }} - - No choices available - +
+ +
+
+ +
diff --git a/client/src/app/shared/components/choice-dialog/choice-dialog.component.scss b/client/src/app/shared/components/choice-dialog/choice-dialog.component.scss index d504e250f..108bd7294 100644 --- a/client/src/app/shared/components/choice-dialog/choice-dialog.component.scss +++ b/client/src/app/shared/components/choice-dialog/choice-dialog.component.scss @@ -15,3 +15,7 @@ mat-radio-group { .scrollmenu-outer { max-height: inherit; } +mat-divider { + margin-top: 10px; + margin-bottom: 10px; +} diff --git a/client/src/app/shared/components/choice-dialog/choice-dialog.component.ts b/client/src/app/shared/components/choice-dialog/choice-dialog.component.ts index c728bbb59..31971506e 100644 --- a/client/src/app/shared/components/choice-dialog/choice-dialog.component.ts +++ b/client/src/app/shared/components/choice-dialog/choice-dialog.component.ts @@ -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(); } diff --git a/client/src/app/site/agenda/components/agenda-list/agenda-list.component.html b/client/src/app/site/agenda/components/agenda-list/agenda-list.component.html index b056f42c4..42b8c29a1 100644 --- a/client/src/app/site/agenda/components/agenda-list/agenda-list.component.html +++ b/client/src/app/site/agenda/components/agenda-list/agenda-list.component.html @@ -12,9 +12,6 @@ {{ selectedRows.length }} selected -
- -
@@ -110,12 +107,6 @@
- - - {{ selectedRows.length }} selected
- -
- -
@@ -40,10 +36,6 @@ {{ assignment.phase }} - - @@ -160,10 +157,6 @@
- +
-
+ + + + + + + - - - - - - - -
- - {{ selectedRows.length }} selected
- -
- -
@@ -100,10 +96,6 @@
-
- - - - - - - - - - - - diff --git a/client/src/app/site/users/components/user-list/user-list.component.ts b/client/src/app/site/users/components/user-list/user-list.component.ts index b48eae73a..87607dd94 100644 --- a/client/src/app/site/users/components/user-list/user-list.component.ts +++ b/client/src/app/site/users/components/user-list/user-list.component.ts @@ -131,23 +131,20 @@ export class UserListComponent extends ListViewBaseComponent 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 { - 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 { + 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,9 +156,15 @@ export class UserListComponent extends ListViewBaseComponent implement * Handler for bulk setting/unsetting the 'active' attribute. * Uses selectedRows defined via multiSelect mode. */ - public async setActiveSelected(active: boolean): Promise { - for (const user of this.selectedRows) { - await this.repo.update({ is_active: active }, user); + public async setActiveSelected(): Promise { + 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); + } } } @@ -169,9 +172,15 @@ export class UserListComponent extends ListViewBaseComponent implement * Handler for bulk setting/unsetting the 'is present' attribute. * Uses selectedRows defined via multiSelect mode. */ - public async setPresentSelected(present: boolean): Promise { - for (const user of this.selectedRows) { - await this.repo.update({ is_present: present }, user); + public async setPresentSelected(): Promise { + 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); + } } } @@ -179,9 +188,16 @@ export class UserListComponent extends ListViewBaseComponent implement * Handler for bulk setting/unsetting the 'is committee' attribute. * Uses selectedRows defined via multiSelect mode. */ - public async setCommitteeSelected(is_committee: boolean): Promise { - for (const user of this.selectedRows) { - await this.repo.update({ is_committee: is_committee }, user); + public async setCommitteeSelected(): Promise { + 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: committee }, user); + } } } @@ -194,7 +210,7 @@ export class UserListComponent extends ListViewBaseComponent implement } /** - * Handler for bulk resetting passwords. Needs multiSelect mode. + * Handler for bulk setting new passwords. Needs multiSelect mode. */ public async resetPasswordsSelected(): Promise { for (const user of this.selectedRows) { diff --git a/client/src/app/site/users/services/user-repository.service.ts b/client/src/app/site/users/services/user-repository.service.ts index 4abec8fd4..86d20548f 100644 --- a/client/src/app/site/users/services/user-repository.service.ts +++ b/client/src/app/site/users/services/user-repository.service.ts @@ -111,7 +111,8 @@ export class UserRepositoryService extends BaseRepository { } /** - * 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 { public async resetPassword(user: ViewUser, password: string): Promise { const path = `/rest/users/user/${user.id}/reset_password/`; await this.httpService.post(path, { password: password }); + await this.update({default_password: password}, user); } /**