mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-22 09:32:41 +00:00
fix: dish prices sometimes go missing
This commit is contained in:
@@ -13,20 +13,19 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {SCAcademicPriceGroup, SCThingThatCanBeOfferedOffer} from '@openstapps/core';
|
||||
import {
|
||||
SCAcademicPriceGroup,
|
||||
SCThingThatCanBeOfferedAvailability,
|
||||
SCThingThatCanBeOfferedOffer,
|
||||
} from '@openstapps/core';
|
||||
import {SettingsProvider} from '../../settings/settings.provider';
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-offers-in-list',
|
||||
templateUrl: 'offers-in-list.html',
|
||||
styleUrls: ['offers-in-list.scss'],
|
||||
})
|
||||
export class OffersInListComponent {
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@Input() set offers(it: Array<SCThingThatCanBeOfferedOffer<SCAcademicPriceGroup>>) {
|
||||
this._offers = it;
|
||||
this.price = it[0].prices?.default;
|
||||
@@ -34,13 +33,14 @@ export class OffersInListComponent {
|
||||
this.price = it[0].prices?.[(group.value as string).replace(/s$/, '') as never];
|
||||
});
|
||||
|
||||
const availabilities = new Set(it.map(offer => offer.availability));
|
||||
this.soldOut = availabilities.has('out of stock') && availabilities.size === 1;
|
||||
if (it.length === 1) {
|
||||
this.availability = it[0].availability;
|
||||
}
|
||||
}
|
||||
|
||||
price?: number;
|
||||
|
||||
soldOut: boolean;
|
||||
availability: SCThingThatCanBeOfferedAvailability;
|
||||
|
||||
_offers: Array<SCThingThatCanBeOfferedOffer<SCAcademicPriceGroup>>;
|
||||
|
||||
|
||||
@@ -13,16 +13,10 @@
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<div>
|
||||
<ion-text *ngIf="price && !soldOut" style="white-space: nowrap">
|
||||
<h2>{{ price | currency : 'EUR' : 'symbol' : undefined : 'de' }}</h2>
|
||||
</ion-text>
|
||||
<ion-text *ngIf="soldOut" color="danger" class="sold-out" style="white-space: nowrap">
|
||||
<h2>{{ 'data.detail.offers.sold_out' | translate }}</h2>
|
||||
</ion-text>
|
||||
<p *ngIf="_offers[0].inPlace && !soldOut" class="place" style="white-space: nowrap">
|
||||
<ion-icon name="pin_drop"></ion-icon>{{ _offers[0].inPlace.name }}<span *ngIf="_offers.length > 1"
|
||||
>...</span
|
||||
<ion-badge
|
||||
*ngIf="price"
|
||||
[color]="availability === 'out of stock' ? 'danger' : availability === 'limited availability' ? 'warning' : 'primary'"
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
{{ availability === 'out of stock' ? ('data.detail.offers.sold_out' | translate) : (price | currency : 'EUR'
|
||||
: 'symbol' : undefined : 'de') }}
|
||||
</ion-badge>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
:host {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
ion-badge {
|
||||
display: inline-block;
|
||||
font-size: 0.8em;
|
||||
margin-top: -0.25em;
|
||||
translate: 0 0.25em;
|
||||
padding: 0.25em;
|
||||
}
|
||||
@@ -20,33 +20,25 @@
|
||||
detail="false"
|
||||
(click)="notifySelect()"
|
||||
>
|
||||
<div class="item-height-placeholder"></div>
|
||||
<ion-thumbnail slot="start" *ngIf="!hideThumbnail" class="ion-margin-end">
|
||||
<ion-icon color="dark" [name]="item.type | dataIcon" [size]="36"></ion-icon>
|
||||
</ion-thumbnail>
|
||||
<ng-container *ngIf="contentTemplateRef; else defaultContent">
|
||||
<ion-label class="ion-text-wrap" [ngSwitch]="true">
|
||||
<div>
|
||||
<ng-container *ngTemplateOutlet="contentTemplateRef; context: {$implicit: item}"></ng-container>
|
||||
</div>
|
||||
</ion-label>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="listItemEndInteraction" [ngSwitch]="item.type">
|
||||
<stapps-rating *ngSwitchCase="'dish'" [item]="$any(item)"></stapps-rating>
|
||||
<stapps-favorite-button *ngSwitchDefault [item]="$any(item)"></stapps-favorite-button>
|
||||
<stapps-rating slot="end" *ngSwitchCase="'dish'" [item]="$any(item)"></stapps-rating>
|
||||
<stapps-favorite-button slot="end" *ngSwitchDefault [item]="$any(item)"></stapps-favorite-button>
|
||||
</ng-container>
|
||||
</ion-item>
|
||||
|
||||
<ng-template #defaultContent>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<div>
|
||||
<ng-template [dataListItemHost]="item"></ng-template>
|
||||
<stapps-action-chip-list
|
||||
*ngIf="listItemChipInteraction && appearance !== 'square'"
|
||||
slot="end"
|
||||
[item]="item"
|
||||
></stapps-action-chip-list>
|
||||
</div>
|
||||
</ion-label>
|
||||
</ng-template>
|
||||
|
||||
@@ -20,34 +20,25 @@
|
||||
|
||||
ion-item::part(native) {
|
||||
height: 100%;
|
||||
padding: var(--spacing-sm);
|
||||
}
|
||||
|
||||
ion-item {
|
||||
--border-color: transparent;
|
||||
--inner-padding-end: 0;
|
||||
--padding-start: 0;
|
||||
@include border-radius-in-parallax(var(--border-radius-default));
|
||||
|
||||
overflow: hidden;
|
||||
margin: var(--spacing-sm);
|
||||
height: 100%;
|
||||
|
||||
ion-thumbnail {
|
||||
--ion-margin: var(--spacing-xs);
|
||||
|
||||
margin-inline-start: var(--spacing-md);
|
||||
margin-inline: var(--spacing-md);
|
||||
margin-block: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ion-label {
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
padding-left: var(--spacing-sm);
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ion-text-wrap ::ng-deep ion-label {
|
||||
@@ -58,9 +49,44 @@ ion-item {
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
|
||||
[slot='end'] {
|
||||
margin-inline-start: 0;
|
||||
margin-block: auto;
|
||||
}
|
||||
|
||||
stapps-action-chip-list {
|
||||
float: bottom;
|
||||
}
|
||||
|
||||
ion-item ::ng-deep {
|
||||
stapps-long-inline-text,
|
||||
.title {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
|
||||
white-space: break-spaces;
|
||||
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.title-sub {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
|
||||
white-space: break-spaces;
|
||||
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
margin-block: var(--spacing-xs);
|
||||
}
|
||||
}
|
||||
|
||||
:host.square ::ng-deep {
|
||||
ion-item {
|
||||
margin: 0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
ion-row {
|
||||
@@ -74,19 +100,12 @@ ion-item {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
stapps-long-inline-text,
|
||||
.title {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
|
||||
white-space: break-spaces;
|
||||
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
height: 2.5em;
|
||||
}
|
||||
|
||||
.title-sub {
|
||||
display: none;
|
||||
.title ~ :last-child {
|
||||
margin-right: 42px;
|
||||
}
|
||||
|
||||
stapps-rating,
|
||||
|
||||
@@ -13,19 +13,7 @@
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<ion-note>
|
||||
<ng-container *ngIf="item.certifications">
|
||||
<ng-container *ngFor="let cert of item.certifications">
|
||||
<abbr [title]="'description' | thingTranslate: cert">
|
||||
<img
|
||||
*ngIf="cert.compactImage"
|
||||
[src]="'compactImage' | thingTranslate: cert"
|
||||
height="16"
|
||||
[alt]="'name' | thingTranslate: cert"
|
||||
/>
|
||||
</abbr>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<div class="sep">•</div>
|
||||
<ion-label> {{ 'categories' | thingTranslate: item | join: ', ' | titlecase }} </ion-label>
|
||||
<ng-container *ngIf="item.characteristics">
|
||||
<ng-container *ngFor="let characteristic of 'characteristics' | thingTranslate: item">
|
||||
<!-- Abbr tag shows the actual name on hover -->
|
||||
@@ -36,6 +24,15 @@
|
||||
></abbr>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<div class="sep">•</div>
|
||||
<ion-label> {{ 'categories' | thingTranslate: item | join: ', ' | titlecase }} </ion-label>
|
||||
<ng-container *ngIf="item.certifications">
|
||||
<ng-container *ngFor="let cert of item.certifications">
|
||||
<abbr *ngIf="cert.compactImage" [title]="'description' | thingTranslate: cert">
|
||||
<img
|
||||
[src]="'compactImage' | thingTranslate: cert"
|
||||
height="16"
|
||||
[alt]="'name' | thingTranslate: cert"
|
||||
/>
|
||||
</abbr>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ion-note>
|
||||
|
||||
@@ -12,13 +12,21 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
:host {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
$size: 16px;
|
||||
|
||||
ion-note {
|
||||
margin: inherit;
|
||||
margin-right: inherit;
|
||||
margin-block-end: inherit;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: var(--spacing-xs) var(--spacing-sm);
|
||||
flex-flow: row wrap;
|
||||
|
||||
list-style: none;
|
||||
}
|
||||
@@ -41,15 +49,5 @@ abbr {
|
||||
}
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
background: var(--background-url);
|
||||
}
|
||||
|
||||
.sep {
|
||||
display: none;
|
||||
margin-inline: var(--spacing-xs);
|
||||
}
|
||||
|
||||
abbr + .sep {
|
||||
display: revert;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<stapps-dish-characteristics *ngIf="item.characteristics" [item]="item"></stapps-dish-characteristics>
|
||||
<stapps-dish-characteristics [item]="item"></stapps-dish-characteristics>
|
||||
<stapps-offers-detail *ngIf="item.offers" [offers]="item.offers"></stapps-offers-detail>
|
||||
<stapps-certifications-in-detail
|
||||
*ngIf="item.certifications"
|
||||
|
||||
@@ -13,19 +13,12 @@
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<ion-grid>
|
||||
<ion-row class="ion-justify-content-between">
|
||||
<ion-col size="11" size-sm="10">
|
||||
<div class="ion-text-wrap">
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: item }}</ion-label>
|
||||
<p class="title-sub ion-hide-sm-down">{{ 'description' | thingTranslate: item }}</p>
|
||||
<ion-label class="title">
|
||||
<stapps-offers-in-list
|
||||
*ngIf="item.offers && item.offers.length > 0"
|
||||
[offers]="item.offers"
|
||||
></stapps-offers-in-list>
|
||||
{{ 'name' | thingTranslate: item }}
|
||||
</ion-label>
|
||||
<p class="title-sub">{{ 'description' | thingTranslate: item | sentencecase }}</p>
|
||||
<stapps-dish-characteristics [item]="item"></stapps-dish-characteristics>
|
||||
</div>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<div class="ion-text-end">
|
||||
<stapps-offers-in-list *ngIf="item.offers" [offers]="item.offers"></stapps-offers-in-list>
|
||||
</div>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
*/
|
||||
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
|
||||
import {PositionService} from '../../../map/position.service';
|
||||
import {filter, Observable, timer} from 'rxjs';
|
||||
import {filter, Observable} from 'rxjs';
|
||||
import {hasValidLocation, isSCFloor, PlaceTypes, PlaceTypesWithDistance} from './place-types';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {LatLng, geoJSON} from 'leaflet';
|
||||
import {trigger, transition, style, animate} from '@angular/animations';
|
||||
|
||||
/**
|
||||
* Shows a place as a list item
|
||||
@@ -26,6 +28,7 @@ import {map} from 'rxjs/operators';
|
||||
templateUrl: 'place-list-item.html',
|
||||
styleUrls: ['place-list-item.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
animations: [trigger('fade', [transition(':enter', [style({opacity: 0}), animate(100)])])],
|
||||
})
|
||||
export class PlaceListItemComponent {
|
||||
_item: PlaceTypesWithDistance;
|
||||
@@ -36,8 +39,13 @@ export class PlaceListItemComponent {
|
||||
@Input() set item(item: PlaceTypes) {
|
||||
this._item = item;
|
||||
if (!isSCFloor(item) && hasValidLocation(item)) {
|
||||
this.distance = timer(0, 10_000).pipe(
|
||||
map(() => this.positionService.getDistance(item.geo.point)),
|
||||
this.distance = this.positionService.watchCurrentLocation().pipe(
|
||||
map(position =>
|
||||
new LatLng(position.latitude, position.longitude).distanceTo(
|
||||
geoJSON(item.geo.point).getBounds().getCenter(),
|
||||
),
|
||||
),
|
||||
|
||||
filter(it => it !== undefined),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,45 +12,29 @@
|
||||
~ You should have received a copy of the GNU General Public License along with
|
||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<div class="ion-text-wrap">
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: _item }}</ion-label>
|
||||
<ng-container *ngIf="_item.type !== 'floor'">
|
||||
<p class="title-sub" *ngIf="_item.openingHours">
|
||||
<span>
|
||||
<stapps-opening-hours [openingHours]="_item.openingHours"></stapps-opening-hours>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<ion-note *ngIf="_item.categories && _item.type !== 'building'; else onlyType">
|
||||
<ion-label> {{ 'categories' | thingTranslate: _item | join: ', ' | titlecase }} </ion-label>
|
||||
<ion-label *ngIf="distance | async as distance" class="distance">
|
||||
<ng-template #distanceView>
|
||||
<ion-label class="distance" *ngIf="distance | async as distance;" @fade>
|
||||
<ion-icon name="directions_walk"></ion-icon>
|
||||
{{ distance | metersLocalized }}
|
||||
</ion-label>
|
||||
</ng-template>
|
||||
<ion-label class="title">{{ 'name' | thingTranslate: _item }}</ion-label>
|
||||
<ng-container *ngIf="_item.type !== 'floor'">
|
||||
<stapps-opening-hours [openingHours]="_item.openingHours"></stapps-opening-hours>
|
||||
<ion-note *ngIf="_item.categories && _item.type !== 'building'; else onlyType">
|
||||
<ion-label> {{ 'categories' | thingTranslate: _item | join: ', ' | titlecase }} </ion-label>
|
||||
<ng-container *ngTemplateOutlet="distanceView"></ng-container>
|
||||
</ion-note>
|
||||
</p>
|
||||
<ng-template #onlyType>
|
||||
<ion-note>
|
||||
<ion-label> {{ 'type' | thingTranslate: _item | titlecase }} </ion-label>
|
||||
<ion-label *ngIf="distance | async as distance" class="distance">
|
||||
<ion-icon name="directions_walk"></ion-icon>
|
||||
{{ distance | metersLocalized }}
|
||||
</ion-label>
|
||||
<ng-container *ngTemplateOutlet="distanceView"></ng-container>
|
||||
</ion-note>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<p *ngIf="_item.description">{{ 'description' | thingTranslate: _item }}</p>
|
||||
</div>
|
||||
</ion-col>
|
||||
<ng-container *ngIf="_item.type !== 'building'">
|
||||
<ion-col size="auto" class="in-place" *ngIf="_item.inPlace">
|
||||
<ion-icon name="pin_drop"></ion-icon
|
||||
><ion-label>{{ 'name' | thingTranslate: _item.inPlace }}</ion-label>
|
||||
<ion-icon name="pin_drop"></ion-icon><ion-label>{{ 'name' | thingTranslate: _item.inPlace }}</ion-label>
|
||||
</ion-col>
|
||||
</ng-container>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
|
||||
@@ -33,3 +33,7 @@ ion-label + ion-label.distance::before {
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
stapps-opening-hours ::ng-deep div {
|
||||
padding-block: var(--spacing-xs);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user