mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-21 17:12:43 +00:00
feat: add easy way to configure search filtering for nested properties
This commit is contained in:
committed by
Rainer Killinger
parent
e75a46633c
commit
2220ab24b3
@@ -13,6 +13,7 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import {CdkVirtualForOf, CdkVirtualScrollViewport, VirtualScrollStrategy} from '@angular/cdk/scrolling';
|
||||
import {BehaviorSubject, Subject, Subscription, takeUntil, timer} from 'rxjs';
|
||||
import {debounceTime, distinctUntilChanged, tap} from 'rxjs/operators';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 StApps
|
||||
* Copyright (C) 2023 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
@@ -159,6 +159,11 @@ describe('ContextMenuComponent', async () => {
|
||||
{
|
||||
field: 'type',
|
||||
buckets: [{count: 10, key: 'date series', checked: true}],
|
||||
info: {
|
||||
onlyOnType: SCThingType.AcademicEvent,
|
||||
field: 'date series',
|
||||
sortOrder: 0,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -210,7 +215,7 @@ function getFilterContextType(): FilterContext {
|
||||
compact: false,
|
||||
options: facetsMock
|
||||
.filter(facet => facet.buckets.length > 0)
|
||||
.map(facet => {
|
||||
.map((facet, i) => {
|
||||
return {
|
||||
buckets: facet.buckets.map(bucket => {
|
||||
return {
|
||||
@@ -222,6 +227,11 @@ function getFilterContextType(): FilterContext {
|
||||
compact: false,
|
||||
field: facet.field,
|
||||
onlyOnType: facet.onlyOnType,
|
||||
info: {
|
||||
onlyOnType: facet.onlyOnType,
|
||||
field: facet.field,
|
||||
sortOrder: i,
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
|
||||
import {SCLanguage, SCThingTranslator, SCThingType, SCTranslations} from '@openstapps/core';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {ContextMenuService} from './context-menu.service';
|
||||
import {FilterContext, SortContext, SortContextOption} from './context-type';
|
||||
import {FilterContext, FilterFacet, SortContext, SortContextOption} from './context-type';
|
||||
|
||||
/**
|
||||
* The context menu
|
||||
@@ -49,6 +49,19 @@ export class ContextMenuComponent implements OnDestroy {
|
||||
*/
|
||||
filterOption: FilterContext;
|
||||
|
||||
/**
|
||||
* Picks facets based on the compact filter option and sorts
|
||||
* them based on
|
||||
*
|
||||
* No specific type => Type name alphabetically => Bucket count
|
||||
*/
|
||||
get facets(): FilterFacet[] {
|
||||
const options = this.filterOption.compact
|
||||
? this.filterOption.options.slice(0, this.compactFilterOptionCount)
|
||||
: this.filterOption.options;
|
||||
return options.filter(it => it.buckets.length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible languages to be used for translation
|
||||
*/
|
||||
@@ -102,18 +115,6 @@ export class ContextMenuComponent implements OnDestroy {
|
||||
this.contextMenuService.contextFilterChanged(this.filterOption);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns translated property name
|
||||
*/
|
||||
getTranslatedPropertyName(property: string, onlyForType?: SCThingType): string {
|
||||
return (
|
||||
this.translator.translatedPropertyNames(
|
||||
onlyForType ?? SCThingType.AcademicEvent,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) as any
|
||||
)[property];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns translated property value
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
~ Copyright (C) 2022 StApps
|
||||
~ Copyright (C) 2023 StApps
|
||||
~ This program is free software: you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU General Public License as published by the Free
|
||||
~ Software Foundation, version 3.
|
||||
@@ -41,7 +41,7 @@
|
||||
<ion-icon *ngIf="!sortOption.reversed" name="arrow_upward"></ion-icon>
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-radio slot="end" [value]="i"> </ion-radio>
|
||||
<ion-radio slot="end" [value]="i"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
@@ -55,31 +55,17 @@
|
||||
</ion-button>
|
||||
</ion-list-header>
|
||||
|
||||
<ion-list
|
||||
class="filter-group"
|
||||
*ngFor="
|
||||
let facet of !filterOption.compact
|
||||
? filterOption.options.slice(0, compactFilterOptionCount)
|
||||
: filterOption.options
|
||||
"
|
||||
>
|
||||
<div *ngIf="!facet.field.includes('.')">
|
||||
<ion-list class="filter-group" *ngFor="let facet of facets">
|
||||
<div>
|
||||
<ion-list-header class="h3">
|
||||
<ion-label>
|
||||
{{
|
||||
(facet.onlyOnType
|
||||
? getTranslatedPropertyName(facet.field, facet.onlyOnType)
|
||||
: getTranslatedPropertyName(facet.field)
|
||||
) | titlecase
|
||||
}}
|
||||
{{
|
||||
facet.onlyOnType
|
||||
? ' | ' + (getTranslatedPropertyValue(facet.onlyOnType, 'type') | titlecase)
|
||||
: ''
|
||||
}}
|
||||
<span *ngIf="facet.info.onlyOnType"
|
||||
><b>{{ facet.info.onlyOnType | titlecase }}</b> /
|
||||
</span>
|
||||
{{ facet.info.field | titlecase }}
|
||||
</ion-label>
|
||||
</ion-list-header>
|
||||
<div *ngIf="facet.buckets.length > 0">
|
||||
<div>
|
||||
<ion-item
|
||||
*ngFor="
|
||||
let bucket of !facet.compact
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import {ContextMenuService} from './context-menu.service';
|
||||
import {SCFacet} from '@openstapps/core';
|
||||
import {FilterContext, SortContext} from './context-type';
|
||||
import {ThingTranslateModule} from '../../../translation/thing-translate.module';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
|
||||
describe('ContextMenuService', () => {
|
||||
let service: ContextMenuService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ThingTranslateModule.forRoot(), TranslateModule.forRoot()],
|
||||
providers: [ContextMenuService],
|
||||
});
|
||||
service = TestBed.inject(ContextMenuService);
|
||||
@@ -123,6 +141,10 @@ const filterContext: FilterContext = {
|
||||
},
|
||||
],
|
||||
field: 'type',
|
||||
info: {
|
||||
field: 'type',
|
||||
sortOrder: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -13,9 +13,19 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Injectable} from '@angular/core';
|
||||
import {SCFacet, SCSearchFilter, SCSearchSort, SCThingType} from '@openstapps/core';
|
||||
import {
|
||||
SCFacet,
|
||||
SCSearchFilter,
|
||||
SCSearchSort,
|
||||
SCThingTranslator,
|
||||
SCThingType,
|
||||
SCTranslations,
|
||||
} from '@openstapps/core';
|
||||
import {Subject} from 'rxjs';
|
||||
import {FilterBucket, FilterContext, FilterFacet, SortContext} from './context-type';
|
||||
import {FilterBucket, FilterContext, FilterFacet, SortContext, TransformedFacet} from './context-type';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {ThingTranslateService} from '../../../translation/thing-translate.service';
|
||||
import {transformFacets} from './facet-filter';
|
||||
|
||||
/**
|
||||
* ContextMenuService provides bidirectional communication of context menu options and search queries
|
||||
@@ -72,6 +82,11 @@ export class ContextMenuService {
|
||||
*/
|
||||
sortQueryChanged$ = this.sortQuery.asObservable();
|
||||
|
||||
constructor(
|
||||
private readonly translate: TranslateService,
|
||||
private readonly thingTranslate: ThingTranslateService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Returns SCSearchFilter if filterContext value is set, undefined otherwise
|
||||
*
|
||||
@@ -178,18 +193,9 @@ export class ContextMenuService {
|
||||
* Updates the filter context options from given facets
|
||||
*/
|
||||
updateContextFilter(facets: SCFacet[]) {
|
||||
// arrange facet field "type" to first position
|
||||
facets.sort((a: SCFacet, b: SCFacet) => {
|
||||
if (a.field === 'type') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (b.field === 'type') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
const language = this.translate.currentLang as keyof SCTranslations<unknown>;
|
||||
const translator = new SCThingTranslator(language);
|
||||
const transformedFacets = transformFacets(facets, language, this.thingTranslate, translator);
|
||||
|
||||
if (!this.contextFilter) {
|
||||
this.contextFilter = {
|
||||
@@ -198,23 +204,24 @@ export class ContextMenuService {
|
||||
};
|
||||
}
|
||||
|
||||
this.updateContextFilterOptions(this.contextFilter, facets);
|
||||
this.updateContextFilterOptions(this.contextFilter, transformedFacets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates context filter with new facets.
|
||||
* It preserves the checked status of existing filter options
|
||||
*/
|
||||
updateContextFilterOptions = (contextFilter: FilterContext, facets: SCFacet[]) => {
|
||||
updateContextFilterOptions = (contextFilter: FilterContext, facets: TransformedFacet[]) => {
|
||||
const newFilterOptions: FilterFacet[] = [];
|
||||
|
||||
// iterate new facets
|
||||
for (const facet of facets) {
|
||||
for (const {facet, info} of facets) {
|
||||
if (facet.buckets.length > 0) {
|
||||
const newFilterFacet: FilterFacet = {
|
||||
buckets: [],
|
||||
field: facet.field,
|
||||
onlyOnType: facet.onlyOnType,
|
||||
info,
|
||||
};
|
||||
newFilterOptions.push(newFilterFacet);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2020 StApps
|
||||
* Copyright (C) 2023 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
@@ -12,7 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {SCFacet, SCFacetBucket} from '@openstapps/core';
|
||||
import {SCFacet, SCFacetBucket, SCThingType} from '@openstapps/core';
|
||||
|
||||
export type ContextType = FilterContext | SortContext;
|
||||
|
||||
@@ -84,6 +84,21 @@ export interface FilterFacet extends SCFacet {
|
||||
* Compact view of the option buckets
|
||||
*/
|
||||
compact?: boolean;
|
||||
/**
|
||||
* Translated information about the facet
|
||||
*/
|
||||
info: FacetInfo;
|
||||
}
|
||||
|
||||
export interface FacetInfo {
|
||||
onlyOnType?: SCThingType;
|
||||
field: string;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export interface TransformedFacet {
|
||||
facet: SCFacet;
|
||||
info: FacetInfo;
|
||||
}
|
||||
|
||||
export interface FilterBucket extends SCFacetBucket {
|
||||
|
||||
70
src/app/modules/menu/context/facet-filter.ts
Normal file
70
src/app/modules/menu/context/facet-filter.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2023 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {TransformedFacet} from './context-type';
|
||||
import {SCFacet, SCThingTranslator, SCThingType, SCTranslations} from '@openstapps/core';
|
||||
import {searchFilters} from '../../../../config/search-filter';
|
||||
import {ThingTranslateService} from '../../../translation/thing-translate.service';
|
||||
|
||||
const filterConfig = Object.entries(searchFilters).map(([pattern, entries]) => {
|
||||
return {
|
||||
typePattern: new RegExp(`^${pattern}$`),
|
||||
facetFilter: Object.entries(entries).map(([pattern, facet]) => ({
|
||||
pattern: new RegExp(`^${pattern}$`),
|
||||
...facet,
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Transforms facets to
|
||||
*
|
||||
* 1. only include facets that are allowed in the options
|
||||
* 2. translates all fields
|
||||
* 3. sorts the facets according to the config
|
||||
*/
|
||||
export function transformFacets(
|
||||
facets: SCFacet[],
|
||||
language: keyof SCTranslations<unknown>,
|
||||
thingTranslate: ThingTranslateService,
|
||||
translator: SCThingTranslator,
|
||||
): TransformedFacet[] {
|
||||
return facets
|
||||
.map(facet => ({
|
||||
facet,
|
||||
info: filterConfig
|
||||
.filter(({typePattern}) => typePattern.test((facet.onlyOnType as string) || ''))
|
||||
.flatMap(({facetFilter}) =>
|
||||
facetFilter
|
||||
.filter(({pattern}) => pattern.test(facet.field))
|
||||
.map(it => ({
|
||||
onlyOnType: facet.onlyOnType
|
||||
? (translator.translatedPropertyValue(facet.onlyOnType, 'type') as SCThingType)
|
||||
: undefined,
|
||||
field:
|
||||
it.translations && it.name
|
||||
? it.translations[language]?.name || it.name
|
||||
: thingTranslate.getPropertyName(
|
||||
facet.onlyOnType || SCThingType.AcademicEvent,
|
||||
facet.field,
|
||||
),
|
||||
sortOrder: it.sortOrder,
|
||||
})),
|
||||
)
|
||||
.sort(({sortOrder: a}, {sortOrder: b}) => a - b)[0],
|
||||
}))
|
||||
.filter(({info}) => !!info)
|
||||
.sort(({info: {sortOrder: a}}, {info: {sortOrder: b}}) => a - b);
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {SCSection} from './sections';
|
||||
import {SCSection} from '../../../../config/profile-page-sections';
|
||||
import {AuthHelperService} from '../../auth/auth-helper.service';
|
||||
import {Observable, Subscription} from 'rxjs';
|
||||
import {SCAuthorizationProviderType} from '@openstapps/core';
|
||||
|
||||
@@ -21,7 +21,7 @@ import {ActivatedRoute} from '@angular/router';
|
||||
import {ScheduleProvider} from '../../calendar/schedule.provider';
|
||||
import moment from 'moment';
|
||||
import {SCIcon} from '../../../util/ion-icon/icon';
|
||||
import {profilePageSections} from './sections';
|
||||
import {profilePageSections} from '../../../../config/profile-page-sections';
|
||||
import {filter, map} from 'rxjs/operators';
|
||||
|
||||
const CourseCard = {
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// TODO: move this to external configuration & stapps core
|
||||
|
||||
import {
|
||||
SCAuthorizationProviderType,
|
||||
SCThing,
|
||||
SCThingOriginType,
|
||||
SCThingRemoteOrigin,
|
||||
SCThingType,
|
||||
} from '@openstapps/core';
|
||||
import {SCIcon} from '../../../util/ion-icon/icon';
|
||||
|
||||
export const SCSectionThingType = 'section' as SCThingType;
|
||||
export const SCSectionLinkThingType = 'section link' as SCThingType;
|
||||
|
||||
const StubOrigin: SCThingRemoteOrigin = {
|
||||
type: SCThingOriginType.Remote,
|
||||
name: 'todo',
|
||||
indexed: new Date(Date.now()).toISOString(),
|
||||
};
|
||||
|
||||
const SCSectionConstantValues: Pick<SCSection, 'type' | 'origin' | 'uid'> = {
|
||||
type: SCSectionThingType,
|
||||
origin: StubOrigin,
|
||||
uid: 'stub',
|
||||
};
|
||||
|
||||
const SCSectionLinkConstantValues: Pick<SCSection, 'type' | 'origin' | 'uid'> = {
|
||||
type: SCSectionLinkThingType,
|
||||
origin: StubOrigin,
|
||||
uid: 'stub',
|
||||
};
|
||||
|
||||
export interface SCSectionLink extends SCThing {
|
||||
link: string[];
|
||||
needsAuth?: true;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export interface SCSection extends SCThing {
|
||||
authProvider?: SCAuthorizationProviderType;
|
||||
links: SCSectionLink[];
|
||||
}
|
||||
|
||||
export const profilePageSections: SCSection[] = [
|
||||
{
|
||||
name: '',
|
||||
links: [
|
||||
{
|
||||
name: 'Favorites',
|
||||
icon: SCIcon`grade`,
|
||||
link: ['/favorites'],
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Favoriten',
|
||||
},
|
||||
},
|
||||
...SCSectionLinkConstantValues,
|
||||
},
|
||||
{
|
||||
name: 'Schedule',
|
||||
icon: SCIcon`calendar_today`,
|
||||
link: ['/schedule'],
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Stundenplan',
|
||||
},
|
||||
},
|
||||
...SCSectionLinkConstantValues,
|
||||
},
|
||||
{
|
||||
name: 'Course Catalog',
|
||||
icon: SCIcon`inventory_2`,
|
||||
link: ['/catalog'],
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Vorlesungs-verzeichnis',
|
||||
},
|
||||
},
|
||||
...SCSectionLinkConstantValues,
|
||||
},
|
||||
{
|
||||
name: 'Settings',
|
||||
icon: SCIcon`settings`,
|
||||
link: ['/settings'],
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Einstellungen',
|
||||
},
|
||||
},
|
||||
...SCSectionLinkConstantValues,
|
||||
},
|
||||
{
|
||||
name: 'Feedback',
|
||||
icon: SCIcon`rate_review`,
|
||||
link: ['/feedback'],
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Feedback',
|
||||
},
|
||||
},
|
||||
...SCSectionLinkConstantValues,
|
||||
},
|
||||
{
|
||||
name: 'About',
|
||||
icon: SCIcon`info`,
|
||||
link: ['/about'],
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Über die App',
|
||||
},
|
||||
},
|
||||
...SCSectionLinkConstantValues,
|
||||
},
|
||||
],
|
||||
translations: {
|
||||
de: {
|
||||
name: '',
|
||||
},
|
||||
},
|
||||
...SCSectionConstantValues,
|
||||
},
|
||||
{
|
||||
name: 'Campus Services',
|
||||
authProvider: 'default',
|
||||
links: [
|
||||
{
|
||||
name: 'Assessments',
|
||||
icon: SCIcon`fact_check`,
|
||||
link: ['/assessments'],
|
||||
needsAuth: true,
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Noten',
|
||||
},
|
||||
},
|
||||
...SCSectionLinkConstantValues,
|
||||
},
|
||||
],
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Campus Dienste',
|
||||
},
|
||||
},
|
||||
...SCSectionConstantValues,
|
||||
},
|
||||
{
|
||||
name: 'Library',
|
||||
authProvider: 'paia',
|
||||
links: [
|
||||
{
|
||||
name: 'Library Catalog',
|
||||
icon: SCIcon`local_library`,
|
||||
link: ['/hebis-search'],
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Bibliotheks-katalog',
|
||||
},
|
||||
},
|
||||
...SCSectionLinkConstantValues,
|
||||
},
|
||||
{
|
||||
name: 'Library Account',
|
||||
icon: SCIcon`badge`,
|
||||
needsAuth: true,
|
||||
link: ['/library-account/profile'],
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Bibliotheks-konto',
|
||||
},
|
||||
},
|
||||
...SCSectionLinkConstantValues,
|
||||
},
|
||||
{
|
||||
name: 'Orders & Reservations',
|
||||
icon: SCIcon`collections_bookmark`,
|
||||
needsAuth: true,
|
||||
link: ['/library-account/holds'],
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Bestellungen & Vormerkungen',
|
||||
},
|
||||
},
|
||||
...SCSectionLinkConstantValues,
|
||||
},
|
||||
{
|
||||
name: 'Checked out items',
|
||||
icon: SCIcon`library_books`,
|
||||
needsAuth: true,
|
||||
link: ['/library-account/checked-out'],
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Deine Ausleihen',
|
||||
},
|
||||
},
|
||||
...SCSectionLinkConstantValues,
|
||||
},
|
||||
{
|
||||
name: 'Fines',
|
||||
icon: SCIcon`request_page`,
|
||||
needsAuth: true,
|
||||
link: ['/library-account/fines'],
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Gebühren',
|
||||
},
|
||||
},
|
||||
...SCSectionLinkConstantValues,
|
||||
},
|
||||
],
|
||||
translations: {
|
||||
de: {
|
||||
name: 'Bibliothek',
|
||||
},
|
||||
},
|
||||
...SCSectionConstantValues,
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user