sidemenu shows
This commit is contained in:
31
angular.json
31
angular.json
@@ -95,7 +95,36 @@
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular/build:unit-test"
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": [
|
||||
"src/polyfills.ts",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"inlineStyleLanguage": "less",
|
||||
"assets": [
|
||||
"src/browserconfig.xml",
|
||||
"src/android-chrome-192x192.png",
|
||||
"src/apple-touch-icon.png",
|
||||
"src/apple-touch-icon-precomposed.png",
|
||||
"src/safari-pinned-tab.svg",
|
||||
"src/favicon.ico",
|
||||
"src/favicon-16x16.png",
|
||||
"src/favicon-32x32.png",
|
||||
"src/mstile-150x150.png",
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest"
|
||||
],
|
||||
"styles": [
|
||||
"src/custom-theme.scss",
|
||||
"src/styles/styles.less",
|
||||
"src/styles/shadow.less"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,5 @@ export interface FilterValues {
|
||||
time: number;
|
||||
owner: string;
|
||||
showType: string;
|
||||
archived: boolean;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<mat-label>Zeitraum</mat-label>
|
||||
<mat-select formControlName="time">
|
||||
@for (time of times; track time) {
|
||||
<mat-option [value]="time.key">{{ time.value }} </mat-option>
|
||||
<mat-option [value]="time.key">{{ time.value }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
@@ -14,7 +14,7 @@
|
||||
<mat-select formControlName="owner">
|
||||
<mat-option value="">Alle</mat-option>
|
||||
@for (owner of owners; track owner) {
|
||||
<mat-option [value]="owner.key">{{ owner.value }} </mat-option>
|
||||
<mat-option [value]="owner.key">{{ owner.value }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
@@ -25,17 +25,18 @@
|
||||
<mat-option value="">Alle</mat-option>
|
||||
<mat-optgroup label="öffentlich">
|
||||
@for (key of showTypePublic; track key) {
|
||||
<mat-option [value]="key">{{ key | showType }} </mat-option>
|
||||
<mat-option [value]="key">{{ key | showType }}</mat-option>
|
||||
}
|
||||
</mat-optgroup>
|
||||
<mat-optgroup label="privat">
|
||||
@for (key of showTypePrivate; track key) {
|
||||
<mat-option [value]="key">{{ key | showType }} </mat-option>
|
||||
<mat-option [value]="key">{{ key | showType }}</mat-option>
|
||||
}
|
||||
</mat-optgroup>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-checkbox formControlName="archived">Archiviert</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<i>Anzahl der Suchergebnisse: {{ shows?.length ?? 0 }}</i>
|
||||
<!-- <i>Anzahl der Suchergebnisse: {{ shows?.length ?? 0 }}</i>-->
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
.third {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
column-gap: 20px;
|
||||
.third,
|
||||
div[formGroup] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.third {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-mdc-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Component, DestroyRef, Input, inject} from '@angular/core';
|
||||
import {Component, DestroyRef, inject, Input} from '@angular/core';
|
||||
import {KeyValue} from '@angular/common';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {FormBuilder, FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
|
||||
@@ -14,28 +14,23 @@ import {MatFormField, MatLabel} from '@angular/material/form-field';
|
||||
import {MatSelect} from '@angular/material/select';
|
||||
import {MatOptgroup, MatOption} from '@angular/material/core';
|
||||
import {ShowTypePipe} from '../../../../widget-modules/pipes/show-type-translater/show-type.pipe';
|
||||
import {MatCheckbox} from '@angular/material/checkbox';
|
||||
|
||||
@Component({
|
||||
selector: 'app-filter',
|
||||
templateUrl: './filter.component.html',
|
||||
styleUrls: ['./filter.component.less'],
|
||||
imports: [ReactiveFormsModule, MatFormField, MatLabel, MatSelect, MatOption, MatOptgroup, ShowTypePipe],
|
||||
imports: [ReactiveFormsModule, MatFormField, MatLabel, MatSelect, MatOption, MatOptgroup, ShowTypePipe, MatCheckbox],
|
||||
})
|
||||
export class FilterComponent {
|
||||
private showService = inject(ShowService);
|
||||
private userService = inject(UserService);
|
||||
private filterStore = inject(FilterStoreService);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
@Input() public shows: Show[] = [];
|
||||
|
||||
public showTypePublic = ShowService.SHOW_TYPE_PUBLIC;
|
||||
public showTypePrivate = ShowService.SHOW_TYPE_PRIVATE;
|
||||
|
||||
public filterFormGroup: FormGroup<{
|
||||
time: FormControl<number>;
|
||||
owner: FormControl<string | null>;
|
||||
showType: FormControl<string | null>;
|
||||
archived: FormControl<boolean>;
|
||||
}>;
|
||||
public times: KeyValue<number, string>[] = [
|
||||
{key: 1, value: 'letzter Monat'},
|
||||
@@ -43,8 +38,11 @@ export class FilterComponent {
|
||||
{key: 12, value: 'letztes Jahr'},
|
||||
{key: 99999, value: 'alle'},
|
||||
];
|
||||
|
||||
public owners: {key: string; value: string}[] = [];
|
||||
private showService = inject(ShowService);
|
||||
private userService = inject(UserService);
|
||||
private filterStore = inject(FilterStoreService);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
public constructor() {
|
||||
const fb = inject(FormBuilder);
|
||||
@@ -53,6 +51,7 @@ export class FilterComponent {
|
||||
time: fb.nonNullable.control(1),
|
||||
owner: fb.control<string | null>(null),
|
||||
showType: fb.control<string | null>(null),
|
||||
archived: fb.nonNullable.control(false),
|
||||
});
|
||||
|
||||
this.filterStore.showFilter$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(filterValues => {
|
||||
@@ -61,6 +60,7 @@ export class FilterComponent {
|
||||
time: filterValues.time,
|
||||
owner: filterValues.owner || null,
|
||||
showType: filterValues.showType || null,
|
||||
archived: !!filterValues.archived,
|
||||
},
|
||||
{emitEvent: false}
|
||||
);
|
||||
@@ -69,6 +69,7 @@ export class FilterComponent {
|
||||
this.filterFormGroup.controls.time.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('time', value));
|
||||
this.filterFormGroup.controls.owner.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('owner', value ?? ''));
|
||||
this.filterFormGroup.controls.showType.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('showType', value ?? ''));
|
||||
this.filterFormGroup.controls.archived.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('archived', value));
|
||||
|
||||
this.owners$()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
|
||||
@@ -1,31 +1,42 @@
|
||||
<div>
|
||||
<!-- <app-list-header *appRole="['leader']"></app-list-header>-->
|
||||
<app-list-header *appRole="['leader']">
|
||||
@if (shows$ | async; as shows) {
|
||||
<app-filter [shows]="publicShows$ | async"></app-filter>
|
||||
}
|
||||
</app-list-header>
|
||||
|
||||
<ng-container *appRole="['leader']">
|
||||
@if (privateShows$ | async; as shows) { @if (shows.length > 0) {
|
||||
@if (showSidebar$ | async) {
|
||||
<app-sidebar>
|
||||
<div class="sidebar-content" sidebar>
|
||||
<app-filter [shows]="(publicShows$ | async) ?? []"></app-filter>
|
||||
</div>
|
||||
<div content>
|
||||
@if (privateShows$ | async; as privateShows) {
|
||||
<app-card [padding]="false" heading="Meine Veranstaltungen">
|
||||
@for (show of shows | sortBy: 'desc':'date'; track trackBy($index, show)) {
|
||||
@for (show of privateShows; track trackBy($index, show)) {
|
||||
<app-list-item
|
||||
[routerLink]="show.id"
|
||||
[showStatusBadge]="show.published ? 'nicht gemeldet' : 'unveröffentlicht'"
|
||||
[showStatusBadgeType]="show.published ? 'error' : 'none'"
|
||||
[showStatusBadgeType]="show.archived ? 'warn' : show.published ? 'error' : 'none'"
|
||||
[showStatusBadge]="show.archived ? 'archiviert' : show.published ? 'nicht gemeldet' : 'unveröffentlicht'"
|
||||
[show]="show"
|
||||
></app-list-item>
|
||||
}
|
||||
<div *appRole="['leader']" class="list-action">
|
||||
<app-button [fullWidth]="true" [icon]="faNewShow" routerLink="new">Neue Veranstaltung anlegen </app-button>
|
||||
</div>
|
||||
</app-card>
|
||||
}
|
||||
|
||||
@if (publicShows$ | async; as shows) { @if (shows.length > 0) {
|
||||
<app-card [padding]="false" heading="Veröffentlichte Veranstaltungen">
|
||||
@for (show of shows; track trackBy($index, show)) {
|
||||
<app-list-item [routerLink]="show.id" [show]="show"></app-list-item>
|
||||
}
|
||||
</app-card>
|
||||
} }
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
</app-sidebar>
|
||||
} @else {
|
||||
<div>
|
||||
@if (publicShows$ | async; as shows) { @if (shows.length > 0) {
|
||||
<app-card [padding]="false" heading="Veröffentlichte Veranstaltungen">
|
||||
@for (show of shows | sortBy: 'desc':'date'; track trackBy($index, show)) {
|
||||
@for (show of shows; track trackBy($index, show)) {
|
||||
<app-list-item [routerLink]="show.id" [show]="show"></app-list-item>
|
||||
}
|
||||
</app-card>
|
||||
} }
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.sidebar-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {BehaviorSubject, of} from 'rxjs';
|
||||
import {skip, take} from 'rxjs/operators';
|
||||
import {ListComponent} from './list.component';
|
||||
import {ShowService} from '../services/show.service';
|
||||
import {UserService} from '../../../services/user/user.service';
|
||||
@@ -59,7 +60,7 @@ describe('ListComponent', () => {
|
||||
] as never);
|
||||
|
||||
component.privateShows$.subscribe(shows => {
|
||||
expect(shows.map(show => show.id)).toEqual(['draft-own', 'pending-own']);
|
||||
expect(shows.map(show => show.id)).toEqual(['pending-own', 'draft-own']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -73,7 +74,41 @@ describe('ListComponent', () => {
|
||||
] as never);
|
||||
|
||||
component.privateShows$.subscribe(shows => {
|
||||
expect(shows.map(show => show.id)).toEqual(['older-draft', 'pending-own']);
|
||||
expect(shows.map(show => show.id)).toEqual(['pending-own', 'older-draft']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide archived own shows until archived filter is enabled', done => {
|
||||
const filterStore = TestBed.inject(FilterStoreService);
|
||||
shows$.next([
|
||||
createShow({id: 'draft-own', owner: 'user-1', published: false, reportedType: null, date: {toDate: () => new Date('2026-03-02')}}),
|
||||
createShow({id: 'archived-own', owner: 'user-1', published: true, archived: true, reportedType: 'reported', date: {toDate: () => new Date('2026-03-03')}}),
|
||||
] as never);
|
||||
|
||||
component.privateShows$.pipe(take(1)).subscribe(shows => {
|
||||
expect(shows.map(show => show.id)).toEqual(['draft-own']);
|
||||
|
||||
component.privateShows$.pipe(skip(1), take(1)).subscribe(updatedShows => {
|
||||
expect(updatedShows.map(show => show.id)).toEqual(['archived-own', 'draft-own']);
|
||||
done();
|
||||
});
|
||||
|
||||
filterStore.updateShowFilter({archived: true});
|
||||
});
|
||||
});
|
||||
|
||||
it('should sort public shows by date descending', done => {
|
||||
const filterStore = TestBed.inject(FilterStoreService);
|
||||
filterStore.updateShowFilter({time: 99999});
|
||||
shows$.next([
|
||||
createShow({id: 'old-public', owner: 'user-2', published: true, archived: false, date: {toDate: () => new Date('2026-01-01')}}),
|
||||
createShow({id: 'new-public', owner: 'user-3', published: true, archived: false, date: {toDate: () => new Date('2026-03-10')}}),
|
||||
createShow({id: 'mid-public', owner: 'user-4', published: true, archived: false, date: {toDate: () => new Date('2026-02-05')}}),
|
||||
] as never);
|
||||
|
||||
component.publicShows$.pipe(take(1)).subscribe(shows => {
|
||||
expect(shows.map(show => show.id)).toEqual(['new-public', 'mid-public', 'old-public']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,23 +7,25 @@ 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';
|
||||
import {FilterComponent} from './filter/filter.component';
|
||||
import {CardComponent} from '../../../widget-modules/components/card/card.component';
|
||||
import {ListItemComponent} from './list-item/list-item.component';
|
||||
import {SortByPipe} from '../../../widget-modules/pipes/sort-by/sort-by.pipe';
|
||||
import {UserService} from '../../../services/user/user.service';
|
||||
import {SidebarComponent} from '../../../widget-modules/components/sidebar/sidebar.component';
|
||||
import {ButtonComponent} from '../../../widget-modules/components/button/button.component';
|
||||
import {faPlus} from '@fortawesome/free-solid-svg-icons';
|
||||
import {RoleDirective} from '../../../services/user/role.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list',
|
||||
templateUrl: './list.component.html',
|
||||
styleUrls: ['./list.component.less'],
|
||||
animations: [fade],
|
||||
imports: [RoleDirective, ListHeaderComponent, FilterComponent, CardComponent, ListItemComponent, RouterLink, AsyncPipe, SortByPipe],
|
||||
imports: [FilterComponent, CardComponent, ListItemComponent, RouterLink, AsyncPipe, SidebarComponent, ButtonComponent, RoleDirective],
|
||||
})
|
||||
export class ListComponent {
|
||||
public faNewShow = faPlus;
|
||||
private showService = inject(ShowService);
|
||||
private filterStore = inject(FilterStoreService);
|
||||
private userService = inject(UserService);
|
||||
@@ -32,9 +34,24 @@ export class ListComponent {
|
||||
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 archived$ = this.filter$.pipe(map((filterValues: FilterValues) => !!filterValues.archived));
|
||||
public shows$ = this.showService.list$();
|
||||
public privateShows$ = combineLatest([this.shows$, this.userService.user$]).pipe(
|
||||
map(([shows, user]) => shows.filter(show => show.owner === user?.id).filter(show => !show.published || show.reportedType === 'pending'))
|
||||
public ownShows$ = this.showService.list$(false, true);
|
||||
public privateShows$ = combineLatest([this.ownShows$, this.userService.user$, this.archived$]).pipe(
|
||||
map(([shows, user, showArchived]) =>
|
||||
shows.filter(show => {
|
||||
if (show.owner !== user?.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (show.archived) {
|
||||
return showArchived;
|
||||
}
|
||||
|
||||
return !show.published || show.reportedType === 'pending';
|
||||
})
|
||||
),
|
||||
map(shows => this.sortShowsByDateDesc(shows))
|
||||
);
|
||||
public queriedPublicShows$ = this.lastMonths$.pipe(switchMap(lastMonths => this.showService.listPublicSince$(lastMonths)));
|
||||
public fallbackPublicShows$ = combineLatest([this.shows$, this.lastMonths$]).pipe(
|
||||
@@ -46,9 +63,10 @@ export class ListComponent {
|
||||
map(([queriedShows, fallbackShows, owner, showType]) => {
|
||||
const shows = queriedShows.length > 0 || fallbackShows.length === 0 ? queriedShows : fallbackShows;
|
||||
|
||||
return shows.filter(show => !owner || show.owner === owner).filter(show => !showType || show.showType === showType);
|
||||
return this.sortShowsByDateDesc(shows.filter(show => !owner || show.owner === owner).filter(show => !showType || show.showType === showType));
|
||||
})
|
||||
);
|
||||
public showSidebar$ = this.userService.user$.pipe(map(user => this.hasSidebarAccess(user?.role)));
|
||||
|
||||
public trackBy = (index: number, show: unknown) => (show as Show).id;
|
||||
|
||||
@@ -58,4 +76,17 @@ export class ListComponent {
|
||||
startDate.setDate(startDate.getDate() - lastMonths * 30);
|
||||
return show.date.toDate() >= startDate;
|
||||
}
|
||||
|
||||
private sortShowsByDateDesc(shows: Show[]): Show[] {
|
||||
return [...shows].sort((left, right) => right.date.toDate().getTime() - left.date.toDate().getTime());
|
||||
}
|
||||
|
||||
private hasSidebarAccess(role: string | null | undefined): boolean {
|
||||
if (!role) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const roles = role.split(';').map(item => item.trim());
|
||||
return roles.includes('admin') || roles.includes('leader');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ describe('ShowService', () => {
|
||||
let service: ShowService;
|
||||
let showDataServiceSpy: jasmine.SpyObj<ShowDataService>;
|
||||
let user$: BehaviorSubject<unknown>;
|
||||
let shows$: BehaviorSubject<unknown[]>;
|
||||
const shows = [
|
||||
{id: 'show-1', owner: 'user-1', published: false, archived: false},
|
||||
{id: 'show-2', owner: 'other-user', published: true, archived: false},
|
||||
@@ -16,8 +17,9 @@ describe('ShowService', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
user$ = new BehaviorSubject<unknown>({id: 'user-1'});
|
||||
shows$ = new BehaviorSubject<unknown[]>(shows as unknown[]);
|
||||
showDataServiceSpy = jasmine.createSpyObj<ShowDataService>('ShowDataService', ['read$', 'listPublicSince$', 'update', 'add'], {
|
||||
list$: of(shows) as unknown as ShowDataService['list$'],
|
||||
list$: shows$.asObservable() as unknown as ShowDataService['list$'],
|
||||
});
|
||||
showDataServiceSpy.read$.and.returnValue(of(shows[0]));
|
||||
showDataServiceSpy.listPublicSince$.and.returnValue(of([shows[1]]));
|
||||
@@ -52,6 +54,25 @@ describe('ShowService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should include own archived shows when requested', done => {
|
||||
service.list$(false, true).subscribe(result => {
|
||||
expect(result.map(show => show.id)).toEqual(['show-1', 'show-2', 'show-3']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include archived shows from other users when requested', done => {
|
||||
shows$.next([
|
||||
...(shows as unknown as unknown[]),
|
||||
{id: 'show-4', owner: 'other-user', published: true, archived: true},
|
||||
]);
|
||||
|
||||
service.list$(false, true).subscribe(result => {
|
||||
expect(result.map(show => show.id)).toEqual(['show-1', 'show-2', 'show-3']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should delegate public listing to the data service', done => {
|
||||
service.listPublicSince$(6).subscribe(result => {
|
||||
expect(result).toEqual([shows[1]]);
|
||||
|
||||
@@ -20,13 +20,17 @@ export class ShowService {
|
||||
public read$ = (showId: string): Observable<Show | null> => this.showDataService.read$(showId);
|
||||
public listPublicSince$ = (lastMonths: number): Observable<Show[]> => this.showDataService.listPublicSince$(lastMonths);
|
||||
|
||||
public list$(publishedOnly = false): Observable<Show[]> {
|
||||
public list$(publishedOnly = false, includeOwnArchived = false): Observable<Show[]> {
|
||||
return this.userService.user$.pipe(
|
||||
switchMap(
|
||||
() => this.showDataService.list$,
|
||||
(user: User | null, shows: Show[]) => ({user, shows})
|
||||
),
|
||||
map(s => s.shows.filter(show => !show.archived).filter(show => show.published || (show.owner === s.user?.id && !publishedOnly)))
|
||||
map(s =>
|
||||
s.shows
|
||||
.filter(show => !show.archived || (includeOwnArchived && show.owner === s.user?.id))
|
||||
.filter(show => show.published || (show.owner === s.user?.id && !publishedOnly))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {NewComponent} from './new/new.component';
|
||||
import {ListComponent} from './list/list.component';
|
||||
import {ShowComponent} from './show/show.component';
|
||||
import {EditComponent} from './edit/edit.component';
|
||||
import {RoleGuard} from '../../widget-modules/guards/role.guard';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -14,6 +15,10 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'new',
|
||||
component: NewComponent,
|
||||
canActivate: [RoleGuard],
|
||||
data: {
|
||||
requiredRoles: ['leader'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ':showId/edit',
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
<app-sidebar>
|
||||
<div sidebar class="sidebar-content">
|
||||
<app-filter [songs]="songs"></app-filter>
|
||||
<div class="sidebar-actions">
|
||||
<app-button [icon]="faNewSong" routerLink="new">Neuen Song anlegen</app-button>
|
||||
</div>
|
||||
</div>
|
||||
<div content>
|
||||
<app-card [padding]="false">
|
||||
@@ -42,6 +39,9 @@
|
||||
<div>{{ song.key }}</div>
|
||||
</div>
|
||||
}
|
||||
<div *appRole="['contributor']" class="list-action">
|
||||
<app-button [fullWidth]="true" [icon]="faNewSong" routerLink="new">Neuen Song anlegen</app-button>
|
||||
</div>
|
||||
</app-card>
|
||||
</div>
|
||||
</app-sidebar>
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
.sidebar-content {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-actions {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {EditComponent} from './song/edit/edit.component';
|
||||
import {NewComponent} from './song/new/new.component';
|
||||
import {EditSongGuard} from './song/edit/edit-song.guard';
|
||||
import {SongListResolver} from './services/song-list.resolver';
|
||||
import {RoleGuard} from '../../widget-modules/guards/role.guard';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -19,6 +20,10 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'new',
|
||||
component: NewComponent,
|
||||
canActivate: [RoleGuard],
|
||||
data: {
|
||||
requiredRoles: ['contributor'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ':songId/edit',
|
||||
|
||||
@@ -15,6 +15,7 @@ const DEFAULT_SHOW_FILTER: ShowFilterValues = {
|
||||
time: 1,
|
||||
owner: '',
|
||||
showType: '',
|
||||
archived: false,
|
||||
};
|
||||
|
||||
@Injectable({
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
:host {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
:host(.full-width) {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
color: var(--text);
|
||||
transition: var(--transition);
|
||||
|
||||
:host(.full-width) & {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-active);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,12 @@ import {FaIconComponent} from '@fortawesome/angular-fontawesome';
|
||||
templateUrl: './button.component.html',
|
||||
styleUrls: ['./button.component.less'],
|
||||
imports: [MatButton, FaIconComponent],
|
||||
host: {
|
||||
'[class.full-width]': 'fullWidth',
|
||||
},
|
||||
})
|
||||
export class ButtonComponent {
|
||||
@Input() public disabled = false;
|
||||
@Input() public fullWidth = false;
|
||||
@Input() public icon: IconProp | null = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user