Merge pull request #4322 from MaximilianKrambach/motionDelegates

direct pdf export and hidden menus for unprivileged users
This commit is contained in:
Emanuel Schütze 2019-02-13 15:37:07 +01:00 committed by GitHub
commit c5912f8515
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 203 additions and 121 deletions

View File

@ -1,4 +1,4 @@
<os-head-bar [mainButton]="true" (mainEvent)="onPlusButton()" [multiSelectMode]="isMultiSelect"> <os-head-bar [mainButton]="canManage" (mainEvent)="onPlusButton()" [multiSelectMode]="isMultiSelect">
<!-- Title --> <!-- Title -->
<div class="title-slot"><h2 translate>Agenda</h2></div> <div class="title-slot"><h2 translate>Agenda</h2></div>
<!-- Menu --> <!-- Menu -->
@ -80,7 +80,9 @@
<ng-container matColumnDef="menu"> <ng-container matColumnDef="menu">
<mat-header-cell *matHeaderCellDef mat-sort-header>Menu</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header>Menu</mat-header-cell>
<mat-cell *matCellDef="let item"> <mat-cell *matCellDef="let item">
<button mat-icon-button [matMenuTriggerFor]="singleItemMenu" [matMenuTriggerData]="{ item: item }"> <button mat-icon-button
*osPerms="'agenda.can_manage'"
[matMenuTriggerFor]="singleItemMenu" [matMenuTriggerData]="{ item: item }">
<mat-icon>more_vert</mat-icon> <mat-icon>more_vert</mat-icon>
</button> </button>
</mat-cell> </mat-cell>
@ -121,7 +123,7 @@
<span translate>Current list of speakers</span> <span translate>Current list of speakers</span>
</button> </button>
<!-- CSV export --> <!-- CSV export -->
<button mat-menu-item (click)="csvExportItemList()"> <button mat-menu-item *osPerms="'agenda.can_manage'" (click)="csvExportItemList()">
<mat-icon>archive</mat-icon> <mat-icon>archive</mat-icon>
<span translate>Export as CSV</span> <span translate>Export as CSV</span>
</button> </button>
@ -201,17 +203,6 @@
<span translate>Done</span> <span translate>Done</span>
</button> </button>
<!-- List of speakers for mobile -->
<button mat-menu-item (click)="onSpeakerIcon(item)" *ngIf="vp.isMobile">
<mat-icon
[matBadge]="item.waitingSpeakerAmount > 0 ? item.waitingSpeakerAmount : null"
matBadgeColor="accent"
>
mic
</mat-icon>
<span translate>List of speakers</span>
</button>
<!-- Edit button --> <!-- Edit button -->
<button mat-menu-item (click)="openEditInfo(item)"> <button mat-menu-item (click)="openEditInfo(item)">
<mat-icon>edit</mat-icon> <mat-icon>edit</mat-icon>

View File

