sidemenu song list

This commit is contained in:
2026-03-16 17:24:10 +01:00
parent f9516bbc4d
commit 3bd359ee9e
19 changed files with 276 additions and 146 deletions

View File

@@ -1,5 +1,15 @@
.third {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
column-gap: 20px;
.third,
:host ::ng-deep form,
div[formGroup] {
display: flex;
flex-direction: column;
gap: 12px;
}
.third {
gap: 0;
}
:host ::ng-deep .mat-mdc-form-field {
width: 100%;
}

View File

@@ -1,43 +1,48 @@
@if (songs$ | async; as songs) {
<div>
<app-list-header [anyFilterActive]="anyFilterActive">
<app-sidebar>
<div sidebar class="sidebar-content">
<app-filter [songs]="songs"></app-filter>
</app-list-header>
<app-card [padding]="false">
@for (song of songs; track trackBy($index, song)) {
<div [routerLink]="song.id" class="list-item">
<div class="number">{{ song.number }}</div>
<div class="title">
<span>{{ song.title }}</span>
@if (song.hasChordValidationIssues) {
<span class="validation-star" title="Akkord-Validierungsfehler vorhanden">*</span>
}
</div>
<div>
<ng-container *appRole="['contributor']">
@if (song.status === 'draft') {
<div class="sidebar-actions">
<app-button [icon]="faNewSong" routerLink="new">Neuen Song anlegen</app-button>
</div>
</div>
<div content>
<app-card [padding]="false">
@for (song of songs; track trackBy($index, song)) {
<div [routerLink]="song.id" class="list-item">
<div class="number">{{ song.number }}</div>
<div class="title">
<span>{{ song.title }}</span>
@if (song.hasChordValidationIssues) {
<span class="validation-star" title="Akkord-Validierungsfehler vorhanden">*</span>
}
</div>
<div>
<ng-container *appRole="['contributor']">
@if (song.status === 'draft') {
<div class="warning">
<fa-icon [icon]="faDraft"></fa-icon>
</div>
} @if (song.status === 'set') {
<div class="neutral">
<fa-icon [icon]="faDraft"></fa-icon>
</div>
} @if (song.status === 'final') {
<div class="success">
<fa-icon [icon]="faFinal"></fa-icon>
</div>
}
</ng-container>
@if (song.legalType === 'open') {
<div class="warning">
<fa-icon [icon]="faDraft"></fa-icon>
</div>
} @if (song.status === 'set') {
<div class="neutral">
<fa-icon [icon]="faDraft"></fa-icon>
</div>
} @if (song.status === 'final') {
<div class="success">
<fa-icon [icon]="faFinal"></fa-icon>
<fa-icon [icon]="faLegal"></fa-icon>
</div>
}
</ng-container>
@if (song.legalType === 'open') {
<div class="warning">
<fa-icon [icon]="faLegal"></fa-icon>
</div>
}
<div>{{ song.key }}</div>
</div>
<div>{{ song.key }}</div>
</div>
}
</app-card>
</div>
}
</app-card>
</div>
</app-sidebar>
}

View File

@@ -1,3 +1,15 @@
.sidebar-content {
padding: 20px;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.sidebar-actions {
margin-top: auto;
}
.list-item {
padding: 5px 20px;
display: grid;

View File

@@ -6,15 +6,16 @@ import {fade} from '../../../animations';
import {ActivatedRoute, RouterLink} from '@angular/router';
import {filterSong} from '../../../services/filter.helper';
import {FilterValues} from './filter/filter-values';
import {faBalanceScaleRight, faCheck, faPencilRuler} from '@fortawesome/free-solid-svg-icons';
import {faBalanceScaleRight, faCheck, faPencilRuler, faPlus} 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';
import {CardComponent} from '../../../widget-modules/components/card/card.component';
import {RoleDirective} from '../../../services/user/role.directive';
import {FaIconComponent} from '@fortawesome/angular-fontawesome';
import {SidebarComponent} from '../../../widget-modules/components/sidebar/sidebar.component';
import {ButtonComponent} from '../../../widget-modules/components/button/button.component';
interface SongListItem extends Song {
hasChordValidationIssues: boolean;
@@ -26,20 +27,21 @@ interface SongListItem extends Song {
styleUrls: ['./song-list.component.less'],
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [fade],
imports: [ListHeaderComponent, FilterComponent, CardComponent, RouterLink, RoleDirective, FaIconComponent, AsyncPipe],
imports: [FilterComponent, CardComponent, RouterLink, RoleDirective, FaIconComponent, AsyncPipe, SidebarComponent, ButtonComponent],
})
export class SongListComponent {
public faLegal = faBalanceScaleRight;
public faDraft = faPencilRuler;
public faFinal = faCheck;
public faNewSong = faPlus;
private route = inject(ActivatedRoute);
private textRenderingService = inject(TextRenderingService);
private filterStore = inject(FilterStoreService);
public anyFilterActive = false;
public songs$: Observable<SongListItem[]> = combineLatest([
this.filterStore.songFilter$,
this.route.data.pipe(map(data => (data['songs'] as Song[]).slice().sort((a, b) => a.number - b.number))),
]).pipe(
map(([filter, songs]) => {
this.anyFilterActive = this.checkIfFilterActive(filter);
return songs
.filter(song => this.filter(song, filter))
.map(song => ({
@@ -49,9 +51,6 @@ export class SongListComponent {
.sort((a, b) => a.title?.localeCompare(b.title));
})
);
public faLegal = faBalanceScaleRight;
public faDraft = faPencilRuler;
public faFinal = faCheck;
public trackBy = (index: number, show: SongListItem) => show.id;
@@ -65,10 +64,6 @@ export class SongListComponent {
return baseFilter;
}
private checkIfFilterActive(filter: FilterValues): boolean {
return !!filter.q || !!filter.type || !!filter.key || !!filter.legalType || !!filter.flag;
}
private checkFlag(flag: string, flags: string) {
if (!flags) {
return false;