diff --git a/src/app/modules/shows/list/filter/filter.component.html b/src/app/modules/shows/list/filter/filter.component.html index 9adf8a5..9d4b545 100644 --- a/src/app/modules/shows/list/filter/filter.component.html +++ b/src/app/modules/shows/list/filter/filter.component.html @@ -16,7 +16,7 @@ Ersteller - Alle + Alle @for (owner of owners; track owner) { {{ owner.value @@ -29,7 +29,7 @@ Art der Veranstaltung - Alle + Alle @for (key of showTypePublic; track key) { {{ diff --git a/src/app/modules/shows/list/filter/filter.component.ts b/src/app/modules/shows/list/filter/filter.component.ts index cd834ad..72c9378 100644 --- a/src/app/modules/shows/list/filter/filter.component.ts +++ b/src/app/modules/shows/list/filter/filter.component.ts @@ -1,6 +1,5 @@ import {Component, Input, inject} from '@angular/core'; import {KeyValue} from '@angular/common'; -import {ActivatedRoute, Router} from '@angular/router'; import {ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup} from '@angular/forms'; import {FilterValues} from './filter-values'; import {Show} from '../../services/show'; @@ -9,6 +8,7 @@ import {distinctUntilChanged, map, switchMap} from 'rxjs/operators'; import {combineLatest, Observable, of} from 'rxjs'; import {dynamicSort, onlyUnique} from '../../../../services/filter.helper'; import {UserService} from '../../../../services/user/user.service'; +import {FilterStoreService} from '../../../../services/filter-store.service'; import {MatFormField, MatLabel} from '@angular/material/form-field'; import {MatSelect} from '@angular/material/select'; import {MatOptgroup, MatOption} from '@angular/material/core'; @@ -21,11 +21,10 @@ import {ShowTypePipe} from '../../../../widget-modules/pipes/show-type-translate imports: [ReactiveFormsModule, MatFormField, MatLabel, MatSelect, MatOption, MatOptgroup, ShowTypePipe], }) export class FilterComponent { - private router = inject(Router); private showService = inject(ShowService); private userService = inject(UserService); + private filterStore = inject(FilterStoreService); - @Input() public route = '/shows/'; @Input() public shows: Show[] = []; public showTypePublic = ShowService.SHOW_TYPE_PUBLIC; @@ -42,7 +41,6 @@ export class FilterComponent { public owners: {key: string; value: string}[] = []; public constructor() { - const activatedRoute = inject(ActivatedRoute); const fb = inject(UntypedFormBuilder); this.filterFormGroup = fb.group({ @@ -51,16 +49,20 @@ export class FilterComponent { showType: null, }); - activatedRoute.queryParams.subscribe(params => { - const filterValues = params as FilterValues; - if (filterValues.time) this.filterFormGroup.controls.time.setValue(+filterValues.time); - this.filterFormGroup.controls.owner.setValue(filterValues.owner ?? null, {emitEvent: false}); - this.filterFormGroup.controls.showType.setValue(filterValues.showType ?? null, {emitEvent: false}); + this.filterStore.showFilter$.subscribe(filterValues => { + this.filterFormGroup.patchValue( + { + time: filterValues.time, + owner: filterValues.owner || null, + showType: filterValues.showType || null, + }, + {emitEvent: false} + ); }); - this.filterFormGroup.controls.time.valueChanges.subscribe(_ => void this.filerValueChanged('time', _ as number)); - this.filterFormGroup.controls.owner.valueChanges.subscribe(_ => void this.filerValueChanged('owner', _ as string)); - this.filterFormGroup.controls.showType.valueChanges.subscribe(_ => void this.filerValueChanged('showType', _ as string)); + this.filterFormGroup.controls.time.valueChanges.subscribe(_ => this.filterValueChanged('time', (_ as number) ?? 1)); + this.filterFormGroup.controls.owner.valueChanges.subscribe(_ => this.filterValueChanged('owner', (_ as string | null) ?? '')); + this.filterFormGroup.controls.showType.valueChanges.subscribe(_ => this.filterValueChanged('showType', (_ as string | null) ?? '')); this.owners$().subscribe(owners => (this.owners = owners)); } @@ -97,12 +99,8 @@ export class FilterComponent { ); }; - private async filerValueChanged(key: string, value: T): Promise { - const route = this.router.createUrlTree([this.route], { - queryParams: {[key]: value || null}, - queryParamsHandling: 'merge', - }); - await this.router.navigateByUrl(route); + private filterValueChanged(key: keyof FilterValues, value: T): void { + this.filterStore.updateShowFilter({[key]: value} as Partial); } private sameOwners(left: {key: string; value: string}[], right: {key: string; value: string}[]): boolean { diff --git a/src/app/modules/shows/list/list.component.ts b/src/app/modules/shows/list/list.component.ts index 4950fe6..dcdcb95 100644 --- a/src/app/modules/shows/list/list.component.ts +++ b/src/app/modules/shows/list/list.component.ts @@ -3,9 +3,9 @@ import {combineLatest} from 'rxjs'; import {Show} from '../services/show'; import {fade} from '../../../animations'; import {ShowService} from '../services/show.service'; -import {FilterValues} from './filter/filter-values'; -import {ActivatedRoute, RouterLink} from '@angular/router'; +import {FilterValues} from './filter/filter-values'import {RouterLink} from '@angular/router'; import {map, switchMap} from 'rxjs/operators'; +import {FilterStoreService} from '../../../services/filter-store.service'; import {RoleDirective} from '../../../services/user/role.directive'; import {ListHeaderComponent} from '../../../widget-modules/components/list-header/list-header.component'; import {AsyncPipe} from '@angular/common'; @@ -23,37 +23,20 @@ import {SortByPipe} from '../../../widget-modules/pipes/sort-by/sort-by.pipe'; }) export class ListComponent { private showService = inject(ShowService); - private activatedRoute = inject(ActivatedRoute); + private filterStore = inject(FilterStoreService); - public lastMonths$ = this.activatedRoute.queryParams.pipe( - map(params => { - const filterValues = params as FilterValues; - if (!filterValues?.time) return 1; - return +filterValues.time; - }) - ); - public owner$ = this.activatedRoute.queryParams.pipe( - map(params => { - const filterValues = params as FilterValues; - return filterValues?.owner; - }) - ); - public showType$ = this.activatedRoute.queryParams.pipe( - map(params => { - const filterValues = params as FilterValues; - return filterValues?.showType; - }) - ); + public filter$ = this.filterStore.showFilter$; + public lastMonths$ = this.filter$.pipe(map((filterValues: FilterValues) => filterValues.time || 1)); + public owner$ = this.filter$.pipe(map((filterValues: FilterValues) => filterValues.owner)); + public showType$ = this.filter$.pipe(map((filterValues: FilterValues) => filterValues.showType)); public shows$ = this.showService.list$(); - public privateShows$ = this.showService.list$().pipe(map(show => show.filter(_ => !_.published))); + public privateShows$ = combineLatest([this.shows$, this.filter$]).pipe( + map(([shows, filter]) => shows.filter(show => !show.published).filter(show => this.matchesPrivateFilter(show, filter))) + ); public queriedPublicShows$ = this.lastMonths$.pipe(switchMap(lastMonths => this.showService.listPublicSince$(lastMonths))); public fallbackPublicShows$ = combineLatest([this.shows$, this.lastMonths$]).pipe( map(([shows, lastMonths]) => { - const startDate = new Date(); - startDate.setHours(0, 0, 0, 0); - startDate.setDate(startDate.getDate() - lastMonths * 30); - - return shows.filter(show => show.published && !show.archived && show.date.toDate() >= startDate); + return shows.filter(show => show.published && !show.archived).filter(show => this.matchesTimeFilter(show, lastMonths)); }) ); public publicShows$ = combineLatest([this.queriedPublicShows$, this.fallbackPublicShows$, this.owner$, this.showType$]).pipe( @@ -65,4 +48,19 @@ export class ListComponent { ); public trackBy = (index: number, show: unknown) => (show as Show).id; + + private matchesFilter(show: Show, filter: FilterValues): boolean { + return this.matchesTimeFilter(show, filter.time || 1) && (!filter.owner || show.owner === filter.owner) && (!filter.showType || show.showType === filter.showType); + } + + private matchesPrivateFilter(show: Show, filter: FilterValues): boolean { + return this.matchesTimeFilter(show, filter.time || 1) && (!filter.showType || show.showType === filter.showType); + } + + private matchesTimeFilter(show: Show, lastMonths: number): boolean { + const startDate = new Date(); + startDate.setHours(0, 0, 0, 0); + startDate.setDate(startDate.getDate() - lastMonths * 30); + return show.date.toDate() >= startDate; + } } diff --git a/src/app/modules/shows/services/show-data.service.ts b/src/app/modules/shows/services/show-data.service.ts index 41787b7..58a61f5 100644 --- a/src/app/modules/shows/services/show-data.service.ts +++ b/src/app/modules/shows/services/show-data.service.ts @@ -24,12 +24,15 @@ export class ShowDataService { public listRaw$ = () => this.dbService.col$(this.collection); public listPublicSince$(lastMonths: number): Observable { - const startDate = new Date(); - startDate.setHours(0, 0, 0, 0); - startDate.setDate(startDate.getDate() - lastMonths * 30); - const startTimestamp = Timestamp.fromDate(startDate); + const queryConstraints: QueryConstraint[] = [where('published', '==', true), orderBy('date', 'desc')]; - const queryConstraints: QueryConstraint[] = [where('published', '==', true), where('date', '>=', startTimestamp), orderBy('date', 'desc')]; + if (lastMonths < 99999) { + const startDate = new Date(); + startDate.setHours(0, 0, 0, 0); + startDate.setDate(startDate.getDate() - lastMonths * 30); + const startTimestamp = Timestamp.fromDate(startDate); + queryConstraints.splice(1, 0, where('date', '>=', startTimestamp)); + } return this.dbService.col$(this.collection, queryConstraints).pipe( map(shows => shows.filter(show => !show.archived)), diff --git a/src/app/modules/songs/song-list/filter/filter.component.ts b/src/app/modules/songs/song-list/filter/filter.component.ts index abdc6ba..94971ac 100644 --- a/src/app/modules/songs/song-list/filter/filter.component.ts +++ b/src/app/modules/songs/song-list/filter/filter.component.ts @@ -1,10 +1,10 @@ import {Component, Input, inject} from '@angular/core'; -import {ActivatedRoute, Router} from '@angular/router'; import {ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup} from '@angular/forms'; import {SongService} from '../../services/song.service'; import {FilterValues} from './filter-values'; import {Song} from '../../services/song'; import {KEYS} from '../../services/key.helper'; +import {FilterStoreService} from '../../../../services/filter-store.service'; import {MatFormField, MatLabel} from '@angular/material/form-field'; import {MatInput} from '@angular/material/input'; import {MatSelect} from '@angular/material/select'; @@ -21,17 +21,15 @@ import {SongTypePipe} from '../../../../widget-modules/pipes/song-type-translate imports: [ReactiveFormsModule, MatFormField, MatLabel, MatInput, MatSelect, MatOption, LegalTypePipe, KeyPipe, SongTypePipe], }) export class FilterComponent { - private router = inject(Router); + private filterStore = inject(FilterStoreService); public filterFormGroup: UntypedFormGroup; - @Input() public route = '/'; @Input() public songs: Song[] = []; public types = SongService.TYPES; public legalType = SongService.LEGAL_TYPE; public keys = KEYS; public constructor() { - const activatedRoute = inject(ActivatedRoute); const fb = inject(UntypedFormBuilder); this.filterFormGroup = fb.group({ @@ -42,20 +40,15 @@ export class FilterComponent { flag: '', }); - activatedRoute.queryParams.subscribe(params => { - const filterValues = params as FilterValues; - if (filterValues.q) this.filterFormGroup.controls.q.setValue(filterValues.q); - if (filterValues.type) this.filterFormGroup.controls.type.setValue(filterValues.type); - if (filterValues.key) this.filterFormGroup.controls.key.setValue(filterValues.key); - if (filterValues.legalType) this.filterFormGroup.controls.legalType.setValue(filterValues.legalType); - if (filterValues.flag) this.filterFormGroup.controls.flag.setValue(filterValues.flag); + this.filterStore.songFilter$.subscribe(filterValues => { + this.filterFormGroup.patchValue(filterValues, {emitEvent: false}); }); - this.filterFormGroup.controls.q.valueChanges.subscribe(_ => void this.filerValueChanged('q', _ as string)); - this.filterFormGroup.controls.key.valueChanges.subscribe(_ => void this.filerValueChanged('key', _ as string)); - this.filterFormGroup.controls.type.valueChanges.subscribe(_ => void this.filerValueChanged('type', _ as string)); - this.filterFormGroup.controls.legalType.valueChanges.subscribe(_ => void this.filerValueChanged('legalType', _ as string)); - this.filterFormGroup.controls.flag.valueChanges.subscribe(_ => void this.filerValueChanged('flag', _ as string)); + this.filterFormGroup.controls.q.valueChanges.subscribe(_ => this.filterValueChanged('q', (_ as string) ?? '')); + this.filterFormGroup.controls.key.valueChanges.subscribe(_ => this.filterValueChanged('key', (_ as string) ?? '')); + this.filterFormGroup.controls.type.valueChanges.subscribe(_ => this.filterValueChanged('type', (_ as string) ?? '')); + this.filterFormGroup.controls.legalType.valueChanges.subscribe(_ => this.filterValueChanged('legalType', (_ as string) ?? '')); + this.filterFormGroup.controls.flag.valueChanges.subscribe(_ => this.filterValueChanged('flag', (_ as string) ?? '')); } public getFlags(): string[] { @@ -69,11 +62,7 @@ export class FilterComponent { return flags.filter((n, i) => flags.indexOf(n) === i); } - private async filerValueChanged(key: string, value: string): Promise { - const route = this.router.createUrlTree([this.route], { - queryParams: {[key]: value}, - queryParamsHandling: 'merge', - }); - await this.router.navigateByUrl(route); + private filterValueChanged(key: keyof FilterValues, value: string): void { + this.filterStore.updateSongFilter({[key]: value} as Partial); } } diff --git a/src/app/modules/songs/song-list/song-list.component.html b/src/app/modules/songs/song-list/song-list.component.html index 5d34a00..3fdca8e 100644 --- a/src/app/modules/songs/song-list/song-list.component.html +++ b/src/app/modules/songs/song-list/song-list.component.html @@ -1,7 +1,7 @@ @if (songs$ | async; as songs) {
- + @for (song of songs; track trackBy($index, song)) { diff --git a/src/app/modules/songs/song-list/song-list.component.ts b/src/app/modules/songs/song-list/song-list.component.ts index f495d7e..a8e647d 100644 --- a/src/app/modules/songs/song-list/song-list.component.ts +++ b/src/app/modules/songs/song-list/song-list.component.ts @@ -4,12 +4,13 @@ import {Song} from '../services/song'; import {map} from 'rxjs/operators'; import {combineLatest, Observable} from 'rxjs'; import {fade} from '../../../animations'; -import {ActivatedRoute, RouterLink} from '@angular/router'; +import {RouterLink} from '@angular/router'; import {filterSong} from '../../../services/filter.helper'; import {FilterValues} from './filter/filter-values'; import {ScrollService} from '../../../services/scroll.service'; import {faBalanceScaleRight, faCheck, faPencilRuler} from '@fortawesome/free-solid-svg-icons'; import {TextRenderingService} from '../services/text-rendering.service'; +import {FilterStoreService} from '../../../services/filter-store.service'; import {AsyncPipe} from '@angular/common'; import {ListHeaderComponent} from '../../../widget-modules/components/list-header/list-header.component'; import {FilterComponent} from './filter/filter.component'; @@ -31,13 +32,13 @@ interface SongListItem extends Song { }) export class SongListComponent implements OnInit, OnDestroy { private songService = inject(SongService); - private activatedRoute = inject(ActivatedRoute); private scrollService = inject(ScrollService); private textRenderingService = inject(TextRenderingService); + private filterStore = inject(FilterStoreService); public anyFilterActive = false; public songs$: Observable = combineLatest([ - this.activatedRoute.queryParams.pipe(map(_ => _ as FilterValues)), + this.filterStore.songFilter$, this.songService.list$().pipe(map(songs => [...songs].sort((a, b) => a.number - b.number))), ]).pipe( map(([filter, songs]) => { diff --git a/src/app/services/filter-store.service.ts b/src/app/services/filter-store.service.ts new file mode 100644 index 0000000..c9bc707 --- /dev/null +++ b/src/app/services/filter-store.service.ts @@ -0,0 +1,51 @@ +import {Injectable} from '@angular/core'; +import {BehaviorSubject, Observable} from 'rxjs'; +import {FilterValues as SongFilterValues} from '../modules/songs/song-list/filter/filter-values'; +import {FilterValues as ShowFilterValues} from '../modules/shows/list/filter/filter-values'; + +const DEFAULT_SONG_FILTER: SongFilterValues = { + q: '', + type: '', + key: '', + legalType: '', + flag: '', +}; + +const DEFAULT_SHOW_FILTER: ShowFilterValues = { + time: 1, + owner: '', + showType: '', +}; + +@Injectable({ + providedIn: 'root', +}) +export class FilterStoreService { + private readonly songFilterSubject = new BehaviorSubject(DEFAULT_SONG_FILTER); + private readonly showFilterSubject = new BehaviorSubject(DEFAULT_SHOW_FILTER); + + public readonly songFilter$: Observable = this.songFilterSubject.asObservable(); + public readonly showFilter$: Observable = this.showFilterSubject.asObservable(); + + public updateSongFilter(filter: Partial): void { + this.songFilterSubject.next({ + ...this.songFilterSubject.value, + ...filter, + }); + } + + public updateShowFilter(filter: Partial): void { + this.showFilterSubject.next({ + ...this.showFilterSubject.value, + ...filter, + }); + } + + public resetSongFilter(): void { + this.songFilterSubject.next(DEFAULT_SONG_FILTER); + } + + public resetShowFilter(): void { + this.showFilterSubject.next(DEFAULT_SHOW_FILTER); + } +}