mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-20 00:23:03 +00:00
feat: show menu for multiple days for canteens and cafes
Closes #19, #79
This commit is contained in:
committed by
Jovan Krunić
parent
66b8720da0
commit
3c079cd189
@@ -16,10 +16,12 @@ import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {DataDetailComponent} from './detail/data-detail.component';
|
||||
import {DataListComponent} from './list/data-list.component';
|
||||
import {FoodDataListComponent} from './list/food-data-list.component';
|
||||
|
||||
const dataRoutes: Routes = [
|
||||
{path: 'search', component: DataListComponent},
|
||||
{path: 'data-detail/:uid', component: DataDetailComponent},
|
||||
{path: 'canteen', component: FoodDataListComponent},
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,9 +35,11 @@ import {OriginDetailComponent} from './elements/origin-detail.component';
|
||||
import {OriginInListComponent} from './elements/origin-in-list.component';
|
||||
import {SimpleCardComponent} from './elements/simple-card.component';
|
||||
import {SkeletonListItem} from './elements/skeleton-list-item.component';
|
||||
import {SkeletonSegment} from './elements/skeleton-segment-button.component';
|
||||
import {SkeletonSimpleCard} from './elements/skeleton-simple-card.component';
|
||||
import {DataListItem} from './list/data-list-item.component';
|
||||
import {DataListComponent} from './list/data-list.component';
|
||||
import {FoodDataListComponent} from './list/food-data-list.component';
|
||||
import {StAppsWebHttpClient} from './stapps-web-http-client.provider';
|
||||
import {ArticleDetailContentComponent} from './types/article/article-detail-content.component';
|
||||
import {ArticleListItem} from './types/article/article-list-item.component';
|
||||
@@ -59,6 +61,7 @@ import {PersonDetailContentComponent} from './types/person/person-detail-content
|
||||
import {PersonListItem} from './types/person/person-list-item.component';
|
||||
import {PlaceDetailContentComponent} from './types/place/place-detail-content.component';
|
||||
import {PlaceListItem} from './types/place/place-list-item.component';
|
||||
import {PlaceMensaDetailComponent} from './types/place/special/mensa/place-mensa-detail.component';
|
||||
import {SemesterDetailContentComponent} from './types/semester/semester-detail-content.component';
|
||||
import {SemesterListItem} from './types/semester/semester-list-item.component';
|
||||
import {VideoDetailContentComponent} from './types/video/video-detail-content.component';
|
||||
@@ -80,6 +83,7 @@ import {VideoListItem} from './types/video/video-list-item.component';
|
||||
CatalogListItem,
|
||||
DataDetailComponent,
|
||||
DataDetailContentComponent,
|
||||
FoodDataListComponent,
|
||||
DataListComponent,
|
||||
DataListItem,
|
||||
DateSeriesDetailContentComponent,
|
||||
@@ -101,9 +105,11 @@ import {VideoListItem} from './types/video/video-list-item.component';
|
||||
PersonListItem,
|
||||
PlaceDetailContentComponent,
|
||||
PlaceListItem,
|
||||
PlaceMensaDetailComponent,
|
||||
SemesterDetailContentComponent,
|
||||
SemesterListItem,
|
||||
SkeletonListItem,
|
||||
SkeletonSegment,
|
||||
VideoDetailContentComponent,
|
||||
VideoListItem,
|
||||
],
|
||||
|
||||
@@ -16,9 +16,9 @@ import {Injectable} from '@angular/core';
|
||||
import {Client} from '@openstapps/api/lib/client';
|
||||
import {SCSearchQuery, SCSearchResponse, SCThingOriginType, SCThings, SCThingType} from '@openstapps/core';
|
||||
import {SCSaveableThing} from '@openstapps/core';
|
||||
import {environment} from '../../../environments/environment';
|
||||
import {StorageProvider} from '../storage/storage.provider';
|
||||
import {StAppsWebHttpClient} from './stapps-web-http-client.provider';
|
||||
import {environment} from './../../../environments/environment';
|
||||
|
||||
export enum DataScope {
|
||||
Local = 'local',
|
||||
|
||||
@@ -27,4 +27,5 @@ export class DataDetailContentComponent {
|
||||
* TODO
|
||||
*/
|
||||
@Input() item: SCThings;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 {Component} from '@angular/core';
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-skeleton-segment-button',
|
||||
templateUrl: 'skeleton-segment-button.html',
|
||||
})
|
||||
export class SkeletonSegment {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<ion-segment-button>
|
||||
<ion-skeleton-text animated style="width: 85%;"></ion-skeleton-text>
|
||||
</ion-segment-button>
|
||||
|
||||
@@ -22,9 +22,9 @@ import {
|
||||
SCThing,
|
||||
} from '@openstapps/core';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
import {Subject} from 'rxjs';
|
||||
import {Subject, Subscription} from 'rxjs';
|
||||
import {debounceTime, distinctUntilChanged} from 'rxjs/operators';
|
||||
import {MenuService} from '../../menu/menu.service';
|
||||
import {ContextMenuService} from '../../menu/context/context-menu.service';
|
||||
import {SettingsProvider} from '../../settings/settings.provider';
|
||||
import {DataProvider} from '../data.provider';
|
||||
|
||||
@@ -34,6 +34,7 @@ import {DataProvider} from '../data.provider';
|
||||
@Component({
|
||||
selector: 'stapps-data-list',
|
||||
templateUrl: 'data-list.html',
|
||||
providers: [ContextMenuService],
|
||||
})
|
||||
export class DataListComponent implements OnInit {
|
||||
|
||||
@@ -50,7 +51,7 @@ export class DataListComponent implements OnInit {
|
||||
/**
|
||||
* Container for queried things
|
||||
*/
|
||||
items: SCThing[];
|
||||
items: Promise<SCThing[]>;
|
||||
|
||||
/**
|
||||
* Page size of queries
|
||||
@@ -77,22 +78,27 @@ export class DataListComponent implements OnInit {
|
||||
*/
|
||||
sortQuery: SCSearchSort | undefined;
|
||||
|
||||
/**
|
||||
* Array of all subscriptions to Observables
|
||||
*/
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param alertController AlertController
|
||||
* @param dataProvider DataProvider
|
||||
* @param menuService MenuService
|
||||
* @param contextMenuService ContextMenuService
|
||||
* @param settingsProvider SettingsProvider
|
||||
* @param logger An angular logger
|
||||
*/
|
||||
constructor(
|
||||
private readonly alertController: AlertController,
|
||||
private readonly dataProvider: DataProvider,
|
||||
private readonly menuService: MenuService,
|
||||
private readonly settingsProvider: SettingsProvider,
|
||||
private readonly logger: NGXLogger,
|
||||
protected readonly alertController: AlertController,
|
||||
protected dataProvider: DataProvider,
|
||||
protected readonly contextMenuService: ContextMenuService,
|
||||
protected readonly settingsProvider: SettingsProvider,
|
||||
protected readonly logger: NGXLogger,
|
||||
) {
|
||||
this.queryTextChanged
|
||||
this.subscriptions.push(this.queryTextChanged
|
||||
.pipe(
|
||||
debounceTime(this.searchQueryDueTime),
|
||||
distinctUntilChanged())
|
||||
@@ -100,30 +106,32 @@ export class DataListComponent implements OnInit {
|
||||
this.from = 0;
|
||||
this.queryText = model;
|
||||
this.fetchAndUpdateItems();
|
||||
});
|
||||
}));
|
||||
|
||||
this.menuService.filterQueryChanged$.subscribe((query) => {
|
||||
this.subscriptions.push(this.contextMenuService.filterQueryChanged$.subscribe((query) => {
|
||||
this.filterQuery = query;
|
||||
this.from = 0;
|
||||
this.fetchAndUpdateItems();
|
||||
});
|
||||
this.menuService.sortQueryChanged$.subscribe((query) => {
|
||||
}));
|
||||
this.subscriptions.push(this.contextMenuService.sortQueryChanged$.subscribe((query) => {
|
||||
this.sortQuery = query;
|
||||
this.from = 0;
|
||||
this.fetchAndUpdateItems();
|
||||
});
|
||||
}));
|
||||
|
||||
this.fetchAndUpdateItems();
|
||||
|
||||
/**
|
||||
* Subscribe to 'settings.changed' events
|
||||
*/
|
||||
this.settingsProvider.settingsActionChanged$.subscribe(({type, payload}) => {
|
||||
this.subscriptions.push(this.settingsProvider.settingsActionChanged$.subscribe(({type, payload}) => {
|
||||
if (type === 'stapps.settings.changed') {
|
||||
const {category, name, value} = payload!;
|
||||
this.logger.log(`received event "settings.changed" with category:
|
||||
${category}, name: ${name}, value: ${JSON.stringify(value)}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +139,7 @@ export class DataListComponent implements OnInit {
|
||||
*
|
||||
* @param append If true fetched data gets appended to existing, override otherwise (default false)
|
||||
*/
|
||||
private async fetchAndUpdateItems(append = false): Promise<void> {
|
||||
protected async fetchAndUpdateItems(append = false): Promise<void> {
|
||||
// build query search options
|
||||
const searchOptions: SCSearchQuery = {
|
||||
from: this.from,
|
||||
@@ -154,17 +162,17 @@ export class DataListComponent implements OnInit {
|
||||
}
|
||||
|
||||
return this.dataProvider.search(searchOptions)
|
||||
.then((result) => {
|
||||
.then(async (result) => {
|
||||
if (append) {
|
||||
let items = await this.items;
|
||||
// append results
|
||||
this.items = this.items.concat(result.data);
|
||||
items = items.concat(result.data);
|
||||
this.items = (async () => items)();
|
||||
} else {
|
||||
// override items with results
|
||||
this.items = result.data;
|
||||
}
|
||||
// update filter options if result contains facets
|
||||
if (typeof result.facets !== 'undefined') {
|
||||
this.updateContextFilter(result.facets);
|
||||
this.items = (async () => {
|
||||
this.updateContextFilter(result.facets);
|
||||
return result.data} )();
|
||||
}
|
||||
}, async (err) => {
|
||||
const alert: HTMLIonAlertElement = await this.alertController.create({
|
||||
@@ -188,11 +196,10 @@ export class DataListComponent implements OnInit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the sort context of menuService
|
||||
* Initialises the possible sort options in ContextMenuService
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// initialise sort option for context menu
|
||||
this.menuService.setContextSort({
|
||||
this.contextMenuService.setContextSort({
|
||||
name: 'sort',
|
||||
reversed: false,
|
||||
value: 'relevance',
|
||||
@@ -213,6 +220,15 @@ export class DataListComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from Observables
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
for (let sub of this.subscriptions) {
|
||||
sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search event of search bar
|
||||
*/
|
||||
@@ -221,9 +237,9 @@ export class DataListComponent implements OnInit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates context filter in menuService with facets
|
||||
* Updates the possible filter options in ContextMenuService with facets
|
||||
*/
|
||||
updateContextFilter(facets: SCFacet[]) {
|
||||
this.menuService.updateContextFilter(facets);
|
||||
this.contextMenuService.updateContextFilter(facets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-buttons slot="end">
|
||||
<ion-menu-button menu="context">
|
||||
<ion-menu-button menu="context" auto-hide="false">
|
||||
<ion-icon name="options"></ion-icon>
|
||||
</ion-menu-button>
|
||||
</ion-buttons>
|
||||
@@ -17,13 +17,15 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content id="data-list">
|
||||
<ion-list *ngIf="items">
|
||||
<stapps-data-list-item [item]="item" *ngFor="let item of items"></stapps-data-list-item>
|
||||
</ion-list>
|
||||
<ion-list *ngIf="!items">
|
||||
<ion-content id="data-list">
|
||||
<div *ngIf="items | async as items; else loading">
|
||||
<ion-list>
|
||||
<stapps-data-list-item [item]="item" *ngFor="let item of items"></stapps-data-list-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
<ng-template #loading>
|
||||
<stapps-skeleton-list-item *ngFor="let skeleton of [1, 2, 3, 4, 5]"></stapps-skeleton-list-item>
|
||||
</ion-list>
|
||||
</ng-template>
|
||||
<ion-infinite-scroll (ionInfinite)="loadMore($event)">
|
||||
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
||||
</ion-infinite-scroll>
|
||||
|
||||
126
src/app/modules/data/list/food-data-list.component.ts
Normal file
126
src/app/modules/data/list/food-data-list.component.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (C) 2018-2020 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 {Component} from '@angular/core';
|
||||
import {
|
||||
SCSearchQuery, SCThing,
|
||||
} from '@openstapps/core';
|
||||
import {DataListComponent} from './data-list.component';
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-data-list',
|
||||
templateUrl: 'data-list.html',
|
||||
})
|
||||
export class FoodDataListComponent extends DataListComponent {
|
||||
/**
|
||||
* Fetches items with set query configuration
|
||||
*
|
||||
* @param append If true fetched data gets appended to existing, override otherwise (default false)
|
||||
*/
|
||||
protected async fetchAndUpdateItems(append = false): Promise<void> {
|
||||
try {
|
||||
// build query search options
|
||||
const searchOptions: SCSearchQuery = {
|
||||
from: this.from,
|
||||
size: this.pageSize,
|
||||
};
|
||||
|
||||
if (this.queryText && this.queryText.length > 0) {
|
||||
// add query string
|
||||
searchOptions.query = this.queryText;
|
||||
}
|
||||
|
||||
if (this.sortQuery) {
|
||||
// add query sorting
|
||||
searchOptions.sort = [this.sortQuery];
|
||||
}
|
||||
|
||||
searchOptions.filter = {
|
||||
arguments: {
|
||||
filters: [
|
||||
{
|
||||
arguments: {
|
||||
field: 'categories',
|
||||
value: 'canteen',
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
{
|
||||
arguments: {
|
||||
field: 'categories',
|
||||
value: 'student canteen',
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
{
|
||||
arguments: {
|
||||
field: 'categories',
|
||||
value: 'cafe',
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
{
|
||||
arguments: {
|
||||
field: 'categories',
|
||||
value: 'restaurant',
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
operation: 'or',
|
||||
},
|
||||
type: 'boolean',
|
||||
};
|
||||
|
||||
if (this.filterQuery !== undefined) {
|
||||
searchOptions.filter = {
|
||||
arguments: {
|
||||
filters: [
|
||||
searchOptions.filter,
|
||||
this.filterQuery,
|
||||
],
|
||||
operation: 'and',
|
||||
},
|
||||
type: 'boolean',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await this.dataProvider.search(searchOptions);
|
||||
|
||||
this.items = (async () => {
|
||||
this.updateContextFilter(result.facets);
|
||||
let items: SCThing[];
|
||||
if(append) {
|
||||
items = (await this.items).concat(result.data);
|
||||
} else {
|
||||
items = result.data;
|
||||
}
|
||||
|
||||
return items;
|
||||
})();
|
||||
} catch (err) {
|
||||
const alert: HTMLIonAlertElement = await this.alertController.create({
|
||||
buttons: ['Dismiss'],
|
||||
header: 'Error',
|
||||
subHeader: err.message,
|
||||
});
|
||||
|
||||
await alert.present();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,12 +14,14 @@
|
||||
*/
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {SCBuilding, SCFloor, SCPointOfInterest, SCRoom, SCThing, SCTranslations} from '@openstapps/core';
|
||||
import {SCThingTranslator} from '@openstapps/core';
|
||||
import {SCThings, SCThingTranslator} from '@openstapps/core';
|
||||
import {DataProvider} from '../../data.provider';
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@Component({
|
||||
providers: [ DataProvider ],
|
||||
selector: 'stapps-place-detail-content',
|
||||
templateUrl: 'place-detail-content.html',
|
||||
})
|
||||
@@ -47,4 +49,26 @@ export class PlaceDetailContentComponent {
|
||||
constructor() {
|
||||
this.translator = new SCThingTranslator(this.language);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param item TODO
|
||||
*/
|
||||
// tslint:disable-next-line:completed-docs prefer-function-over-method
|
||||
hasCategories(item: SCThings): item is SCThings & { categories: string[]; } {
|
||||
// tslint:disable-next-line:completed-docs
|
||||
return typeof (item as { categories: string[]; }).categories !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function as 'typeof' is not accessible in HTML
|
||||
*
|
||||
* @param item TODO
|
||||
*/
|
||||
isMensaThing(item: SCThings): boolean {
|
||||
return this.hasCategories(item) &&
|
||||
((item.categories as string[]).includes('canteen') || (item.categories as string[]).includes('cafe')
|
||||
|| (item.categories as string[]).includes('student canteen') || (item.categories as string[]).includes('restaurant'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<stapps-place-mensa-detail-content [item]="item" [language]="language" *ngIf="isMensaThing(item)"></stapps-place-mensa-detail-content>
|
||||
<ng-container *ngIf="item.type !== 'floor'">
|
||||
<stapps-simple-card *ngIf="item.type !== 'floor' && item.categories" [title]="'Categories'" [content]="translator.translate(item).categories()"></stapps-simple-card>
|
||||
<stapps-address-detail *ngIf="item.type !== 'floor' && item.address" [address]="item.address"></stapps-address-detail>
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 moment, {Moment} from 'moment';
|
||||
|
||||
import {AfterViewInit, ChangeDetectorRef, Component, Input} from '@angular/core';
|
||||
import {SCDish, SCPlace, SCThing, SCThingTranslator, SCTranslations} from '@openstapps/core';
|
||||
import {PlaceMensaService} from './place-mensa-service';
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@Component({
|
||||
providers: [PlaceMensaService],
|
||||
selector: 'stapps-place-mensa-detail-content',
|
||||
templateUrl: 'place-mensa.html',
|
||||
})
|
||||
export class PlaceMensaDetailComponent implements AfterViewInit {
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
dishes: Promise<Record<string, SCDish[]>> | null = null;
|
||||
|
||||
/**
|
||||
* number of days to display mensa menus for
|
||||
*/
|
||||
displayRange = 5;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@Input() item: SCPlace;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@Input() language: keyof SCTranslations<SCThing>;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
selectedDay: string;
|
||||
|
||||
/**
|
||||
* First day to display menu items for
|
||||
*/
|
||||
startingDay: Moment;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
translator: SCThingTranslator;
|
||||
|
||||
|
||||
constructor(private mensaService: PlaceMensaService, private changeDectectorRef: ChangeDetectorRef) {
|
||||
// TODO: translation
|
||||
this.translator = new SCThingTranslator(this.language);
|
||||
this.startingDay = moment();
|
||||
this.startingDay.hour(0);
|
||||
this.startingDay.minute(0);
|
||||
this.startingDay.millisecond(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
ngAfterViewInit() {
|
||||
this.dishes = this.mensaService.getAllDishes(this.item)
|
||||
.then((dishesResult) => {
|
||||
const out = this.splitDishes(dishesResult, this.item, this.startingDay, this.displayRange);
|
||||
for (const key in out) {
|
||||
if (out.hasOwnProperty(key)) {
|
||||
this.selectedDay = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.changeDectectorRef.detectChanges();
|
||||
|
||||
return out;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a list of dishes with availability start and end into multiple lists by day
|
||||
*/
|
||||
// tslint:disable-next-line:prefer-function-over-method
|
||||
splitDishes(dishes: SCDish[], place: SCPlace, startingDay: Moment, displayRange: number): Record<string, SCDish[]> {
|
||||
const out: Record<string, SCDish[]> = {};
|
||||
|
||||
for (let i = 0; i < displayRange; i++) {
|
||||
const nextDay: Moment = moment(startingDay)
|
||||
.add(1, 'days');
|
||||
const selectedDishes: SCDish[] = [];
|
||||
for (const dish of dishes) {
|
||||
// go through all offers
|
||||
if (dish.offers === undefined) { continue; }
|
||||
for (const offer of dish.offers) {
|
||||
if (offer.inPlace === undefined || offer.inPlace.uid !== place.uid) { continue; }
|
||||
// get availabilities
|
||||
const availabilityStarts = offer.availabilityStarts === undefined ? undefined : moment(offer.availabilityStarts);
|
||||
const availabilityEnds = offer.availabilityEnds === undefined ? undefined : moment(offer.availabilityEnds);
|
||||
|
||||
if ((availabilityStarts === undefined || availabilityStarts.isBefore(nextDay))
|
||||
&& (availabilityEnds === undefined || availabilityEnds.isAfter(startingDay))) {
|
||||
selectedDishes.push(dish);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selectedDishes.length) {
|
||||
out[startingDay.format('YYYY-MM-DD')] = selectedDishes;
|
||||
}
|
||||
// tslint:disable-next-line:no-parameter-reassignment
|
||||
startingDay = nextDay;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 {Injectable} from '@angular/core';
|
||||
import {SCDish, SCPlace, SCThingType} from '@openstapps/core';
|
||||
import {DataProvider} from '../../../../data.provider';
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PlaceMensaService {
|
||||
constructor(private dataProvider: DataProvider) { }
|
||||
|
||||
/**
|
||||
* Fetches all dishes for this building
|
||||
*/
|
||||
async getAllDishes(place: SCPlace): Promise<SCDish[]> {
|
||||
// use filter to get all dishes with the building's uid in one of the offer's inPlace field
|
||||
// TODO: make sure this actually works with ES
|
||||
const result = await this.dataProvider.search({
|
||||
filter: {
|
||||
arguments: {
|
||||
filters: [
|
||||
{
|
||||
arguments: {
|
||||
field: 'offers.inPlace.uid',
|
||||
value: place.uid,
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
{
|
||||
arguments: {
|
||||
field: 'type',
|
||||
value: SCThingType.Dish,
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
operation: 'and',
|
||||
},
|
||||
type: 'boolean',
|
||||
},
|
||||
size: 1000,
|
||||
});
|
||||
|
||||
return result.data as SCDish[];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<ng-container>
|
||||
<div *ngIf="dishes | async as dishes; else loading">
|
||||
<ion-segment [(ngModel)]="selectedDay">
|
||||
<ion-segment-button *ngFor="let day of dishes | keyvalue" [value]="day.key">
|
||||
<ion-label>{{day.key | amParse:'YYYY-MM-DD' | amDateFormat:'dddd, L'}}</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
<div [ngSwitch]="selectedDay">
|
||||
<div *ngFor="let day of dishes | keyvalue">
|
||||
<ion-list *ngSwitchCase="day.key">
|
||||
<stapps-data-list-item [item]="dish" *ngFor="let dish of day.value"></stapps-data-list-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #loading>
|
||||
<ion-segment>
|
||||
<stapps-skeleton-segment-button *ngFor="let skeleton of [1, 2, 3]"></stapps-skeleton-segment-button>
|
||||
</ion-segment>
|
||||
<ion-list>
|
||||
<stapps-skeleton-list-item *ngFor="let skeleton of [1, 2]"></stapps-skeleton-list-item>
|
||||
</ion-list>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
@@ -21,7 +21,7 @@ import {TranslateModule,} from '@ngx-translate/core';
|
||||
import {SCFacet, SCThingType} from '@openstapps/core';
|
||||
import {ContextMenuComponent} from './context-menu.component';
|
||||
import {SettingsModule} from '../../settings/settings.module';
|
||||
import {MenuService} from '../menu.service';
|
||||
import {ContextMenuService} from '../context/context-menu.service';
|
||||
import {FilterContext, SortContext} from './context-type';
|
||||
|
||||
describe('ContextMenuComponent', async () => {
|
||||
@@ -36,7 +36,7 @@ describe('ContextMenuComponent', async () => {
|
||||
ChildrenOutletContexts,
|
||||
Location,
|
||||
UrlSerializer,
|
||||
MenuService,
|
||||
ContextMenuService,
|
||||
{provide: LocationStrategy, useClass: PathLocationStrategy},
|
||||
{provide: APP_BASE_HREF, useValue: '/'},
|
||||
],
|
||||
|
||||
@@ -20,8 +20,9 @@ import {
|
||||
SCThingType,
|
||||
SCTranslations,
|
||||
} from '@openstapps/core';
|
||||
import {MenuService} from '../menu.service';
|
||||
import {ContextMenuService} from './context-menu.service';
|
||||
import {FilterContext, SortContext, SortContextOption} from './context-type';
|
||||
import {Subscription} from 'rxjs';
|
||||
|
||||
/**
|
||||
* The context menu
|
||||
@@ -68,30 +69,45 @@ export class ContextMenuComponent {
|
||||
*/
|
||||
translator: SCThingTranslator;
|
||||
|
||||
/**
|
||||
* Array of all Subscriptions
|
||||
*/
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
|
||||
constructor(private translateService: TranslateService,
|
||||
private readonly menuService: MenuService) {
|
||||
private readonly contextMenuService: ContextMenuService) {
|
||||
this.language = this.translateService.currentLang as keyof SCTranslations<SCLanguage>;
|
||||
this.translator = new SCThingTranslator(this.language);
|
||||
|
||||
this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
|
||||
this.subscriptions.push(this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
|
||||
this.language = event.lang as keyof SCTranslations<SCLanguage>;
|
||||
this.translator = new SCThingTranslator(this.language);
|
||||
});
|
||||
}));
|
||||
|
||||
this.menuService.filterContextChanged$.subscribe((filterContext) => {
|
||||
this.subscriptions.push(this.contextMenuService.filterContextChanged$.subscribe((filterContext) => {
|
||||
this.filterOption = filterContext;
|
||||
});
|
||||
}));
|
||||
|
||||
this.menuService.sortOptions.subscribe((sortContext) => {
|
||||
this.subscriptions.push(this.contextMenuService.sortOptions.subscribe((sortContext) => {
|
||||
this.sortOption = sortContext;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from Observables
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
for (let sub of this.subscriptions) {
|
||||
sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets selected filter options and updates listener
|
||||
*/
|
||||
filterChanged = () => {
|
||||
this.menuService.contextFilterChanged(this.filterOption);
|
||||
this.contextMenuService.contextFilterChanged(this.filterOption);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +133,7 @@ export class ContextMenuComponent {
|
||||
option.options.forEach((filterFacet) => filterFacet.buckets.forEach((filterBucket) => {
|
||||
filterBucket.checked = false;
|
||||
}));
|
||||
this.menuService.contextFilterChanged(this.filterOption);
|
||||
this.contextMenuService.contextFilterChanged(this.filterOption);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,6 +150,6 @@ export class ContextMenuComponent {
|
||||
option.reversed = false;
|
||||
}
|
||||
}
|
||||
this.menuService.contextSortChanged(option);
|
||||
this.contextMenuService.contextSortChanged(option);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import {MenuService} from './menu.service';
|
||||
import {ContextMenuService} from './context-menu.service';
|
||||
import {SCFacet} from '@openstapps/core';
|
||||
import {FilterContext, SortContext} from './context/context-type';
|
||||
import {FilterContext, SortContext} from './context-type';
|
||||
|
||||
describe('MenuService', () => {
|
||||
let service: MenuService;
|
||||
describe('ContextMenuService', () => {
|
||||
let service: ContextMenuService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [MenuService]
|
||||
providers: [ContextMenuService]
|
||||
});
|
||||
service = TestBed.get(MenuService);
|
||||
service = TestBed.get(ContextMenuService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
@@ -15,13 +15,13 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {SCFacet, SCFacetBucket, SCSearchFilter, SCSearchSort} from '@openstapps/core';
|
||||
import {Subject} from 'rxjs';
|
||||
import {FilterBucket, FilterContext, FilterFacet, SortContext} from './context/context-type';
|
||||
import {FilterBucket, FilterContext, FilterFacet, SortContext} from './context-type';
|
||||
|
||||
/**
|
||||
* MenuService provides bidirectional communication of context menu options and search queries
|
||||
* ContextMenuService provides bidirectional communication of context menu options and search queries
|
||||
*/
|
||||
@Injectable()
|
||||
export class MenuService {
|
||||
export class ContextMenuService {
|
||||
|
||||
/**
|
||||
* Local filter context object
|
||||
@@ -82,9 +82,10 @@ export class MenuService {
|
||||
const filters: SCSearchFilter[] = [];
|
||||
|
||||
filterContext.options.forEach((filterFacet) => {
|
||||
const optionFilters: SCSearchFilter[] = [];
|
||||
filterFacet.buckets.forEach((filterBucket) => {
|
||||
if (filterBucket.checked) {
|
||||
filters.push(
|
||||
optionFilters.push(
|
||||
{
|
||||
arguments: {
|
||||
field: filterFacet.field,
|
||||
@@ -94,13 +95,22 @@ export class MenuService {
|
||||
});
|
||||
}
|
||||
});
|
||||
if (optionFilters.length > 0) {
|
||||
filters.push({
|
||||
arguments: {
|
||||
filters: optionFilters,
|
||||
operation: 'or',
|
||||
},
|
||||
type: 'boolean',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (filters.length > 0) {
|
||||
return {
|
||||
arguments: {
|
||||
filters: filters,
|
||||
operation: 'or',
|
||||
operation: 'and',
|
||||
},
|
||||
type: 'boolean',
|
||||
};
|
||||
@@ -20,7 +20,7 @@ import {IonicModule} from '@ionic/angular';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {SettingsModule} from '../settings/settings.module';
|
||||
import {ContextMenuComponent} from './context/context-menu.component';
|
||||
import {MenuService} from './menu.service';
|
||||
import {ContextMenuService} from './context/context-menu.service';
|
||||
import {NavigationComponent} from './navigation/navigation.component';
|
||||
|
||||
/**
|
||||
@@ -44,7 +44,7 @@ import {NavigationComponent} from './navigation/navigation.component';
|
||||
SettingsModule,
|
||||
],
|
||||
providers: [
|
||||
MenuService,
|
||||
ContextMenuService,
|
||||
],
|
||||
})
|
||||
export class MenuModule {}
|
||||
|
||||
Reference in New Issue
Block a user