From 02d8724757adaf880b6ea1dfa89039fda68016d9 Mon Sep 17 00:00:00 2001 From: Bastian Rihm Date: Thu, 4 Sep 2025 17:04:14 +0200 Subject: [PATCH 1/8] Add hierarchy commitee sort --- .../app/domain/models/comittees/committee.ts | 2 +- .../base-sort-list.service.ts | 2 +- .../committee-list.component.html | 6 +++- .../committee-sort.service.ts | 30 +++++++++++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/client/src/app/domain/models/comittees/committee.ts b/client/src/app/domain/models/comittees/committee.ts index 75260074c3..777273ed35 100644 --- a/client/src/app/domain/models/comittees/committee.ts +++ b/client/src/app/domain/models/comittees/committee.ts @@ -9,7 +9,7 @@ export class Committee extends BaseModel { public name!: string; public description!: string; public external_id!: string; - public parent_id!: string; + public parent_id!: Id; public child_ids!: Id[]; public meeting_ids!: Id[]; // (meeting/committee_id)[]; diff --git a/client/src/app/site/base/base-sort.service/base-sort-list.service.ts b/client/src/app/site/base/base-sort.service/base-sort-list.service.ts index 3bc36693ce..ad95a914e6 100644 --- a/client/src/app/site/base/base-sort.service/base-sort-list.service.ts +++ b/client/src/app/site/base/base-sort.service/base-sort-list.service.ts @@ -403,7 +403,7 @@ export abstract class BaseSortListService return Array.isArray(a) && Array.isArray(b) ? a.equals(b) : a === b; } - private compareHelperFunction(itemA: V, itemB: V, alternativeProperty: OsSortProperty): number { + protected compareHelperFunction(itemA: V, itemB: V, alternativeProperty: OsSortProperty): number { return ( this.sortItems( itemA, diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html index 1cdc6a3bc9..9505479384 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html @@ -34,7 +34,11 @@

{{ 'Committees' | translate }}

[sortService]="sortService" [(selectedRows)]="selectedRows" > -
+
@if (!isMultiSelect && committee.canAccess()) { } diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-sort.service/committee-sort.service.ts b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-sort.service/committee-sort.service.ts index 924d27f7a9..57e5803d79 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-sort.service/committee-sort.service.ts +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-sort.service/committee-sort.service.ts @@ -1,5 +1,6 @@ import { Injectable, ProviderToken } from '@angular/core'; import { _ } from '@ngx-translate/core'; +import { Id } from 'src/app/domain/definitions/key-types'; import { BaseRepository } from 'src/app/gateways/repositories/base-repository'; import { CommitteeRepositoryService } from 'src/app/gateways/repositories/committee-repository.service'; import { BaseSortListService, OsSortingOption } from 'src/app/site/base/base-sort.service'; @@ -12,6 +13,8 @@ import { ViewCommittee } from '../../../../view-models'; export class CommitteeSortService extends BaseSortListService { protected storageKey = `CommitteeList`; + private hierarchySort = true; + protected repositoryToken: ProviderToken> = CommitteeRepositoryService; private readonly staticSortOptions: OsSortingOption[] = [ @@ -30,4 +33,31 @@ export class CommitteeSortService extends BaseSortListService { protected getSortOptions(): OsSortingOption[] { return this.staticSortOptions; } + + /** + * Sorts the given array according to this services sort settings and returns it. + */ + public override async sort(array: ViewCommittee[]): Promise { + if (this.hierarchySort) { + const input = [...array]; + return (await this.doHierarchySort(input, null)).flat(Infinity); + } + + return super.sort(array); + } + + private async doHierarchySort(remaining: ViewCommittee[], parentId: Id): Promise { + const result = []; + let i = remaining.length; + while (i--) { + const entry = remaining[i]; + if (entry && (entry.parent_id ?? null) === parentId) { + remaining.splice(i, 1); + result.push([entry, await this.doHierarchySort(remaining, entry.id)]); + } + } + + const alternativeProperty = (await this.getDefaultDefinition()).sortProperty; + return result.sort((a, b) => this.compareHelperFunction(a[0], b[0], alternativeProperty)); + } } From f659d91377b99249c7cc63915e53a2c079c462ef Mon Sep 17 00:00:00 2001 From: Bastian Rihm Date: Thu, 4 Sep 2025 18:07:03 +0200 Subject: [PATCH 2/8] Add sort toggle --- .../committee-list/committee-list.module.ts | 2 ++ .../committee-list.component.html | 16 +++++++++++++- .../committee-list.component.ts | 8 +++++++ .../committee-sort.service.ts | 22 +++++++++++++++++-- 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/committee-list.module.ts b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/committee-list.module.ts index 2264b9c039..a95a37ecfd 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/committee-list.module.ts +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/committee-list.module.ts @@ -4,6 +4,7 @@ import { MatButtonModule } from '@angular/material/button'; import { MatDividerModule } from '@angular/material/divider'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { OpenSlidesTranslationModule } from 'src/app/site/modules/translations'; import { DirectivesModule } from 'src/app/ui/directives'; import { ChipComponent } from 'src/app/ui/modules/chip'; @@ -32,6 +33,7 @@ import { CommitteeListServiceModule } from './services/committee-list-service.mo OpenSlidesTranslationModule.forChild(), MatDividerModule, MatMenuModule, + MatTooltipModule, MatIconModule, MatButtonModule, DirectivesModule diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html index 9505479384..77984a42a5 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html @@ -22,6 +22,20 @@

{{ 'Committees' | translate }}

{{ selectedRows.length }} {{ 'selected' | translate }} + +
+
+ @if (selectedView === 'hierarchy') { + + } @else { + + } +
+
{{ 'Committees' | translate }}
@if (!isMultiSelect && committee.canAccess()) { diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.ts b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.ts index e187bea4d0..f7b0be8121 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.ts +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.ts @@ -33,6 +33,8 @@ export class CommitteeListComponent extends BaseListViewComponent return this.translate.instant(`Agenda items are in process. Please wait ...`); } + public selectedView = `list`; + public constructor( protected override translate: TranslateService, public committeeController: CommitteeControllerService, @@ -50,6 +52,12 @@ export class CommitteeListComponent extends BaseListViewComponent super.setTitle(`Committees`); this.canMultiSelect = true; this.listStorageIndex = COMMITTEE_LIST_STORAGE_INDEX; + this.selectedView = this.sortService.hierarchySort ? `hierarchy` : `list`; + } + + public onChangeView(type: string): void { + this.selectedView = type; + this.sortService.hierarchySort = this.selectedView === `hierarchy`; } public editSingle(committee: ViewCommittee): void { diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-sort.service/committee-sort.service.ts b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-sort.service/committee-sort.service.ts index 57e5803d79..889de6e370 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-sort.service/committee-sort.service.ts +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-sort.service/committee-sort.service.ts @@ -1,9 +1,10 @@ import { Injectable, ProviderToken } from '@angular/core'; import { _ } from '@ngx-translate/core'; +import { BehaviorSubject, map, Observable, withLatestFrom } from 'rxjs'; import { Id } from 'src/app/domain/definitions/key-types'; import { BaseRepository } from 'src/app/gateways/repositories/base-repository'; import { CommitteeRepositoryService } from 'src/app/gateways/repositories/committee-repository.service'; -import { BaseSortListService, OsSortingOption } from 'src/app/site/base/base-sort.service'; +import { BaseSortListService, OsSortingDefinition, OsSortingOption } from 'src/app/site/base/base-sort.service'; import { ViewCommittee } from '../../../../view-models'; @@ -13,7 +14,14 @@ import { ViewCommittee } from '../../../../view-models'; export class CommitteeSortService extends BaseSortListService { protected storageKey = `CommitteeList`; - private hierarchySort = true; + private _hierarchySort = new BehaviorSubject(true); + public get hierarchySort(): boolean { + return this._hierarchySort.value; + } + + public set hierarchySort(value: boolean) { + this._hierarchySort.next(value); + } protected repositoryToken: ProviderToken> = CommitteeRepositoryService; @@ -34,6 +42,16 @@ export class CommitteeSortService extends BaseSortListService { return this.staticSortOptions; } + /** + * Updates every time when there's a new sortDefinition. Emits said sortDefinition. + */ + public override get sortingUpdatedObservable(): Observable> { + return super.sortingUpdatedObservable.pipe( + withLatestFrom(this._hierarchySort), + map(([sort, _]) => sort) + ); + } + /** * Sorts the given array according to this services sort settings and returns it. */ From 6fea8deb9d8395a6443dd0cc4b810ff454d6991f Mon Sep 17 00:00:00 2001 From: Bastian Rihm Date: Fri, 5 Sep 2025 10:42:53 +0200 Subject: [PATCH 3/8] Persist last sorting in storage --- .../base-sort-list.service.ts | 45 +++++++++++++++---- .../site/base/base-sort.service/os-sort.ts | 1 + .../committee-list.component.html | 4 +- .../committee-list.component.ts | 9 ++-- .../committee-sort.service.ts | 31 ++++++------- 5 files changed, 58 insertions(+), 32 deletions(-) diff --git a/client/src/app/site/base/base-sort.service/base-sort-list.service.ts b/client/src/app/site/base/base-sort.service/base-sort-list.service.ts index ad95a914e6..d599bf29d7 100644 --- a/client/src/app/site/base/base-sort.service/base-sort-list.service.ts +++ b/client/src/app/site/base/base-sort.service/base-sort-list.service.ts @@ -65,6 +65,23 @@ export abstract class BaseSortListService return this.sortDefinition?.sortAscending; } + /** + * Set the current sorting order + * + * @param ascending ascending sorting if true, descending sorting if false + */ + public set additionalInfo(additional: unknown) { + this.sortDefinition!.additionalInfo = additional; + this.updateSortDefinitions(); + } + + /** + * @returns wether current the sorting is ascending or descending + */ + public get additionalInfo(): unknown { + return this.sortDefinition?.additionalInfo; + } + public get hasSortOptionSelected(): boolean { const defaultDef = this._defaultDefinitionSubject.value; const current = this.sortDefinition; @@ -192,7 +209,7 @@ export abstract class BaseSortListService .pipe(distinctUntilChanged((prev, curr) => prev?.sortProperty === curr?.sortProperty)) .subscribe(defaultDef => { if (this._isDefaultSorting && defaultDef) { - this.setSorting(defaultDef.sortProperty, defaultDef.sortAscending); + this.setSorting(defaultDef.sortProperty, defaultDef.sortAscending, defaultDef.additionalInfo); } else if (defaultDef && this.sortDefinition?.sortProperty === defaultDef?.sortProperty) { this.updateSortDefinitions(); } @@ -253,12 +270,13 @@ export abstract class BaseSortListService * @param property a sorting property of a view model * @param ascending ascending or descending */ - public setSorting(property: OsSortProperty, ascending: boolean): void { + public setSorting(property: OsSortProperty, ascending: boolean, additionalInfo?: unknown): void { if (!this.sortDefinition) { - this.sortDefinition = { sortProperty: property, sortAscending: ascending }; + this.sortDefinition = { sortProperty: property, sortAscending: ascending, additionalInfo }; } else { this.sortDefinition!.sortProperty = property; this.sortDefinition!.sortAscending = ascending; + this.sortDefinition!.additionalInfo = additionalInfo; this.updateSortDefinitions(); } this.hasLoaded.resolve(true); @@ -368,31 +386,36 @@ export abstract class BaseSortListService } private async loadDefinition(): Promise { - let [sortProperty, sortAscending]: [OsSortProperty, boolean] = await Promise.all([ + let [sortProperty, sortAscending, additionalInfo]: [OsSortProperty, boolean, any] = await Promise.all([ this.store.get>(this.calcStorageKey(`sorting_property`, this.storageKey)), - this.store.get(this.calcStorageKey(`sorting_ascending`, this.storageKey)) + this.store.get(this.calcStorageKey(`sorting_ascending`, this.storageKey)), + this.store.get(this.calcStorageKey(`sorting_additional_info`, this.storageKey)) ]); const defaultDef = await this.getDefaultDefinition(); sortAscending = sortAscending ?? defaultDef.sortAscending; sortProperty = sortProperty ?? defaultDef.sortProperty; + additionalInfo = additionalInfo ?? defaultDef.additionalInfo; this.sortDefinition = { sortAscending, - sortProperty + sortProperty, + additionalInfo }; this.updateSortDefinitions(); this.hasLoaded.resolve(true); } private async setSortingAfterMeetingChange(meetingId: Id): Promise { - let [sortProperty, sortAscending]: [OsSortProperty, boolean] = await Promise.all([ + let [sortProperty, sortAscending, additionalInfo]: [OsSortProperty, boolean, any] = await Promise.all([ this.store.get>(`sorting_property_${this.storageKey}_${meetingId}`), - this.store.get(`sorting_ascending_${this.storageKey}_${meetingId}`) + this.store.get(`sorting_ascending_${this.storageKey}_${meetingId}`), + this.store.get(`sorting_additional_info_${this.storageKey}_${meetingId}`) ]); const defaultDef = await this.getDefaultDefinition(); sortProperty = sortProperty ?? defaultDef.sortProperty; sortAscending = sortAscending ?? defaultDef.sortAscending; - this.setSorting(sortProperty, sortAscending); + additionalInfo = additionalInfo ?? defaultDef.additionalInfo; + this.setSorting(sortProperty, sortAscending, additionalInfo); } /** @@ -426,6 +449,10 @@ export abstract class BaseSortListService this.store.set(this.calcStorageKey(`sorting_property`, this.storageKey), this.sortDefinition?.sortProperty); } this.store.set(this.calcStorageKey(`sorting_ascending`, this.storageKey), this.sortDefinition?.sortAscending); + this.store.set( + this.calcStorageKey(`sorting_additional_info`, this.storageKey), + this.sortDefinition?.additionalInfo + ); } private calculateDefaultStatus(): void { diff --git a/client/src/app/site/base/base-sort.service/os-sort.ts b/client/src/app/site/base/base-sort.service/os-sort.ts index 470403eaef..a4d65d1f4a 100644 --- a/client/src/app/site/base/base-sort.service/os-sort.ts +++ b/client/src/app/site/base/base-sort.service/os-sort.ts @@ -6,6 +6,7 @@ export type SortDefinition = keyof T | OsSortingDefinition; export interface OsSortingDefinition { sortProperty: OsSortProperty; sortAscending: boolean; + additionalInfo?: unknown; } /** diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html index 77984a42a5..f8b6a13b0c 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html @@ -26,11 +26,11 @@

{{ 'Committees' | translate }}

@if (selectedView === 'hierarchy') { - } @else { - } diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.ts b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.ts index f7b0be8121..bb7ddce1bc 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.ts +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.ts @@ -52,12 +52,15 @@ export class CommitteeListComponent extends BaseListViewComponent super.setTitle(`Committees`); this.canMultiSelect = true; this.listStorageIndex = COMMITTEE_LIST_STORAGE_INDEX; - this.selectedView = this.sortService.hierarchySort ? `hierarchy` : `list`; + this.subscriptions.push( + this.sortService.hierarchySort.subscribe(active => { + this.selectedView = active ? `hierarchy` : `list`; + }) + ); } public onChangeView(type: string): void { - this.selectedView = type; - this.sortService.hierarchySort = this.selectedView === `hierarchy`; + this.sortService.hierarchySort = type === `hierarchy`; } public editSingle(committee: ViewCommittee): void { diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-sort.service/committee-sort.service.ts b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-sort.service/committee-sort.service.ts index 889de6e370..51f1cdba97 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-sort.service/committee-sort.service.ts +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-sort.service/committee-sort.service.ts @@ -1,10 +1,10 @@ import { Injectable, ProviderToken } from '@angular/core'; import { _ } from '@ngx-translate/core'; -import { BehaviorSubject, map, Observable, withLatestFrom } from 'rxjs'; +import { map, Observable } from 'rxjs'; import { Id } from 'src/app/domain/definitions/key-types'; import { BaseRepository } from 'src/app/gateways/repositories/base-repository'; import { CommitteeRepositoryService } from 'src/app/gateways/repositories/committee-repository.service'; -import { BaseSortListService, OsSortingDefinition, OsSortingOption } from 'src/app/site/base/base-sort.service'; +import { BaseSortListService, OsSortingOption } from 'src/app/site/base/base-sort.service'; import { ViewCommittee } from '../../../../view-models'; @@ -14,13 +14,14 @@ import { ViewCommittee } from '../../../../view-models'; export class CommitteeSortService extends BaseSortListService { protected storageKey = `CommitteeList`; - private _hierarchySort = new BehaviorSubject(true); - public get hierarchySort(): boolean { - return this._hierarchySort.value; + public get hierarchySort(): Observable { + return this.sortingUpdatedObservable.pipe(map(sorting => (sorting?.additionalInfo as any)?.hierarchySort)); } public set hierarchySort(value: boolean) { - this._hierarchySort.next(value); + this.additionalInfo = { + hierarchySort: value + }; } protected repositoryToken: ProviderToken> = CommitteeRepositoryService; @@ -34,7 +35,10 @@ export class CommitteeSortService extends BaseSortListService { public constructor() { super({ sortProperty: `name`, - sortAscending: true + sortAscending: true, + additionalInfo: { + hierarchySort: true + } }); } @@ -42,21 +46,12 @@ export class CommitteeSortService extends BaseSortListService { return this.staticSortOptions; } - /** - * Updates every time when there's a new sortDefinition. Emits said sortDefinition. - */ - public override get sortingUpdatedObservable(): Observable> { - return super.sortingUpdatedObservable.pipe( - withLatestFrom(this._hierarchySort), - map(([sort, _]) => sort) - ); - } - /** * Sorts the given array according to this services sort settings and returns it. */ public override async sort(array: ViewCommittee[]): Promise { - if (this.hierarchySort) { + const additionalInfo = (await this.getDefaultDefinition()).additionalInfo; + if ((this.additionalInfo as any)?.hierarchySort ?? (additionalInfo as any)?.hierarchySort) { const input = [...array]; return (await this.doHierarchySort(input, null)).flat(Infinity); } From 9866477815a068ab3d4b35ab3b70f9e149bbe2ea Mon Sep 17 00:00:00 2001 From: Bastian Rihm Date: Sat, 6 Sep 2025 00:06:40 +0200 Subject: [PATCH 4/8] Update comments --- .../site/base/base-sort.service/base-sort-list.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/app/site/base/base-sort.service/base-sort-list.service.ts b/client/src/app/site/base/base-sort.service/base-sort-list.service.ts index d599bf29d7..d4e3d07d13 100644 --- a/client/src/app/site/base/base-sort.service/base-sort-list.service.ts +++ b/client/src/app/site/base/base-sort.service/base-sort-list.service.ts @@ -66,9 +66,9 @@ export abstract class BaseSortListService } /** - * Set the current sorting order + * Set the additional infos regarding sorting order * - * @param ascending ascending sorting if true, descending sorting if false + * @param additional info, can be any JSON serializable value */ public set additionalInfo(additional: unknown) { this.sortDefinition!.additionalInfo = additional; @@ -76,7 +76,7 @@ export abstract class BaseSortListService } /** - * @returns wether current the sorting is ascending or descending + * @returns Additional info for sorting order */ public get additionalInfo(): unknown { return this.sortDefinition?.additionalInfo; From 1ddd28eb65ef1e8db6b3ed93b776a3e882fe74b2 Mon Sep 17 00:00:00 2001 From: Bastian Rihm Date: Fri, 12 Sep 2025 12:08:30 +0200 Subject: [PATCH 5/8] WIP: Hierachy lines --- .../committee-list.component.html | 6 ++- .../committee-list.component.scss | 42 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html index f8b6a13b0c..a783af39d4 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html @@ -46,12 +46,14 @@

{{ 'Committees' | translate }}

[listObservableProvider]="committeeController" [multiSelect]="isMultiSelect" [sortService]="sortService" + [class.committee-hierarchy]="selectedView === 'hierarchy'" [(selectedRows)]="selectedRows" >
@if (!isMultiSelect && committee.canAccess()) { diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.scss b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.scss index 43c160d8a7..9feb778a16 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.scss +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.scss @@ -17,3 +17,45 @@ flex-flow: column; width: 75px; } + +.committee-hierarchy { + .cell-title:not(.is-child) { + &:after, + &:before { + content: ''; + display: inline-block; + width: 27px; + height: 50%; + border: solid #ddd; + border-width: 0 0 2px 2px; + margin-right: 8px; + } + &:after { + position: absolute; + left: 0; + bottom: 0; + border-width: 0 0 0 2px; + } + } + + .cell-title.is-child { + padding-left: calc(var(--committee-level) * 25px); + + &:before, + &:after { + content: ''; + display: inline-block; + width: 27px; + height: 50%; + border: solid #ddd; + border-width: 0 0 2px 2px; + margin-right: 8px; + } + &:after { + position: absolute; + left: calc(var(--committee-level) * 25px); + bottom: 0; + border-width: 0 0 0 2px; + } + } +} From ece9432d94d1ee27ce46d9c8fa8c475460e904aa Mon Sep 17 00:00:00 2001 From: Bastian Rihm Date: Tue, 30 Sep 2025 10:01:35 +0200 Subject: [PATCH 6/8] Make committees expandable in listview --- .../committee-list.component.html | 17 ++++- .../committee-list.component.scss | 4 ++ .../committee-list.component.ts | 11 +++- .../committee-filter.service.ts | 63 ++++++++++++++++++- 4 files changed, 92 insertions(+), 3 deletions(-) diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html index a783af39d4..d87bc5fe16 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html @@ -40,13 +40,13 @@

{{ 'Committees' | translate }}

{{ 'Committees' | translate }} @if (!isMultiSelect && committee.canAccess()) { } + @if (selectedView === 'hierarchy') { + @if (committee.child_ids?.length) { + + } + + + }
{{ committee.name }} diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.scss b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.scss index 9feb778a16..a57fcf7daf 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.scss +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.scss @@ -58,4 +58,8 @@ border-width: 0 0 0 2px; } } + + .cell-title .stretch-to-fill-parent { + left: calc(calc(var(--committee-level) * 25px) + 29px); + } } diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.ts b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.ts index bb7ddce1bc..93988e7c98 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.ts +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.ts @@ -33,7 +33,16 @@ export class CommitteeListComponent extends BaseListViewComponent return this.translate.instant(`Agenda items are in process. Please wait ...`); } - public selectedView = `list`; + private _selectedView = `list`; + + public get selectedView(): string { + return this._selectedView; + } + + public set selectedView(value) { + this._selectedView = value; + this.filterService.hierarchyFilterActive = value === 'hierarchy'; + } public constructor( protected override translate: TranslateService, diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-filter.service/committee-filter.service.ts b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-filter.service/committee-filter.service.ts index 7f4eae321f..19a98c9d49 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-filter.service/committee-filter.service.ts +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/services/committee-list-filter.service/committee-filter.service.ts @@ -1,5 +1,8 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { _ } from '@ngx-translate/core'; +import { map, Observable } from 'rxjs'; +import { Id } from 'src/app/domain/definitions/key-types'; +import { StorageService } from 'src/app/gateways/storage.service'; import { BaseFilterListService, OsFilter } from 'src/app/site/base/base-filter.service'; import { OrganizationTagControllerService } from 'src/app/site/pages/organization/pages/organization-tags/services/organization-tag-controller.service'; import { ActiveFiltersService } from 'src/app/site/services/active-filters.service'; @@ -13,6 +16,18 @@ import { CommitteeListServiceModule } from '../committee-list-service.module'; export class CommitteeFilterService extends BaseFilterListService { protected storageKey = `CommitteeList`; + private _hierarchyFilterActive = false; + + public get hierarchyFilterActive(): boolean { + return this._hierarchyFilterActive; + } + + public set hierarchyFilterActive(value) { + this._hierarchyFilterActive = value; + } + + private expandedCommittees: Set = new Set(); + private orgaTagFilterOptions: OsFilter = { property: `organization_tag_ids`, label: _(`Tags`), @@ -20,6 +35,8 @@ export class CommitteeFilterService extends BaseFilterListService options: [] }; + private store = inject(StorageService); + public constructor(organizationTagRepo: OrganizationTagControllerService, store: ActiveFiltersService) { super(store); this.updateFilterForRepo({ @@ -50,4 +67,48 @@ export class CommitteeFilterService extends BaseFilterListService this.orgaTagFilterOptions ]; } + + public override get outputObservable(): Observable { + return super.outputObservable.pipe( + map(output => { + if (this.hierarchyFilterActive) { + return output.filter( + el => + !el.all_parent_ids?.length || + el.all_parent_ids?.every(id => this.expandedCommittees.has(id)) + ); + } + + return output; + }) + ); + } + + public override storeActiveFilters(): void { + super.storeActiveFilters(); + this.store.set(`${this.storageKey}_expanded`, Array.from(this.expandedCommittees)); + } + + public override async initFilters(inputData: Observable): Promise { + const expanded = await this.store.get(`${this.storageKey}_expanded`); + if (expanded) { + this.expandedCommittees = new Set(expanded); + } + + await super.initFilters(inputData); + } + + public isExpanded(id: Id): boolean { + return this.expandedCommittees.has(id); + } + + public toggleExpanded(id: Id): void { + if (this.expandedCommittees.has(id)) { + this.expandedCommittees.delete(id); + } else { + this.expandedCommittees.add(id); + } + + this.storeActiveFilters(); + } } From 73c7733c952ab7f7e3ef405b86f1401c6e144b19 Mon Sep 17 00:00:00 2001 From: Bastian Rihm Date: Tue, 30 Sep 2025 12:16:41 +0200 Subject: [PATCH 7/8] Update style --- .../committee-list.component.html | 2 + .../committee-list.component.scss | 42 +++++++++++++++---- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html index d87bc5fe16..6e8b85076a 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-list/components/committee-list/committee-list.component.html @@ -53,6 +53,7 @@

{{ 'Committees' | translate }}

*osScrollingTableCell="'name'; row as committee" class="cell-slot fill cell-title" [class.is-child]="!!committee.all_parent_ids?.length" + [class.no-children]="!committee.child_ids?.length" [style.--committee-level]="committee.all_parent_ids?.length || 0" > @if (!isMultiSelect && committee.canAccess()) { @@ -62,6 +63,7 @@

{{ 'Committees' | translate }}

@if (committee.child_ids?.length) {