Merge remote-tracking branch 'origin/development' into feature/HIMA-50-create-address-change-screen

This commit is contained in:
Peter Skrlj
2019-02-11 08:22:47 +01:00
40 changed files with 352 additions and 112 deletions

View File

@@ -6,11 +6,11 @@ import { FeedRecommandation } from 'src/app/core/models/feed-recommandation.mode
export const feedMock: FeedCard[] = <FeedCard[]> [ export const feedMock: FeedCard[] = <FeedCard[]> [
<FeedCard> { <FeedCard> {
id: 1, id: '1',
cardTitle: 'BESTSELLER', cardTitle: 'BESTSELLER',
type: 'BOOK', type: 'BOOK',
books: <FeedBook> { books: <FeedBook> {
id: 1, id: '1',
firstBookAuthor: 'Michelle Obama', firstBookAuthor: 'Michelle Obama',
firstBookTitle: 'Becoming Meine Geschichte', firstBookTitle: 'Becoming Meine Geschichte',
firstBookType: 'B', firstBookType: 'B',
@@ -26,29 +26,29 @@ export const feedMock: FeedCard[] = <FeedCard[]> [
} }
}, },
<FeedCard> { <FeedCard> {
id: 2, id: '2',
cardTitle: 'EVENT', cardTitle: 'EVENT',
type: 'EVENT', type: 'EVENT',
event: <FeedEvent> { event: <FeedEvent[]> [{
id: 1, id: 1,
title: 'Lesung Manfred Bomm', title: 'Lesung Manfred Bomm',
// tslint:disable-next-line:max-line-length // tslint:disable-next-line:max-line-length
content: 'Möchten Sie an der Lesung des Krimiautors Manfred Bomm „Himmelsfelsen“ teilnehmen? Die Lesung findet am Dienstag, den 11.12.18 in der Filliale am Stachus in unserem große…' content: 'Möchten Sie an der Lesung des Krimiautors Manfred Bomm „Himmelsfelsen“ teilnehmen? Die Lesung findet am Dienstag, den 11.12.18 in der Filliale am Stachus in unserem große…'
} }]
}, },
<FeedCard> { <FeedCard> {
id: 3, id: '3',
cardTitle: 'NEWS', cardTitle: 'NEWS',
type: 'NEWS', type: 'NEWS',
news: <FeedNews> { news: <FeedNews[]> [{
id: 1, id: 1,
title: 'Neueröffnung am Stachus in München', title: 'Neueröffnung am Stachus in München',
// tslint:disable-next-line:max-line-length // tslint:disable-next-line:max-line-length
content: 'Sie sind das erste Mal nach der Neueröffnung in unserem Store? Entdecken Sie unsere neue Lesewelten, sowie neue interessante Veranstaltungen b…' content: 'Sie sind das erste Mal nach der Neueröffnung in unserem Store? Entdecken Sie unsere neue Lesewelten, sowie neue interessante Veranstaltungen b…'
} }]
}, },
<FeedCard> { <FeedCard> {
id: 4, id: '4',
cardTitle: 'EMPFEHLUNG', cardTitle: 'EMPFEHLUNG',
type: 'REC', type: 'REC',
recommandation: <FeedRecommandation> { recommandation: <FeedRecommandation> {
@@ -59,11 +59,11 @@ export const feedMock: FeedCard[] = <FeedCard[]> [
} }
}, },
<FeedCard> { <FeedCard> {
id: 5, id: '5',
cardTitle: 'NEUERSCHEINUNG FANTASY', cardTitle: 'NEUERSCHEINUNG FANTASY',
type: 'BOOK', type: 'BOOK',
books: <FeedBook> { books: <FeedBook> {
id: 1, id: '1',
firstBookAuthor: 'Joanne K. Rowling', firstBookAuthor: 'Joanne K. Rowling',
firstBookTitle: 'Grindelwalds verbrechen', firstBookTitle: 'Grindelwalds verbrechen',
firstBookType: 'B', firstBookType: 'B',

View File

@@ -105,7 +105,7 @@ export function _feedServiceEndpointProviderFactory(conf: ConfigService) {
deps: [ConfigService] deps: [ConfigService]
}, },
// { provide: CatSearchService, useClass: CatSearchMockService }, // Uncomment if u want to use the CatSearchMockService // { provide: CatSearchService, useClass: CatSearchMockService }, // Uncomment if u want to use the CatSearchMockService
{ provide: FeedService, useClass: FeedMockService } // Uncomment if u want to use the FeedMockService // { provide: FeedService, useClass: FeedMockService } // Uncomment if u want to use the FeedMockService
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

View File

@@ -345,7 +345,7 @@
width: 13px; width: 13px;
position: relative; position: relative;
top: 2px; top: 2px;
padding-right: 10px; padding-right: 8px;
} }
span { span {
@@ -359,7 +359,7 @@
} }
&-delivery-info { &-delivery-info {
font-weight: bold; font-weight: 600;
} }
} }
@@ -387,6 +387,7 @@
.dropdown-selected-text { .dropdown-selected-text {
padding: 5px; padding: 5px;
font-weight: 600;
&-active { &-active {
background-color: #E9EDF9; background-color: #E9EDF9;

View File

@@ -2,26 +2,34 @@
<div class="menu-grid"> <div class="menu-grid">
<div class="menu-item-grid align-center" (click)="routeToMenu('/article-search', 'articlesearch')"> <div class="menu-item-grid align-center" (click)="routeToMenu('/article-search', 'articlesearch')">
<div> <div>
<img class="menu-icon" *ngIf="router.url === '/article-search'; else articleSearchImageElse" src="/assets/images/Icon_Artikelsuche.svg"> <img class="menu-icon" *ngIf="router.url === '/article-search' ||
router.url.startsWith('/search-results') ||
router.url.startsWith('/product-details');
else articleSearchImageElse" src="/assets/images/Icon_Artikelsuche.svg">
<ng-template #articleSearchImageElse> <ng-template #articleSearchImageElse>
<img class="menu-icon" src="/assets/images/Icon_Artikelsuche_inactive.svg"> <img class="menu-icon" src="/assets/images/Icon_Artikelsuche_inactive.svg">
</ng-template> </ng-template>
</div> </div>
<span *ngIf="router.url === '/article-search'; else articleSearchLabelElse" class="menu-item selected">Artikelsuche</span> <span *ngIf="router.url === '/article-search' ||
router.url.startsWith('/search-results') ||
router.url.startsWith('/product-details');
else articleSearchLabelElse" class="menu-item selected">Artikelsuche</span>
<ng-template #articleSearchLabelElse> <ng-template #articleSearchLabelElse>
<span class="menu-item">Artikelsuche</span> <span class="menu-item">Artikelsuche</span>
</ng-template> </ng-template>
</div> </div>
<div class="menu-item-grid align-center" (click)="routeToMenu('/customer-search', 'customersearch')"> <div class="menu-item-grid align-center" (click)="routeToMenu('/customer-search', 'customersearch')">
<div> <div>
<img class="menu-icon" *ngIf="router.url === '/customer-search'; else customerSearchImageElse" src="/assets/images/Icon_Kundensuche.svg"> <img class="menu-icon" *ngIf="router.url === '/customer-search'; else customerSearchImageElse"
src="/assets/images/Icon_Kundensuche.svg">
<ng-template #customerSearchImageElse> <ng-template #customerSearchImageElse>
<img class="menu-icon" src="/assets/images/Icon_Kundensuche_inactive.svg"> <img class="menu-icon" src="/assets/images/Icon_Kundensuche_inactive.svg">
</ng-template> </ng-template>
</div> </div>
<span *ngIf="router.url === '/customer-search'; else customerSearchLabelElse" class="menu-item selected">Kundensuche</span> <span *ngIf="router.url === '/customer-search'; else customerSearchLabelElse"
class="menu-item selected">Kundensuche</span>
<ng-template #customerSearchLabelElse> <ng-template #customerSearchLabelElse>
<span class="menu-item">Kundensuche</span> <span class="menu-item">Kundensuche</span>
</ng-template> </ng-template>
</div> </div>
<div class="menu-item-grid align-center"> <div class="menu-item-grid align-center">

View File

@@ -12,7 +12,7 @@
.menu-grid { .menu-grid {
display: grid; display: grid;
grid-template-columns: auto auto auto auto auto; grid-template-columns: 20% 20% 20% 20% 20%;
padding-top: 20px; padding-top: 20px;
} }

View File

@@ -23,7 +23,7 @@
</div> </div>
</div> </div>
<div class="price align-right"> <div class="price align-right">
<span>{{ product.price }}</span> <span>{{ price }}</span>
<span class="currency">{{ product.currency }}</span> <span class="currency">{{ product.currency }}</span>
</div> </div>
</div> </div>
@@ -41,7 +41,7 @@
</div> </div>
</div> </div>
<div class="stock-container align-right"> <div class="stock-container align-right">
<div *ngIf="product.availability" class="available-stock"> <div *ngIf="product.itemsInStock > 0" class="available-stock">
<div class="available-icon-container"> <div class="available-icon-container">
<img <img
class="available-icon" class="available-icon"
@@ -67,7 +67,7 @@
<span>{{ product.itemsInStock }}x</span> <span>{{ product.itemsInStock }}x</span>
</div> </div>
</div> </div>
<div *ngIf="!product.availability" class="not-available-stock"> <div *ngIf="product.itemsInStock === 0" class="not-available-stock">
<span>{{ product.notAvailableReason }}</span> <span>{{ product.notAvailableReason }}</span>
</div> </div>
</div> </div>
@@ -85,7 +85,7 @@
</div> </div>
</div> </div>
<div class="order"> <div class="order">
<span>{{ product.category }}</span> <span>{{ product.location }}</span>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -123,4 +123,8 @@
.type-icon-container { .type-icon-container {
padding-top: 3px; padding-top: 3px;
}
.publisher {
max-width: 300px;
} }

View File

@@ -29,6 +29,13 @@ export class ProductCardComponent implements OnInit {
} }
} }
get price() {
if (this._product.price.toString().indexOf('.') === -1) {
return this._product.price + ',00';
}
return this._product.price.toString().replace('.', ',');
}
eanChangedSub = new ReplaySubject<string>(); eanChangedSub = new ReplaySubject<string>();
imageUrl$: Observable<string>; imageUrl$: Observable<string>;

View File

@@ -35,10 +35,10 @@
<div class="stock-label"> <div class="stock-label">
<span>Lieferbar</span> <span>Lieferbar</span>
</div> </div>
<div class="home-icon"> <div class="home-icon" *ngIf="product.quantity > 0">
<img class="icon" src="../../../assets/images/Icon_House.svg"> <img class="icon" src="../../../assets/images/Icon_House.svg">
</div> </div>
<div class="stock-quantity"> <div class="stock-quantity" *ngIf="product.quantity > 0">
<span>{{product.quantity}}</span> <span>{{product.quantity}}</span>
</div> </div>
</div> </div>

View File

@@ -81,7 +81,7 @@
.type { .type {
display: grid; display: grid;
grid-template-columns: min-content auto; grid-template-columns: min-content auto;
grid-gap: 10px; grid-gap: 13px;
} }
.type span { .type span {

View File

@@ -86,7 +86,7 @@ export class ProductDetailsComponent implements OnInit {
if (item.pr) { if (item.pr) {
ean = item.pr.ean; ean = item.pr.ean;
eanTag = ean; eanTag = ean;
productIcon$ = this.catImageService.getImageUrl(ean, { width: 469, height: 575}); productIcon$ = this.catImageService.getImageUrl(ean, { width: 469, height: 575 });
locale = item.pr.locale; locale = item.pr.locale;
publicationDate = getFormatedPublicationDate(item.pr.publicationDate); publicationDate = getFormatedPublicationDate(item.pr.publicationDate);
format = this.selectedItem ? this.selectedItem.pr.formatDetail : null; format = this.selectedItem ? this.selectedItem.pr.formatDetail : null;
@@ -109,6 +109,11 @@ export class ProductDetailsComponent implements OnInit {
if (item.av.length > 0) { if (item.av.length > 0) {
quantity = (item.av[0].qty ? item.av[0].qty : 0) + 'x'; quantity = (item.av[0].qty ? item.av[0].qty : 0) + 'x';
price = item.av[0].price.value.value + ' ' + item.av[0].price.value.currency; price = item.av[0].price.value.value + ' ' + item.av[0].price.value.currency;
if (item.av[0].price.value.value.toString().indexOf('.') === -1) {
price = item.av[0].price.value.value + ',00 ' + item.av[0].price.value.currency;
} else {
price = item.av[0].price.value.value.toString().replace('.', ',') + ' ' + item.av[0].price.value.currency;
}
} }
return { return {

View File

@@ -11,9 +11,9 @@
<app-product-card *ngFor="let product of products" [product]="product"> <app-product-card *ngFor="let product of products" [product]="product">
</app-product-card> </app-product-card>
</div> </div>
<!-- [style.padding.px]="30" -->
<app-loading <app-loading
[style.padding.px]="30" loading="loading"
loading="true"
text="Inhalte werden geladen" text="Inhalte werden geladen"
></app-loading> ></app-loading>
</div> </div>

View File

@@ -23,6 +23,7 @@ import { ProductMapping } from 'src/app/core/mappings/product.mapping';
export class SearchResultsComponent implements OnInit { export class SearchResultsComponent implements OnInit {
currentSearch: Search; currentSearch: Search;
products: Product[]; products: Product[];
loading = true;
@Select(ProcessState.getProducts) products$: Observable<ItemDTO[]>; @Select(ProcessState.getProducts) products$: Observable<ItemDTO[]>;
skip = 0; skip = 0;
@@ -30,7 +31,7 @@ export class SearchResultsComponent implements OnInit {
private store: Store, private store: Store,
private router: Router, private router: Router,
private productMapping: ProductMapping private productMapping: ProductMapping
) {} ) { }
ngOnInit() { ngOnInit() {
this.loadCurrentSearch(); this.loadCurrentSearch();
@@ -67,6 +68,9 @@ export class SearchResultsComponent implements OnInit {
filter(f => Array.isArray(f)), filter(f => Array.isArray(f)),
map(items => items.map(item => this.productMapping.fromItemDTO(item))) map(items => items.map(item => this.productMapping.fromItemDTO(item)))
) )
.subscribe(data => (this.products = data)); .subscribe(
data => (this.products = data),
() => this.loading = false
);
} }
} }

View File

@@ -0,0 +1,74 @@
import { FeedCard } from '../models/feed-card.model';
import { FeedDTO } from 'feed-service';
import { FeedBook } from '../models/feed-book.model';
import { FeedEvent } from '../models/feed-event.model';
import { FeedNews } from '../models/feed-news.model';
import { FeedRecommandation } from '../models/feed-recommandation.model';
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class FeedMapping {
constructor () {}
fromFeedDTO (feed: FeedDTO): FeedCard {
let books: FeedBook = null;
const event: FeedEvent[] = [];
const news: FeedNews[] = [];
const recommandation: FeedRecommandation = null;
if (feed.type === 'products' && feed.items[0]) {
books = {
id: feed.id,
firstBookTypeIcon: feed.items[0].pr.format,
firstBookEan: feed.items[0].pr.ean,
firstBookAuthor: feed.items[0].pr.name,
firstBookTitle: feed.items[0].pr.additionalName,
firstBookType: feed.items[0].pr.formatDetail,
firstBookIcon: feed.items[0].pr.format,
firstBookLanguage: feed.items[0].pr.locale,
firstBookPrice: feed.items[0].av[0].price.value.value,
firstBookCurrency: feed.items[0].av[0].price.value.currency
};
if (feed.items[1]) {
books = {
...books,
secondBookEan: feed.items[1].pr.ean,
secondBookAuthor: feed.items[1].pr.name,
secondBookTitle: feed.items[1].pr.additionalName,
secondBookType: feed.items[1].pr.formatDetail,
secondBookTypeIcon: feed.items[1].pr.format,
secondBookIcon: feed.items[1].pr.format,
secondBookLanguage: feed.items[1].pr.locale,
secondBookPrice: feed.items[1].av[0].price.value.value,
secondBookCurrency: feed.items[1].av[0].price.value.currency
};
}
} else if (feed.type === 'events') {
feed.items.forEach (
i => event.push(<FeedEvent> {
id: i.id,
title: i.name,
content: i.desc
})
);
} else if (feed.type === 'info') {
feed.items.forEach (
i => news.push(<FeedNews> {
id: i.id,
title: i.heading,
content: i.text
})
);
}
return <FeedCard> {
id: feed.id,
cardTitle: feed.label,
type: feed.type,
books: books,
event: event,
news: news,
recommandation: recommandation
};
}
}

View File

@@ -9,7 +9,7 @@ export class FilterItemMapping {
fromOptionDto(option: OptionDTO): FilterItem { fromOptionDto(option: OptionDTO): FilterItem {
return { return {
id: option.key, id: option.value,
name: option.label, name: option.label,
selected: false selected: false
}; };

View File

@@ -45,7 +45,7 @@ export class ProductMapping {
err: '', err: '',
category: item.pr.productGroup, category: item.pr.productGroup,
icon: '', icon: '',
notAvailableReason: '', notAvailableReason: itemsInStock === 0 ? 'Not in stock' : '',
publisher: item.pr.manufacturer, publisher: item.pr.manufacturer,
recommandation: false, recommandation: false,
serial: item.pr.serial, serial: item.pr.serial,

View File

@@ -1,15 +1,21 @@
export interface FeedBook { export interface FeedBook {
id: number; id: string;
firstBookEan: string;
firstBookAuthor: string; firstBookAuthor: string;
firstBookTitle: string; firstBookTitle: string;
firstBookType: string; firstBookType: string;
firstBookTypeIcon: string;
firstBookLanguage: string; firstBookLanguage: string;
firstBookPrice: string; firstBookPrice: string;
firstBookCurrency: string;
firstBookIcon: string; firstBookIcon: string;
secondBookAuthor: string; secondBookEan?: string;
secondBookTitle: string; secondBookAuthor?: string;
secondBookType: string; secondBookTitle?: string;
secondBookLanguage: string; secondBookType?: string;
secondBookPrice: string; secondBookTypeIcon?: string;
secondBookIcon: string; secondBookLanguage?: string;
secondBookPrice?: string;
secondBookIcon?: string;
secondBookCurrency?: string;
} }

View File

@@ -4,11 +4,11 @@ import { FeedNews } from './feed-news.model';
import { FeedRecommandation } from './feed-recommandation.model'; import { FeedRecommandation } from './feed-recommandation.model';
export interface FeedCard { export interface FeedCard {
id: number; id: string;
cardTitle: string; cardTitle: string;
type: string; type: string;
books: FeedBook; books: FeedBook;
event: FeedEvent; event: FeedEvent[];
news: FeedNews; news: FeedNews[];
recommandation: FeedRecommandation; recommandation: FeedRecommandation;
} }

View File

@@ -3,14 +3,18 @@ import { Observable, of } from 'rxjs';
import { Filter } from '../models/filter.model'; import { Filter } from '../models/filter.model';
import { filterMock } from 'mocks/filters.mock'; import { filterMock } from 'mocks/filters.mock';
import { CatSearchService, ApiResponse, UISettingsDTO } from 'cat-service'; import { CatSearchService, ApiResponse, UISettingsDTO } from 'cat-service';
import { expand } from 'rxjs/operators';
import { FilterItem } from '../models/filter-item.model'; import { FilterItem } from '../models/filter-item.model';
import { FilterMapping } from '../mappings/filter.mapping';
import { map } from 'rxjs/operators';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class FilterService { export class FilterService {
constructor(private service: CatSearchService) {} constructor(
private service: CatSearchService,
private filterMapping: FilterMapping
) { }
selectFilterById(filters: Filter[], id: string): Observable<Filter[]> { selectFilterById(filters: Filter[], id: string): Observable<Filter[]> {
const newFilterState = filters.map((filter: Filter) => { const newFilterState = filters.map((filter: Filter) => {
@@ -43,6 +47,17 @@ export class FilterService {
return of(newFilterState); return of(newFilterState);
} }
toggleFilterItemsByName(filters: Filter[], name: string): Observable<Filter[]> {
const newFilterState = filters.map((filter: Filter) => {
if (filter.expanded === true) {
const newItemsState = this.toggleItemByName(filter.items, name);
return { ...filter, items: newItemsState };
}
return { ...filter };
});
return of(newFilterState);
}
private toggleItem(items: FilterItem[], id: string): FilterItem[] { private toggleItem(items: FilterItem[], id: string): FilterItem[] {
return items.map((item: FilterItem) => { return items.map((item: FilterItem) => {
if (item.id === id) { if (item.id === id) {
@@ -52,19 +67,30 @@ export class FilterService {
}); });
} }
private toggleItemByName(items: FilterItem[], name: string): FilterItem[] {
return items.map((item: FilterItem) => {
if (item.name === name) {
return { ...item, selected: !item.selected };
}
return { ...item };
});
}
// service method to get the first 3 filters // service method to get the first 3 filters
getFilters(): Observable<Filter[]> { getFilters(): Observable<Filter[]> {
// TODO: implement call to backend API to get filter metadata return this.service.settings().pipe(
// this.service.settings().subscribe( map(
// (data: ApiResponse<UISettingsDTO>) => (data: ApiResponse<UISettingsDTO>) =>
// console.log(data.result.filter, data.result.orderBy, data) this.filterMapping.fromUiSettingsDto(data.result).slice(0, 3)
// ); ));
return of(filterMock.slice(0, 3));
} }
// service method to get filters metadata // service method to get filters metadata
getFullFilter(): Observable<Filter[]> { getFullFilter(): Observable<Filter[]> {
// TODO: implement call to backend API to get filter metadata return this.service.settings().pipe(
return of(filterMock); map(
(data: ApiResponse<UISettingsDTO>) =>
this.filterMapping.fromUiSettingsDto(data.result)
));
} }
} }

View File

@@ -12,6 +12,7 @@ import {
} from 'cat-service'; } from 'cat-service';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { Search } from '../models/search.model'; import { Search } from '../models/search.model';
import { FilterMapping } from '../mappings/filter.mapping';
@Injectable({ @Injectable({
@@ -22,7 +23,8 @@ export class ProductService {
searchResponse$: Observable<PagedApiResponse<ItemDTO>>; searchResponse$: Observable<PagedApiResponse<ItemDTO>>;
constructor( constructor(
private searchService: CatSearchService private searchService: CatSearchService,
private filterMapping: FilterMapping
) { } ) { }
persistLastSearchToLocalStorage(param: string) { persistLastSearchToLocalStorage(param: string) {
@@ -70,7 +72,8 @@ export class ProductService {
take: search.take take: search.take
}; };
return this.searchService.search(queryToken).pipe( const queryWithFilters = this.filterMapping.toQueryTokenDto(queryToken, search.fitlers);
return this.searchService.search(queryWithFilters).pipe(
map(response => { map(response => {
if (response.error) { if (response.error) {
throw new Error(response.message); throw new Error(response.message);

View File

@@ -5,6 +5,7 @@ export const LOAD_FULL_FILTERS = '[FILTERS] Load full';
export const SELECT_FILTER_BY_ID = '[FILTERS] Select by id'; export const SELECT_FILTER_BY_ID = '[FILTERS] Select by id';
export const UNSELECT_FILTER_BY_ID = '[FILTERS] Unselect by id'; export const UNSELECT_FILTER_BY_ID = '[FILTERS] Unselect by id';
export const TOGGLE_FILTER_ITEM_BY_ID = '[FILTERS] Toggle item by id'; export const TOGGLE_FILTER_ITEM_BY_ID = '[FILTERS] Toggle item by id';
export const TOGGLE_FILTER_ITEM_BY_NAME = '[FILTERS] Toggle item by name';
export class LoadFilters { export class LoadFilters {
static readonly type = LOAD_FILTERS; static readonly type = LOAD_FILTERS;
@@ -31,3 +32,9 @@ export class ToggleFilterItemById {
constructor(public id: string, public payload: Filter[]) {} constructor(public id: string, public payload: Filter[]) {}
} }
export class ToggleFilterItemByName {
static readonly type = TOGGLE_FILTER_ITEM_BY_NAME;
constructor(public name: string, public payload: Filter[]) {}
}

View File

@@ -2,7 +2,10 @@ import { State, Selector, Action, StateContext } from '@ngxs/store';
import { LoadFeed } from '../actions/feed.actions'; import { LoadFeed } from '../actions/feed.actions';
import { feedMock } from 'mocks/feed.mock'; import { feedMock } from 'mocks/feed.mock';
import { FeedCard } from '../../models/feed-card.model'; import { FeedCard } from '../../models/feed-card.model';
import { FeedService } from 'feed-service'; import { FeedService, FeedDTO } from 'feed-service';
import { map } from 'rxjs/operators';
import { FeedMapping } from 'src/app/core/mappings/feed.mapping';
import { PagedApiResponse } from 'projects/feed-service/src/lib';
export class FeedStateModel { export class FeedStateModel {
feed: FeedCard[]; feed: FeedCard[];
@@ -16,7 +19,7 @@ export class FeedStateModel {
}) })
export class FeedState { export class FeedState {
constructor(private feedService: FeedService) {} constructor(private feedService: FeedService, private feedMapping: FeedMapping) { }
@Selector() @Selector()
static getFeed(state: FeedStateModel) { static getFeed(state: FeedStateModel) {
@@ -26,13 +29,20 @@ export class FeedState {
@Action(LoadFeed) @Action(LoadFeed)
load(ctx: StateContext<FeedStateModel>) { load(ctx: StateContext<FeedStateModel>) {
const state = ctx.getState(); const state = ctx.getState();
// TODO: implement api call to state this.feedService.info().subscribe(
// this.feedService.info().subscribe( (feed: PagedApiResponse<FeedDTO<any>>) => {
// (feed: any) => console.log(feed) const feeds = feed.result.map(t =>
// ); this.feedMapping.fromFeedDTO(t)
ctx.patchState({ );
...state, ctx.patchState({
feed: [...feedMock] ...state,
}); feed: [...feeds]
});
}
);
// ctx.patchState({
// ...state,
// feed: [...feedMock]
// });
} }
} }

View File

@@ -1,6 +1,13 @@
import { Filter } from '../../models/filter.model'; import { Filter } from '../../models/filter.model';
import { State, Selector, Action, StateContext } from '@ngxs/store'; import { State, Selector, Action, StateContext } from '@ngxs/store';
import { LoadFilters, LoadFullFilters, SelectFilterById, UnselectFilterById, ToggleFilterItemById } from '../actions/filter.actions'; import {
LoadFilters,
LoadFullFilters,
SelectFilterById,
UnselectFilterById,
ToggleFilterItemById,
ToggleFilterItemByName
} from '../actions/filter.actions';
import { load } from '@angular/core/src/render3'; import { load } from '@angular/core/src/render3';
import { FilterService } from '../../services/filter.service'; import { FilterService } from '../../services/filter.service';
@@ -16,13 +23,18 @@ export class FilterStateModel {
}) })
export class FilterState { export class FilterState {
constructor(private filterService: FilterService) {} constructor(private filterService: FilterService) { }
@Selector() @Selector()
static getFilters(state: FilterStateModel) { static getFilters(state: FilterStateModel) {
return state.filters; return state.filters;
} }
@Selector()
static getSelectedFilters(state: FilterStateModel) {
return state.filters.filter(f => f.items.find(i => i.selected === true));
}
@Action(LoadFilters) @Action(LoadFilters)
load(ctx: StateContext<FilterStateModel>) { load(ctx: StateContext<FilterStateModel>) {
const state = ctx.getState(); const state = ctx.getState();
@@ -87,4 +99,17 @@ export class FilterState {
} }
); );
} }
@Action(ToggleFilterItemByName)
toggleItemByName(ctx: StateContext<FilterStateModel>, { name, payload }: ToggleFilterItemByName) {
const state = ctx.getState();
this.filterService.toggleFilterItemsByName(payload, name).subscribe(
(filters: Filter[]) => {
ctx.patchState({
...state,
filters: [...filters]
});
}
);
}
} }

View File

@@ -9,7 +9,7 @@ import { ProductService } from '../../services/product.service';
import { RecentArticleSearch } from '../../models/recent-article-search.model'; import { RecentArticleSearch } from '../../models/recent-article-search.model';
import { GetProducts, LoadRecentProducts, AddSelectedProduct } from '../actions/product.actions'; import { GetProducts, LoadRecentProducts, AddSelectedProduct } from '../actions/product.actions';
import { ItemDTO } from 'dist/cat-service/lib/dtos'; import { ItemDTO } from 'dist/cat-service/lib/dtos';
import { getCurrentProcess } from '../../utils/process.util' import { getCurrentProcess } from '../../utils/process.util';
export class ProcessStateModel { export class ProcessStateModel {
processes: Process[]; processes: Process[];
@@ -202,7 +202,7 @@ export class ProcessState {
(process: Process) => { (process: Process) => {
if (process.selected === true) { if (process.selected === true) {
if (removeLastBreadcrumb) { if (removeLastBreadcrumb) {
let updateBreadcrumbs: Breadcrumb[] = []; const updateBreadcrumbs: Breadcrumb[] = [];
process.breadcrumbs.forEach((breadcrumb: Breadcrumb, index: number) => { process.breadcrumbs.forEach((breadcrumb: Breadcrumb, index: number) => {
if ((process.breadcrumbs.length - 1) !== index) { if ((process.breadcrumbs.length - 1) !== index) {
updateBreadcrumbs.push(breadcrumb); updateBreadcrumbs.push(breadcrumb);

View File

@@ -3,7 +3,7 @@ import { Filter } from 'src/app/core/models/filter.model';
import { import {
SelectFilterById, SelectFilterById,
UnselectFilterById, UnselectFilterById,
ToggleFilterItemById ToggleFilterItemByName
} from 'src/app/core/store/actions/filter.actions'; } from 'src/app/core/store/actions/filter.actions';
import { FilterItem } from 'src/app/core/models/filter-item.model'; import { FilterItem } from 'src/app/core/models/filter-item.model';
import { Store } from '@ngxs/store'; import { Store } from '@ngxs/store';
@@ -31,8 +31,9 @@ export class FilterItemComponent implements OnInit {
} }
selectItem(item: FilterItem) { selectItem(item: FilterItem) {
this.store.dispatch(new ToggleFilterItemById(item.id, this.filters)); this.store.dispatch(new ToggleFilterItemByName(item.name, this.filters));
} }
ngOnInit() {} ngOnInit() {
}
} }

View File

@@ -4,7 +4,7 @@
display: grid; display: grid;
grid-template-columns: auto auto auto auto; grid-template-columns: auto auto auto auto;
//grid-gap: 5vh; //grid-gap: 5vh;
margin-top: 15px; margin-top: 5px;
width: 80%; width: 80%;
margin-left: 15%; margin-left: 15%;
line-height: 3; line-height: 3;

View File

@@ -26,6 +26,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Select, Store } from '@ngxs/store'; import { Select, Store } from '@ngxs/store';
import { FilterState } from 'src/app/core/store/state/filter.state';
@Component({ @Component({
selector: 'app-text-search', selector: 'app-text-search',
@@ -41,6 +42,7 @@ export class TextSearchComponent implements OnInit, AfterViewInit {
products: Product[]; products: Product[];
filters: Filter[]; filters: Filter[];
@Select(ProcessState.getProcesses) processes$: Observable<Process[]>; @Select(ProcessState.getProcesses) processes$: Observable<Process[]>;
@Select(FilterState.getSelectedFilters) filters$: Observable<Filter[]>;
processes: Process[]; processes: Process[];
@Input() @Input()
@@ -62,7 +64,8 @@ export class TextSearchComponent implements OnInit, AfterViewInit {
if (!this.searchParams) { if (!this.searchParams) {
return; return;
} }
this.loadSelectedFilters(); this.filters$.subscribe(f => this.filters = f);
console.log(this.filters);
const search = <Search>{ const search = <Search>{
query: this.searchParams, query: this.searchParams,
fitlers: this.filters, fitlers: this.filters,
@@ -112,13 +115,6 @@ export class TextSearchComponent implements OnInit, AfterViewInit {
.subscribe((data: any) => this.searchProductsHandler(data)); .subscribe((data: any) => this.searchProductsHandler(data));
} }
loadSelectedFilters() {
// TODO filter selected filters
// this.store.select('filters').subscribe(
// (data: Filter[]) => this.filters = data
// );
}
loadProcesses() { loadProcesses() {
this.processes$.subscribe((data: Process[]) => (this.processes = data)); this.processes$.subscribe((data: Process[]) => (this.processes = data));
} }
@@ -142,6 +138,7 @@ export class TextSearchComponent implements OnInit, AfterViewInit {
} }
createTab() { createTab() {
this.searchParams = this.searchInput.input;
this.loadProcesses(); this.loadProcesses();
if (this.processes.length === 0) { if (this.processes.length === 0) {
this.createProcess(); this.createProcess();

View File

@@ -2,38 +2,38 @@
<div class="card-title align-left"> <div class="card-title align-left">
<span>{{card.cardTitle}}</span> <span>{{card.cardTitle}}</span>
</div> </div>
<div class="card-items"> <div class="card-items" [ngClass]="{'single-item-mode': !card.books.secondBookAuthor}">
<div class="item align-left"> <div class="item align-left">
<div class="item-logo"> <div class="item-logo">
<img class="book-image" src="../../../assets/images/{{card.books.firstBookIcon}}"> <img class="book-image" [src]='firstBookImageUrl$ | async'>
</div> </div>
<div class="item-content"> <div class="item-content">
<div class="book-author wrap-text-more">{{card.books.firstBookAuthor}}</div> <div class="book-author wrap-text-more">{{card.books.firstBookAuthor}}</div>
<div class="book-title wrap-text-more">{{card.books.firstBookTitle}}</div> <div class="book-title wrap-text-more">{{card.books.firstBookTitle}}</div>
<div class="item-detail"> <div class="item-detail" [ngClass]="{'single-item-detail-mode': !card.books.secondBookAuthor}">
<div class="book-type"> <div class="book-type" [ngClass]="{'book-type-single-mode': !card.books.secondBookAuthor}">
<div><img class="book-type-icon" src="../../../assets/images/Icon_Book.svg"></div> <div><img class="book-type-icon" src="../../../assets/images/Icon_{{card.books.firstBookTypeIcon}}.svg"></div>
<div class="item-detail wrap-text-more">{{card.books.firstBookType}}</div> <div class="item-detail wrap-text-more">{{card.books.firstBookType}}</div>
<div class="separator">|</div> <div class="separator">|</div>
</div> </div>
<div class="book-language"> <div class="book-language" [ngClass]="{'book-language-single-mode': !card.books.secondBookAuthor}">
<div class="item-details wrap-text-more">{{card.books.firstBookLanguage}}</div> <div class="item-details wrap-text-more">{{card.books.firstBookLanguage}}</div>
<div class="separator">|</div> <div class="separator">|</div>
</div> </div>
<div class="item-details wrap-text-more">{{card.books.firstBookPrice}}</div> <div class="item-details wrap-text-more">{{price}}</div>
</div> </div>
</div> </div>
</div> </div>
<div class="item align-left"> <div class="item align-left" *ngIf="card.books.secondBookAuthor">
<div class="item-logo"> <div class="item-logo">
<img class="book-image" src="../../../assets/images/{{card.books.secondBookIcon}}"> <img class="book-image" [src]='secondBookImageUrl$ | async'>
</div> </div>
<div class="item-content"> <div class="item-content">
<div class="book-author wrap-text-more">{{card.books.secondBookAuthor}}</div> <div class="book-author wrap-text-more">{{card.books.secondBookAuthor}}</div>
<div class="book-title wrap-text-more">{{card.books.secondBookTitle}}</div> <div class="book-title wrap-text-more">{{card.books.secondBookTitle}}</div>
<div class="item-detail"> <div class="item-detail">
<div class="book-type"> <div class="book-type">
<div><img class="book-type-icon" src="../../../assets/images/Icon_Book.svg"></div> <div><img class="book-type-icon" src="../../../assets/images/Icon_{{card.books.secondBookTypeIcon}}.svg"></div>
<div class="item-detail wrap-text-more">{{card.books.secondBookType}}</div> <div class="item-detail wrap-text-more">{{card.books.secondBookType}}</div>
<div class="separator">|</div> <div class="separator">|</div>
</div> </div>
@@ -41,7 +41,7 @@
<div class="item-details wrap-text-more">{{card.books.secondBookLanguage}}</div> <div class="item-details wrap-text-more">{{card.books.secondBookLanguage}}</div>
<div class="separator">|</div> <div class="separator">|</div>
</div> </div>
<div class="item-details wrap-text-more">{{card.books.secondBookPrice}}</div> <div class="item-details wrap-text-more">{{secondPrice}}</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -25,6 +25,10 @@
margin-top: 15px; margin-top: 15px;
} }
.single-item-mode {
grid-template-columns: auto;
}
@media screen and (max-width: 563px) { @media screen and (max-width: 563px) {
.card-items { .card-items {
display: grid; display: grid;
@@ -53,6 +57,11 @@
grid-template-columns: auto auto auto; grid-template-columns: auto auto auto;
} }
.single-item-detail-mode {
grid-template-columns: max-content max-content auto;
grid-gap: 2vh;
}
.book-author { .book-author {
height: 21px; height: 21px;
font-size: 16px; font-size: 16px;
@@ -70,6 +79,10 @@
grid-template-columns: auto auto auto; grid-template-columns: auto auto auto;
} }
.book-type-single-mode {
grid-gap: 1vh;
}
.separator .item-details { .separator .item-details {
height: 21px; height: 21px;
font-size: 16px; font-size: 16px;
@@ -80,3 +93,11 @@
display: grid; display: grid;
grid-template-columns: auto auto; grid-template-columns: auto auto;
} }
.book-language-single-mode {
grid-gap: 1vh;
}
.book-type-icon {
margin-top: 2px;
}

View File

@@ -1,5 +1,7 @@
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit, Input } from '@angular/core';
import { FeedCard } from 'src/app/core/models/feed-card.model'; import { FeedCard } from 'src/app/core/models/feed-card.model';
import { CatImageService } from 'cat-service';
import { Observable, of } from 'rxjs';
@Component({ @Component({
selector: 'app-book-card', selector: 'app-book-card',
@@ -9,10 +11,34 @@ import { FeedCard } from 'src/app/core/models/feed-card.model';
export class BookCardComponent implements OnInit { export class BookCardComponent implements OnInit {
@Input() card: FeedCard; @Input() card: FeedCard;
firstBookImageUrl$: Observable<string>;
secondBookImageUrl$: Observable<string>;
constructor() { } constructor(private catImageService: CatImageService) { }
get price() {
if (this.card.books.firstBookPrice.toString().indexOf('.') === -1) {
return this.card.books.firstBookPrice + ',00 ' + this.card.books.firstBookCurrency;
}
return this.card.books.firstBookPrice.replace('.', ',') + ' ' + this.card.books.firstBookCurrency;
}
get secondPrice() {
if (this.card.books.secondBookPrice.toString().indexOf('.') === -1) {
return this.card.books.secondBookPrice + ',00 ' + this.card.books.secondBookPrice;
}
return this.card.books.secondBookPrice.replace('.', ',') + ' ' + this.card.books.secondBookPrice;
}
ngOnInit() { ngOnInit() {
this.catImageService.getImageUrl(this.card.books.firstBookEan).subscribe(
(url: string) => this.firstBookImageUrl$ = of(url)
);
if (this.card.books.secondBookEan) {
this.catImageService.getImageUrl(this.card.books.secondBookEan).subscribe(
(url: string) => this.secondBookImageUrl$ = of(url)
);
}
} }
} }

View File

@@ -1,11 +1,11 @@
<div class="card-container"> <div class="card-container" *ngFor="let event of card.event">
<div class="card-title align-left"> <div class="card-title align-left">
<span>{{card.cardTitle}}</span> <span>{{card.cardTitle}}</span>
</div> </div>
<div class="event-title align-left wrap-text-more"> <div class="event-title align-left wrap-text-more">
<span>{{card.event.title}}</span> <span [innerHTML]='event.title'></span>
</div> </div>
<div class="event-content align-left"> <div class="event-content align-left">
<span>{{card.event.content}}</span> <span [innerHTML]='event.content'></span>
</div> </div>
</div> </div>

View File

@@ -1,16 +1,16 @@
<div class="card-container"> <div class="card-container card-container-no-image" *ngFor="let news of card.news">
<div class="card-icon-container align-center"> <!-- <div class="card-icon-container align-center">
<img class="news-icon" src="../../../assets/images/News_Icon.svg"> <img class="news-icon" src="../../../assets/images/News_Icon.svg">
</div> </div> -->
<div class="card-content-container"> <div class="card-content-container">
<div class="card-title align-left"> <div class="card-title align-left">
<span>{{card.cardTitle}}</span> <span>{{card.cardTitle}}</span>
</div> </div>
<div class="news-title align-left wrap-text-more"> <div class="news-title align-left wrap-text-more">
<span>{{card.news.title}}</span> <span>{{news.title}}</span>
</div> </div>
<div class="news-content align-left"> <div class="news-content align-left">
<span>{{card.news.content}}</span> <span>{{news.content}}</span>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -13,6 +13,10 @@
border-radius: 4px; border-radius: 4px;
} }
.card-container-no-image {
grid-template-columns: auto;
}
.card-content-container { .card-content-container {
display: grid; display: grid;
grid-template-columns: auto; grid-template-columns: auto;

View File

@@ -1,6 +1,7 @@
<div *ngFor="let card of feed$ | async"> <div *ngFor="let card of feed$ | async">
<app-book-card *ngIf="card.type === 'BOOK'" [card]="card"></app-book-card> <app-book-card *ngIf="card.type === 'products'" [card]="card"></app-book-card>
<app-event-card *ngIf="card.type === 'EVENT'" [card]="card"></app-event-card> <app-event-card *ngIf="card.type === 'events'" [card]="card"></app-event-card>
<app-news-card *ngIf="card.type === 'NEWS'" [card]="card"></app-news-card> <app-news-card *ngIf="card.type === 'info'" [card]="card"></app-news-card>
<app-recommandation-card *ngIf="card.type === 'REC'" [card]="card"></app-recommandation-card> <app-recommandation-card *ngIf="card.type === 'REC'" [card]="card"></app-recommandation-card>
</div> </div>
<app-loading [loading]="loading" text="Inhalte werden geladen"></app-loading>

View File

@@ -12,6 +12,8 @@ import { FeedState } from 'src/app/core/store/state/feed.state';
}) })
export class DashboardComponent implements OnInit { export class DashboardComponent implements OnInit {
loading = true;
@Select(FeedState.getFeed) feed$: Observable<FeedCard[]>; @Select(FeedState.getFeed) feed$: Observable<FeedCard[]>;
constructor( constructor(
private store: Store private store: Store
@@ -19,5 +21,8 @@ export class DashboardComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.store.dispatch(new LoadFeed()); this.store.dispatch(new LoadFeed());
this.feed$.subscribe(
() => this.loading = false
);
} }
} }

View File

@@ -6,8 +6,9 @@ import { BookCardComponent } from './components/book-card/book-card.component';
import { EventCardComponent } from './components/event-card/event-card.component'; import { EventCardComponent } from './components/event-card/event-card.component';
import { NewsCardComponent } from './components/news-card/news-card.component'; import { NewsCardComponent } from './components/news-card/news-card.component';
import { RecommandationCardComponent } from './components/recommandation-card/recommandation-card.component'; import { RecommandationCardComponent } from './components/recommandation-card/recommandation-card.component';
import { SharedModule } from 'src/app/shared/shared.module';
@NgModule({ @NgModule({
imports: [CommonModule], imports: [CommonModule, SharedModule],
exports: [DashboardComponent], exports: [DashboardComponent],
declarations: [ declarations: [
DashboardComponent, DashboardComponent,

View File

@@ -82,7 +82,7 @@
left: 0; left: 0;
/* semi-transparent black */ /* semi-transparent black */
background-color: #000; background-color: #1B1E32;
opacity: 0.75; opacity: 0.75;
/* z-index must be below .jw-modal and above everything else */ /* z-index must be below .jw-modal and above everything else */

View File

@@ -29,7 +29,7 @@ import {
src="/assets/images/close.svg" src="/assets/images/close.svg"
/> />
<img <img
(click)="search.emit(input)" (click)="emitSearch(input)"
class="search-icon" class="search-icon"
src="/assets/images/search.svg" src="/assets/images/search.svg"
/> />
@@ -62,6 +62,10 @@ export class SearchComponent implements OnInit {
this.change(''); this.change('');
} }
emitSearch(input: string) {
this.search.emit(input);
}
keyHandler(event: any) { keyHandler(event: any) {
if (event.key === 'Enter') { if (event.key === 'Enter') {
this.search.emit(this.input); this.search.emit(this.input);

View File

@@ -6,9 +6,9 @@
<base href="/"> <base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700" rel="stylesheet"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600,700" rel="stylesheet">
</head> </head>
<body> <body>
<app-root></app-root> <app-root></app-root>