@ -17,6 +17,7 @@ import { DurationService } from 'app/core/ui-services/duration.service';
import { ItemInfoDialogComponent } from '../item-info-dialog/item-info-dialog.component'; import { ItemInfoDialogComponent } from '../item-info-dialog/item-info-dialog.component';
import { PdfDocumentService } from 'app/core/ui-services/pdf-document.service'; import { PdfDocumentService } from 'app/core/ui-services/pdf-document.service';
import { ViewportService } from 'app/core/ui-services/viewport.service'; import { ViewportService } from 'app/core/ui-services/viewport.service';
import { OperatorService } from 'app/core/core-services/operator.service';
/** /**
* List view for the agenda. * List view for the agenda.
@ -35,10 +36,19 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
/** /**
* Determine the display columns in mobile view * Determine the display columns in mobile view
*/ */
public displayedColumnsMobile: string[] = ['title', 'menu']; public displayedColumnsMobile: string[] = ['title', 'speakers', 'menu'];
public isNumberingAllowed: boolean; public isNumberingAllowed: boolean;
/**
* Helper to check main button permissions
*
* @returns true if the operator can manage agenda items
*/
public get canManage(): boolean {
return this.operator.hasPerms('agenda.can_manage');
}
/** /**
* The usual constructor for components * The usual constructor for components
* @param titleService Setting the browser tab title * @param titleService Setting the browser tab title
@ -56,6 +66,7 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
* @param filterService: service for filtering data * @param filterService: service for filtering data
* @param agendaPdfService: service for preparing a pdf of the agenda * @param agendaPdfService: service for preparing a pdf of the agenda
* @param pdfService: Service for exporting a pdf * @param pdfService: Service for exporting a pdf
* @param operator the current user
*/ */
public constructor( public constructor(
titleService: Title, titleService: Title,
@ -72,7 +83,8 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
private csvExport: AgendaCsvExportService, private csvExport: AgendaCsvExportService,
public filterService: AgendaFilterListService, public filterService: AgendaFilterListService,
private agendaPdfService: AgendaPdfService, private agendaPdfService: AgendaPdfService,
private pdfService: PdfDocumentService private pdfService: PdfDocumentService,
private operator: OperatorService
) { ) {
super(titleService, translate, matSnackBar); super(titleService, translate, matSnackBar);
@ -116,6 +128,9 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
* @param item The view item that was clicked * @param item The view item that was clicked
*/ */
public openEditInfo(item: ViewItem): void { public openEditInfo(item: ViewItem): void {
if (!this.canManage) {
return;
}
const dialogRef = this.dialog.open(ItemInfoDialogComponent, { const dialogRef = this.dialog.open(ItemInfoDialogComponent, {
width: '400px', width: '400px',
data: item, data: item,

View File

@ -30,8 +30,14 @@
</os-head-bar> </os-head-bar>
<mat-card> <mat-card>
<button mat-raised-button color="primary" (click)="onFollowRecButton()" [disabled]="isFollowingProhibited()"> <button
<mat-icon>done_all</mat-icon> *osPerms="['motions.can_manage', 'motions.can_manage_metadata']"
mat-raised-button
color="primary"
(click)="onFollowRecButton()"
[disabled]="isFollowingProhibited()"
>
<mat-icon>done_all</mat-icon>&nbsp;
<span translate>Follow recommendations for all motions</span> <span translate>Follow recommendations for all motions</span>
</button> </button>
@ -98,20 +104,20 @@
<span translate>List of speakers</span> <span translate>List of speakers</span>
</button> </button>
<button mat-menu-item> <button mat-menu-item *osPerms="'core.can_manage_projector'">
<mat-icon>videocam</mat-icon> <mat-icon>videocam</mat-icon>
<span translate>Project</span> <span translate>Project</span>
</button> </button>
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<button mat-menu-item (click)="toggleEditMode()">
<mat-icon>edit</mat-icon>
<span translate>Edit title</span>
</button>
<mat-divider></mat-divider>
<mat-divider></mat-divider> <button mat-menu-item class="red-warning-text" (click)="onDeleteBlockButton()">
<mat-icon>delete</mat-icon>
<button mat-menu-item (click)="toggleEditMode()"> <span translate>Delete</span>
<mat-icon>edit</mat-icon> </button>
<span translate>Edit title</span> </div>
</button>
<button mat-menu-item class="red-warning-text" (click)="onDeleteBlockButton()">
<mat-icon>delete</mat-icon>
<span translate>Delete</span>
</button>
</mat-menu> </mat-menu>

View File

@ -13,6 +13,7 @@ import { MotionRepositoryService } from 'app/core/repositories/motions/motion-re
import { ViewMotionBlock } from '../../models/view-motion-block'; import { ViewMotionBlock } from '../../models/view-motion-block';
import { PromptService } from 'app/core/ui-services/prompt.service'; import { PromptService } from 'app/core/ui-services/prompt.service';
import { ViewMotion } from '../../models/view-motion'; import { ViewMotion } from '../../models/view-motion';
import { OperatorService } from '../../../../core/core-services/operator.service';
/** /**
* Detail component to display one motion block * Detail component to display one motion block
@ -50,6 +51,7 @@ export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion
* @param titleService Setting the title * @param titleService Setting the title
* @param translate translations * @param translate translations
* @param matSnackBar showing errors * @param matSnackBar showing errors
* @param operator the current user
* @param router navigating * @param router navigating
* @param route determine the blocks ID by the route * @param route determine the blocks ID by the route
* @param repo the motion blocks repository * @param repo the motion blocks repository
@ -60,6 +62,7 @@ export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion
titleService: Title, titleService: Title,
translate: TranslateService, translate: TranslateService,
matSnackBar: MatSnackBar, matSnackBar: MatSnackBar,
private operator: OperatorService,
private router: Router, private router: Router,
private route: ActivatedRoute, private route: ActivatedRoute,
private repo: MotionBlockRepositoryService, private repo: MotionBlockRepositoryService,
@ -117,7 +120,11 @@ export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion
* @returns an array of strings building the column definition * @returns an array of strings building the column definition
*/ */
public getColumnDefinition(): string[] { public getColumnDefinition(): string[] {
return ['title', 'state', 'recommendation', 'remove']; let columns = ['title', 'state', 'recommendation'];
if (this.operator.hasPerms('motions.can_manage_manage')) {
columns = columns.concat('remove');
}
return columns;
} }
/** /**

View File

@ -1,4 +1,4 @@
<os-head-bar [nav]="false" [mainButton]="true" (mainEvent)="onPlusButton()"> <os-head-bar [nav]="false" [mainButton]="canEdit" (mainEvent)="onPlusButton()">
<!-- Title --> <!-- Title -->
<div class="title-slot"><h2 translate>Motion blocks</h2></div> <div class="title-slot"><h2 translate>Motion blocks</h2></div>
</os-head-bar> </os-head-bar>
@ -7,17 +7,11 @@
<mat-card class="os-card" *ngIf="blockToCreate"> <mat-card class="os-card" *ngIf="blockToCreate">
<mat-card-title translate>New motion block</mat-card-title> <mat-card-title translate>New motion block</mat-card-title>
<mat-card-content> <mat-card-content>
<form [formGroup]="createBlockForm" <form [formGroup]="createBlockForm" (keydown)="onKeyDown($event)">
(keydown)="onKeyDown($event)">
<!-- Title --> <!-- Title -->
<p> <p>
<mat-form-field> <mat-form-field>
<input <input formControlName="title" matInput placeholder="{{ 'Title' | translate }}" required />
formControlName="title"
matInput
placeholder="{{ 'Title' | translate }}"
required
/>
<mat-error *ngIf="createBlockForm.get('title').hasError('required')" translate> <mat-error *ngIf="createBlockForm.get('title').hasError('required')" translate>
A name is required A name is required
</mat-error> </mat-error>
@ -76,7 +70,12 @@
<ng-container matColumnDef="menu"> <ng-container matColumnDef="menu">
<mat-header-cell *matHeaderCellDef>Menu</mat-header-cell> <mat-header-cell *matHeaderCellDef>Menu</mat-header-cell>
<mat-cell *matCellDef="let block"> <mat-cell *matCellDef="let block">
<button mat-icon-button [matMenuTriggerFor]="singleItemMenu" [matMenuTriggerData]="{ item: block }"> <button
*ngIf="canEdit"
mat-icon-button
[matMenuTriggerFor]="singleItemMenu"
[matMenuTriggerData]="{ item: block }"
>
<mat-icon>more_vert</mat-icon> <mat-icon>more_vert</mat-icon>
</button> </button>
</mat-cell> </mat-cell>

View File

@ -7,14 +7,15 @@ import { MatSnackBar } from '@angular/material';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
import { ListViewBaseComponent } from 'app/site/base/list-view-base'; import { ListViewBaseComponent } from 'app/site/base/list-view-base';
import { MotionBlock } from 'app/shared/models/motions/motion-block'; import { MotionBlock } from 'app/shared/models/motions/motion-block';
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service'; import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service';
import { ViewMotionBlock } from '../../models/view-motion-block'; import { OperatorService } from 'app/core/core-services/operator.service';
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
import { PromptService } from 'app/core/ui-services/prompt.service'; import { PromptService } from 'app/core/ui-services/prompt.service';
import { ViewItem } from 'app/site/agenda/models/view-item'; import { ViewItem } from 'app/site/agenda/models/view-item';
import { ViewMotionBlock } from '../../models/view-motion-block';
/** /**
* Table for the motion blocks * Table for the motion blocks
@ -50,6 +51,15 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
*/ */
public itemVisibility = itemVisibilityChoices; public itemVisibility = itemVisibilityChoices;
/**
* helper for permission checks
*
* @returns true if the user may alter motions or their metadata
*/
public get canEdit(): boolean {
return this.operator.hasPerms('motions.can_manage', 'motions.can_manage_metadata');
}
/** /**
* Constructor for the motion block list view * Constructor for the motion block list view
* *
@ -63,6 +73,8 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
* @param DS the dataStore * @param DS the dataStore
* @param formBuilder creates forms * @param formBuilder creates forms
* @param promptService the delete prompt * @param promptService the delete prompt
* @param itemRepo
* @param operator permission checks
*/ */
public constructor( public constructor(
titleService: Title, titleService: Title,
@ -74,7 +86,8 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
private agendaRepo: ItemRepositoryService, private agendaRepo: ItemRepositoryService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private promptService: PromptService, private promptService: PromptService,
private itemRepo: ItemRepositoryService private itemRepo: ItemRepositoryService,
private operator: OperatorService
) { ) {
super(titleService, translate, matSnackBar); super(titleService, translate, matSnackBar);

View File

@ -71,7 +71,7 @@
<span translate>List of speakers</span> <span translate>List of speakers</span>
</button> </button>
<!-- Project --> <!-- Project -->
<button mat-menu-item> <button mat-menu-item *osPerms="'core.can_manage_projector'">
<mat-icon>videocam</mat-icon> <mat-icon>videocam</mat-icon>
<span translate>Project</span> <span translate>Project</span>
</button> </button>

View File

@ -193,6 +193,12 @@
<span translate>Import</span> <span translate>Import</span>
</button> </button>
</div> </div>
<div *ngIf="!perms.isAllowed('manage')">
<button mat-menu-item (click)="directPdfExport()">
<mat-icon>archive</mat-icon>
<span translate>Export as PDF</span>
</button>
</div>
</div> </div>
<div *ngIf="isMultiSelect"> <div *ngIf="isMultiSelect">
<button mat-menu-item (click)="selectAll()"> <button mat-menu-item (click)="selectAll()">

View File

@ -17,7 +17,7 @@ import { MotionRepositoryService } from 'app/core/repositories/motions/motion-re
import { MotionSortListService } from '../../services/motion-sort-list.service'; import { MotionSortListService } from '../../services/motion-sort-list.service';
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service'; import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
import { ViewCategory } from '../../models/view-category'; import { ViewCategory } from '../../models/view-category';
import { ViewMotion } from '../../models/view-motion'; import { ViewMotion, LineNumberingMode, ChangeRecoMode } from '../../models/view-motion';
import { ViewMotionBlock } from '../../models/view-motion-block'; import { ViewMotionBlock } from '../../models/view-motion-block';
import { ViewTag } from 'app/site/tags/models/view-tag'; import { ViewTag } from 'app/site/tags/models/view-tag';
import { ViewWorkflow } from '../../models/view-workflow'; import { ViewWorkflow } from '../../models/view-workflow';
@ -274,4 +274,15 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
public getStateLabel(motion: ViewMotion): string { public getStateLabel(motion: ViewMotion): string {
return this.motionRepo.getExtendedStateLabel(motion); return this.motionRepo.getExtendedStateLabel(motion);
} }
/**
* Directly export all motions as pdf, using the current default config settings
*/
public directPdfExport(): void {
this.pdfExport.exportMotionCatalog(
this.dataSource.data,
this.configService.instant<string>('motions_default_line_numbering') as LineNumberingMode,
this.configService.instant<string>('motions_recommendation_text_mode') as ChangeRecoMode
);
}
} }

View File

@ -108,6 +108,7 @@ export class LocalPermissionsService {
(motion.state && (motion.state &&
motion.state.allow_submitter_edit && motion.state.allow_submitter_edit &&
motion.submitters && motion.submitters &&
motion.submitters.length &&
motion.submitters.some(submitter => submitter.id === this.operator.user.id)) motion.submitters.some(submitter => submitter.id === this.operator.user.id))
); );
} }
@ -123,6 +124,7 @@ export class LocalPermissionsService {
motion.state && motion.state &&
motion.state.allow_submitter_edit && motion.state.allow_submitter_edit &&
motion.submitters && motion.submitters &&
motion.submitters.length &&
motion.submitters.some(submitter => submitter.id === this.operator.user.id) motion.submitters.some(submitter => submitter.id === this.operator.user.id)
); );
} }

View File

@ -13,7 +13,7 @@
</div> </div>
</a> </a>
</div> </div>
<div class="column-right"> <div class="column-right" *osPerms="'core.can_manage_projector'">
<div class="control-group"> <div class="control-group">
<div class="button-size">{{ projector.scroll }}</div> <div class="button-size">{{ projector.scroll }}</div>
<button type="button" mat-icon-button (click)="scroll(scrollScaleDirection.Up)"> <button type="button" mat-icon-button (click)="scroll(scrollScaleDirection.Up)">

View File

@ -1,4 +1,4 @@
<os-head-bar [nav]="true" [mainButton]="true" (mainEvent)="onPlusButton()"> <os-head-bar [nav]="true" [mainButton]="canManage" (mainEvent)="onPlusButton()">
<!-- Title --> <!-- Title -->
<div class="title-slot"> <div class="title-slot">
<h2 translate>Projectors</h2> <h2 translate>Projectors</h2>
@ -44,7 +44,7 @@
<ng-container class="meta-text-block-title"> <ng-container class="meta-text-block-title">
{{ projector.name | translate }} {{ projector.name | translate }}
</ng-container> </ng-container>
<ng-container class="meta-text-block-action-row"> <ng-container class="meta-text-block-action-row" *ngIf="canManage">
<button mat-icon-button *ngIf="editId !== projector.id" (click)=onEditButton(projector)> <button mat-icon-button *ngIf="editId !== projector.id" (click)=onEditButton(projector)>
<mat-icon>edit</mat-icon> <mat-icon>edit</mat-icon>
</button> </button>

View File

@ -11,6 +11,7 @@ import { Projector } from 'app/shared/models/core/projector';
import { BaseViewComponent } from 'app/site/base/base-view'; import { BaseViewComponent } from 'app/site/base/base-view';
import { PromptService } from 'app/core/ui-services/prompt.service'; import { PromptService } from 'app/core/ui-services/prompt.service';
import { ClockSlideService } from '../../services/clock-slide.service'; import { ClockSlideService } from '../../services/clock-slide.service';
import { OperatorService } from 'app/core/core-services/operator.service';
/** /**
* All supported aspect rations for projectors. * All supported aspect rations for projectors.
@ -61,6 +62,15 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
*/ */
public projectors: ViewProjector[]; public projectors: ViewProjector[];
/**
* Helper to check manage permissions
*
* @returns true if the user can manage projectors
*/
public get canManage(): boolean {
return this.operator.hasPerms('core.can_manage_projector');
}
/** /**
* Constructor. Initializes all forms. * Constructor. Initializes all forms.
* *
@ -70,6 +80,8 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
* @param repo * @param repo
* @param formBuilder * @param formBuilder
* @param promptService * @param promptService
* @param clockSlideService
* @param operator OperatorService
*/ */
public constructor( public constructor(
titleService: Title, titleService: Title,
@ -78,7 +90,8 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
private repo: ProjectorRepositoryService, private repo: ProjectorRepositoryService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private promptService: PromptService, private promptService: PromptService,
private clockSlideService: ClockSlideService private clockSlideService: ClockSlideService,
private operator: OperatorService
) { ) {
super(titleService, translate, matSnackBar); super(titleService, translate, matSnackBar);

View File

@ -15,25 +15,33 @@
<!-- Menu --> <!-- Menu -->
<div class="menu-slot"> <div class="menu-slot">
<button type="button" mat-icon-button [matMenuTriggerFor]="userExtraMenu"> <button
type="button"
mat-icon-button
*ngIf="isAllowed('changePersonal')"
[matMenuTriggerFor]="userExtraMenu"
>
<mat-icon>more_vert</mat-icon> <mat-icon>more_vert</mat-icon>
</button> </button>
</div> </div>
<mat-menu #userExtraMenu="matMenu"> <mat-menu #userExtraMenu="matMenu">
<button mat-menu-item class="red-warning-text" (click)="deleteUserButton()"> <button mat-menu-item *ngIf="isAllowed('changePersonal')" (click)="changePassword()">
<mat-icon>delete</mat-icon>
<span translate>Delete</span>
</button>
<button mat-menu-item (click)="changePassword()">
<mat-icon>security</mat-icon> <mat-icon>security</mat-icon>
<span translate>Change password</span> <span translate>Change password</span>
</button> </button>
<!-- PDF --> <!-- PDF -->
<button mat-menu-item *ngIf="isAllowed('seePersonal')" (click)="onDownloadPdf()"> <button mat-menu-item *ngIf="isAllowed('manage')" (click)="onDownloadPdf()">
<mat-icon>picture_as_pdf</mat-icon> <mat-icon>picture_as_pdf</mat-icon>
<span translate>PDF</span> <span translate>PDF</span>
</button> </button>
<div *ngIf="isAllowed('delete')">
<mat-divider></mat-divider>
<button mat-menu-item class="red-warning-text" (click)="deleteUserButton()">
<mat-icon>delete</mat-icon>
<span translate>Delete</span>
</button>
</div>
</mat-menu> </mat-menu>
</os-head-bar> </os-head-bar>
@ -61,7 +69,6 @@
[value]="user.title" [value]="user.title"
/> />
</mat-form-field> </mat-form-field>
<!-- First name --> <!-- First name -->
<mat-form-field <mat-form-field
class="form37 distance force-min-with" class="form37 distance force-min-with"
@ -97,7 +104,7 @@
placeholder="{{ 'Email' | translate }}" placeholder="{{ 'Email' | translate }}"
name="email" name="email"
formControlName="email" formControlName="email"
[value]="user.email" [value]="user.email ? user.email: null"
/> />
<mat-error *ngIf="personalInfoForm.get('email').hasError('email')" translate> <mat-error *ngIf="personalInfoForm.get('email').hasError('email')" translate>
Please enter a valid email address Please enter a valid email address
@ -126,10 +133,7 @@
</mat-form-field> </mat-form-field>
<!-- Participant Number --> <!-- Participant Number -->
<mat-form-field <mat-form-field class="form25 force-min-with" *ngIf="user.number || (editUser && isAllowed('manage'))">
class="form25 force-min-with"
*ngIf="user.number || (editUser && isAllowed('manage'))"
>
<input <input
type="text" type="text"
matInput matInput
@ -144,7 +148,9 @@
<!-- Groups --> <!-- Groups -->
<mat-form-field *ngIf="(user.groups && user.groups.length > 0) || editUser"> <mat-form-field *ngIf="(user.groups && user.groups.length > 0) || editUser">
<mat-select placeholder="{{ 'Groups' | translate }}" formControlName="groups_id" multiple> <mat-select placeholder="{{ 'Groups' | translate }}" formControlName="groups_id" multiple>
<mat-option *ngFor="let group of groups" [value]="group.id">{{ group.getTitle() | translate }}</mat-option> <mat-option *ngFor="let group of groups" [value]="group.id">{{
group.getTitle() | translate
}}</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>

View File

@ -166,6 +166,7 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
* correct permission to perform the given action. * correct permission to perform the given action.
* *
* actions might be: * actions might be:
* - delete (deleting the user) (users.can_manage and not ownPage)
* - seeName (title, 1st, last) (user.can_see_name or ownPage) * - seeName (title, 1st, last) (user.can_see_name or ownPage)
* - seeExtra (checkboxes, comment) (user.can_see_extra_data) * - seeExtra (checkboxes, comment) (user.can_see_extra_data)
* - seePersonal (mail, username, about) (user.can_see_extra_data or ownPage) * - seePersonal (mail, username, about) (user.can_see_extra_data or ownPage)
@ -176,6 +177,8 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
*/ */
public isAllowed(action: string): boolean { public isAllowed(action: string): boolean {
switch (action) { switch (action) {
case 'delete':
return this.operator.hasPerms('users.can_manage') && !this.ownPage;
case 'manage': case 'manage':
return this.operator.hasPerms('users.can_manage'); return this.operator.hasPerms('users.can_manage');
case 'seeName': case 'seeName':

View File

@ -1,4 +1,4 @@
<os-head-bar mainButton="true" (mainEvent)="onPlusButton()" [multiSelectMode]="isMultiSelect"> <os-head-bar [mainButton]="canAddUser" (mainEvent)="onPlusButton()" [multiSelectMode]="isMultiSelect">
<!-- Title --> <!-- Title -->
<div class="title-slot"><h2 translate>Participants</h2></div> <div class="title-slot"><h2 translate>Participants</h2></div>
@ -50,7 +50,7 @@
<ng-container matColumnDef="group"> <ng-container matColumnDef="group">
<mat-header-cell *matHeaderCellDef mat-sort-header>Group</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header>Group</mat-header-cell>
<mat-cell *matCellDef="let user"> <mat-cell *matCellDef="let user">
<div class='groupsCell'> <div class="groupsCell">
<span *ngIf="user.groups && user.groups.length"> <span *ngIf="user.groups && user.groups.length">
<mat-icon>people</mat-icon> <mat-icon>people</mat-icon>
{{ user.groups }} {{ user.groups }}
@ -108,15 +108,14 @@
<button mat-menu-item (click)="pdfExportUserList()"> <button mat-menu-item (click)="pdfExportUserList()">
<mat-icon>picture_as_pdf</mat-icon> <mat-icon>picture_as_pdf</mat-icon>
<span translate>List of participants (PDF)</span> <span translate>List of participants (PDF)</span>
</button> </button>
<button mat-menu-item (click)="onDownloadAccessPdf()"> <button mat-menu-item *osPerms="'users.can_manage'" (click)="onDownloadAccessPdf()">
<mat-icon>picture_as_pdf</mat-icon> <mat-icon>picture_as_pdf</mat-icon>
<span translate>Access data (PDF)</span> <span translate>Access data (PDF)</span>
</button> </button>
<button mat-menu-item (click)="csvExportUserList()"> <button mat-menu-item *osPerms="'users.can_manage'" (click)="csvExportUserList()">
<mat-icon>archive</mat-icon> <mat-icon>archive</mat-icon>
<span translate>Export as CSV</span> <span translate>Export as CSV</span>
</button> </button>
@ -125,70 +124,72 @@
<mat-icon>cloud_upload</mat-icon> <mat-icon>cloud_upload</mat-icon>
<span translate>Import</span><span>&nbsp;...</span> <span translate>Import</span><span>&nbsp;...</span>
</button> </button>
</div>
<div *ngIf="isMultiSelect">
<button mat-menu-item (click)="selectAll()">
<mat-icon>done_all</mat-icon>
<span translate>Select all</span>
</button>
<button mat-menu-item (click)="deselectAll()"> <div *ngIf="isMultiSelect">
<mat-icon>clear</mat-icon> <button mat-menu-item (click)="selectAll()">
<span translate>Deselect all</span> <mat-icon>done_all</mat-icon>
</button> <span translate>Select all</span>
<div *osPerms="'users.can_manage'">
<mat-divider></mat-divider>
<button mat-menu-item (click)="setGroupSelected()">
<mat-icon>people</mat-icon>
<span translate>Add/remove groups ...</span>
</button> </button>
<div *ngIf="presenceViewConfigured"> <button mat-menu-item (click)="deselectAll()">
<button mat-menu-item *osPerms="'users.can_manage'" routerLink="presence"> <mat-icon>clear</mat-icon>
<mat-icon>transfer_within_a_station</mat-icon> <span translate>Deselect all</span>
<span translate>Presence</span>
</button>
</div>
<button mat-menu-item *osPerms="'users.can_manage'" routerLink="import">
<mat-icon>cloud_upload</mat-icon>
<span translate>Import</span><span>&nbsp;...</span>
</button> </button>
<div *osPerms="'users.can_manage'"> <div *osPerms="'users.can_manage'">
<mat-divider></mat-divider> <mat-divider></mat-divider>
<button mat-menu-item (click)="setGroupSelected()">
<button mat-menu-item (click)="setActiveSelected()"> <mat-icon>people</mat-icon>
<mat-icon>block</mat-icon> <span translate>Add/remove groups ...</span>
<span translate>Enable/disable account ...</span>
</button> </button>
<button mat-menu-item (click)="setPresentSelected()"> <div *ngIf="presenceViewConfigured">
<mat-icon>check_box</mat-icon> <button mat-menu-item *osPerms="'users.can_manage'" routerLink="presence">
<span translate>Set presence ...</span> <mat-icon>transfer_within_a_station</mat-icon>
<span translate>Presence</span>
</button>
</div>
<button mat-menu-item *osPerms="'users.can_manage'" routerLink="import">
<mat-icon>save_alt</mat-icon>
<span translate>Import</span><span>&nbsp;...</span>
</button> </button>
<button mat-menu-item (click)="setCommitteeSelected()"> <div *osPerms="'users.can_manage'">
<mat-icon>account_balance</mat-icon> <mat-divider></mat-divider>
<span translate>Set committee ...</span>
</button>
<mat-divider></mat-divider> <button mat-menu-item (click)="setActiveSelected()">
<mat-icon>block</mat-icon>
<span translate>Enable/disable account ...</span>
</button>
<button mat-menu-item (click)="sendInvitationEmailSelected()"> <button mat-menu-item (click)="setPresentSelected()">
<mat-icon>mail</mat-icon> <mat-icon>check_box</mat-icon>
<span translate>Send invitation email</span> <span translate>Set presence ...</span>
</button> </button>
<button mat-menu-item (click)="resetPasswordsSelected()">
<mat-icon>vpn_key</mat-icon> <button mat-menu-item (click)="setCommitteeSelected()">
<span translate>Generate new passwords</span> <mat-icon>account_balance</mat-icon>
</button> <span translate>Set committee ...</span>
<mat-divider></mat-divider> </button>
<button mat-menu-item class="red-warning-text" (click)="deleteSelected()">
<mat-icon>delete</mat-icon> <mat-divider></mat-divider>
<span translate>Delete</span>
</button> <button mat-menu-item (click)="sendInvitationEmailSelected()">
<mat-icon>mail</mat-icon>
<span translate>Send invitation email</span>
</button>
<button mat-menu-item (click)="resetPasswordsSelected()">
<mat-icon>vpn_key</mat-icon>
<span translate>Generate new passwords</span>
</button>
<mat-divider></mat-divider>
<button mat-menu-item class="red-warning-text" (click)="deleteSelected()">
<mat-icon>delete</mat-icon>
<span translate>Delete</span>
</button>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -51,6 +51,15 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
return this._presenceViewConfigured; return this._presenceViewConfigured;
} }
/**
* Helper to check for main button permissions
*
* @returns true if the user should be able to create users
*/
public get canAddUser(): boolean {
return this.operator.hasPerms('users.can_manage');
}
/** /**
* The usual constructor for components * The usual constructor for components
* @param titleService Serivce for setting the title * @param titleService Serivce for setting the title