sorting tree view filters
- callList - refactored agendaSort
This commit is contained in:
parent
5e33c500c3
commit
37f0baf165
@ -103,6 +103,8 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
||||
|
||||
/**
|
||||
* Function that will be used for filtering the nodes.
|
||||
* Should return false for an item is to be visible
|
||||
* TODO this inverts all other 'sorting' conventions elsewhere!
|
||||
*/
|
||||
private activeFilter: (node: T) => boolean;
|
||||
|
||||
|
@ -31,8 +31,8 @@
|
||||
<span class="sort-grid">
|
||||
<div class="hint">{{ 'Visibility' | translate }}</div>
|
||||
<div>
|
||||
<mat-checkbox *ngFor="let option of filterOptions" [(ngModel)]="option.state" (change)="onFilterChange(option.name)">
|
||||
<mat-icon matTooltip="{{ option.name | translate }}">{{ getIcon(option.name) }}</mat-icon> {{ option.name | translate }}
|
||||
<mat-checkbox *ngFor="let option of filterOptions" [(ngModel)]="option.state" (change)="onFilterChange(option.id)">
|
||||
<mat-icon matTooltip="{{ option.label | translate }}">{{ getIcon(option.label) }}</mat-icon> {{ option.label | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</span>
|
||||
|
@ -1,17 +1,15 @@
|
||||
import { Component, ViewChild, EventEmitter, OnInit } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable, BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
||||
import { BaseViewComponent } from '../../../base/base-view';
|
||||
import { SortingTreeComponent } from 'app/shared/components/sorting-tree/sorting-tree.component';
|
||||
import { ViewItem } from '../../models/view-item';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { CanComponentDeactivate } from 'app/shared/utils/watch-sorting-tree.guard';
|
||||
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
||||
import { SortTreeViewComponent, SortTreeFilterOption } from 'app/site/base/sort-tree.component';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { ViewItem } from '../../models/view-item';
|
||||
|
||||
/**
|
||||
* Sort view for the agenda.
|
||||
@ -21,58 +19,25 @@ import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
templateUrl: './agenda-sort.component.html',
|
||||
styleUrls: ['./agenda-sort.component.scss']
|
||||
})
|
||||
export class AgendaSortComponent extends BaseViewComponent implements CanComponentDeactivate, OnInit {
|
||||
export class AgendaSortComponent extends SortTreeViewComponent<ViewItem> implements OnInit {
|
||||
/**
|
||||
* Reference to the view child
|
||||
* All agendaItems sorted by their weight {@link ViewItem.weight}
|
||||
*/
|
||||
@ViewChild('osSortedTree')
|
||||
public osSortTree: SortingTreeComponent<ViewItem>;
|
||||
|
||||
/**
|
||||
* Emitter to emit if the nodes should expand or collapse.
|
||||
*/
|
||||
public readonly changeState: EventEmitter<Boolean> = new EventEmitter<Boolean>();
|
||||
|
||||
/**
|
||||
* Emitter who emits the filters to the sorting tree.
|
||||
*/
|
||||
public readonly changeFilter: EventEmitter<(item: ViewItem) => boolean> = new EventEmitter<
|
||||
(item: ViewItem) => boolean
|
||||
>();
|
||||
public itemsObservable: Observable<ViewItem[]>;
|
||||
|
||||
/**
|
||||
* These are the available options for filtering the nodes.
|
||||
* Adds the property `state` to identify if the option is marked as active.
|
||||
* When reset the filters, the option `state` will be set to `false`.
|
||||
*/
|
||||
public filterOptions = itemVisibilityChoices.map(item => {
|
||||
return { ...item, state: false };
|
||||
public filterOptions: SortTreeFilterOption[] = itemVisibilityChoices.map(item => {
|
||||
return { label: item.name, id: item.key, state: false };
|
||||
});
|
||||
|
||||
/**
|
||||
* BehaviourSubject to get informed every time the filters change.
|
||||
*/
|
||||
private activeFilters: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]);
|
||||
|
||||
/**
|
||||
* Boolean to check if changes has been made.
|
||||
*/
|
||||
public hasChanged = false;
|
||||
|
||||
/**
|
||||
* Boolean to check if filters are active, so they could be removed.
|
||||
*/
|
||||
public hasActiveFilter = false;
|
||||
|
||||
/**
|
||||
* Array, that holds the number of visible nodes and amount of available nodes.
|
||||
*/
|
||||
public seenNodes: [number, number] = [0, 0];
|
||||
|
||||
/**
|
||||
* All agendaItems sorted by their weight {@link ViewItem.weight}
|
||||
*/
|
||||
public itemsObservable: Observable<ViewItem[]>;
|
||||
protected activeFilters: BehaviorSubject<number[]> = new BehaviorSubject<number[]>([]);
|
||||
|
||||
/**
|
||||
* Updates the incoming/changing agenda items.
|
||||
@ -87,12 +52,30 @@ export class AgendaSortComponent extends BaseViewComponent implements CanCompone
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
private agendaRepo: ItemRepositoryService,
|
||||
private promptService: PromptService
|
||||
promptService: PromptService
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
super(title, translate, matSnackBar, promptService);
|
||||
this.itemsObservable = this.agendaRepo.getViewModelListObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to emit the active filters.
|
||||
* Filters will be stored in an array to prevent duplicated options.
|
||||
* Furthermore if the option is already included in this array, then it will be deleted.
|
||||
* This array will be emitted.
|
||||
*
|
||||
* @param filter Is the filter that was activated by the user.
|
||||
*/
|
||||
public onFilterChange(filter: number): void {
|
||||
const value = this.activeFilters.value;
|
||||
if (!value.includes(filter)) {
|
||||
value.push(filter);
|
||||
} else {
|
||||
value.splice(value.indexOf(filter), 1);
|
||||
}
|
||||
this.activeFilters.next(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* OnInit method
|
||||
*/
|
||||
@ -100,64 +83,17 @@ export class AgendaSortComponent extends BaseViewComponent implements CanCompone
|
||||
/**
|
||||
* Passes the active filters as an array to the subject.
|
||||
*/
|
||||
const filter = this.activeFilters.subscribe((value: string[]) => {
|
||||
const filter = this.activeFilters.subscribe((value: number[]) => {
|
||||
this.hasActiveFilter = value.length === 0 ? false : true;
|
||||
this.changeFilter.emit(
|
||||
(item: ViewItem): boolean => {
|
||||
return !(value.includes(item.verboseType) || value.length === 0);
|
||||
return !(value.includes(item.type) || value.length === 0);
|
||||
}
|
||||
);
|
||||
});
|
||||
this.subscriptions.push(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to save the tree by click.
|
||||
*/
|
||||
public async onSave(): Promise<void> {
|
||||
await this.agendaRepo
|
||||
.sortItems(this.osSortTree.getTreeData())
|
||||
.then(() => this.osSortTree.setSubscription(), this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to restore the old state.
|
||||
*/
|
||||
public async onCancel(): Promise<void> {
|
||||
if (await this.canDeactivate()) {
|
||||
this.osSortTree.setSubscription();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get an info if changes has been made.
|
||||
*
|
||||
* @param hasChanged Boolean received from the tree to see that changes has been made.
|
||||
*/
|
||||
public receiveChanges(hasChanged: boolean): void {
|
||||
this.hasChanged = hasChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to receive the new number of visible nodes when the filter has changed.
|
||||
*
|
||||
* @param nextNumberOfSeenNodes is an array with two indices:
|
||||
* The first gives the number of currently shown nodes.
|
||||
* The second tells how many nodes available.
|
||||
*/
|
||||
public onChangeAmountOfItems(nextNumberOfSeenNodes: [number, number]): void {
|
||||
this.seenNodes = nextNumberOfSeenNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to emit if the nodes should be expanded or collapsed.
|
||||
*
|
||||
* @param nextState Is the next state, expanded or collapsed, the nodes should be.
|
||||
*/
|
||||
public onStateChange(nextState: boolean): void {
|
||||
this.changeState.emit(nextState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to set the active filters to null.
|
||||
*/
|
||||
@ -169,36 +105,21 @@ export class AgendaSortComponent extends BaseViewComponent implements CanCompone
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to emit the active filters.
|
||||
* Filters will be stored in an array to prevent duplicated options.
|
||||
* Furthermore if the option is already included in this array, then it will be deleted.
|
||||
* This array will be emitted.
|
||||
*
|
||||
* @param filter Is the filter that was activated by the user.
|
||||
* Function to save the tree by click.
|
||||
*/
|
||||
public onFilterChange(filter: string): void {
|
||||
const value = this.activeFilters.value;
|
||||
if (!value.includes(filter)) {
|
||||
value.push(filter);
|
||||
} else {
|
||||
value.splice(value.indexOf(filter), 1);
|
||||
}
|
||||
this.activeFilters.next(value);
|
||||
public async onSave(): Promise<void> {
|
||||
await this.agendaRepo
|
||||
.sortItems(this.osSortTree.getTreeData())
|
||||
.then(() => this.osSortTree.setSubscription(), this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to open a prompt dialog,
|
||||
* so the user will be warned if he has made changes and not saved them.
|
||||
* Function to emit if the nodes should be expanded or collapsed.
|
||||
*
|
||||
* @returns The result from the prompt dialog.
|
||||
* @param nextState Is the next state, expanded or collapsed, the nodes should be.
|
||||
*/
|
||||
public async canDeactivate(): Promise<boolean> {
|
||||
if (this.hasChanged) {
|
||||
const title = this.translate.instant('Do you really want to exit this page?');
|
||||
const content = this.translate.instant('You made changes.');
|
||||
return await this.promptService.open(title, content);
|
||||
}
|
||||
return true;
|
||||
public onStateChange(nextState: boolean): void {
|
||||
this.changeState.emit(nextState);
|
||||
}
|
||||
|
||||
/**
|
||||
|
127
client/src/app/site/base/sort-tree.component.ts
Normal file
127
client/src/app/site/base/sort-tree.component.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import { ViewChild, EventEmitter } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { BaseViewModel } from './base-view-model';
|
||||
import { BaseViewComponent } from './base-view';
|
||||
import { CanComponentDeactivate } from 'app/shared/utils/watch-sorting-tree.guard';
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { SortingTreeComponent } from 'app/shared/components/sorting-tree/sorting-tree.component';
|
||||
|
||||
export interface SortTreeFilterOption extends Identifiable {
|
||||
label: string;
|
||||
id: number;
|
||||
state: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract Sort view for hierarchic item trees
|
||||
*/
|
||||
export abstract class SortTreeViewComponent<V extends BaseViewModel> extends BaseViewComponent
|
||||
implements CanComponentDeactivate {
|
||||
/**
|
||||
* Reference to the view child
|
||||
*/
|
||||
@ViewChild('osSortedTree')
|
||||
public osSortTree: SortingTreeComponent<V>;
|
||||
|
||||
/**
|
||||
* Emitter to emit if the nodes should expand or collapse.
|
||||
*/
|
||||
public readonly changeState: EventEmitter<Boolean> = new EventEmitter<Boolean>();
|
||||
|
||||
/**
|
||||
* Emitter that emits the filters to the sorting tree.
|
||||
* TODO note that the boolean function currently requires false if the item
|
||||
* is to be visible!
|
||||
*/
|
||||
public readonly changeFilter: EventEmitter<(item: V) => boolean> = new EventEmitter<(item: V) => boolean>();
|
||||
|
||||
/**
|
||||
* Boolean to check if changes has been made.
|
||||
*/
|
||||
public hasChanged = false;
|
||||
|
||||
/**
|
||||
* Boolean to check if filters are active, so they could be removed.
|
||||
*/
|
||||
public hasActiveFilter = false;
|
||||
|
||||
/**
|
||||
* Array that holds the number of visible nodes(0) and amount of available
|
||||
* nodes(1).
|
||||
*/
|
||||
public seenNodes: [number, number] = [0, 0];
|
||||
|
||||
/**
|
||||
* Updates the incoming/changing agenda items.
|
||||
* @param title
|
||||
* @param translate
|
||||
* @param matSnackBar
|
||||
* @param promptService
|
||||
*/
|
||||
public constructor(
|
||||
title: Title,
|
||||
public translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
protected promptService: PromptService
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to restore the old state.
|
||||
*/
|
||||
public async onCancel(): Promise<void> {
|
||||
if (await this.canDeactivate()) {
|
||||
this.osSortTree.setSubscription();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to set an info if changes has been made.
|
||||
*
|
||||
* @param hasChanged Boolean received from the tree to see that changes has been made.
|
||||
*/
|
||||
public receiveChanges(hasChanged: boolean): void {
|
||||
this.hasChanged = hasChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to receive the new number of visible nodes when the filter has changed.
|
||||
*
|
||||
* @param nextNumberOfSeenNodes is an array with two indices:
|
||||
* The first gives the number of currently shown nodes.
|
||||
* The second tells how many nodes available.
|
||||
*/
|
||||
public onChangeAmountOfItems(nextNumberOfSeenNodes: [number, number]): void {
|
||||
this.seenNodes = nextNumberOfSeenNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to emit if the nodes should be expanded or collapsed.
|
||||
*
|
||||
* @param nextState Is the next state, expanded or collapsed, the nodes should be.
|
||||
*/
|
||||
public onStateChange(nextState: boolean): void {
|
||||
this.changeState.emit(nextState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to open a prompt dialog, so the user will be warned if they have
|
||||
* made changes and not saved them.
|
||||
*
|
||||
* @returns The result from the prompt dialog.
|
||||
*/
|
||||
public async canDeactivate(): Promise<boolean> {
|
||||
if (this.hasChanged) {
|
||||
const title = this.translate.instant('Do you really want to exit this page?');
|
||||
const content = this.translate.instant('You made changes.');
|
||||
return await this.promptService.open(title, content);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,10 +1,4 @@
|
||||
<os-head-bar
|
||||
prevUrl="../.."
|
||||
[nav]="false"
|
||||
[editMode]="hasChanged"
|
||||
(mainEvent)="onCancel()"
|
||||
(saveEvent)="onSave()">
|
||||
|
||||
<os-head-bar prevUrl="../.." [nav]="false" [editMode]="hasChanged" (mainEvent)="onCancel()" (saveEvent)="onSave()">
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
<h2 translate>Call list</h2>
|
||||
@ -12,19 +6,83 @@
|
||||
|
||||
<!-- Export menu -->
|
||||
<div class="menu-slot">
|
||||
<button type="button" mat-icon-button [matMenuTriggerFor]="downloadMenu">
|
||||
<button type="button" mat-icon-button [matMenuTriggerFor]="mainMenu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</os-head-bar>
|
||||
|
||||
<div class="custom-table-header flex-spaced on-transition-fade">
|
||||
<div class="filter-count">
|
||||
<span> {{ seenNodes[0] }} </span><span translate>of</span>
|
||||
<span> {{ seenNodes[1] }}</span>
|
||||
</div>
|
||||
<div class="current-filters" *ngIf="hasActiveFilter">
|
||||
<div><span translate>Active filters</span>: </div>
|
||||
<div>
|
||||
<button mat-button (click)="resetFilters()">
|
||||
<mat-icon inline>cancel</mat-icon>
|
||||
<span translate>Clear all</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-spaced margin-right-5">
|
||||
<div *ngIf="categoryFilterOptions.length">
|
||||
<button mat-button [matMenuTriggerFor]="catFilterMenu">
|
||||
<span
|
||||
class="upper"
|
||||
[matBadge]="activeCatFilterCount > 0 ? activeCatFilterCount : null"
|
||||
matBadgeColor="accent"
|
||||
[matBadgeOverlap]="false"
|
||||
translate
|
||||
>
|
||||
Categories
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="tagFilterOptions.length">
|
||||
<button mat-button [matMenuTriggerFor]="tagFilterMenu">
|
||||
<span
|
||||
class="upper"
|
||||
[matBadge]="activeTagFilterCount > 0 ? activeTagFilterCount : null"
|
||||
matBadgeColor="accent"
|
||||
[matBadgeOverlap]="false"
|
||||
translate
|
||||
>
|
||||
Tags
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-menu #catFilterMenu="matMenu">
|
||||
<div *ngFor="let catOption of categoryFilterOptions">
|
||||
<button mat-menu-item (click)="onFilterChange('category', catOption.id)">
|
||||
<mat-icon *ngIf="catOption.state">checked</mat-icon>
|
||||
<span>{{ catOption.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</mat-menu>
|
||||
<mat-menu #tagFilterMenu="matMenu">
|
||||
<div *ngFor="let tagOption of tagFilterOptions">
|
||||
<button mat-menu-item (click)="onFilterChange('tag', tagOption.id)">
|
||||
<mat-icon *ngIf="tagOption.state">checked</mat-icon>
|
||||
<span>{{ tagOption.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</mat-menu>
|
||||
|
||||
<mat-card>
|
||||
<os-sorting-tree
|
||||
#osSortedTree
|
||||
parentKey="sort_parent_id"
|
||||
weightKey="weight"
|
||||
(visibleNodes)="onChangeAmountOfItems($event)"
|
||||
(hasChanged)="receiveChanges($event)"
|
||||
[model]="motionsObservable">
|
||||
[model]="motionsObservable"
|
||||
[filterChange]="changeFilter"
|
||||
>
|
||||
<ng-template #innerNode let-item="item">
|
||||
<div class="line">
|
||||
<div class="left">
|
||||
@ -41,7 +99,7 @@
|
||||
</os-sorting-tree>
|
||||
</mat-card>
|
||||
|
||||
<mat-menu #downloadMenu="matMenu">
|
||||
<mat-menu #mainMenu="matMenu">
|
||||
<button mat-menu-item (click)="pdfExportCallList()">
|
||||
<mat-icon>picture_as_pdf</mat-icon>
|
||||
<span translate>Export as PDF</span>
|
||||
|
@ -20,3 +20,6 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
.margin-right-5 {
|
||||
margin-right: 5px !important;
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
|
||||
import { Observable, BehaviorSubject } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import { CanComponentDeactivate } from 'app/shared/utils/watch-sorting-tree.guard';
|
||||
import { CategoryRepositoryService } from 'app/core/repositories/motions/category-repository.service';
|
||||
import { FlatNode } from 'app/core/ui-services/tree.service';
|
||||
import { MotionCsvExportService } from 'app/site/motions/services/motion-csv-export.service';
|
||||
import { MotionPdfExportService } from 'app/site/motions/services/motion-pdf-export.service';
|
||||
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { SortingTreeComponent } from 'app/shared/components/sorting-tree/sorting-tree.component';
|
||||
import { SortTreeViewComponent, SortTreeFilterOption } from 'app/site/base/sort-tree.component';
|
||||
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
|
||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
|
||||
@ -22,15 +22,12 @@ import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
@Component({
|
||||
selector: 'os-call-list',
|
||||
templateUrl: './call-list.component.html',
|
||||
styleUrls: ['./call-list.component.scss']
|
||||
styleUrls: [
|
||||
'./call-list.component.scss',
|
||||
'../../../../shared/components/sort-filter-bar/sort-filter-bar.component.scss'
|
||||
]
|
||||
})
|
||||
export class CallListComponent extends BaseViewComponent implements CanComponentDeactivate {
|
||||
/**
|
||||
* Reference to the sorting tree.
|
||||
*/
|
||||
@ViewChild('osSortedTree')
|
||||
private osSortTree: SortingTreeComponent<ViewMotion>;
|
||||
|
||||
export class CallListComponent extends SortTreeViewComponent<ViewMotion> implements OnInit {
|
||||
/**
|
||||
* All motions sorted first by weight, then by id.
|
||||
*/
|
||||
@ -47,11 +44,46 @@ export class CallListComponent extends BaseViewComponent implements CanComponent
|
||||
public hasChanged = false;
|
||||
|
||||
/**
|
||||
* Updates the motions member, and sorts it.
|
||||
* A (dynamically updating) list of available and active filters by tag
|
||||
*/
|
||||
public tagFilterOptions: SortTreeFilterOption[] = [];
|
||||
|
||||
/**
|
||||
* The current amount of tag filters active
|
||||
*/
|
||||
public activeTagFilterCount = 0;
|
||||
|
||||
/**
|
||||
* A (dynamically updating) list of available and active filters by category
|
||||
*/
|
||||
public categoryFilterOptions: SortTreeFilterOption[] = [];
|
||||
|
||||
/**
|
||||
* The current amount of category filters active
|
||||
*/
|
||||
public activeCatFilterCount = 0;
|
||||
|
||||
/**
|
||||
* BehaviorSubject to get informed every time the tag filter changes.
|
||||
*/
|
||||
protected activeTagFilters: BehaviorSubject<number[]> = new BehaviorSubject<number[]>([]);
|
||||
|
||||
/**
|
||||
* BehaviourSubject to get informed every time the category filter changes.
|
||||
*/
|
||||
protected activeCatFilters: BehaviorSubject<number[]> = new BehaviorSubject<number[]>([]);
|
||||
|
||||
/**
|
||||
* constructor. Subscribes to the motions
|
||||
*
|
||||
* @param title
|
||||
* @param translate
|
||||
* @param matSnackBar
|
||||
* @param motionRepo
|
||||
* @param motionCsvExport
|
||||
* @param motionPdfExport
|
||||
* @param tagRepo
|
||||
* @param categoryRepo
|
||||
* @param promptService
|
||||
*/
|
||||
public constructor(
|
||||
@ -61,9 +93,11 @@ export class CallListComponent extends BaseViewComponent implements CanComponent
|
||||
private motionRepo: MotionRepositoryService,
|
||||
private motionCsvExport: MotionCsvExportService,
|
||||
private motionPdfExport: MotionPdfExportService,
|
||||
private promptService: PromptService
|
||||
private tagRepo: TagRepositoryService,
|
||||
private categoryRepo: CategoryRepositoryService,
|
||||
promptService: PromptService
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
super(title, translate, matSnackBar, promptService);
|
||||
|
||||
this.motionsObservable = this.motionRepo.getViewModelListObservable();
|
||||
this.motionsObservable.subscribe(motions => {
|
||||
@ -72,6 +106,75 @@ export class CallListComponent extends BaseViewComponent implements CanComponent
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes filters and filter subscriptions
|
||||
*/
|
||||
public ngOnInit(): void {
|
||||
this.subscriptions.push(
|
||||
this.activeTagFilters.subscribe((value: number[]) => this.onSubscribedFilterChange('tag', value))
|
||||
);
|
||||
this.subscriptions.push(
|
||||
this.activeCatFilters.subscribe((value: number[]) => this.onSubscribedFilterChange('category', value))
|
||||
);
|
||||
this.subscriptions.push(
|
||||
this.tagRepo.getViewModelListBehaviorSubject().subscribe(tags => {
|
||||
if (tags && tags.length) {
|
||||
this.tagFilterOptions = tags.map(tag => {
|
||||
return {
|
||||
label: tag.name,
|
||||
id: tag.id,
|
||||
state:
|
||||
this.tagFilterOptions &&
|
||||
this.tagFilterOptions.some(tagfilter => {
|
||||
return tagfilter.id === tag.id && tagfilter.state === true;
|
||||
})
|
||||
};
|
||||
});
|
||||
this.tagFilterOptions.push({
|
||||
label: this.translate.instant('No tags'),
|
||||
id: 0,
|
||||
state:
|
||||
this.tagFilterOptions &&
|
||||
this.tagFilterOptions.some(tagfilter => {
|
||||
return tagfilter.id === 0 && tagfilter.state === true;
|
||||
})
|
||||
});
|
||||
} else {
|
||||
this.tagFilterOptions = [];
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.subscriptions.push(
|
||||
this.categoryRepo.getViewModelListBehaviorSubject().subscribe(categories => {
|
||||
if (categories && categories.length) {
|
||||
this.categoryFilterOptions = categories.map(cat => {
|
||||
return {
|
||||
label: cat.prefixedName,
|
||||
id: cat.id,
|
||||
state:
|
||||
this.categoryFilterOptions &&
|
||||
this.categoryFilterOptions.some(catfilter => {
|
||||
return catfilter.id === cat.id && catfilter.state === true;
|
||||
})
|
||||
};
|
||||
});
|
||||
this.categoryFilterOptions.push({
|
||||
label: this.translate.instant('No category'),
|
||||
id: 0,
|
||||
state:
|
||||
this.categoryFilterOptions &&
|
||||
this.categoryFilterOptions.some(catfilter => {
|
||||
return catfilter.id === 0 && catfilter.state === true;
|
||||
})
|
||||
});
|
||||
} else {
|
||||
this.categoryFilterOptions = [];
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to save changes on click.
|
||||
*/
|
||||
@ -91,7 +194,7 @@ export class CallListComponent extends BaseViewComponent implements CanComponent
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get an info if changes has been made.
|
||||
* Function to register the info that changes has been made.
|
||||
*
|
||||
* @param hasChanged Boolean received from the tree to see that changes has been made.
|
||||
*/
|
||||
@ -113,21 +216,6 @@ export class CallListComponent extends BaseViewComponent implements CanComponent
|
||||
this.motionPdfExport.exportPdfCallList(this.motions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to open a prompt dialog,
|
||||
* so the user will be warned if he has made changes and not saved them.
|
||||
*
|
||||
* @returns The result from the prompt dialog.
|
||||
*/
|
||||
public async canDeactivate(): Promise<boolean> {
|
||||
if (this.hasChanged) {
|
||||
const title = this.translate.instant('Do you really want to exit this page?');
|
||||
const content = this.translate.instant('You made changes.');
|
||||
return await this.promptService.open(title, content);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tags associated with the motion of a sorting item
|
||||
*
|
||||
@ -138,4 +226,77 @@ export class CallListComponent extends BaseViewComponent implements CanComponent
|
||||
const motion = this.motionRepo.getViewModel(item.id);
|
||||
return motion ? motion.tags : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles a filter. An array with the filter ids array will be emitted
|
||||
* as active/model/Filters
|
||||
*
|
||||
* @param model either 'tag' or 'category' for the filter that changed
|
||||
* @param filter the filter that is supposed to be toggled.
|
||||
*/
|
||||
public onFilterChange(model: 'tag' | 'category', filter: number): void {
|
||||
const value = model === 'tag' ? this.activeTagFilters.value : this.activeCatFilters.value;
|
||||
if (!value.includes(filter)) {
|
||||
value.push(filter);
|
||||
} else {
|
||||
value.splice(value.indexOf(filter), 1);
|
||||
}
|
||||
if (model === 'tag') {
|
||||
this.activeTagFilters.next(value);
|
||||
} else {
|
||||
this.activeCatFilters.next(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to unset all active filters.
|
||||
*/
|
||||
public resetFilters(): void {
|
||||
this.activeTagFilters.next([]);
|
||||
this.activeCatFilters.next([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to trigger an update of the filter itself and the information about
|
||||
* the state of filters
|
||||
*
|
||||
* @param model the property/model the filter is for
|
||||
* @param value
|
||||
*/
|
||||
private onSubscribedFilterChange(model: 'tag' | 'category', value: number[]): void {
|
||||
if (model === 'tag') {
|
||||
this.activeTagFilterCount = value.length;
|
||||
this.tagFilterOptions.forEach(filterOption => {
|
||||
filterOption.state = value && value.some(v => v === filterOption.id);
|
||||
});
|
||||
} else {
|
||||
this.activeCatFilterCount = value.length;
|
||||
this.categoryFilterOptions.forEach(filterOption => {
|
||||
filterOption.state = value && value.some(v => v === filterOption.id);
|
||||
});
|
||||
}
|
||||
this.hasActiveFilter = this.activeCatFilterCount > 0 || this.activeTagFilterCount > 0;
|
||||
|
||||
const currentTagFilters = this.tagFilterOptions.filter(option => option.state === true);
|
||||
const currentCategoryFilters = this.categoryFilterOptions.filter(option => option.state === true);
|
||||
this.changeFilter.emit(
|
||||
// TODO this is ugly and potentially reversed
|
||||
(item: ViewMotion): boolean => {
|
||||
if (currentTagFilters.length) {
|
||||
if (!item.tags || !item.tags.length) {
|
||||
if (!currentTagFilters.some(filter => filter.id === 0)) {
|
||||
return true;
|
||||
}
|
||||
} else if (!item.tags.some(tag => currentTagFilters.some(filter => filter.id === tag.id))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (currentCategoryFilters.length) {
|
||||
const category_id = item.category_id || 0;
|
||||
return !currentCategoryFilters.some(filter => filter.id === category_id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user