Merge pull request #4112 from MaximilianKrambach/sortSearchFixes
sortSearch improvements (fixes #4098)
This commit is contained in:
commit
c3ed0d0dad
@ -22,11 +22,15 @@ export interface OsFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes a list of available options for a drop down menu of a filter
|
* Describes a list of available options for a drop down menu of a filter.
|
||||||
|
* A filter condition of null will be interpreted as a negative filter
|
||||||
|
* ('None of the other filter options').
|
||||||
|
* Filter condition numbers/number arrays will be checked against numerical
|
||||||
|
* values and as id(s) for objects.
|
||||||
*/
|
*/
|
||||||
export interface OsFilterOption {
|
export interface OsFilterOption {
|
||||||
label: string;
|
label: string;
|
||||||
condition: string | boolean | number;
|
condition: string | boolean | number | number[];
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +59,47 @@ export abstract class FilterListService<M extends BaseModel, V extends BaseViewM
|
|||||||
|
|
||||||
protected name: string;
|
protected name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns the total count of items before the filter
|
||||||
|
*/
|
||||||
|
public get totalCount(): number {
|
||||||
|
return this.currentRawData ? this.currentRawData.length : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns the amount of items that pass the current filters
|
||||||
|
*/
|
||||||
|
public get filteredCount(): number {
|
||||||
|
return this.filteredData ? this.filteredData.length : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount of filters currently in use by this filter Service
|
||||||
|
*
|
||||||
|
* @returns a number of filters
|
||||||
|
*/
|
||||||
|
public get activeFilterCount(): number {
|
||||||
|
if (!this.filterDefinitions || !this.filterDefinitions.length) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let filters = 0;
|
||||||
|
for (const filter of this.filterDefinitions) {
|
||||||
|
if (filter.count) {
|
||||||
|
filters += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean indicationg if there are any filters described in this service
|
||||||
|
*
|
||||||
|
* @returns true if there are defined filters (regardless of current state)
|
||||||
|
*/
|
||||||
|
public get hasFilterOptions(): boolean {
|
||||||
|
return this.filterDefinitions && this.filterDefinitions.length ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*/
|
*/
|
||||||
@ -102,6 +147,12 @@ export abstract class FilterListService<M extends BaseModel, V extends BaseViewM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a filter option.
|
||||||
|
*
|
||||||
|
* @param filterName: The property name of this filter
|
||||||
|
* @param option: The option to disable
|
||||||
|
*/
|
||||||
public removeFilterOption(filterName: string, option: OsFilterOption): void {
|
public removeFilterOption(filterName: string, option: OsFilterOption): void {
|
||||||
const filter = this.filterDefinitions.find(f => f.property === filterName);
|
const filter = this.filterDefinitions.find(f => f.property === filterName);
|
||||||
if (filter) {
|
if (filter) {
|
||||||
@ -213,59 +264,101 @@ export abstract class FilterListService<M extends BaseModel, V extends BaseViewM
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to see if a model instance passes a filter
|
* Checks if a given ViewBaseModel passes the filter.
|
||||||
|
*
|
||||||
* @param item
|
* @param item
|
||||||
* @param filter
|
* @param filter
|
||||||
|
* @returns true if the item is to be dispalyed according to the filter
|
||||||
*/
|
*/
|
||||||
private checkIncluded(item: V, filter: OsFilter): boolean {
|
private checkIncluded(item: V, filter: OsFilter): boolean {
|
||||||
|
const nullFilter = filter.options.find(
|
||||||
|
option => typeof option !== 'string' && option.isActive && option.condition === null
|
||||||
|
);
|
||||||
|
let passesNullFilter = true;
|
||||||
for (const option of filter.options) {
|
for (const option of filter.options) {
|
||||||
|
// ignored options
|
||||||
if (typeof option === 'string') {
|
if (typeof option === 'string') {
|
||||||
continue;
|
continue;
|
||||||
}
|
} else if (nullFilter && option === nullFilter) {
|
||||||
if (option.isActive) {
|
continue;
|
||||||
if (option.condition === null) {
|
// active option. The item is included if it passes this test
|
||||||
return this.checkIncludedNegative(item, filter);
|
} else if (option.isActive) {
|
||||||
}
|
if (this.checkFilterIncluded(item, filter, option)) {
|
||||||
if (item[filter.property] === undefined) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (item[filter.property] instanceof BaseModel) {
|
|
||||||
if (item[filter.property].id === option.condition) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (item[filter.property] === option.condition) {
|
|
||||||
return true;
|
|
||||||
} else if (item[filter.property].toString() === option.condition) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// if a null filter is set, the item needs to not pass all inactive filters
|
||||||
|
} else if (
|
||||||
|
nullFilter &&
|
||||||
|
(item[filter.property] !== null || item[filter.property] !== undefined) &&
|
||||||
|
this.checkFilterIncluded(item, filter, option)
|
||||||
|
) {
|
||||||
|
passesNullFilter = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (nullFilter && passesNullFilter) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if none of the defined non-null filters apply,
|
* Checks an item against a single filter option.
|
||||||
* aka 'items that match no filter'
|
*
|
||||||
* @param item: A viewModel
|
* @param item A BaseModel to be checked
|
||||||
* @param filter
|
* @param filter The parent filter
|
||||||
|
* @param option The option to be checked
|
||||||
|
* @returns true if the filter condition matches the item
|
||||||
*/
|
*/
|
||||||
private checkIncludedNegative(item: V, filter: OsFilter): boolean {
|
private checkFilterIncluded(item: V, filter: OsFilter, option: OsFilterOption): boolean {
|
||||||
if (item[filter.property] === undefined) {
|
if (item[filter.property] === undefined || item[filter.property] === null) {
|
||||||
|
return false;
|
||||||
|
} else if (Array.isArray(item[filter.property])) {
|
||||||
|
const compareValueCondition = (value, condition): boolean => {
|
||||||
|
if (value === condition) {
|
||||||
|
return true;
|
||||||
|
} else if (value.hasOwnProperty('id') && value.id === condition) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
for (const value of item[filter.property]) {
|
||||||
|
if (Array.isArray(option.condition)) {
|
||||||
|
for (const condition of option.condition) {
|
||||||
|
if (compareValueCondition(value, condition)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (compareValueCondition(value, option.condition)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(option.condition)) {
|
||||||
|
if (
|
||||||
|
option.condition.indexOf(item[filter.property]) > -1 ||
|
||||||
|
option.condition.indexOf(item[filter.property].id) > -1
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (typeof item[filter.property] === 'object' && item[filter.property].hasOwnProperty('id')) {
|
||||||
|
if (item[filter.property].id === option.condition) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (item[filter.property] === option.condition) {
|
||||||
|
return true;
|
||||||
|
} else if (item[filter.property].toString() === option.condition) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
for (const option of filter.options) {
|
return false;
|
||||||
if (typeof option === 'string' || option.condition === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (item[filter.property] === option.condition) {
|
|
||||||
return false;
|
|
||||||
} else if (item[filter.property].toString() === option.condition) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a translatable label or filter property used for displaying the filter
|
||||||
|
*
|
||||||
|
* @param filter
|
||||||
|
* @returns a name, capitalized first character
|
||||||
|
*/
|
||||||
public getFilterName(filter: OsFilter): string {
|
public getFilterName(filter: OsFilter): string {
|
||||||
if (filter.label) {
|
if (filter.label) {
|
||||||
return filter.label;
|
return filter.label;
|
||||||
@ -275,20 +368,24 @@ export abstract class FilterListService<M extends BaseModel, V extends BaseViewM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get hasActiveFilters(): number {
|
/**
|
||||||
if (!this.filterDefinitions || !this.filterDefinitions.length) {
|
* Removes all active options of a given filter, clearing it
|
||||||
return 0;
|
* @param filter
|
||||||
}
|
*/
|
||||||
let filters = 0;
|
public clearFilter(filter: OsFilter): void {
|
||||||
for (const filter of this.filterDefinitions) {
|
filter.options.forEach(option => {
|
||||||
if (filter.count) {
|
if (typeof option === 'object' && option.isActive) {
|
||||||
filters += 1;
|
this.removeFilterOption(filter.property, option);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
return filters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasFilterOptions(): boolean {
|
/**
|
||||||
return this.filterDefinitions && this.filterDefinitions.length ? true : false;
|
* Removes all filters currently in use from this filterService
|
||||||
|
*/
|
||||||
|
public clearAllFilters(): void {
|
||||||
|
this.filterDefinitions.forEach(filter => {
|
||||||
|
this.clearFilter(filter);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,54 @@
|
|||||||
<div class="custom-table-header on-transition-fade">
|
<div class="custom-table-header flex-spaced on-transition-fade">
|
||||||
<button mat-button *ngIf="hasFilters" (click)="filterMenu.opened ? filterMenu.close() : filterMenu.open()">
|
<div class="filter-count" *ngIf="filterService">
|
||||||
<span *ngIf="filterService.hasActiveFilters > 0">{{ filterService.hasActiveFilters }} </span>
|
<span>{{ filterService.filteredCount }} </span><span translate>of</span>
|
||||||
<span class="upper" translate>Filter</span>
|
<span> {{ filterService.totalCount }}</span>
|
||||||
</button>
|
</div>
|
||||||
<button mat-button *ngIf="vp.isMobile && hasSorting" (click)="openSortDropDown()">
|
<div class="current-filters" *ngIf="filterService && filterService.activeFilterCount">
|
||||||
<span class="upper" translate>Sort</span>
|
<div>
|
||||||
</button>
|
<span translate>Active filters</span>:
|
||||||
<button mat-button *ngIf="!vp.isMobile && hasSorting" [matMenuTriggerFor]="menu">
|
</div>
|
||||||
<span class="upper" translate>Sort</span>
|
<div>
|
||||||
</button>
|
<button mat-button (click)="filterService.clearAllFilters()">
|
||||||
<input
|
<mat-icon inline>cancel</mat-icon>
|
||||||
matInput
|
<span translate>Clear all</span>
|
||||||
*ngIf="isSearchBar"
|
</button>
|
||||||
(keyup)="applySearch($event, $event.target.value)"
|
</div>
|
||||||
osAutofocus
|
<div *ngFor="let filter of filterService.filterDefinitions">
|
||||||
placeholder="{{ translate.instant('Search') }}"
|
<button mat-button *ngIf="filter.count" (click)="filterService.clearFilter(filter)">
|
||||||
[ngClass]="vp.isMobile ? 'vp' : ''"
|
<mat-icon inline>close</mat-icon>
|
||||||
/>
|
<span translate>{{ filterService.getFilterName(filter) }}</span>
|
||||||
<button mat-button (click)="toggleSearchBar()">
|
</button>
|
||||||
<mat-icon>{{ isSearchBar ? 'keyboard_arrow_right' : 'search' }}</mat-icon>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
|
<div>
|
||||||
|
<button mat-button *ngIf="hasFilters" (click)="filterMenu.opened ? filterMenu.close() : filterMenu.open()">
|
||||||
|
<span *ngIf="!filterService.activeFilterCount" class="upper" translate>
|
||||||
|
Filter
|
||||||
|
</span>
|
||||||
|
<span *ngIf="filterService.activeFilterCount">
|
||||||
|
{{ filterService.activeFilterCount }}
|
||||||
|
<span *ngIf="filterService.activeFilterCount === 1" class="upper" translate>Filter</span>
|
||||||
|
<span *ngIf="filterService.activeFilterCount > 1" class="upper" translate>Filters</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button mat-button *ngIf="vp.isMobile && hasSorting" (click)="openSortDropDown()">
|
||||||
|
<span class="upper" translate>Sort</span>
|
||||||
|
</button>
|
||||||
|
<button mat-button *ngIf="!vp.isMobile && hasSorting" [matMenuTriggerFor]="menu">
|
||||||
|
<span class="upper" translate>Sort</span>
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
*ngIf="isSearchBar"
|
||||||
|
(keyup)="applySearch($event, $event.target.value)"
|
||||||
|
osAutofocus
|
||||||
|
placeholder="{{ translate.instant('Search') }}"
|
||||||
|
[ngClass]="vp.isMobile ? 'vp' : ''"
|
||||||
|
/>
|
||||||
|
<button mat-button (click)="toggleSearchBar()">
|
||||||
|
<mat-icon>{{ isSearchBar ? 'keyboard_arrow_right' : 'search' }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Header for the filter side bar -->
|
<!-- Header for the filter side bar -->
|
||||||
|
@ -8,3 +8,26 @@
|
|||||||
span.right-with-margin {
|
span.right-with-margin {
|
||||||
margin-right: 25px;
|
margin-right: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-spaced {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-count {
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-filters {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin: 5px;
|
||||||
|
button {
|
||||||
|
padding: 3px;
|
||||||
|
font-size: 80%;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -40,7 +40,7 @@ export class OsSortFilterBarComponent<V extends BaseViewModel> {
|
|||||||
* be a FilterListService extendingFilterListService.
|
* be a FilterListService extendingFilterListService.
|
||||||
*/
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
public filterService: any; // TODO a FilterListService extendingFilterListService
|
public filterService: any; // TODO a FilterListService extending FilterListService
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
public searchFieldChange = new EventEmitter<string>();
|
public searchFieldChange = new EventEmitter<string>();
|
||||||
@ -112,7 +112,7 @@ export class OsSortFilterBarComponent<V extends BaseViewModel> {
|
|||||||
* Checks if there is an active FilterService present
|
* Checks if there is an active FilterService present
|
||||||
*/
|
*/
|
||||||
public get hasFilters(): boolean {
|
public get hasFilters(): boolean {
|
||||||
if (this.filterService && this.filterService.hasFilterOptions()) {
|
if (this.filterService && this.filterService.hasFilterOptions) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -13,78 +13,80 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
<os-sort-filter-bar [filterService] = "filterService" (searchFieldChange)="searchFilter($event)"></os-sort-filter-bar>
|
<mat-drawer-container class="on-transition-fade">
|
||||||
|
<os-sort-filter-bar [filterService] = "filterService" (searchFieldChange)="searchFilter($event)"></os-sort-filter-bar>
|
||||||
|
|
||||||
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
|
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
|
||||||
<!-- selector column -->
|
<!-- selector column -->
|
||||||
<ng-container matColumnDef="selector">
|
<ng-container matColumnDef="selector">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header class="checkbox-cell"></mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header class="checkbox-cell"></mat-header-cell>
|
||||||
<mat-cell (click)="selectItem(item, $event)" *matCellDef="let item" class="checkbox-cell">
|
<mat-cell (click)="selectItem(item, $event)" *matCellDef="let item" class="checkbox-cell">
|
||||||
<mat-icon>{{ isSelected(item) ? 'check_circle' : '' }}</mat-icon>
|
<mat-icon>{{ isSelected(item) ? 'check_circle' : '' }}</mat-icon>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- title column -->
|
<!-- title column -->
|
||||||
<ng-container matColumnDef="title">
|
<ng-container matColumnDef="title">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Topic</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header>Topic</mat-header-cell>
|
||||||
<!-- <mat-cell (click)="onTitleColumn(item)" *matCellDef="let item"> -->
|
<!-- <mat-cell (click)="onTitleColumn(item)" *matCellDef="let item"> -->
|
||||||
<mat-cell (click)="selectItem(item, $event)" *matCellDef="let item">
|
<mat-cell (click)="selectItem(item, $event)" *matCellDef="let item">
|
||||||
<span *ngIf="item.closed"> <mat-icon class="done-check">check</mat-icon> </span>
|
<span *ngIf="item.closed"> <mat-icon class="done-check">check</mat-icon> </span>
|
||||||
<span> {{ item.getListTitle() }} </span>
|
<span> {{ item.getListTitle() }} </span>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Info column -->
|
<!-- Info column -->
|
||||||
<ng-container matColumnDef="info">
|
<ng-container matColumnDef="info">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Info</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header>Info</mat-header-cell>
|
||||||
<mat-cell (click)="openEditInfo(item)" *matCellDef="let item">
|
<mat-cell (click)="openEditInfo(item)" *matCellDef="let item">
|
||||||
<div class="info-col-items">
|
<div class="info-col-items">
|
||||||
<div *ngIf="item.verboseType">
|
<div *ngIf="item.verboseType">
|
||||||
<mat-icon>visibility</mat-icon>
|
<mat-icon>visibility</mat-icon>
|
||||||
{{ item.verboseType | translate }}
|
{{ item.verboseType | translate }}
|
||||||
|
</div>
|
||||||
|
<div *ngIf="item.duration">
|
||||||
|
<mat-icon>access_time</mat-icon>
|
||||||
|
{{ durationService.durationToString(item.duration) }}
|
||||||
|
</div>
|
||||||
|
<div *ngIf="item.comment">
|
||||||
|
<mat-icon>comment</mat-icon>
|
||||||
|
{{ item.comment }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="item.duration">
|
</mat-cell>
|
||||||
<mat-icon>access_time</mat-icon>
|
</ng-container>
|
||||||
{{ durationService.durationToString(item.duration) }}
|
|
||||||
</div>
|
|
||||||
<div *ngIf="item.comment">
|
|
||||||
<mat-icon>comment</mat-icon>
|
|
||||||
{{ item.comment }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-cell>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- Speakers column -->
|
<!-- Speakers column -->
|
||||||
<ng-container matColumnDef="speakers">
|
<ng-container matColumnDef="speakers">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Speakers</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header>Speakers</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let item">
|
<mat-cell *matCellDef="let item">
|
||||||
<button mat-icon-button (click)="onSpeakerIcon(item)">
|
<button mat-icon-button (click)="onSpeakerIcon(item)">
|
||||||
<mat-icon [matBadge]="item.speakerAmount > 0 ? item.speakerAmount : null" matBadgeColor="accent">
|
<mat-icon [matBadge]="item.speakerAmount > 0 ? item.speakerAmount : null" matBadgeColor="accent">
|
||||||
mic
|
mic
|
||||||
</mat-icon>
|
</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- menu -->
|
<!-- menu -->
|
||||||
<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 [matMenuTriggerFor]="singleItemMenu" [matMenuTriggerData]="{ item: item }">
|
||||||
<mat-icon>more_vert</mat-icon>
|
<mat-icon>more_vert</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
||||||
<mat-row
|
<mat-row
|
||||||
class="lg"
|
class="lg"
|
||||||
[ngClass]="selectedRows.indexOf(row) >= 0 ? 'selected' : ''"
|
[ngClass]="selectedRows.indexOf(row) >= 0 ? 'selected' : ''"
|
||||||
*matRowDef="let row; columns: getColumnDefinition()"
|
*matRowDef="let row; columns: getColumnDefinition()"
|
||||||
></mat-row>
|
></mat-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>
|
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>
|
||||||
|
</mat-drawer-container>
|
||||||
|
|
||||||
<mat-menu #agendaMenu="matMenu">
|
<mat-menu #agendaMenu="matMenu">
|
||||||
<div *ngIf="!isMultiSelect">
|
<div *ngIf="!isMultiSelect">
|
||||||
|
@ -33,6 +33,7 @@ export class AgendaFilterListService extends FilterListService<Item, ViewItem> {
|
|||||||
options: [{ label: 'Open', condition: false }, { label: 'Closed', condition: true }]
|
options: [{ label: 'Open', condition: false }, { label: 'Closed', condition: true }]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
this.updateFilterDefinitions(this.filterOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createVisibilityFilterOptions(): OsFilterOption[] {
|
private createVisibilityFilterOptions(): OsFilterOption[] {
|
||||||
|
@ -10,8 +10,7 @@ export class AssignmentSortListService extends SortListService<ViewAssignment> {
|
|||||||
sortProperty: 'assignment',
|
sortProperty: 'assignment',
|
||||||
sortAscending: true,
|
sortAscending: true,
|
||||||
options: [
|
options: [
|
||||||
{ property: 'agendaItem', label: 'agenda Item' },
|
{ property: 'assignment', label: 'Name' },
|
||||||
{ property: 'assignment' },
|
|
||||||
{ property: 'phase' },
|
{ property: 'phase' },
|
||||||
{ property: 'candidateAmount', label: 'Number of candidates' }
|
{ property: 'candidateAmount', label: 'Number of candidates' }
|
||||||
]
|
]
|
||||||
|
@ -134,6 +134,15 @@ export class ViewMotion extends BaseViewModel {
|
|||||||
return this._state;
|
return this._state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current state of thw workflow is final
|
||||||
|
*
|
||||||
|
* @returns true if it is final
|
||||||
|
*/
|
||||||
|
public get isFinalState(): boolean {
|
||||||
|
return this._state.isFinalState;
|
||||||
|
}
|
||||||
|
|
||||||
public get state_id(): number {
|
public get state_id(): number {
|
||||||
return this.motion && this.motion.state_id ? this.motion.state_id : null;
|
return this.motion && this.motion.state_id ? this.motion.state_id : null;
|
||||||
}
|
}
|
||||||
@ -209,6 +218,18 @@ export class ViewMotion extends BaseViewModel {
|
|||||||
return this._attachments ? this._attachments : null;
|
return this._attachments ? this._attachments : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the comments' section ids of a motion. Used in filter by motionComment
|
||||||
|
*
|
||||||
|
* @returns an array of ids, or an empty array
|
||||||
|
*/
|
||||||
|
public get commentSectionIds(): number[] {
|
||||||
|
if (!this.motion) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.motion.comments.map(comment => comment.section_id);
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
motion?: Motion,
|
motion?: Motion,
|
||||||
category?: Category,
|
category?: Category,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { FilterListService, OsFilter } from '../../../core/services/filter-list.service';
|
import { FilterListService, OsFilter, OsFilterOption } from '../../../core/services/filter-list.service';
|
||||||
import { Motion } from '../../../shared/models/motions/motion';
|
import { Motion } from '../../../shared/models/motions/motion';
|
||||||
import { ViewMotion } from '../models/view-motion';
|
import { ViewMotion } from '../models/view-motion';
|
||||||
import { CategoryRepositoryService } from './category-repository.service';
|
import { CategoryRepositoryService } from './category-repository.service';
|
||||||
@ -8,6 +8,7 @@ import { WorkflowRepositoryService } from './workflow-repository.service';
|
|||||||
import { StorageService } from '../../../core/services/storage.service';
|
import { StorageService } from '../../../core/services/storage.service';
|
||||||
import { MotionRepositoryService } from './motion-repository.service';
|
import { MotionRepositoryService } from './motion-repository.service';
|
||||||
import { MotionBlockRepositoryService } from './motion-block-repository.service';
|
import { MotionBlockRepositoryService } from './motion-block-repository.service';
|
||||||
|
import { MotionCommentSectionRepositoryService } from './motion-comment-section-repository.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -19,19 +20,22 @@ export class MotionFilterListService extends FilterListService<Motion, ViewMotio
|
|||||||
* generated dynamically, as the options change with the datastore
|
* generated dynamically, as the options change with the datastore
|
||||||
*/
|
*/
|
||||||
public get filterOptions(): OsFilter[] {
|
public get filterOptions(): OsFilter[] {
|
||||||
return [this.flowFilterOptions, this.categoryFilterOptions, this.motionBlockFilterOptions].concat(
|
return [
|
||||||
this.staticFilterOptions
|
this.flowFilterOptions,
|
||||||
);
|
this.categoryFilterOptions,
|
||||||
|
this.motionBlockFilterOptions,
|
||||||
|
this.recommendationFilterOptions,
|
||||||
|
this.motionCommentFilterOptions
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter definitions for the workflow filter. Options will be generated by
|
* Filter definitions for the workflow filter. Options will be generated by
|
||||||
* getFilterOptions (as the workflows available may change)
|
* getFilterOptions (as the workflows available may change)
|
||||||
*/
|
*/
|
||||||
public flowFilterOptions = {
|
public flowFilterOptions: OsFilter = {
|
||||||
property: 'state',
|
property: 'state',
|
||||||
label: 'State',
|
label: 'State',
|
||||||
isActive: false,
|
|
||||||
options: []
|
options: []
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -39,35 +43,46 @@ export class MotionFilterListService extends FilterListService<Motion, ViewMotio
|
|||||||
* Filter definitions for the category filter. Options will be generated by
|
* Filter definitions for the category filter. Options will be generated by
|
||||||
* getFilterOptions (as the categories available may change)
|
* getFilterOptions (as the categories available may change)
|
||||||
*/
|
*/
|
||||||
public categoryFilterOptions = {
|
public categoryFilterOptions: OsFilter = {
|
||||||
property: 'category',
|
property: 'category',
|
||||||
isActive: false,
|
|
||||||
options: []
|
options: []
|
||||||
};
|
};
|
||||||
|
|
||||||
public motionBlockFilterOptions = {
|
public motionBlockFilterOptions: OsFilter = {
|
||||||
property: 'motion_block_id',
|
property: 'motion_block_id',
|
||||||
label: 'Motion block',
|
label: 'Motion block',
|
||||||
isActive: false,
|
|
||||||
options: []
|
|
||||||
};
|
|
||||||
public commentFilterOptions = {
|
|
||||||
property: 'comment',
|
|
||||||
isActive: false,
|
|
||||||
options: []
|
options: []
|
||||||
};
|
};
|
||||||
|
|
||||||
public staticFilterOptions = [
|
public motionCommentFilterOptions: OsFilter = {
|
||||||
// TODO favorite (attached to user:whoamI!)
|
property: 'commentSectionIds',
|
||||||
// TODO personalNote (attached to user:whoamI!)
|
label: 'Comment',
|
||||||
];
|
options: []
|
||||||
|
};
|
||||||
|
|
||||||
|
public recommendationFilterOptions: OsFilter = {
|
||||||
|
property: 'recommendation',
|
||||||
|
label: 'Recommendation',
|
||||||
|
options: []
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor. Subscribes to a variety of Repository to dynamically update
|
||||||
|
* the available filters
|
||||||
|
*
|
||||||
|
* @param store The browser's storage; required for fetching filters from any previous sessions
|
||||||
|
* @param workflowRepo Subscribing to filters by states/Recommendation
|
||||||
|
* @param categoryRepo Subscribing to filters by Categories
|
||||||
|
* @param motionBlockRepo Subscribing to filters by MotionBlock
|
||||||
|
* @param commentRepo subycribing filter by presense of comment
|
||||||
|
* @param motionRepo the motion's own repository, required by the parent
|
||||||
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
store: StorageService,
|
store: StorageService,
|
||||||
private workflowRepo: WorkflowRepositoryService,
|
private workflowRepo: WorkflowRepositoryService,
|
||||||
private categoryRepo: CategoryRepositoryService,
|
private categoryRepo: CategoryRepositoryService,
|
||||||
private motionBlockRepo: MotionBlockRepositoryService,
|
private motionBlockRepo: MotionBlockRepositoryService,
|
||||||
// private commentRepo: MotionCommentRepositoryService
|
private commentRepo: MotionCommentSectionRepositoryService,
|
||||||
motionRepo: MotionRepositoryService
|
motionRepo: MotionRepositoryService
|
||||||
) {
|
) {
|
||||||
super(store, motionRepo);
|
super(store, motionRepo);
|
||||||
@ -77,6 +92,20 @@ export class MotionFilterListService extends FilterListService<Motion, ViewMotio
|
|||||||
this.subscribeComments();
|
this.subscribeComments();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Notes/Favorite
|
||||||
|
// does not work, some cloning error. I want to:
|
||||||
|
// 'check all items in filterService against this function, in the
|
||||||
|
// scope of motion-filter.service'
|
||||||
|
// public getNoteFilterFn(): Function {
|
||||||
|
// const notesRepo = this.notesRepo;
|
||||||
|
// return (m: ViewMotion) => {
|
||||||
|
// return notesRepo.hasPersonalNote('Motion', m.id)
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscibes to changing MotionBlocks, and updates the filter accordingly
|
||||||
|
*/
|
||||||
private subscribeMotionBlocks(): void {
|
private subscribeMotionBlocks(): void {
|
||||||
this.motionBlockRepo.getViewModelListObservable().subscribe(motionBlocks => {
|
this.motionBlockRepo.getViewModelListObservable().subscribe(motionBlocks => {
|
||||||
const motionBlockOptions = [];
|
const motionBlockOptions = [];
|
||||||
@ -87,20 +116,25 @@ export class MotionFilterListService extends FilterListService<Motion, ViewMotio
|
|||||||
isActive: false
|
isActive: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
motionBlockOptions.push('-');
|
if (motionBlocks.length) {
|
||||||
motionBlockOptions.push({
|
motionBlockOptions.push('-');
|
||||||
condition: null,
|
motionBlockOptions.push({
|
||||||
label: 'No motion block set',
|
condition: null,
|
||||||
isActive: false
|
label: 'No motion block set',
|
||||||
});
|
isActive: false
|
||||||
|
});
|
||||||
|
}
|
||||||
this.motionBlockFilterOptions.options = motionBlockOptions;
|
this.motionBlockFilterOptions.options = motionBlockOptions;
|
||||||
this.updateFilterDefinitions(this.filterOptions);
|
this.updateFilterDefinitions(this.filterOptions);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscibes to changing Categories, and updates the filter accordingly
|
||||||
|
*/
|
||||||
private subscribeCategories(): void {
|
private subscribeCategories(): void {
|
||||||
this.categoryRepo.getViewModelListObservable().subscribe(categories => {
|
this.categoryRepo.getViewModelListObservable().subscribe(categories => {
|
||||||
const categoryOptions = [];
|
const categoryOptions: (OsFilterOption | string)[] = [];
|
||||||
categories.forEach(cat => {
|
categories.forEach(cat => {
|
||||||
categoryOptions.push({
|
categoryOptions.push({
|
||||||
condition: cat.id,
|
condition: cat.id,
|
||||||
@ -108,36 +142,96 @@ export class MotionFilterListService extends FilterListService<Motion, ViewMotio
|
|||||||
isActive: false
|
isActive: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if (categories.length) {
|
||||||
|
categoryOptions.push('-');
|
||||||
|
categoryOptions.push({
|
||||||
|
label: 'No category set',
|
||||||
|
condition: null
|
||||||
|
});
|
||||||
|
}
|
||||||
this.categoryFilterOptions.options = categoryOptions;
|
this.categoryFilterOptions.options = categoryOptions;
|
||||||
this.updateFilterDefinitions(this.filterOptions);
|
this.updateFilterDefinitions(this.filterOptions);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscibes to changing Workflows, and updates the state and recommendation filters accordingly
|
||||||
|
*/
|
||||||
private subscribeWorkflows(): void {
|
private subscribeWorkflows(): void {
|
||||||
this.workflowRepo.getViewModelListObservable().subscribe(workflows => {
|
this.workflowRepo.getViewModelListObservable().subscribe(workflows => {
|
||||||
const workflowOptions = [];
|
const workflowOptions: (OsFilterOption | string)[] = [];
|
||||||
|
const finalStates: number[] = [];
|
||||||
|
const nonFinalStates: number[] = [];
|
||||||
|
const recommendationOptions: (OsFilterOption | string)[] = [];
|
||||||
workflows.forEach(workflow => {
|
workflows.forEach(workflow => {
|
||||||
workflowOptions.push(workflow.name);
|
workflowOptions.push(workflow.name);
|
||||||
|
recommendationOptions.push(workflow.name);
|
||||||
workflow.states.forEach(state => {
|
workflow.states.forEach(state => {
|
||||||
|
if (state.isFinalState) {
|
||||||
|
finalStates.push(state.id);
|
||||||
|
} else {
|
||||||
|
nonFinalStates.push(state.id);
|
||||||
|
}
|
||||||
workflowOptions.push({
|
workflowOptions.push({
|
||||||
condition: state.name,
|
condition: state.id,
|
||||||
label: state.name,
|
label: state.name,
|
||||||
isActive: false
|
isActive: false
|
||||||
});
|
});
|
||||||
|
if (state.recommendation_label) {
|
||||||
|
recommendationOptions.push({
|
||||||
|
condition: state.id,
|
||||||
|
label: state.recommendation_label,
|
||||||
|
isActive: false
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
workflowOptions.push('-');
|
if (workflowOptions.length) {
|
||||||
workflowOptions.push({
|
workflowOptions.push('-');
|
||||||
condition: null,
|
workflowOptions.push({
|
||||||
label: 'no workflow set',
|
label: 'Done',
|
||||||
isActive: false
|
condition: finalStates
|
||||||
});
|
});
|
||||||
|
workflowOptions.push({
|
||||||
|
label: 'Undone',
|
||||||
|
condition: nonFinalStates
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (recommendationOptions.length) {
|
||||||
|
recommendationOptions.push('-');
|
||||||
|
recommendationOptions.push({
|
||||||
|
label: 'No recommendation',
|
||||||
|
condition: null
|
||||||
|
});
|
||||||
|
}
|
||||||
this.flowFilterOptions.options = workflowOptions;
|
this.flowFilterOptions.options = workflowOptions;
|
||||||
|
this.recommendationFilterOptions.options = recommendationOptions;
|
||||||
this.updateFilterDefinitions(this.filterOptions);
|
this.updateFilterDefinitions(this.filterOptions);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscibes to changing Comments, and updates the filter accordingly
|
||||||
|
*/
|
||||||
private subscribeComments(): void {
|
private subscribeComments(): void {
|
||||||
// TODO
|
this.commentRepo.getViewModelListObservable().subscribe(comments => {
|
||||||
|
const commentOptions: (OsFilterOption | string)[] = [];
|
||||||
|
comments.forEach(comm => {
|
||||||
|
commentOptions.push({
|
||||||
|
condition: comm.id,
|
||||||
|
label: comm.name,
|
||||||
|
isActive: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (comments.length) {
|
||||||
|
commentOptions.push('-');
|
||||||
|
commentOptions.push({
|
||||||
|
label: 'No comment',
|
||||||
|
condition: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.motionCommentFilterOptions.options = commentOptions;
|
||||||
|
this.updateFilterDefinitions(this.filterOptions);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ export class UserFilterListService extends FilterListService<User, ViewUser> {
|
|||||||
public subscribeGroups(): void {
|
public subscribeGroups(): void {
|
||||||
this.groupRepo.getViewModelListObservable().subscribe(groups => {
|
this.groupRepo.getViewModelListObservable().subscribe(groups => {
|
||||||
const groupOptions = [];
|
const groupOptions = [];
|
||||||
groupOptions.forEach(group => {
|
groups.forEach(group => {
|
||||||
groupOptions.push({
|
groupOptions.push({
|
||||||
condition: group.name,
|
condition: group.name,
|
||||||
label: group.name,
|
label: group.name,
|
||||||
|
Loading…
Reference in New Issue
Block a user