multiSelect on listViews
This commit is contained in:
parent
fed085c319
commit
b5aebe5615
@ -1,13 +1,13 @@
|
||||
<mat-toolbar color='primary' *ngIf="!vp.isMobile"></mat-toolbar>
|
||||
<mat-toolbar color='primary' class="sticky-toolbar">
|
||||
<mat-toolbar color="primary" [ngClass]="multiSelectMode ? 'multi-select' : '' " *ngIf="!vp.isMobile"></mat-toolbar>
|
||||
<mat-toolbar color="primary" [ngClass]="multiSelectMode ? 'multi-select' : '' " class="sticky-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<!-- Nav menu -->
|
||||
<button mat-icon-button class="on-transition-fade" *ngIf="vp.isMobile && nav" (click)='clickHamburgerMenu()'>
|
||||
<button mat-icon-button class="on-transition-fade" *ngIf="vp.isMobile && nav && !multiSelectMode" (click)='clickHamburgerMenu()'>
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
|
||||
<!-- Exit / Back button -->
|
||||
<button mat-icon-button class="on-transition-fade" *ngIf="!nav && !editMode" (click)="onBackButton()">
|
||||
<button mat-icon-button class="on-transition-fade" *ngIf="!nav && !editMode && !multiSelectMode" (click)="onBackButton()">
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
|
||||
@ -16,22 +16,28 @@
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="toolbar-left-text on-transition-fade">
|
||||
<div class="toolbar-left-text on-transition-fade" *ngIf="!multiSelectMode">
|
||||
<!-- Title slot -->
|
||||
<ng-content select=".title-slot"></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
<div class=spacer></div>
|
||||
<div class="toolbar-right">
|
||||
|
||||
<!-- centered information slot-->
|
||||
<div *ngIf="multiSelectMode" class=spacer></div>
|
||||
<div class="toolbar-centered on-transition-fade" *ngIf="multiSelectMode">
|
||||
<ng-content select=".central-info-slot"></ng-content>
|
||||
</div>
|
||||
<div class=spacer></div>
|
||||
|
||||
<div class="toolbar-right">
|
||||
<!-- Extra controls slot -->
|
||||
<div class="extra-controls-wrapper on-transition-fade">
|
||||
<ng-content select=".extra-controls-slot"></ng-content>
|
||||
</div>
|
||||
|
||||
<!-- Main action button - desktop -->
|
||||
<button mat-mini-fab color="accent" class="on-transition-fade" *ngIf="mainButton && !editMode && !vp.isMobile"
|
||||
(click)="sendMainEvent()">
|
||||
<button mat-mini-fab color="accent" class="on-transition-fade"
|
||||
*ngIf="mainButton && !editMode && !vp.isMobile && !multiSelectMode" (click)="sendMainEvent()">
|
||||
<mat-icon>{{ mainButtonIcon }}</mat-icon>
|
||||
</button>
|
||||
|
||||
@ -46,6 +52,8 @@
|
||||
</mat-toolbar>
|
||||
|
||||
<!-- Main action button - mobile-->
|
||||
<button mat-fab class="head-button on-transition-fade" *ngIf="mainButton && !editMode && vp.isMobile" (click)=sendMainEvent()>
|
||||
|
||||
<button mat-fab class="head-button on-transition-fade"
|
||||
*ngIf="mainButton && !editMode && vp.isMobile && !multiSelectMode" (click)=sendMainEvent()>
|
||||
<mat-icon>{{ mainButtonIcon }}</mat-icon>
|
||||
</button>
|
||||
|
@ -18,6 +18,10 @@
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
.toolbar-centered {
|
||||
margin: auto;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.toolbar-right {
|
||||
display: contents;
|
||||
@ -29,3 +33,7 @@
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
mat-toolbar.multi-select {
|
||||
background-color: #757575;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import { MainMenuService } from '../../../core/services/main-menu.service';
|
||||
* [mainButton]="opCanEdit()"
|
||||
* [mainButtonIcon]="edit"
|
||||
* [editMode]="editMotion"
|
||||
* [multiSelectMode]="isMultiSelect"
|
||||
* (mainEvent)="setEditMode(!editMotion)"
|
||||
* (saveEvent)="saveMotion()">
|
||||
*
|
||||
@ -34,6 +35,13 @@ import { MainMenuService } from '../../../core/services/main-menu.service';
|
||||
* <mat-icon>more_vert</mat-icon>
|
||||
* </button>
|
||||
* </div>
|
||||
* <!-- MultiSelect info -->
|
||||
* <div class="central-info-slot">
|
||||
* <button mat-icon-button (click)="toggleMultiSelect()">
|
||||
* <mat-icon>arrow_back</mat-icon>
|
||||
* </button>
|
||||
* <span>{{ selectedRows.length }} </span><span translate>selected</span>
|
||||
* </div>
|
||||
* </os-head-bar>
|
||||
* ```
|
||||
*/
|
||||
@ -61,6 +69,12 @@ export class HeadBarComponent {
|
||||
@Input()
|
||||
public editMode = false;
|
||||
|
||||
/**
|
||||
* Determine multiSelect mode: changed interactions and head bar
|
||||
*/
|
||||
@Input()
|
||||
public multiSelectMode = false;
|
||||
|
||||
/**
|
||||
* Determine if there should be the main action button
|
||||
*/
|
||||
|
@ -1,21 +1,50 @@
|
||||
<os-head-bar [mainButton]="true" (mainEvent)=onPlusButton()>
|
||||
<os-head-bar [mainButton]="true" (mainEvent)=onPlusButton() [multiSelectMode]="isMultiSelect">
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
<h2 translate>Agenda</h2>
|
||||
</div>
|
||||
<!-- Menu -->
|
||||
<div class="menu-slot">
|
||||
<button type="button" mat-icon-button [matMenuTriggerFor]="agendaMenu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Multiselect info -->
|
||||
<div class="central-info-slot">
|
||||
<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>
|
||||
<ng-container matColumnDef="selector">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header class="checkbox-cell"></mat-header-cell>
|
||||
<mat-cell *matCellDef="let item" class="checkbox-cell">
|
||||
<mat-icon>{{ isSelected(item) ? 'check_circle' : '' }}</mat-icon>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- title column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Topic</mat-header-cell>
|
||||
<mat-cell *matCellDef="let item" (click)="selectAgendaItem(item)">{{ item.getListTitle() }}</mat-cell>
|
||||
<mat-cell *matCellDef="let item">{{ item.getListTitle() }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Duration column -->
|
||||
<ng-container matColumnDef="duration">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Duration</mat-header-cell>
|
||||
<mat-cell *matCellDef="let item" (click)="selectAgendaItem(item)">{{ item.duration }}</mat-cell>
|
||||
<mat-cell *matCellDef="let item">{{ item.duration }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Speakers column -->
|
||||
@ -32,7 +61,45 @@
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="['title', 'duration', 'speakers']"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: ['title', 'duration', 'speakers']"></mat-row>
|
||||
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
||||
<mat-row [ngClass]="selectedRows.indexOf(row) >= 0 ? 'selected': ''" (click)='selectItem(row, $event)'
|
||||
*matRowDef="let row; columns: getColumnDefinition()"></mat-row>
|
||||
</mat-table>
|
||||
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>
|
||||
|
||||
<mat-menu #agendaMenu="matMenu">
|
||||
<div *ngIf="!isMultiSelect">
|
||||
<button mat-menu-item *osPerms="'agenda.can_manage'" (click)="toggleMultiSelect()">
|
||||
<mat-icon>library_add</mat-icon>
|
||||
<span translate>MultiSelect</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isMultiSelect" >
|
||||
<div *osPerms="'agenda.can_manage'">
|
||||
<button mat-menu-item (click)="deleteSelected()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span translate>Delete selected</span>
|
||||
</button>
|
||||
<mat-divider></mat-divider>
|
||||
<button mat-menu-item (click)="setClosedSelected(true)">
|
||||
<mat-icon>done</mat-icon>
|
||||
<span translate>Close selected</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="setClosedSelected(false)">
|
||||
<mat-icon>redo</mat-icon>
|
||||
<span translate>Open selected</span>
|
||||
</button>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
<button mat-menu-item (click)="setVisibilitySelected(true)">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
<span translate>Set visible</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="setVisibilitySelected(false)">
|
||||
<mat-icon>visibility_off</mat-icon>
|
||||
<span translate>Set invisible</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-menu>
|
||||
|
@ -2,11 +2,12 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { ViewItem } from '../../models/view-item';
|
||||
import { ListViewBaseComponent } from 'app/site/base/list-view-base';
|
||||
import { AgendaRepositoryService } from '../../services/agenda-repository.service';
|
||||
import { PromptService } from '../../../../core/services/prompt.service';
|
||||
|
||||
/**
|
||||
* List view for the agenda.
|
||||
@ -24,7 +25,9 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
|
||||
* @param matSnackBar Shows errors and messages
|
||||
* @param route Angulars ActivatedRoute
|
||||
* @param router Angulars router
|
||||
* @param repo the agenda repository
|
||||
* @param repo the agenda repository,
|
||||
* promptService:
|
||||
*
|
||||
*/
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
@ -32,9 +35,13 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
|
||||
matSnackBar: MatSnackBar,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private repo: AgendaRepositoryService
|
||||
private repo: AgendaRepositoryService,
|
||||
private promptService: PromptService
|
||||
) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
|
||||
// activate multiSelect mode for this listview
|
||||
this.canMultiSelect = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,18 +53,17 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
|
||||
this.initTable();
|
||||
this.repo.getViewModelListObservable().subscribe(newAgendaItem => {
|
||||
this.dataSource.data = newAgendaItem;
|
||||
this.checkSelection();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for click events on agenda item rows
|
||||
* Links to the content object if any
|
||||
*
|
||||
* Handler for click events on an agenda item row. Links to the content object
|
||||
* Gets content object from the repository rather than from the model
|
||||
* to avoid race conditions
|
||||
* @param item the item that was selected from the list view
|
||||
*/
|
||||
public selectAgendaItem(item: ViewItem): void {
|
||||
public singleSelectAction(item: ViewItem): void {
|
||||
const contentObject = this.repo.getContentObject(item.item);
|
||||
this.router.navigate([contentObject.getDetailStateURL()]);
|
||||
}
|
||||
@ -77,4 +83,47 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
|
||||
public onPlusButton(): void {
|
||||
this.router.navigate(['topics/new'], { relativeTo: this.route });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for deleting multiple entries. Needs items in selectedRows, which
|
||||
* is only filled with any data in multiSelect mode
|
||||
*/
|
||||
public async deleteSelected(): Promise<void> {
|
||||
const content = this.translate.instant('This will delete all selected agenda items.');
|
||||
if (await this.promptService.open('Are you sure?', content)) {
|
||||
for (const agenda of this.selectedRows) {
|
||||
await this.repo.delete(agenda);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets multiple entries' open/closed state. Needs items in selectedRows, which
|
||||
* is only filled with any data in multiSelect mode
|
||||
* @param closed true if the item is to be considered done
|
||||
*/
|
||||
public async setClosedSelected(closed: boolean): Promise<void> {
|
||||
for (const agenda of this.selectedRows) {
|
||||
await this.repo.update({ closed: closed }, agenda);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets multiple entries' visibility. Needs items in selectedRows, which
|
||||
* is only filled with any data in multiSelect mode.
|
||||
* @param visible true if the item is to be shown
|
||||
*/
|
||||
public async setVisibilitySelected(visible: boolean): Promise<void> {
|
||||
for (const agenda of this.selectedRows) {
|
||||
await this.repo.update({ is_hidden: visible }, agenda);
|
||||
}
|
||||
}
|
||||
|
||||
public getColumnDefinition(): string[] {
|
||||
const list = ['title', 'duration', 'speakers'];
|
||||
if (this.isMultiSelect) {
|
||||
return ['selector'].concat(list);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,40 @@
|
||||
<os-head-bar plusButton=true (plusButtonClicked)=onPlusButton()>
|
||||
<os-head-bar plusButton=true (plusButtonClicked)=onPlusButton() [multiSelectMode]="isMultiSelect">
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
|
||||
<h2 translate>Elections</h2>
|
||||
</div>
|
||||
|
||||
<!-- Menu -->
|
||||
<div class="menu-slot">
|
||||
<button type="button" mat-icon-button [matMenuTriggerFor]="assignmentMenu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Multiselect info -->
|
||||
<div class="central-info-slot">
|
||||
<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>
|
||||
<ng-container matColumnDef="selector">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header class="checkbox-cell" ></mat-header-cell>
|
||||
<mat-cell *matCellDef="let assignment" class="checkbox-cell" >
|
||||
<mat-icon>{{ isSelected(assignment) ? 'check_circle' : '' }}</mat-icon>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<!-- name column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Title</mat-header-cell>
|
||||
@ -35,15 +57,30 @@
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="['title', 'phase', 'candidates']"></mat-header-row>
|
||||
<mat-row (click)="selectAssignment(row)" *matRowDef="let row; columns: ['title', 'phase', 'candidates']"></mat-row>
|
||||
<mat-header-row *matHeaderRowDef="getColumnDefintion()"></mat-header-row>
|
||||
<mat-row [ngClass]="selectedRows.indexOf(row) >= 0 ? 'selected': ''" (click)='selectItem(row, $event)'
|
||||
*matRowDef="let row; columns: getColumnDefintion()">
|
||||
</mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>
|
||||
|
||||
<mat-menu #assignmentMenu="matMenu">
|
||||
<button mat-menu-item (click)="downloadAssignmentButton()">
|
||||
<mat-icon>archive</mat-icon>
|
||||
<span translate>Export ...</span>
|
||||
</button>
|
||||
<div *ngIf="!isMultiSelect">
|
||||
<button mat-menu-item *osPerms="'assignment.can_manage'" (click)="toggleMultiSelect()">
|
||||
<mat-icon>library_add</mat-icon>
|
||||
<span translate>MultiSelect</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="downloadAssignmentButton()">
|
||||
<mat-icon>archive</mat-icon>
|
||||
<span translate>Export ...</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isMultiSelect">
|
||||
<button mat-menu-item *osPerms="'assignment.can_manage'" (click)="deleteSelected()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span translate>Delete assignments</span>
|
||||
</button>
|
||||
</div>
|
||||
</mat-menu>
|
||||
|
@ -5,6 +5,7 @@ import { ViewAssignment } from '../models/view-assignment';
|
||||
import { ListViewBaseComponent } from '../../base/list-view-base';
|
||||
import { AssignmentRepositoryService } from '../services/assignment-repository.service';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { PromptService } from '../../../core/services/prompt.service';
|
||||
|
||||
/**
|
||||
* Listview for the assignments
|
||||
@ -23,14 +24,18 @@ export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignmen
|
||||
* @param translate
|
||||
* @param matSnackBar
|
||||
* @param repo the repository
|
||||
* @param promptService
|
||||
*/
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
private repo: AssignmentRepositoryService
|
||||
private repo: AssignmentRepositoryService,
|
||||
private promptService: PromptService
|
||||
) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
// activate multiSelect mode for this listview
|
||||
this.canMultiSelect = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,6 +47,7 @@ export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignmen
|
||||
this.initTable();
|
||||
this.repo.getViewModelListObservable().subscribe(newAssignments => {
|
||||
this.dataSource.data = newAssignments;
|
||||
this.checkSelection();
|
||||
});
|
||||
}
|
||||
|
||||
@ -53,10 +59,10 @@ export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignmen
|
||||
}
|
||||
|
||||
/**
|
||||
* Select an row in the table
|
||||
* @param assignment
|
||||
* Action to be performed after a click on a row in the table, if in single select mode
|
||||
* @param assignment The entry of row clicked
|
||||
*/
|
||||
public selectAssignment(assignment: ViewAssignment): void {
|
||||
public singleSelectAction(assignment: ViewAssignment): void {
|
||||
console.log('select assignment list: ', assignment);
|
||||
}
|
||||
|
||||
@ -67,4 +73,25 @@ export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignmen
|
||||
public downloadAssignmentButton(): void {
|
||||
console.log('Hello World');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for deleting multiple entries. Needs items in selectedRows, which
|
||||
* is only filled with any data in multiSelect mode
|
||||
*/
|
||||
public async deleteSelected(): Promise<void> {
|
||||
const content = this.translate.instant('This will delete all selected assignments.');
|
||||
if (await this.promptService.open('Are you sure?', content)) {
|
||||
for (const assignment of this.selectedRows) {
|
||||
await this.repo.delete(assignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getColumnDefintion(): string[] {
|
||||
const list = ['title', 'phase', 'candidates'];
|
||||
if (this.isMultiSelect) {
|
||||
return ['selector'].concat(list);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,22 @@ export abstract class ListViewBaseComponent<V extends BaseViewModel> extends Bas
|
||||
*/
|
||||
public dataSource: MatTableDataSource<V>;
|
||||
|
||||
/**
|
||||
* Toggle for enabling the multiSelect mode. Defaults to false (inactive)
|
||||
*/
|
||||
protected canMultiSelect = false;
|
||||
|
||||
/**
|
||||
* Current state of the multiSelect mode. TODO Could be merged with edit mode?
|
||||
*/
|
||||
private _multiSelectModus = false;
|
||||
|
||||
/**
|
||||
* An array of currently selected items, upon which multiselect actions can be performed
|
||||
* see {@link selectItem}.
|
||||
*/
|
||||
public selectedRows: V[];
|
||||
|
||||
/**
|
||||
* The table itself
|
||||
*/
|
||||
@ -37,6 +53,7 @@ export abstract class ListViewBaseComponent<V extends BaseViewModel> extends Bas
|
||||
*/
|
||||
public constructor(titleService: Title, translate: TranslateService, matSnackBar: MatSnackBar) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
this.selectedRows = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -49,4 +66,88 @@ export abstract class ListViewBaseComponent<V extends BaseViewModel> extends Bas
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default click action on selecting an item. In multiselect modus,
|
||||
* this just adds/removes from a selection, else it performs a {@link singleSelectAction}
|
||||
* @param row The clicked row's {@link ViewModel}
|
||||
* @param event The Mouse event
|
||||
*/
|
||||
public selectItem(row: V, event: MouseEvent): void {
|
||||
event.stopPropagation();
|
||||
if (!this._multiSelectModus) {
|
||||
this.singleSelectAction(row);
|
||||
} else {
|
||||
const idx = this.selectedRows.indexOf(row);
|
||||
if ( idx < 0){
|
||||
this.selectedRows.push(row);
|
||||
} else {
|
||||
this.selectedRows.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to perform an action on click on a row, if not in MultiSelect Modus.
|
||||
* Should be overridden by implementations. Currently there is no default action.
|
||||
* @param row a ViewModel
|
||||
*/
|
||||
public singleSelectAction(row: V) : void {
|
||||
}
|
||||
|
||||
/**
|
||||
* enables/disables the multiSelect Mode
|
||||
*/
|
||||
public toggleMultiSelect() : void {
|
||||
if (!this.canMultiSelect || this.isMultiSelect) {
|
||||
this._multiSelectModus = false;
|
||||
this.clearSelection();
|
||||
} else {
|
||||
this._multiSelectModus = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current state of the multiSelect modus
|
||||
*/
|
||||
public get isMultiSelect(): boolean {
|
||||
return this._multiSelectModus;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if a row is currently selected in the multiSelect modus.
|
||||
* @param item The row's entry
|
||||
*/
|
||||
public isSelected(item: V): boolean {
|
||||
if (!this._multiSelectModus) {
|
||||
return false;
|
||||
}
|
||||
return this.selectedRows.indexOf(item) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler to quickly unselect all items.
|
||||
*/
|
||||
public clearSelection(): void {
|
||||
this.selectedRows = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the array of selected items against the datastore data. This is
|
||||
* meant to reselect items by their id even if some of their data changed,
|
||||
* and to remove selected data that don't exist anymore.
|
||||
* To be called after an update of data. Checks if updated selected items
|
||||
* are still present in the dataSource, and (re-)selects them. This should
|
||||
* be called as the observed datasource updates.
|
||||
*/
|
||||
protected checkSelection(): void {
|
||||
const newSelection = [];
|
||||
this.selectedRows.forEach(selectedrow => {
|
||||
const newrow = this.dataSource.data.find(item => item.id === selectedrow.id);
|
||||
if (newrow) {
|
||||
newSelection.push(newrow);
|
||||
}
|
||||
});
|
||||
this.selectedRows = newSelection;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,11 @@
|
||||
<os-head-bar [mainButton]="true" (mainEvent)="onMainEvent()" [editMode]="editFile" (saveEvent)="onSaveEditedFile()">
|
||||
<os-head-bar
|
||||
[mainButton]="true"
|
||||
[editMode]="editFile"
|
||||
[multiSelectMode]="isMultiSelect"
|
||||
(mainEvent)="onMainEvent()"
|
||||
(saveEvent)="onSaveEditedFile()"
|
||||
>
|
||||
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
<h2 *ngIf="!editFile" translate>Files</h2>
|
||||
@ -37,19 +44,48 @@
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Multiselect menu -->
|
||||
<div class="multiselect-menu-slot">
|
||||
<button type="button" mat-icon-button [matMenuTriggerFor]="mediafilesMultiSelectMenu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Multiselect info -->
|
||||
<div *ngIf="this.isMultiSelect" class="central-info-slot">
|
||||
<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>
|
||||
|
||||
<ng-container matColumnDef="selector">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header class="checkbox-cell"></mat-header-cell>
|
||||
<mat-cell *matCellDef="let item" class="checkbox-cell">
|
||||
<mat-icon>{{ isSelected(item) ? 'check_circle' : '' }}</mat-icon>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Filename -->
|
||||
<ng-container matColumnDef="title">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
|
||||
<mat-cell *matCellDef="let file" (click)="download(file)">{{ file.title }}</mat-cell>
|
||||
<mat-cell *matCellDef="let file">{{ file.title }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Info -->
|
||||
<ng-container matColumnDef="info">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Group</mat-header-cell>
|
||||
<mat-cell *matCellDef="let file" (click)="download(file)">
|
||||
<mat-cell *matCellDef="let file">
|
||||
<div class="file-info-cell">
|
||||
<span> <mat-icon [inline]="true">insert_drive_file</mat-icon> {{ file.type }} </span>
|
||||
<span> <mat-icon [inline]="true">data_usage</mat-icon> {{ file.size }} </span>
|
||||
@ -86,7 +122,8 @@
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: getColumnDefinition()"></mat-row>
|
||||
<mat-row *matRowDef="let row; columns: getColumnDefinition()"
|
||||
[ngClass]="selectedRows.indexOf(row) >= 0 ? 'selected': ''" (click)='selectItem(row, $event)'></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>
|
||||
@ -130,9 +167,14 @@
|
||||
|
||||
<!-- Menu for Mediafiles -->
|
||||
<mat-menu #mediafilesMenu="matMenu">
|
||||
<!-- Delete all files - later replaced with multi-select function -->
|
||||
<button mat-menu-item class="red-warning-text" (click)="onDeleteAllFiles()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span translate>Delete all files</span>
|
||||
<button mat-menu-item *osPerms="'mediafiles.can_manage'" (click)="toggleMultiSelect()">
|
||||
<mat-icon>library_add</mat-icon>
|
||||
<span translate>MultiSelect</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
<mat-menu #mediafilesMultiSelectMenu="matMenu">
|
||||
<button mat-menu-item *osPerms="'mediafiles.can_manage'" (click)="deleteSelected()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span translate>Delete selected</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
@ -84,6 +84,9 @@ export class MediafileListComponent extends ListViewBaseComponent<ViewMediafile>
|
||||
public vp: ViewportService
|
||||
) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
|
||||
// emables multiSelection for this listView
|
||||
this.canMultiSelect = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -170,18 +173,15 @@ export class MediafileListComponent extends ListViewBaseComponent<ViewMediafile>
|
||||
}
|
||||
|
||||
/**
|
||||
* triggers a routine to delete all MediaFiles
|
||||
* TODO: Remove after Multiselect
|
||||
*
|
||||
* @deprecated to be removed once multi selection is implemented
|
||||
* Handler to delete several files at once. Requires data in selectedRows, which
|
||||
* will be made available in multiSelect mode
|
||||
*/
|
||||
public async onDeleteAllFiles(): Promise<void> {
|
||||
const content = this.translate.instant('This will delete all files.');
|
||||
public async deleteSelected(): Promise<void> {
|
||||
const content = this.translate.instant('This will delete all selected files.');
|
||||
if (await this.promptService.open('Are you sure?', content)) {
|
||||
const viewMediafiles = this.dataSource.data;
|
||||
viewMediafiles.forEach(file => {
|
||||
this.repo.delete(file);
|
||||
});
|
||||
for (const mediafile of this.selectedRows) {
|
||||
await this.repo.delete(mediafile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,7 +257,11 @@ export class MediafileListComponent extends ListViewBaseComponent<ViewMediafile>
|
||||
* @returns the column definition for the screen size
|
||||
*/
|
||||
public getColumnDefinition(): string[] {
|
||||
return this.vp.isMobile ? this.displayedColumnsMobile : this.displayedColumnsDesktop;
|
||||
const columns = this.vp.isMobile ? this.displayedColumnsMobile : this.displayedColumnsDesktop;
|
||||
if (this.isMultiSelect){
|
||||
return ['selector'].concat(columns);
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -265,7 +269,7 @@ export class MediafileListComponent extends ListViewBaseComponent<ViewMediafile>
|
||||
*
|
||||
* @param file the select file to download
|
||||
*/
|
||||
public download(file: ViewMediafile): void {
|
||||
public singleSelectAction(file: ViewMediafile): void {
|
||||
window.open(file.downloadUrl);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<os-head-bar [mainButton]="true" (mainEvent)=onPlusButton()>
|
||||
<os-head-bar [mainButton]="true" (mainEvent)=onPlusButton() [multiSelectMode]="isMultiSelect">
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
<h2 translate>Motions</h2>
|
||||
@ -10,6 +10,21 @@
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Multiselect info -->
|
||||
<div class="central-info-slot">
|
||||
<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">
|
||||
@ -22,10 +37,17 @@
|
||||
</div>
|
||||
|
||||
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
|
||||
<ng-container matColumnDef="selector" >
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header class="checkbox-cell"></mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion" class="checkbox-cell">
|
||||
<mat-icon>{{ isSelected(motion) ? 'check_circle' : '' }}</mat-icon>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- identifier column -->
|
||||
<ng-container matColumnDef="identifier">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Identifier</mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion" (click)="selectMotion(motion)">
|
||||
<mat-cell *matCellDef="let motion">
|
||||
<div class="innerTable">
|
||||
{{ motion.identifier }}
|
||||
</div>
|
||||
@ -35,7 +57,7 @@
|
||||
<!-- title column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Title</mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion" (click)="selectMotion(motion)">
|
||||
<mat-cell *matCellDef="let motion">
|
||||
<div class="innerTable">
|
||||
<span class="motion-list-title">{{ motion.title }}</span>
|
||||
<br>
|
||||
@ -50,7 +72,7 @@
|
||||
<!-- state column -->
|
||||
<ng-container matColumnDef="state">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>State</mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion" (click)="selectMotion(motion)">
|
||||
<mat-cell *matCellDef="let motion">
|
||||
<!--div *ngIf='isDisplayIcon(motion.state) && motion.state' class='innerTable'>
|
||||
<mat-icon>{{ getStateIcon(motion.state) }}</mat-icon>
|
||||
</div>-->
|
||||
@ -64,7 +86,7 @@
|
||||
<ng-container matColumnDef="speakers">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Speakers</mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion">
|
||||
<button mat-icon-button (click)="onSpeakerIcon(motion)">
|
||||
<button mat-icon-button (click)="onSpeakerIcon(motion, $event)">
|
||||
<mat-icon
|
||||
[matBadge]="motion.agendaSpeakerAmount > 0 ? motion.agendaSpeakerAmount : null"
|
||||
matBadgeColor="accent">
|
||||
@ -74,34 +96,55 @@
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="columnsToDisplayMinWidth"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: columnsToDisplayMinWidth"></mat-row>
|
||||
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
||||
<mat-row [ngClass]="selectedRows.indexOf(row) >= 0 ? 'selected': ''" (click)='selectItem(row, $event)'
|
||||
*matRowDef="let row; columns: getColumnDefinition()">
|
||||
</mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>
|
||||
|
||||
<mat-menu #motionListMenu="matMenu">
|
||||
<button mat-menu-item routerLink="category">
|
||||
<mat-icon>device_hub</mat-icon>
|
||||
<span translate>Categories</span>
|
||||
</button>
|
||||
<div *ngIf="!isMultiSelect">
|
||||
<button mat-menu-item *osPerms="'motions.can_manage'" (click)="toggleMultiSelect()">
|
||||
<mat-icon>library_add</mat-icon>
|
||||
<span translate>MultiSelect</span>
|
||||
</button>
|
||||
<button mat-menu-item routerLink="category">
|
||||
<mat-icon>device_hub</mat-icon>
|
||||
<span translate>Categories</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item routerLink="comment-section">
|
||||
<mat-icon>speaker_notes</mat-icon>
|
||||
<span translate>Comment sections</span>
|
||||
</button>
|
||||
<button mat-menu-item routerLink="comment-section">
|
||||
<mat-icon>speaker_notes</mat-icon>
|
||||
<span translate>Comment sections</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item routerLink="statute-paragraphs" *ngIf="statutesEnabled">
|
||||
<mat-icon>account_balance</mat-icon>
|
||||
<span translate>Statute paragraphs</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item *osPerms="'motions.can_manage'" routerLink="call-list">
|
||||
<mat-icon>sort</mat-icon>
|
||||
<span translate>Call list</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="csvExportMotionList()">
|
||||
<mat-icon>archive</mat-icon>
|
||||
<span translate>Export as CSV</span>
|
||||
</button>
|
||||
<button mat-menu-item routerLink="statute-paragraphs" *ngIf="statutesEnabled">
|
||||
<mat-icon>account_balance</mat-icon>
|
||||
<span translate>Statute paragraphs</span>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="isMultiSelect">
|
||||
<div *osPerms="'motions.can_manage'">
|
||||
<button mat-menu-item (click)="deleteSelected()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span translate>Delete selected</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="openSetStatusMenu()">
|
||||
<mat-icon>sentiment_satisfied</mat-icon>
|
||||
<!-- TODO: icon -->
|
||||
<span translate>Set status</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="openSetCategoryMenu()">
|
||||
<mat-icon>sentiment_satisfied</mat-icon>
|
||||
<!-- TODO: icon -->
|
||||
<span translate>Set categories</span>
|
||||
</button>
|
||||
</div>
|
||||
<button mat-menu-item (click)="csvExportMotionList()">
|
||||
<mat-icon>archive</mat-icon>
|
||||
<span translate>Export as CSV</span>
|
||||
</button>
|
||||
</div>
|
||||
</mat-menu>
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { MotionRepositoryService } from '../../services/motion-repository.service';
|
||||
@ -11,6 +10,8 @@ import { ListViewBaseComponent } from '../../../base/list-view-base';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { ConfigService } from '../../../../core/services/config.service';
|
||||
import { CsvExportService } from 'app/core/services/csv-export.service';
|
||||
import { Category } from '../../../../shared/models/motions/category';
|
||||
import { PromptService } from '../../../../core/services/prompt.service';
|
||||
|
||||
/**
|
||||
* Component that displays all the motions in a Table using DataSource.
|
||||
@ -22,13 +23,14 @@ import { CsvExportService } from 'app/core/services/csv-export.service';
|
||||
})
|
||||
export class MotionListComponent extends ListViewBaseComponent<ViewMotion> implements OnInit {
|
||||
/**
|
||||
* Use for minimal width
|
||||
* Use for minimal width. Please note the 'selector' row for multiSelect mode,
|
||||
* to be able to display an indicator for the state of selection
|
||||
*/
|
||||
public columnsToDisplayMinWidth = ['identifier', 'title', 'state', 'speakers'];
|
||||
|
||||
/**
|
||||
* Use for maximal width
|
||||
*
|
||||
* Use for maximal width. Please note the 'selector' row for multiSelect mode,
|
||||
* to be able to display an indicator for the state of selection
|
||||
* TODO: Needs vp.desktop check
|
||||
*/
|
||||
public columnsToDisplayFullWidth = ['identifier', 'title', 'state', 'speakers'];
|
||||
@ -50,6 +52,7 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
* @param configService The configuration provider
|
||||
* @param repo Motion Repository
|
||||
* @param csvExport CSV Export Service
|
||||
* @param promptService
|
||||
*/
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
@ -59,9 +62,13 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
private route: ActivatedRoute,
|
||||
private configService: ConfigService,
|
||||
private repo: MotionRepositoryService,
|
||||
private csvExport: CsvExportService
|
||||
private csvExport: CsvExportService,
|
||||
private promptService: PromptService
|
||||
) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
|
||||
// enable multiSelect for this listView
|
||||
this.canMultiSelect = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,6 +80,7 @@ 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) {
|
||||
@ -90,11 +98,10 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a motion from list. Executed via click.
|
||||
*
|
||||
* The action performed on a click in single select modus
|
||||
* @param motion The row the user clicked at
|
||||
*/
|
||||
public selectMotion(motion: ViewMotion): void {
|
||||
public singleSelectAction(motion: ViewMotion): void {
|
||||
this.router.navigate(['./' + motion.id], { relativeTo: this.route });
|
||||
}
|
||||
|
||||
@ -137,7 +144,8 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
*
|
||||
* @param motion indicates the row that was clicked on
|
||||
*/
|
||||
public onSpeakerIcon(motion: ViewMotion): void {
|
||||
public onSpeakerIcon(motion: ViewMotion, event: MouseEvent): void {
|
||||
event.stopPropagation();
|
||||
this.router.navigate([`/agenda/${motion.agenda_item_id}/speakers`]);
|
||||
}
|
||||
|
||||
@ -166,4 +174,62 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
this.translate.instant('Motions') + '.csv'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the items selected.
|
||||
* SelectedRows is only filled with data in multiSelect mode
|
||||
*/
|
||||
public async deleteSelected(): Promise<void> {
|
||||
const content = this.translate.instant('This will delete all selected motions.');
|
||||
if (await this.promptService.open('Are you sure?', content)) {
|
||||
for (const motion of this.selectedRows) {
|
||||
await this.repo.delete(motion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status in bulk.
|
||||
* SelectedRows is only filled with data in multiSelect mode
|
||||
* TODO: currently not yet functional, because no status (or state_id) is being selected
|
||||
* in the ui
|
||||
* @param status TODO: May still change type
|
||||
*/
|
||||
public async setStatusSelected(status: Partial<WorkflowState>): Promise<void> {
|
||||
// TODO: check if id is there
|
||||
for (const motion of this.selectedRows) {
|
||||
await this.repo.update({ state_id: status.id }, motion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the category for all selected items.
|
||||
* SelectedRows is only filled with data in multiSelect mode
|
||||
* TODO: currently not yet functional, because no category is being selected in the ui
|
||||
* @param category TODO: May still change type
|
||||
*/
|
||||
public async setCategorySelected(category: Partial<Category>): Promise<void> {
|
||||
for (const motion of this.selectedRows) {
|
||||
await this.repo.update({ state_id: category.id }, motion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Open an extra submenu. Design still undecided. Will be used for deciding
|
||||
* the status of setStatusSelected
|
||||
*/
|
||||
public openSetStatusMenu(): void {}
|
||||
|
||||
/**
|
||||
* TODO: Open an extra submenu. Design still undecided. Will be used for deciding
|
||||
* the status of setCategorySelected
|
||||
*/
|
||||
public openSetCategoryMenu(): void {}
|
||||
|
||||
public getColumnDefinition(): string[] {
|
||||
if (this.isMultiSelect) {
|
||||
return ['selector'].concat(this.columnsToDisplayMinWidth);
|
||||
}
|
||||
return this.columnsToDisplayMinWidth;
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
||||
public async update(update: Partial<Motion>, viewMotion: ViewMotion): Promise<void> {
|
||||
const motion = viewMotion.motion;
|
||||
motion.patchValues(update);
|
||||
await this.dataSend.partialUpdateModel(motion);
|
||||
return await this.dataSend.partialUpdateModel(motion);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,7 +134,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
||||
* @param viewMotion
|
||||
*/
|
||||
public async delete(viewMotion: ViewMotion): Promise<void> {
|
||||
await this.dataSend.deleteModel(viewMotion.motion);
|
||||
return await this.dataSend.deleteModel(viewMotion.motion);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,5 @@
|
||||
<os-head-bar [mainButton]="true" [nav]="true" [editMode]="editTag"
|
||||
(mainEvent)="setEditMode(!editTag)" (saveEvent)="saveTag()">
|
||||
(mainEvent)="setEditMode(!editTag)" (saveEvent)="saveTag()" [multiSelectMode]="isMultiSelect">>
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
<h2 *ngIf="!editTag && !newTag" translate>Tags</h2>
|
||||
@ -14,11 +14,12 @@
|
||||
|
||||
<!-- remove button -->
|
||||
<div class="extra-controls-slot on-transition-fade">
|
||||
<button *ngIf="editTag && !newTag" type="button" mat-button (click)="deleteSelectedTag()">
|
||||
<button *ngIf="!isMultiSelect && editTag && !newTag" type="button" mat-button (click)="deleteSelectedTag()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span translate>Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</os-head-bar>
|
||||
|
||||
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
|
||||
@ -27,7 +28,7 @@
|
||||
<mat-cell *matCellDef="let tag">{{ tag.getTitle() }}</mat-cell>
|
||||
</ng-container>
|
||||
<mat-header-row *matHeaderRowDef="['name']"></mat-header-row>
|
||||
<mat-row (click)="selectTag(row)" *matRowDef="let row; columns: ['name']"></mat-row>
|
||||
<mat-row (click)='selectItem(row, $event)' *matRowDef="let row; columns: ['name']"></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>
|
||||
|
@ -116,10 +116,10 @@ export class TagListComponent extends ListViewBaseComponent<ViewTag> implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a row in the table
|
||||
* Handler for a click on a row in the table
|
||||
* @param viewTag
|
||||
*/
|
||||
public selectTag(viewTag: ViewTag): void {
|
||||
public singleSelectAction(viewTag: ViewTag): void {
|
||||
this.selectedTag = viewTag;
|
||||
this.setEditMode(true, false);
|
||||
this.tagForm.setValue({ name: this.selectedTag.name });
|
||||
|
@ -1,4 +1,4 @@
|
||||
<os-head-bar mainButton=true (mainEvent)=onPlusButton()>
|
||||
<os-head-bar mainButton=true (mainEvent)=onPlusButton() [multiSelectMode]="isMultiSelect">
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
<h2 translate>Participants</h2>
|
||||
@ -10,6 +10,21 @@
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Multiselect info -->
|
||||
<div class="central-info-slot">
|
||||
<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">
|
||||
@ -22,6 +37,12 @@
|
||||
</div>
|
||||
|
||||
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
|
||||
<ng-container matColumnDef="selector">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header class="checkbox-cell"></mat-header-cell>
|
||||
<mat-cell *matCellDef="let user" class="checkbox-cell">
|
||||
<mat-icon>{{ isSelected(user) ? 'check_circle' : '' }}</mat-icon>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- name column -->
|
||||
<ng-container matColumnDef="name">
|
||||
@ -58,25 +79,91 @@
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="['name', 'group', 'presence']"></mat-header-row>
|
||||
<mat-row (click)="selectUser(row)" *matRowDef="let row; columns: ['name', 'group', 'presence']"></mat-row>
|
||||
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
||||
<mat-row [ngClass]="selectedRows.indexOf(row) >= 0 ? 'selected': ''" (click)='selectItem(row, $event)'
|
||||
*matRowDef="let row; columns: getColumnDefinition()">
|
||||
</mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>
|
||||
|
||||
<mat-menu #userMenu="matMenu">
|
||||
<button mat-menu-item *osPerms="'users.can_manage'" routerLink="groups">
|
||||
<mat-icon>people</mat-icon>
|
||||
<span translate>Groups</span>
|
||||
</button>
|
||||
<div *ngIf="!isMultiSelect">
|
||||
<button mat-menu-item *osPerms="'users.can_manage'" (click)="toggleMultiSelect()">
|
||||
<mat-icon>library_add</mat-icon>
|
||||
<span translate>MultiSelect</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item>
|
||||
<mat-icon>save_alt</mat-icon>
|
||||
<span translate>Import ...</span>
|
||||
</button>
|
||||
<button mat-menu-item *osPerms="'users.can_manage'" routerLink="groups">
|
||||
<mat-icon>people</mat-icon>
|
||||
<span translate>Groups</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="csvExportUserList()">
|
||||
<mat-icon>archive</mat-icon>
|
||||
<span translate>Export as CSV</span>
|
||||
</button>
|
||||
<button mat-menu-item>
|
||||
<mat-icon>save_alt</mat-icon>
|
||||
<span translate>Import ...</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="csvExportUserList()">
|
||||
<mat-icon>archive</mat-icon>
|
||||
<span translate>Export as CSV</span>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="isMultiSelect">
|
||||
<div *osPerms="'users.can_manage'">
|
||||
<button mat-menu-item (click)="setGroupSelected(null)">
|
||||
<mat-icon>archive</mat-icon>
|
||||
<span translate>Set groups</span>
|
||||
<!-- TODO bottomsheet/menu? -->
|
||||
</button>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<button mat-menu-item (click)="setActiveSelected(true)">
|
||||
<mat-icon>add_circle</mat-icon>
|
||||
<span translate>Set active</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="setActiveSelected(false)">
|
||||
<mat-icon>remove_circle</mat-icon>
|
||||
<span translate>Set inactive</span>
|
||||
</button>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<button mat-menu-item (click)="setPresentSelected(true)">
|
||||
<mat-icon>add_circle</mat-icon>
|
||||
<span translate>Set as present</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="setPresentSelected(false)">
|
||||
<mat-icon>remove_circle</mat-icon>
|
||||
<span translate>Set as not present</span>
|
||||
</button>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<button mat-menu-item (click)="setCommitteeSelected(true)">
|
||||
<mat-icon>add_circle</mat-icon>
|
||||
<span translate>Set as committee</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="setCommitteeSelected(false)">
|
||||
<mat-icon>remove_circle</mat-icon>
|
||||
<span translate>Unset committee</span>
|
||||
</button>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<button mat-menu-item (click)="sendInvitationSelected()">
|
||||
<mat-icon>mail</mat-icon>
|
||||
<span translate>Send invitations</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="deleteSelected()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span translate>Delete selected</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-menu>
|
||||
|
@ -8,6 +8,8 @@ import { UserRepositoryService } from '../../services/user-repository.service';
|
||||
import { ListViewBaseComponent } from '../../../base/list-view-base';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { Group } from '../../../../shared/models/users/group';
|
||||
import { PromptService } from '../../../../core/services/prompt.service';
|
||||
|
||||
/**
|
||||
* Component for the user list view.
|
||||
@ -28,7 +30,8 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
||||
* @param repo the user repository
|
||||
* @param router the router service
|
||||
* @param route the local route
|
||||
* @param csvExport CSV export Service
|
||||
* @param csvExport CSV export Service,
|
||||
* @param promptService
|
||||
*/
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
@ -37,9 +40,13 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
||||
private repo: UserRepositoryService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
protected csvExport: CsvExportService
|
||||
protected csvExport: CsvExportService,
|
||||
private promptService: PromptService
|
||||
) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
|
||||
// enable multiSelect for this listView
|
||||
this.canMultiSelect = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,6 +59,7 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
||||
this.initTable();
|
||||
this.repo.getViewModelListObservable().subscribe(newUsers => {
|
||||
this.dataSource.data = newUsers;
|
||||
this.checkSelection();
|
||||
});
|
||||
}
|
||||
|
||||
@ -65,11 +73,10 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the click on a user row
|
||||
*
|
||||
* Handles the click on a user row if not in multiSelect modus
|
||||
* @param row selected row
|
||||
*/
|
||||
public selectUser(row: ViewUser): void {
|
||||
public singleSelectAction(row: ViewUser): void {
|
||||
this.router.navigate([`./${row.id}`], { relativeTo: this.route });
|
||||
}
|
||||
|
||||
@ -103,4 +110,92 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
||||
this.translate.instant('Participants') + '.csv'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk deletes users. Needs multiSelect mode to fill selectedRows
|
||||
*/
|
||||
public async deleteSelected(): Promise<void> {
|
||||
const content = this.translate.instant('This will delete all selected assignments.');
|
||||
if (await this.promptService.open('Are you sure?', content)) {
|
||||
for (const user of this.selectedRows) {
|
||||
await this.repo.delete(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Not yet as expected
|
||||
* Bulk sets the group for users. TODO: Group is still not decided in the ui
|
||||
* @param group TODO: type may still change
|
||||
* @param unset toggle for adding or removing from the group
|
||||
*/
|
||||
public async setGroupSelected(group: Partial<Group>, unset?: boolean): Promise<void> {
|
||||
this.selectedRows.forEach(vm => {
|
||||
const groups = vm.groupIds;
|
||||
const idx = groups.indexOf(group.id);
|
||||
if (unset && idx >= 0) {
|
||||
groups.slice(idx, 1);
|
||||
} else if (!unset && idx < 0) {
|
||||
groups.push(group.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for bulk resetting passwords. Needs multiSelect mode.
|
||||
* TODO: Not yet implemented (no service yet)
|
||||
*/
|
||||
public async resetPasswordsSelected(): Promise<void> {
|
||||
// for (const user of this.selectedRows) {
|
||||
// await this.resetPassword(user);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for bulk setting/unsetting the 'active' attribute.
|
||||
* Uses selectedRows defined via multiSelect mode.
|
||||
*/
|
||||
public async setActiveSelected(active: boolean): Promise<void> {
|
||||
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> {
|
||||
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> {
|
||||
for (const user of this.selectedRows) {
|
||||
await this.repo.update({ is_committee: is_committee }, user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for bulk sending e-mail invitations. Uses selectedRows defined via
|
||||
* multiSelect mode. TODO: Not yet implemented (no service)
|
||||
*/
|
||||
public async sendInvitationSelected(): Promise<void> {
|
||||
// this.selectedRows.forEach(vm => {
|
||||
// TODO if !vm.emailSent {vm.sendInvitation}
|
||||
// });
|
||||
}
|
||||
|
||||
public getColumnDefinition(): string[] {
|
||||
const columns = ['name', 'group', 'presence'];
|
||||
if (this.isMultiSelect) {
|
||||
return ['selector'].concat(columns);
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
|
@ -47,14 +47,14 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
|
||||
updateUser.username = viewUser.username;
|
||||
}
|
||||
|
||||
await this.dataSend.updateModel(updateUser);
|
||||
return await this.dataSend.updateModel(updateUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a given user
|
||||
*/
|
||||
public async delete(viewUser: ViewUser): Promise<void> {
|
||||
await this.dataSend.deleteModel(viewUser.user);
|
||||
return await this.dataSend.deleteModel(viewUser.user);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,7 +2,7 @@
|
||||
@include mat-core();
|
||||
|
||||
/** Import brand theme and (new) component themes */
|
||||
@import './assets/styles/openslides-theme';
|
||||
@import './assets/styles/openslides-theme.scss';
|
||||
@import './app/site/site.component.scss-theme';
|
||||
@import '../node_modules/roboto-fontface/css/roboto/roboto-fontface.css';
|
||||
@import '../node_modules/roboto-fontface/css/roboto-condensed/roboto-condensed-fontface.css';
|
||||
@ -110,6 +110,10 @@ body {
|
||||
cursor: pointer;
|
||||
background-color: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
mat-row.selected {
|
||||
cursor: pointer;
|
||||
background-color: rgba(0, 0, 0, 0.055);
|
||||
}
|
||||
}
|
||||
|
||||
.card-plus-distance {
|
||||
@ -153,6 +157,18 @@ mat-panel-title mat-icon {
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
|
||||
.hidden-cell {
|
||||
flex: 0;
|
||||
width: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkbox-cell {
|
||||
flex: 1;
|
||||
max-width: 30px;
|
||||
}
|
||||
|
||||
// ngx-file-drop requires the custom style in the global css file
|
||||
.file-drop-style {
|
||||
margin: auto;
|
||||
|
Loading…
Reference in New Issue
Block a user