diff --git a/client/src/app/core/ui-services/base-filter-list.service.ts b/client/src/app/core/ui-services/base-filter-list.service.ts index 41c20e573..3764c2f8a 100644 --- a/client/src/app/core/ui-services/base-filter-list.service.ts +++ b/client/src/app/core/ui-services/base-filter-list.service.ts @@ -38,6 +38,7 @@ export interface OsFilterOption { condition: OsFilterOptionCondition; isActive?: boolean; isChild?: boolean; + children?: OsFilterOption[]; } /** @@ -54,12 +55,13 @@ export interface OsFilterIndicator { */ interface HierarchyModel extends BaseViewModel { parent: BaseViewModel; + children: BaseViewModel[]; } /** * Define the type of a filter condition */ -type OsFilterOptionCondition = string | boolean | number | number[]; +export type OsFilterOptionCondition = string | boolean | number | number[]; /** * Filter for the list view. List views can subscribe to its' dataService (providing filter definitions) @@ -292,7 +294,16 @@ export abstract class BaseFilterListService { return { condition: model.id, label: model.getTitle(), - isChild: !!model.parent + isChild: !!model.parent, + children: + model.children && model.children.length + ? model.children.map(child => { + return { + label: child.getTitle(), + condition: child.id + }; + }) + : undefined }; }); @@ -373,6 +384,7 @@ export abstract class BaseFilterListService { */ protected addFilterOption(filterProperty: string, option: OsFilterOption): void { const filter = this.filterDefinitions.find(f => f.property === filterProperty); + if (filter) { const filterOption = filter.options.find( o => typeof o !== 'string' && o.condition === option.condition @@ -387,6 +399,12 @@ export abstract class BaseFilterListService { } else { filter.count += 1; } + + if (filterOption.children && filterOption.children.length) { + for (const child of filterOption.children) { + this.addFilterOption(filterProperty, child); + } + } } } } @@ -420,6 +438,12 @@ export abstract class BaseFilterListService { if (filter.count) { filter.count -= 1; } + + if (filterOption.children && filterOption.children.length) { + for (const child of filterOption.children) { + this.removeFilterOption(filterProperty, child); + } + } } } } diff --git a/client/src/app/site/motions/models/view-category.ts b/client/src/app/site/motions/models/view-category.ts index 7854a2b08..43db386aa 100644 --- a/client/src/app/site/motions/models/view-category.ts +++ b/client/src/app/site/motions/models/view-category.ts @@ -32,6 +32,14 @@ export class ViewCategory extends BaseViewModel implements CategoryTit return this._parent; } + public get oldestParent(): ViewCategory { + if (!this.parent_id) { + return this; + } else { + return this.parent.oldestParent; + } + } + public get children(): ViewCategory[] { return this._children || []; } @@ -64,6 +72,17 @@ export class ViewCategory extends BaseViewModel implements CategoryTit return this.prefix ? this.prefix + ' - ' + this.name : this.name; } + /** + * The amount of all motions on this category and all children + */ + public get totalAmountOfMotions(): number { + let totalAmount = this.motions.length; + for (const child of this.children) { + totalAmount += child.totalAmountOfMotions; + } + return totalAmount; + } + /** * @returns the name with all parents in brackets: " (, )" */ diff --git a/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.html b/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.html index 879e7d01d..d0de72002 100644 --- a/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.html +++ b/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.html @@ -133,7 +133,7 @@
- + {{ motion.category.nameWithParentAbove }}
@@ -169,32 +169,38 @@ @@ -221,7 +227,7 @@
@@ -305,7 +311,7 @@ *ngIf="categories.length" (click)="multiselectWrapper(multiselectService.setCategory(selectedRows))" > - device_hub + categorySet category diff --git a/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.ts b/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.ts index 29c0ff36a..1ab33d3ae 100644 --- a/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.ts +++ b/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.ts @@ -14,6 +14,7 @@ import { MotionBlockRepositoryService } from 'app/core/repositories/motions/moti import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service'; import { WorkflowRepositoryService } from 'app/core/repositories/motions/workflow-repository.service'; import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service'; +import { OsFilterOptionCondition } from 'app/core/ui-services/base-filter-list.service'; import { ConfigService } from 'app/core/ui-services/config.service'; import { ColumnRestriction } from 'app/shared/components/list-view-table/list-view-table.component'; import { infoDialogSettings, largeDialogSettings } from 'app/shared/utils/dialog-settings'; @@ -40,7 +41,7 @@ interface TileCategoryInformation { filter: string; name: string; prefix?: string; - condition: number | boolean | null; + condition: OsFilterOptionCondition; amountOfMotions: number; } @@ -157,12 +158,11 @@ export class MotionListComponent extends BaseListViewComponent imple * List of `TileCategoryInformation`. * Necessary to not iterate over the values of the map below. */ - public tileCategories: TileCategoryInformation[] = []; + public listTiles: TileCategoryInformation[]; - /** - * Map of information about the categories relating to their id. - */ - public informationOfMotionsInTileCategories: { [id: number]: TileCategoryInformation } = {}; + private motionTiles: TileCategoryInformation[] = []; + + private categoryTiles: TileCategoryInformation[] = []; /** * The verbose name for the motions. @@ -257,34 +257,87 @@ export class MotionListComponent extends BaseListViewComponent imple this.motionRepo.getViewModelListObservable().subscribe(motions => { if (motions && motions.length) { - this.createTiles(motions); + this.createMotionTiles(motions); } }); } - private createTiles(motions: ViewMotion[]): void { - this.informationOfMotionsInTileCategories = {}; - for (const motion of motions) { - if (motion.star) { - this.countMotions(-1, true, 'star', 'Favorites'); - } + /** + * Publishes the tileList + */ + private createTileList(): void { + this.listTiles = this.categoryTiles.concat(this.motionTiles); + } - if (motion.category_id) { - this.countMotions( - motion.category_id, - motion.category_id, - 'category', - motion.category.name, - motion.category.prefix - ); + /** + * Creates the tiles for categories. + * Filters thous without parent, sorts them by theit weight, maps them to TileInfo and publishes + * the result + */ + private createCategoryTiles(categories: ViewCategory[]): void { + this.categoryTiles = categories + .filter(category => !category.parent_id && !!category.totalAmountOfMotions) + .sort((a, b) => a.weight - b.weight) + .map(category => { + return { + filter: 'category', + name: category.name, + condition: category.id, + amountOfMotions: category.totalAmountOfMotions, + prefix: category.prefix + }; + }); + } + + /** + * Creates the tiles for motions + * @param motions + */ + private createMotionTiles(motions: ViewMotion[]): void { + this.motionTiles = []; + let favoriteMotions = 0; + let motionsWithNotes = 0; + let motionsWithoutCategory = 0; + const localCategories = new Set(); + + for (const motion of motions) { + if (!motion.category_id) { + motionsWithoutCategory++; } else { - this.countMotions(-2, null, 'category', 'No category'); + localCategories.add(motion.category.oldestParent); } + favoriteMotions += +this.motionHasProp(motion, 'star'); + motionsWithNotes += +this.motionHasProp(motion, 'hasNotes'); } - this.tileCategories = Object.values(this.informationOfMotionsInTileCategories).sort((a, b) => - ('' + a.prefix).localeCompare(b.prefix) - ); + this.addToTileInfo('Favorites', 'star', true, favoriteMotions); + this.addToTileInfo('Personal Note', 'hasNote', true, motionsWithNotes); + this.addToTileInfo('No category', 'category', null, motionsWithoutCategory); + + this.createCategoryTiles(Array.from(localCategories)); + + this.createTileList(); + } + + /** + * @returns true if the motion has the given prop + */ + private motionHasProp(motion: ViewMotion, property: string, positive: boolean = true): boolean { + return !!motion[property] === positive ? true : false; + } + + /** + * Helper function to add new tile information to the tileCategories-List + */ + private addToTileInfo(name: string, filter: string, condition: OsFilterOptionCondition, amount: number): void { + if (amount) { + this.motionTiles.push({ + filter: filter, + name: name, + condition: condition, + amountOfMotions: amount + }); + } } /** @@ -338,37 +391,6 @@ export class MotionListComponent extends BaseListViewComponent imple }); } - /** - * Function to count the motions in their related categories. - * - * @param id The key of TileCategory in `informationOfMotionsInTileCategories` object - * @param condition The condition, if the tile is selected - * @param filter The filter, if the tile is selected - * @param name The title of the tile - * @param prefix The prefix of the category - */ - private countMotions( - id: number, - condition: number | boolean | null, - filter: string, - name: string, - prefix?: string - ): void { - let info = this.informationOfMotionsInTileCategories[id]; - if (info) { - ++info.amountOfMotions; - } else { - info = { - filter, - name, - condition, - prefix, - amountOfMotions: 1 - }; - } - this.informationOfMotionsInTileCategories[id] = info; - } - /** * Wraps multiselect actions to close the multiselect mode or throw an error if one happens. *
star block - {{ tileCategory.prefix }} + speaker_notes + + + {{ tile.prefix }} + + + category + +