mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Compare commits
43 Commits
0a1f25a1ee
...
feature/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddd1b81d0d | ||
|
|
c79a1cdad1 | ||
|
|
edf978f5cf | ||
|
|
4bfe35a8b9 | ||
|
|
4b7c26b009 | ||
|
|
2a442dde85 | ||
|
|
45bb39f466 | ||
|
|
b2731432ed | ||
|
|
8c424bab43 | ||
|
|
571c748a66 | ||
|
|
c104f36255 | ||
|
|
672245c467 | ||
|
|
0ae92e34c6 | ||
|
|
ce72ce48b2 | ||
|
|
bd5ec27425 | ||
|
|
27a83242c5 | ||
|
|
7bf23c4bcc | ||
|
|
0bc80e12aa | ||
|
|
b586e3da9e | ||
|
|
a433f2cfe4 | ||
|
|
ff368d68b7 | ||
|
|
40ebd72263 | ||
|
|
11eca1e25a | ||
|
|
b8d0153232 | ||
|
|
0a195e78e5 | ||
|
|
97c26f5fa1 | ||
|
|
8dac64d5b8 | ||
|
|
2c295b0797 | ||
|
|
4344f4617c | ||
|
|
c451d2b329 | ||
|
|
96d1a4b826 | ||
|
|
958a388fb5 | ||
|
|
a908767f08 | ||
|
|
3c0406031a | ||
|
|
79bb9b8c11 | ||
|
|
341b202bc4 | ||
|
|
f284dc1db5 | ||
|
|
2144ec838c | ||
|
|
bff10cb2ff | ||
|
|
d303b1444b | ||
|
|
150e7965ee | ||
|
|
3fcf3d9396 | ||
|
|
52278b8baf |
@@ -1504,5 +1504,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"analytics": false
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export interface Breadcrumb {
|
||||
/**
|
||||
* Url
|
||||
*/
|
||||
path: string;
|
||||
path: string | any[];
|
||||
|
||||
/**
|
||||
* Query Parameter
|
||||
|
||||
@@ -3,11 +3,17 @@ import { Platform } from '@angular/cdk/platform';
|
||||
import { NativeContainerService } from 'native-container';
|
||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||
|
||||
const MATCH_TABLET = '(max-width: 1023px)';
|
||||
const MATCH_TABLET = '(max-width: 1024px)';
|
||||
|
||||
const MATCH_DESKTOP_SMALL = '(min-width: 1024px and max-width: 1439px)';
|
||||
const MATCH_DESKTOP_SMALL = '(min-width: 1025px) and (max-width: 1439px)';
|
||||
|
||||
const MATCH_DESKTOP = '(min-width: 1440px)';
|
||||
const MATCH_DESKTOP = '(min-width: 1280px)';
|
||||
|
||||
const MATCH_DESKTOP_LARGE = '(min-width: 1440px)';
|
||||
|
||||
const MATCH_DESKTOP_X_LARGE = '(min-width: 1920px)';
|
||||
|
||||
const MATCH_DESKTOP_XX_LARGE = '(min-width: 2736px)';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -37,6 +43,24 @@ export class EnvironmentService {
|
||||
|
||||
matchDesktop$ = this._breakpointObserver.observe(MATCH_DESKTOP);
|
||||
|
||||
matchDesktopLarge(): boolean {
|
||||
return this._breakpointObserver.isMatched(MATCH_DESKTOP_LARGE);
|
||||
}
|
||||
|
||||
matchDesktopLarge$ = this._breakpointObserver.observe(MATCH_DESKTOP_LARGE);
|
||||
|
||||
matchDesktopXLarge(): boolean {
|
||||
return this._breakpointObserver.isMatched(MATCH_DESKTOP_X_LARGE);
|
||||
}
|
||||
|
||||
matchDesktopXLarge$ = this._breakpointObserver.observe(MATCH_DESKTOP_X_LARGE);
|
||||
|
||||
matchDesktopXXLarge(): boolean {
|
||||
return this._breakpointObserver.isMatched(MATCH_DESKTOP_XX_LARGE);
|
||||
}
|
||||
|
||||
matchDesktopXXLarge$ = this._breakpointObserver.observe(MATCH_DESKTOP_XX_LARGE);
|
||||
|
||||
/**
|
||||
* @deprecated Use `matchDesktopSmall` or 'matchDesktop' instead.
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -9,6 +10,7 @@ export class CanActivateProductGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _checkoutService: DomainCheckoutService,
|
||||
private readonly _navigationService: ProductCatalogNavigationService,
|
||||
private readonly _router: Router
|
||||
) {}
|
||||
|
||||
@@ -38,7 +40,7 @@ export class CanActivateProductGuard implements CanActivate {
|
||||
}
|
||||
|
||||
if (!lastActivatedProcessId) {
|
||||
await this.fromCartProcess(processes, route);
|
||||
await this.fromCartProcess(processes);
|
||||
return false;
|
||||
} else {
|
||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
|
||||
@@ -48,7 +50,7 @@ export class CanActivateProductGuard implements CanActivate {
|
||||
}
|
||||
|
||||
// Bei offener Artikelsuche/Kundensuche und Klick auf Footer Artikelsuche
|
||||
async fromCartProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot) {
|
||||
async fromCartProcess(processes: ApplicationProcess[]) {
|
||||
const newProcessId = Date.now();
|
||||
await this._applicationService.createProcess({
|
||||
id: newProcessId,
|
||||
@@ -57,7 +59,7 @@ export class CanActivateProductGuard implements CanActivate {
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
});
|
||||
|
||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(newProcessId)]));
|
||||
await this._navigationService.navigateToProductSearch({ processId: newProcessId });
|
||||
}
|
||||
|
||||
// Bei offener Warenausgabe und Klick auf Footer Artikelsuche
|
||||
|
||||
@@ -263,6 +263,16 @@
|
||||
"name": "event-available",
|
||||
"data": "M438 830 296 688l58-58 84 84 168-168 58 58-226 226ZM200 976q-33 0-56.5-23.5T120 896V336q0-33 23.5-56.5T200 256h40v-80h80v80h320v-80h80v80h40q33 0 56.5 23.5T840 336v560q0 33-23.5 56.5T760 976H200Zm0-80h560V496H200v400Zm0-480h560v-80H200v80Zm0 0v-80 80Z",
|
||||
"viewBox": "0 96 960 960"
|
||||
},
|
||||
{
|
||||
"name": "remove",
|
||||
"data": "M200 606v-60h560v60H200Z",
|
||||
"viewBox": "0 96 960 960"
|
||||
},
|
||||
{
|
||||
"name": "add",
|
||||
"data": "M450 856V606H200v-60h250V296h60v250h250v60H510v250h-60Z",
|
||||
"viewBox": "0 96 960 960"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
|
||||
@@ -264,6 +264,16 @@
|
||||
"name": "event-available",
|
||||
"data": "M438 830 296 688l58-58 84 84 168-168 58 58-226 226ZM200 976q-33 0-56.5-23.5T120 896V336q0-33 23.5-56.5T200 256h40v-80h80v80h320v-80h80v80h40q33 0 56.5 23.5T840 336v560q0 33-23.5 56.5T760 976H200Zm0-80h560V496H200v400Zm0-480h560v-80H200v80Zm0 0v-80 80Z",
|
||||
"viewBox": "0 96 960 960"
|
||||
},
|
||||
{
|
||||
"name": "remove",
|
||||
"data": "M200 606v-60h560v60H200Z",
|
||||
"viewBox": "0 96 960 960"
|
||||
},
|
||||
{
|
||||
"name": "add",
|
||||
"data": "M450 856V606H200v-60h250V296h60v250h250v60H510v250h-60Z",
|
||||
"viewBox": "0 96 960 960"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
|
||||
@@ -264,6 +264,16 @@
|
||||
"name": "event-available",
|
||||
"data": "M438 830 296 688l58-58 84 84 168-168 58 58-226 226ZM200 976q-33 0-56.5-23.5T120 896V336q0-33 23.5-56.5T200 256h40v-80h80v80h320v-80h80v80h40q33 0 56.5 23.5T840 336v560q0 33-23.5 56.5T760 976H200Zm0-80h560V496H200v400Zm0-480h560v-80H200v80Zm0 0v-80 80Z",
|
||||
"viewBox": "0 96 960 960"
|
||||
},
|
||||
{
|
||||
"name": "remove",
|
||||
"data": "M200 606v-60h560v60H200Z",
|
||||
"viewBox": "0 96 960 960"
|
||||
},
|
||||
{
|
||||
"name": "add",
|
||||
"data": "M450 856V606H200v-60h250V296h60v250h250v60H510v250h-60Z",
|
||||
"viewBox": "0 96 960 960"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<div class="page-price-update-item__item-details">
|
||||
<div class="page-price-update-item__item-contributors flex flex-row">
|
||||
{{ environment.isTablet() ? (item?.product?.contributors | substr: 42) : item?.product?.contributors }}
|
||||
{{ environment.isTablet() ? (item?.product?.contributors | substr: 38) : item?.product?.contributors }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -43,7 +43,7 @@
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
{{ item?.product?.formatDetail }}
|
||||
{{ environment.isTablet() ? (item?.product?.formatDetail | substr: 25) : item?.product?.formatDetail }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,124 +1,240 @@
|
||||
<ng-container *ngIf="!showRecommendations">
|
||||
<div #detailsContainer class="product-card">
|
||||
<div class="page-article-details__container px-5 relative">
|
||||
<ng-container *ngIf="store.item$ | async; let item">
|
||||
<div class="product-details">
|
||||
<div class="product-image">
|
||||
<button class="image-button" (click)="showImages()">
|
||||
<img (load)="loadImage()" [src]="item.imageId | productImage: 195:315:true" alt="product image" />
|
||||
<ui-icon *ngIf="imageLoaded$ | async" icon="search_add" size="22px"></ui-icon>
|
||||
</button>
|
||||
<div class="page-article-details__product-details mb-3">
|
||||
<div class="page-article-details__product-bookmark justify-self-end">
|
||||
<div *ngIf="showArchivBadge$ | async" class="archiv-badge">
|
||||
<button [uiOverlayTrigger]="archivTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-px-5">
|
||||
<img src="/assets/images/bookmark_benachrichtigung_archiv.svg" alt="Archiv Badge" />
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #archivTooltip [closeable]="true">
|
||||
<ng-container *ngIf="isAvailable$ | async; else notAvailable">
|
||||
Archivtitel. Wird nicht mehr gedruckt. Artikel ist bestellbar, weil lieferbar.
|
||||
</ng-container>
|
||||
<ng-template #notAvailable>
|
||||
Archivtitel. Wird nicht mehr gedruckt. Nicht bestellbar.
|
||||
</ng-template>
|
||||
</ui-tooltip>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="showSubscriptionBadge$ | async">
|
||||
<button [uiOverlayTrigger]="subscribtionTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-px-5">
|
||||
<img src="/assets/images/bookmark_subscription.svg" alt="Fortsetzungsartikel Badge" />
|
||||
</button>
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #subscribtionTooltip [closeable]="true"
|
||||
>Artikel ist ein Fortsetzungsartikel,<br />
|
||||
Artikel muss über eine Aboabteilung<br />
|
||||
bestellt werden.
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
<div *ngIf="showPromotionBadge$ | async" class="promotion-badge">
|
||||
<button [uiOverlayTrigger]="promotionTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-px-5">
|
||||
<ui-icon-badge icon="gift" alt="Prämienkatalog Badge"></ui-icon-badge>
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #promotionTooltip [closeable]="true">
|
||||
Dieser Artikel befindet sich im Prämienkatalog.
|
||||
</ui-tooltip>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button (click)="showReviews()" class="recessions" *ngIf="item.reviews?.length > 0">
|
||||
<div class="page-article-details__product-image-recessions flex flex-col items-center">
|
||||
<div class="page-article-details__product-image">
|
||||
<button class="border-none outline-none bg-transparent relative" (click)="showImages()">
|
||||
<img
|
||||
class="max-h-[19.6875rem] max-w-[12.1875rem] rounded-card"
|
||||
(load)="loadImage()"
|
||||
[src]="item.imageId | productImage: 195:315:true"
|
||||
alt="product image"
|
||||
/>
|
||||
<ui-icon
|
||||
class="absolute text-[#A7B9CB] inline-block bottom-[14px] right-[18px]"
|
||||
*ngIf="imageLoaded$ | async"
|
||||
icon="search_add"
|
||||
size="25px"
|
||||
></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
(click)="showReviews()"
|
||||
class="page-article-details__product-recessions flex flex-col mt-2 items-center bg-transparent border-none outline-none"
|
||||
*ngIf="item.reviews?.length > 0"
|
||||
>
|
||||
<ui-stars [rating]="store.reviewRating$ | async"></ui-stars>
|
||||
|
||||
<div class="cta-recessions">{{ item.reviews.length }} Rezensionen</div>
|
||||
<div class="text-regular text-[#0556B4] font-bold">{{ item.reviews.length }} Rezensionen</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="product-info">
|
||||
<div class="row" [class.bookmark-badge-gap]="isBadgeVisible$ | async">
|
||||
<div>
|
||||
<a
|
||||
*ngFor="let contributor of contributors$ | async; let last = last"
|
||||
class="autor"
|
||||
[routerLink]="['/kunde', applicationService.activatedProcessId, 'product', 'search', 'results']"
|
||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||
>
|
||||
{{ contributor }}{{ last ? '' : ';' }}
|
||||
</a>
|
||||
<div class="page-article-details__product-contributors">
|
||||
<a
|
||||
*ngFor="let contributor of contributors$ | async; let last = last"
|
||||
class="text-[#0556B4] font-semibold no-underline text-base"
|
||||
[routerLink]="resultsPath"
|
||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||
>
|
||||
{{ contributor }}{{ last ? '' : ';' }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-print justify-self-end" [class.mt-4]="isBadgeVisible$ | async">
|
||||
<button class="bg-transparent text-brand font-bold text-lg outline-none border-none p-0" (click)="print()">Drucken</button>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-title text-2xl font-bold mb-6">
|
||||
{{ item.product?.name }}
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-misc flex flex-col mb-4">
|
||||
<div
|
||||
class="page-article-details__product-format flex items-center font-bold text-sm"
|
||||
*ngIf="item?.product?.format && item?.product?.formatDetail"
|
||||
>
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
class="flex mr-2 h-[1.125rem]"
|
||||
[src]="'/assets/images/Icon_' + item.product?.format + '.svg'"
|
||||
[alt]="item.product?.formatDetail"
|
||||
/>
|
||||
{{ item.product?.formatDetail }}
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-volume" *ngIf="item?.product?.volume">Band/Reihe {{ item?.product?.volume }}</div>
|
||||
|
||||
<div class="page-article-details__product-publication">{{ publicationDate$ | async }}</div>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-price-info flex flex-col mb-4">
|
||||
<div
|
||||
class="page-article-details__product-price font-bold text-xl self-end"
|
||||
*ngIf="item.catalogAvailability?.price?.value?.value; else retailPrice"
|
||||
>
|
||||
{{ item.catalogAvailability?.price?.value?.value | currency: item.catalogAvailability?.price?.value?.currency:'code' }}
|
||||
</div>
|
||||
<ng-template #retailPrice>
|
||||
<div
|
||||
class="page-article-details__product-price font-bold text-xl self-end"
|
||||
*ngIf="store.takeAwayAvailability$ | async; let takeAwayAvailability"
|
||||
>
|
||||
{{ takeAwayAvailability?.retailPrice?.value?.value | currency: takeAwayAvailability?.retailPrice?.value?.currency:'code' }}
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<button class="cta-print right" (click)="print()">Drucken</button>
|
||||
</div>
|
||||
<div class="title">
|
||||
{{ item.product?.name }}
|
||||
<div class="page-article-details__product-points self-end" *ngIf="store.promotionPoints$ | async; let promotionPoints">
|
||||
{{ promotionPoints }} Lesepunkte
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div>
|
||||
<div class="format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
class="format-icon"
|
||||
[src]="'/assets/images/Icon_' + item.product?.format + '.svg'"
|
||||
[alt]="item.product?.formatDetail"
|
||||
/>
|
||||
{{ item.product?.formatDetail }}
|
||||
<!-- TODO: Ticket PREISGEBUNDEN -->
|
||||
<div class="page-article-details__product-price-bound self-end"></div>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-origin-infos flex flex-col mb-4">
|
||||
<div class="page-article-details__product-manufacturer" data-name="product-manufacturer">{{ item.product?.manufacturer }}</div>
|
||||
|
||||
<div class="page-article-details__product-language" *ngIf="item?.product?.locale" data-name="product-language">
|
||||
{{ item?.product?.locale }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-stock flex justify-end items-center">
|
||||
<div class="h-5 w-16 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="store.fetchingTakeAwayAvailability$ | async"></div>
|
||||
<div
|
||||
class="flex flex-row py-4 pl-4"
|
||||
[uiOverlayTrigger]="tooltip"
|
||||
[overlayTriggerDisabled]="!(stockTooltipText$ | async)"
|
||||
*ngIf="!(store.fetchingTakeAwayAvailability$ | async)"
|
||||
>
|
||||
<ng-container *ngIf="store.takeAwayAvailability$ | async; let takeAwayAvailability">
|
||||
<ui-icon class="mr-2 mb-1" icon="home" size="15px"></ui-icon>
|
||||
<span class="font-bold text-sm">{{ takeAwayAvailability.inStock || 0 }}x</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-8" [closeable]="true">
|
||||
{{ stockTooltipText$ | async }}
|
||||
</ui-tooltip>
|
||||
|
||||
<div class="page-article-details__product-ean-specs flex flex-col">
|
||||
<div class="page-article-details__product-ean" data-name="product-ean">{{ item.product?.ean }}</div>
|
||||
|
||||
<div class="page-article-details__product-specs">
|
||||
<ng-container *ngIf="item?.specs?.length > 0">
|
||||
{{ (item?.specs)[0]?.value }}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-availabilities flex flex-row items-center justify-end mt-4">
|
||||
<div
|
||||
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
|
||||
*ngIf="store.fetchingTakeAwayAvailability$ | async; else showAvailabilityTakeAwayIcon"
|
||||
></div>
|
||||
<ng-template #showAvailabilityTakeAwayIcon>
|
||||
<div
|
||||
*ngIf="store.isTakeAwayAvailabilityAvailable$ | async"
|
||||
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center"
|
||||
>
|
||||
<ui-icon class="mx-1" icon="shopping_bag" size="18px"> </ui-icon>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<div
|
||||
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
|
||||
*ngIf="store.fetchingPickUpAvailability$ | async; else showAvailabilityPickUpIcon"
|
||||
></div>
|
||||
<ng-template #showAvailabilityPickUpIcon>
|
||||
<div
|
||||
*ngIf="store.isPickUpAvailabilityAvailable$ | async"
|
||||
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
|
||||
>
|
||||
<ui-icon class="mx-1" icon="box_out" size="18px"></ui-icon>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<div
|
||||
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
|
||||
*ngIf="store.fetchingDeliveryAvailability$ | async; else showAvailabilityDeliveryIcon"
|
||||
></div>
|
||||
<ng-template #showAvailabilityDeliveryIcon>
|
||||
<div
|
||||
*ngIf="showDeliveryTruck$ | async"
|
||||
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
|
||||
>
|
||||
<ui-icon class="-mb-px-5 -mt-px-5 mx-1" icon="truck" size="30px"></ui-icon>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<div
|
||||
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
|
||||
*ngIf="store.fetchingDeliveryB2BAvailability$ | async; else showAvailabilityDeliveryB2BIcon"
|
||||
></div>
|
||||
<ng-template #showAvailabilityDeliveryB2BIcon>
|
||||
<div
|
||||
*ngIf="showDeliveryB2BTruck$ | async"
|
||||
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
|
||||
>
|
||||
<ui-icon class="-mb-px-10 -mt-px-10 mx-1" icon="truck_b2b" size="30px"> </ui-icon>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<span *ngIf="store.isDownload$ | async" class="flex flex-row items-center">
|
||||
<div class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3">
|
||||
<ui-icon class="mx-1" icon="download" size="18px"></ui-icon>
|
||||
<span class="font-bold">Download</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__shelf-ssc">
|
||||
<div class="page-article-details__ssc flex justify-end my-2 font-bold text-lg">
|
||||
<div class="w-52 h-px-20 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="fetchingAvailabilities$ | async"></div>
|
||||
<ng-container *ngIf="!(fetchingAvailabilities$ | async)">
|
||||
<div *ngIf="store.sscText$ | async; let sscText">
|
||||
{{ sscText }}
|
||||
</div>
|
||||
<div *ngIf="item?.product?.volume">Band/Reihe {{ item?.product?.volume }}</div>
|
||||
|
||||
<div>{{ publicationDate$ | async }}</div>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<div class="price" *ngIf="item.catalogAvailability?.price?.value?.value; else retailPrice">
|
||||
{{ item.catalogAvailability?.price?.value?.value | currency: item.catalogAvailability?.price?.value?.currency:'code' }}
|
||||
</div>
|
||||
<ng-template #retailPrice>
|
||||
<div class="price" *ngIf="store.takeAwayAvailability$ | async; let takeAwayAvailability">
|
||||
{{
|
||||
takeAwayAvailability?.retailPrice?.value?.value | currency: takeAwayAvailability?.retailPrice?.value?.currency:'code'
|
||||
}}
|
||||
</div>
|
||||
</ng-template>
|
||||
<div *ngIf="store.promotionPoints$ | async; let promotionPoints">{{ promotionPoints }} Lesepunkte</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="row stock">
|
||||
<div data-name="product-manufacturer">{{ item.product?.manufacturer }}</div>
|
||||
|
||||
<div class="right quantity" [uiOverlayTrigger]="tooltip" [overlayTriggerDisabled]="!(stockTooltipText$ | async)">
|
||||
<div class="fetching small" *ngIf="store.fetchingTakeAwayAvailability$ | async"></div>
|
||||
<ng-container *ngIf="!(store.fetchingTakeAwayAvailability$ | async)">
|
||||
<ng-container *ngIf="store.takeAwayAvailability$ | async; let takeAwayAvailability">
|
||||
<ui-icon icon="home" size="22px"></ui-icon>
|
||||
{{ takeAwayAvailability.inStock || 0 }}x
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-8" [closeable]="true">
|
||||
{{ stockTooltipText$ | async }}
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
<div *ngIf="item?.product?.locale" data-name="product-language">{{ item?.product?.locale }}</div>
|
||||
|
||||
<div class="row">
|
||||
<div data-name="product-ean">{{ item.product?.ean }}</div>
|
||||
<div class="right">
|
||||
<div class="availability-icons">
|
||||
<div class="fetching xsmall" *ngIf="store.fetchingTakeAwayAvailability$ | async; else showAvailabilityTakeAwayIcon"></div>
|
||||
<ng-template #showAvailabilityTakeAwayIcon>
|
||||
<ui-icon *ngIf="store.isTakeAwayAvailabilityAvailable$ | async" icon="shopping_bag" size="18px"> </ui-icon>
|
||||
</ng-template>
|
||||
|
||||
<div class="fetching xsmall" *ngIf="store.fetchingPickUpAvailability$ | async; else showAvailabilityPickUpIcon"></div>
|
||||
<ng-template #showAvailabilityPickUpIcon>
|
||||
<ui-icon *ngIf="store.isPickUpAvailabilityAvailable$ | async" icon="box_out" size="18px"></ui-icon>
|
||||
</ng-template>
|
||||
|
||||
<div class="fetching xsmall" *ngIf="store.fetchingDeliveryAvailability$ | async; else showAvailabilityDeliveryIcon"></div>
|
||||
<ng-template #showAvailabilityDeliveryIcon>
|
||||
<ui-icon *ngIf="showDeliveryTruck$ | async" class="truck" icon="truck" size="30px"></ui-icon>
|
||||
</ng-template>
|
||||
|
||||
<div
|
||||
class="fetching xsmall"
|
||||
*ngIf="store.fetchingDeliveryB2BAvailability$ | async; else showAvailabilityDeliveryB2BIcon"
|
||||
></div>
|
||||
<ng-template #showAvailabilityDeliveryB2BIcon>
|
||||
<ui-icon *ngIf="showDeliveryB2BTruck$ | async" class="truck_b2b" icon="truck_b2b" size="40px"> </ui-icon>
|
||||
</ng-template>
|
||||
|
||||
<span *ngIf="store.isDownload$ | async" class="download-icon">
|
||||
<ui-icon icon="download" size="18px"></ui-icon>
|
||||
<span class="label">Download</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="shelfinfo right" *ngIf="store.isDownload$ | async">
|
||||
<div class="page-article-details__shelfinfo" *ngIf="store.isDownload$ | async">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
|
||||
@@ -145,24 +261,7 @@
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="specs">
|
||||
<ng-container *ngIf="item?.specs?.length > 0">
|
||||
{{ (item?.specs)[0]?.value }}
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="right ssc">
|
||||
<div class="fetching" *ngIf="fetchingAvailabilities$ | async"></div>
|
||||
<ng-container *ngIf="!(fetchingAvailabilities$ | async)">
|
||||
<div *ngIf="store.sscText$ | async; let sscText">
|
||||
{{ sscText }}
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="shelfinfo right" *ngIf="!(store.isDownload$ | async)">
|
||||
<div class="page-article-details__shelfinfo text-right" *ngIf="!(store.isDownload$ | async)">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
|
||||
@@ -186,109 +285,115 @@
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bookmark">
|
||||
<div *ngIf="showArchivBadge$ | async" class="archiv-badge">
|
||||
<button [uiOverlayTrigger]="archivTooltip" class="bookmark-badge">
|
||||
<img src="/assets/images/bookmark_benachrichtigung_archiv.svg" alt="Archiv Badge" />
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #archivTooltip [closeable]="true">
|
||||
<ng-container *ngIf="isAvailable$ | async; else notAvailable">
|
||||
Archivtitel. Wird nicht mehr gedruckt. Artikel ist bestellbar, weil lieferbar.
|
||||
</ng-container>
|
||||
<ng-template #notAvailable>
|
||||
Archivtitel. Wird nicht mehr gedruckt. Nicht bestellbar.
|
||||
</ng-template>
|
||||
</ui-tooltip>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="showSubscriptionBadge$ | async">
|
||||
<button [uiOverlayTrigger]="subscribtionTooltip" class="bookmark-badge">
|
||||
<img src="/assets/images/bookmark_subscription.svg" alt="Fortsetzungsartikel Badge" />
|
||||
</button>
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #subscribtionTooltip [closeable]="true"
|
||||
>Artikel ist ein Fortsetzungsartikel,<br />
|
||||
Artikel muss über eine Aboabteilung<br />
|
||||
bestellt werden.
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
<div *ngIf="showPromotionBadge$ | async" class="promotion-badge">
|
||||
<button [uiOverlayTrigger]="promotionTooltip" class="bookmark-badge">
|
||||
<ui-icon-badge icon="gift" alt="Prämienkatalog Badge"></ui-icon-badge>
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #promotionTooltip [closeable]="true">
|
||||
Dieser Artikel befindet sich im Prämienkatalog.
|
||||
</ui-tooltip>
|
||||
</button>
|
||||
<div class="page-article-details__product-formats-container mt-3" *ngIf="item.family?.length > 0">
|
||||
<hr class="bg-[#E6EFF9] border-t-2" />
|
||||
<div class="pt-3">
|
||||
<div class="page-article-details__product-formats">
|
||||
<span class="mr-2">Auch verfügbar als</span>
|
||||
|
||||
<ui-slider [scrollDistance]="250">
|
||||
<a
|
||||
class="mr-4 text-[#0556B4] font-bold no-underline px-2"
|
||||
*ngFor="let format of item.family"
|
||||
[routerLink]="getDetailsPath(format.product.ean)"
|
||||
[queryParamsHandling]="!(isTablet$ | async) ? 'preserve' : ''"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<img
|
||||
class="mr-2"
|
||||
*ngIf="!!format.product?.format"
|
||||
[src]="'/assets/images/OF_Icon_' + format.product?.format + '.svg'"
|
||||
alt="format icon"
|
||||
/>
|
||||
{{ format.product?.formatDetail }}
|
||||
<span class="ml-1">{{ format.catalogAvailability?.price?.value?.value | currency: '€' }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</ui-slider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="product-actions">
|
||||
<button *ngIf="!(store.isDownload$ | async)" class="cta-availabilities" (click)="showAvailabilities()">
|
||||
Vorrätig in anderer Filiale?
|
||||
</button>
|
||||
<button
|
||||
class="cta-continue"
|
||||
(click)="showPurchasingModal()"
|
||||
[disabled]="
|
||||
!(isAvailable$ | async) || (fetchingAvailabilities$ | async) || (item?.features && (item?.features)[0]?.key === 'PFO')
|
||||
"
|
||||
>
|
||||
In den Warenkorb
|
||||
</button>
|
||||
</div>
|
||||
<hr class="bg-[#E6EFF9] border-t-2 my-3" />
|
||||
|
||||
<hr />
|
||||
<ng-container *ngIf="item.family?.length > 0">
|
||||
<div class="product-formats">
|
||||
<span class="label">Auch verfügbar als</span>
|
||||
|
||||
<ui-slider [scrollDistance]="250">
|
||||
<a
|
||||
class="product-family"
|
||||
*ngFor="let format of item.family"
|
||||
[routerLink]="['/kunde', applicationService.activatedProcessId, 'product', 'details', 'ean', format.product.ean]"
|
||||
>
|
||||
<span class="format-detail">
|
||||
<img
|
||||
*ngIf="!!format.product?.format"
|
||||
[src]="'/assets/images/OF_Icon_' + format.product?.format + '.svg'"
|
||||
alt="format icon"
|
||||
/>
|
||||
{{ format.product?.formatDetail }}
|
||||
<span class="price">{{ format.catalogAvailability?.price?.value?.value | currency: '€' }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</ui-slider>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
</ng-container>
|
||||
|
||||
<div class="product-description" *ngIf="item.texts?.length > 0">
|
||||
<div class="info">
|
||||
<div
|
||||
#description
|
||||
class="page-article-details__product-description flex flex-col flex-grow overflow-hidden overflow-y-scroll"
|
||||
*ngIf="item.texts?.length > 0"
|
||||
>
|
||||
<div class="whitespace-pre-line">
|
||||
{{ item.texts[0].value }}
|
||||
</div>
|
||||
|
||||
<div class="product-text">
|
||||
<button class="font-bold flex flex-row text-[#0556B4] items-center mt-2" *ngIf="!showMore" (click)="showMore = !showMore">
|
||||
Mehr <ui-icon class="ml-2" size="15px" icon="arrow"></ui-icon>
|
||||
</button>
|
||||
|
||||
<div
|
||||
*ngIf="showMore"
|
||||
class="page-article-details__product-description-text flex flex-col whitespace-pre-line mb-px-100 break-words"
|
||||
>
|
||||
<span *ngFor="let text of item.texts | slice: 1">
|
||||
<h3 class="header">{{ text.label }}</h3>
|
||||
<h3 class="my-4 text-regular font-bold">{{ text.label }}</h3>
|
||||
{{ text.value }}
|
||||
</span>
|
||||
<button class="scroll-top-cta" (click)="scrollTop()">
|
||||
<ui-icon class="arrow" icon="arrow" size="20px"></ui-icon>
|
||||
|
||||
<button class="font-bold flex flex-row text-[#0556B4] items-center mt-2" (click)="showMore = !showMore">
|
||||
<ui-icon class="transform ml-0 mr-2 rotate-180" size="15px" icon="arrow"></ui-icon> Weniger
|
||||
</button>
|
||||
|
||||
<button class="page-article-details__scroll-top-cta" (click)="scrollTop(description)">
|
||||
<ui-icon class="text-[#0556B4]" icon="arrow" size="20px"></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!showRecommendations">
|
||||
<div
|
||||
*ngIf="store.item$ | async; let item"
|
||||
class="page-article-details__actions w-full absolute text-center left-0 bottom-10 z-fixed"
|
||||
>
|
||||
<button
|
||||
*ngIf="!(store.isDownload$ | async)"
|
||||
class="text-brand border-2 border-brand bg-white font-bold text-lg px-[1.375rem] py-4 rounded-full mr-px-30"
|
||||
(click)="showAvailabilities()"
|
||||
>
|
||||
Bestände in anderen Filialen
|
||||
</button>
|
||||
<button
|
||||
class="text-white bg-brand border-brand font-bold text-lg px-[1.375rem] py-4 rounded-full border-none no-underline"
|
||||
(click)="showPurchasingModal()"
|
||||
[disabled]="
|
||||
!(isAvailable$ | async) || (fetchingAvailabilities$ | async) || (item?.features && (item?.features)[0]?.key === 'PFO')
|
||||
"
|
||||
>
|
||||
In den Warenkorb
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="page-article-details__product-recommendations -mx-5">
|
||||
<button
|
||||
*ngIf="store.item$ | async; let item"
|
||||
class="shadow-[#dce2e9_0px_-2px_18px_0px] sticky bottom-4 border-none outline-none left-0 right-0 flex items-center px-5 h-14 min-h-[3.5rem] bg-white w-full"
|
||||
(click)="showRecommendations = true"
|
||||
>
|
||||
<span class="uppercase text-[#0556B4] font-bold text-small">Empfehlungen</span>
|
||||
<img class="absolute right-5 bottom-3 h-12" src="assets/images/recommendation_tag.png" alt="recommendation icon" />
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<button *ngIf="store.item$ | async; let item" class="product-recommendations" (click)="showRecommendations = true">
|
||||
<span class="label">Empfehlungen</span>
|
||||
<img src="assets/images/recommendation_tag.png" alt="recommendation icon" />
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
<div class="recommendations-overlay" @slideYAnimation *ngIf="showRecommendations">
|
||||
<button class="product-button" (click)="showRecommendations = false">{{ (store.item$ | async)?.product?.name }}</button>
|
||||
<div class="page-article-details__recommendations-overlay absolute top-16 rounded-t-card" @slideYAnimation *ngIf="showRecommendations">
|
||||
<button
|
||||
class="h-[3.75rem] shadow-[0_-2px_24px_0_#dce2e9] flex flex-row justify-center items-center w-full text-xl bg-white text-ucla-blue font-bold border-none outline-none rounded-t-card"
|
||||
(click)="showRecommendations = false"
|
||||
>
|
||||
{{ (store.item$ | async)?.product?.name }}
|
||||
</button>
|
||||
<page-article-recommendations (close)="showRecommendations = false"></page-article-recommendations>
|
||||
</div>
|
||||
|
||||
@@ -1,269 +1,99 @@
|
||||
:host {
|
||||
@apply flex flex-col;
|
||||
@apply box-border block h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||
}
|
||||
|
||||
.product-card {
|
||||
@apply flex flex-col bg-white w-full rounded-card shadow-card;
|
||||
|
||||
.product-details {
|
||||
@apply flex flex-row p-5;
|
||||
|
||||
.bookmark {
|
||||
@apply absolute flex;
|
||||
top: 52px;
|
||||
right: 25px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.bookmark-badge {
|
||||
@apply p-0 m-0 outline-none border-none bg-transparent relative;
|
||||
}
|
||||
|
||||
.promotion-badge {
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.bookmark-badge-gap {
|
||||
@apply mt-px-35;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
@apply flex flex-col items-center justify-start mr-5;
|
||||
|
||||
.recessions {
|
||||
@apply flex flex-col items-center mt-4 bg-transparent border-none outline-none;
|
||||
|
||||
.cta-recessions {
|
||||
@apply text-regular text-dark-cerulean font-bold mt-2;
|
||||
}
|
||||
}
|
||||
|
||||
.image-button {
|
||||
@apply border-none outline-none bg-transparent relative;
|
||||
|
||||
ui-icon {
|
||||
@apply absolute text-dark-cerulean inline-block;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
@apply rounded-xl shadow-card;
|
||||
box-shadow: 0 0 18px 0 #b8b3b7;
|
||||
max-height: 315px;
|
||||
max-width: 195px;
|
||||
}
|
||||
}
|
||||
|
||||
.product-info {
|
||||
@apply w-full;
|
||||
|
||||
.title {
|
||||
@apply text-3xl font-bold mb-6;
|
||||
}
|
||||
|
||||
.format,
|
||||
.ssc,
|
||||
.quantity {
|
||||
@apply font-bold text-lg;
|
||||
}
|
||||
|
||||
.stock {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.quantity {
|
||||
@apply flex justify-end mt-4;
|
||||
|
||||
ui-icon {
|
||||
@apply mr-1 text-ucla-blue;
|
||||
}
|
||||
}
|
||||
|
||||
.format {
|
||||
@apply flex items-center;
|
||||
|
||||
.format-icon {
|
||||
@apply flex mr-2;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.ssc {
|
||||
@apply flex justify-end my-2;
|
||||
}
|
||||
|
||||
.price {
|
||||
@apply font-bold text-xl;
|
||||
}
|
||||
|
||||
.shelfinfo {
|
||||
@apply text-ucla-blue;
|
||||
}
|
||||
|
||||
.fetching {
|
||||
@apply w-52 h-px-20;
|
||||
background-color: #e6eff9;
|
||||
animation: load 0.75s linear infinite;
|
||||
}
|
||||
|
||||
.xsmall {
|
||||
@apply w-6;
|
||||
}
|
||||
|
||||
.small {
|
||||
@apply w-16;
|
||||
}
|
||||
|
||||
.medium {
|
||||
@apply w-40;
|
||||
}
|
||||
|
||||
.availability-icons {
|
||||
@apply flex flex-row items-center justify-end text-dark-cerulean mt-4;
|
||||
|
||||
ui-icon {
|
||||
@apply mx-1;
|
||||
}
|
||||
|
||||
.truck {
|
||||
@apply -mb-px-5 -mt-px-5;
|
||||
}
|
||||
|
||||
.truck_b2b {
|
||||
@apply -mb-px-10 -mt-px-10;
|
||||
}
|
||||
|
||||
.label {
|
||||
@apply font-bold;
|
||||
}
|
||||
|
||||
.download-icon {
|
||||
@apply flex flex-row items-center;
|
||||
}
|
||||
}
|
||||
|
||||
.cta-print {
|
||||
@apply bg-transparent text-brand font-bold text-xl outline-none border-none p-0;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
@apply grid items-end;
|
||||
grid-template-columns: auto auto;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
@apply text-right self-start;
|
||||
}
|
||||
|
||||
hr {
|
||||
@apply bg-glitter h-1;
|
||||
}
|
||||
|
||||
.product-description {
|
||||
@apply flex flex-col flex-grow px-5 py-5;
|
||||
min-height: calc(100vh - 769px);
|
||||
|
||||
.info {
|
||||
@apply whitespace-pre-line;
|
||||
}
|
||||
|
||||
.product-text {
|
||||
@apply flex flex-col whitespace-pre-line mb-px-100 break-words;
|
||||
|
||||
h3 {
|
||||
@apply my-4;
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply text-regular font-bold;
|
||||
}
|
||||
|
||||
.scroll-top-cta {
|
||||
@apply flex items-center justify-center self-end border-none outline-none bg-white relative rounded p-0 mt-8 mr-4;
|
||||
box-shadow: 0px 0px 20px 0px rgba(89, 100, 112, 0.5);
|
||||
transform: rotate(-90deg);
|
||||
border-radius: 100%;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
|
||||
.arrow {
|
||||
color: #1f466c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-actions {
|
||||
@apply text-right px-5 py-4;
|
||||
|
||||
.cta-availabilities {
|
||||
@apply text-brand border-none border-brand bg-white font-bold text-lg px-4 py-2 rounded-full;
|
||||
}
|
||||
.cta-continue {
|
||||
@apply text-white bg-brand font-bold text-lg px-4 py-2 rounded-full border-none ml-4 no-underline;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-inactive-branch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-formats {
|
||||
@apply grid whitespace-nowrap items-center px-5 py-4;
|
||||
grid-template-rows: auto;
|
||||
grid-template-columns: auto 1fr;
|
||||
max-width: 100%;
|
||||
|
||||
.label {
|
||||
@apply mr-2;
|
||||
}
|
||||
|
||||
.product-family {
|
||||
@apply mr-4 text-active-customer font-bold no-underline px-2;
|
||||
|
||||
.format-detail {
|
||||
@apply flex items-center;
|
||||
|
||||
img {
|
||||
@apply mr-2;
|
||||
}
|
||||
}
|
||||
|
||||
.price {
|
||||
@apply ml-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.page-article-details__container {
|
||||
@apply h-full w-full bg-white rounded-card shadow-card flex flex-col;
|
||||
}
|
||||
|
||||
.product-recommendations {
|
||||
@apply sticky bottom-0 border-none outline-none left-0 right-0 flex items-center px-5 h-16 bg-white w-full;
|
||||
box-shadow: #dce2e9 0px -2px 18px 0px;
|
||||
|
||||
.label {
|
||||
@apply uppercase text-active-customer font-bold text-small;
|
||||
}
|
||||
|
||||
img {
|
||||
@apply absolute right-5 bottom-5 h-12;
|
||||
}
|
||||
.page-article-details__product-details {
|
||||
@apply grid gap-x-5;
|
||||
grid-template-columns: max-content auto;
|
||||
grid-template-rows: 2.1875rem repeat(11, minmax(auto, max-content));
|
||||
grid-template-areas:
|
||||
'. . . bookmark'
|
||||
'image contributors contributors contributors'
|
||||
'image title title print'
|
||||
'image title title .'
|
||||
'image misc misc price'
|
||||
'image misc misc price'
|
||||
'image origin origin stock'
|
||||
'image origin origin stock'
|
||||
'image specs availabilities availabilities'
|
||||
'image specs ssc ssc'
|
||||
'image . ssc ssc'
|
||||
'image . ssc ssc';
|
||||
}
|
||||
|
||||
.recommendations-overlay {
|
||||
@apply absolute w-full top-0 rounded-t-card;
|
||||
top: 56px;
|
||||
.page-article-details__product-bookmark {
|
||||
grid-area: bookmark;
|
||||
}
|
||||
|
||||
.product-button {
|
||||
@apply flex flex-row justify-center items-center w-full text-xl bg-white text-ucla-blue font-bold border-none outline-none rounded-t-card;
|
||||
box-shadow: 0 -2px 24px 0 #dce2e9;
|
||||
height: 60px;
|
||||
.page-article-details__product-image-recessions {
|
||||
grid-area: image;
|
||||
}
|
||||
|
||||
.page-article-details__product-contributors {
|
||||
grid-area: contributors;
|
||||
}
|
||||
|
||||
.page-article-details__product-print {
|
||||
grid-area: print;
|
||||
}
|
||||
|
||||
.page-article-details__product-title {
|
||||
grid-area: title;
|
||||
}
|
||||
|
||||
.page-article-details__product-misc {
|
||||
grid-area: misc;
|
||||
}
|
||||
|
||||
.page-article-details__product-price-info {
|
||||
grid-area: price;
|
||||
}
|
||||
|
||||
.page-article-details__product-origin-infos {
|
||||
grid-area: origin;
|
||||
}
|
||||
|
||||
.page-article-details__product-stock {
|
||||
grid-area: stock;
|
||||
}
|
||||
|
||||
.page-article-details__product-ean-specs {
|
||||
grid-area: specs;
|
||||
}
|
||||
|
||||
.page-article-details__product-availabilities {
|
||||
grid-area: availabilities;
|
||||
}
|
||||
|
||||
.page-article-details__shelf-ssc {
|
||||
grid-area: ssc;
|
||||
}
|
||||
|
||||
.page-article-details__product-description-text {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.page-article-details__product-formats {
|
||||
@apply grid whitespace-nowrap items-center max-w-full;
|
||||
grid-template-rows: auto;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
|
||||
.page-article-details__scroll-top-cta {
|
||||
@apply flex items-center justify-center self-end border-none outline-none bg-white relative rounded p-0 mt-8 mr-4;
|
||||
box-shadow: 0px 0px 20px 0px rgba(89, 100, 112, 0.5);
|
||||
transform: rotate(-90deg);
|
||||
border-radius: 100%;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
}
|
||||
|
||||
.page-article-details__actions {
|
||||
&:disabled {
|
||||
@apply bg-inactive-branch;
|
||||
}
|
||||
}
|
||||
|
||||
.autor {
|
||||
@apply text-active-customer font-bold no-underline;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BranchDTO } from '@swagger/checkout';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { ModalReviewsComponent } from '@modal/reviews';
|
||||
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
||||
import { debounceTime, filter, first, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
import { debounceTime, filter, first, map, shareReplay, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { ArticleDetailsStore } from './article-details.store';
|
||||
import { ModalImagesComponent } from 'apps/modal/images/src/public-api';
|
||||
import { ProductImageService } from 'apps/cdn/product-image/src/public-api';
|
||||
@@ -20,6 +20,8 @@ import { DateAdapter } from '@ui/common';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { PurchaseOptionsModalService } from '@shared/modals/purchase-options-modal';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'page-article-details',
|
||||
@@ -113,6 +115,19 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
get isTablet$() {
|
||||
return this._environment.matchTablet$.pipe(
|
||||
map((state) => state?.matches),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
|
||||
get resultsPath() {
|
||||
return this._navigationService.getArticleSearchResultsPath(this.applicationService.activatedProcessId);
|
||||
}
|
||||
|
||||
showMore: boolean = false;
|
||||
|
||||
constructor(
|
||||
public readonly applicationService: ApplicationService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
@@ -126,6 +141,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
public elementRef: ElementRef,
|
||||
private _purchaseOptionsModalService: PurchaseOptionsModalService,
|
||||
private _availability: DomainAvailabilityService,
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
private _environment: EnvironmentService,
|
||||
private _router: Router
|
||||
) {}
|
||||
|
||||
@@ -161,16 +178,30 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
filter((f) => !!f)
|
||||
);
|
||||
|
||||
const more$ = this.activatedRoute.params.subscribe(() => (this.showMore = false));
|
||||
|
||||
this.subscriptions.add(processIdSubscription);
|
||||
this.subscriptions.add(more$);
|
||||
this.subscriptions.add(this.store.loadItemById(id$));
|
||||
this.subscriptions.add(this.store.loadItemByEan(ean$));
|
||||
this.subscriptions.add(this.store.item$.pipe(filter((item) => !!item)).subscribe((item) => this.updateBreadcrumb(item)));
|
||||
this.subscriptions.add(
|
||||
this.store.item$
|
||||
.pipe(
|
||||
withLatestFrom(this.isTablet$),
|
||||
filter(([item, isTablet]) => !!item)
|
||||
)
|
||||
.subscribe(([item, isTablet]) => (isTablet ? this.updateBreadcrumb(item) : this.updateBreadcrumbDesktop(item)))
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.unsubscribe();
|
||||
}
|
||||
|
||||
getDetailsPath(ean?: string) {
|
||||
return this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, ean });
|
||||
}
|
||||
|
||||
async updateBreadcrumb(item: ItemDTO) {
|
||||
const crumbs = await this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog', 'details', `${item.id}`])
|
||||
@@ -184,13 +215,41 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
this.breadcrumb.addBreadcrumbIfNotExists({
|
||||
key: this.applicationService.activatedProcessId,
|
||||
name: item.product?.name,
|
||||
path: `/kunde/${this.applicationService.activatedProcessId}/product/details/${item.id}`,
|
||||
path: this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: item.id }),
|
||||
params: this.activatedRoute.snapshot.queryParams,
|
||||
tags: ['catalog', 'details', `${item.id}`],
|
||||
section: 'customer',
|
||||
});
|
||||
}
|
||||
|
||||
async updateBreadcrumbDesktop(item: ItemDTO) {
|
||||
const crumbs = await this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog', 'details'])
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
if (crumbs.length === 0) {
|
||||
this.breadcrumb.addBreadcrumbIfNotExists({
|
||||
key: this.applicationService.activatedProcessId,
|
||||
name: item.product?.name,
|
||||
path: this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: item.id }),
|
||||
params: this.activatedRoute.snapshot.queryParams,
|
||||
tags: ['catalog', 'details', `${item.id}`],
|
||||
section: 'customer',
|
||||
});
|
||||
} else {
|
||||
const crumb = crumbs.find((_) => true);
|
||||
this.breadcrumb.patchBreadcrumb(crumb.id, {
|
||||
key: this.applicationService.activatedProcessId,
|
||||
name: item.product?.name,
|
||||
path: this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: item.id }),
|
||||
params: this.activatedRoute.snapshot.queryParams,
|
||||
tags: ['catalog', 'details', `${item.id}`],
|
||||
section: 'customer',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async print() {
|
||||
const item = await this.store.item$.pipe(first()).toPromise();
|
||||
this.uiModal.open({
|
||||
@@ -302,9 +361,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
scrollTop() {
|
||||
const element = this.elementRef.nativeElement.closest('.main-wrapper');
|
||||
element?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
scrollTop(div: HTMLDivElement) {
|
||||
div?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
loadImage() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
:host {
|
||||
@apply flex flex-col bg-white;
|
||||
@apply flex flex-col bg-white h-[calc(100vh-16.5rem-3.75rem)] desktop-small:h-[calc(100vh-15.1rem-3.75rem)];
|
||||
box-shadow: 0px -2px 24px 0px #dce2e9;
|
||||
height: calc(100vh - 342px);
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
||||
@@ -1,13 +1 @@
|
||||
<button class="filter" [class.active]="hasFilter$ | async" (click)="filterActive$.next(true); shellFilterOverlay.open()">
|
||||
<ui-icon size="20px" icon="filter_alit"></ui-icon>
|
||||
<span class="label">Filter</span>
|
||||
</button>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
<shell-filter-overlay #shellFilterOverlay>
|
||||
<page-article-search-filter
|
||||
*ngIf="filterActive$ | async"
|
||||
(close)="filterActive$.next(false); shellFilterOverlay.close()"
|
||||
></page-article-search-filter>
|
||||
</shell-filter-overlay>
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
:host {
|
||||
@apply flex flex-col w-full box-content relative;
|
||||
}
|
||||
|
||||
.filter {
|
||||
@apply font-sans flex self-end items-center mb-4 font-bold bg-wild-blue-yonder border-0 text-regular py-px-8 px-px-15 rounded-filter justify-center z-sticky;
|
||||
width: 106px;
|
||||
min-width: 106px;
|
||||
|
||||
.label {
|
||||
@apply ml-px-5;
|
||||
}
|
||||
|
||||
&.active {
|
||||
@apply bg-active-customer text-white ml-px-5;
|
||||
}
|
||||
@apply flex flex-col w-full h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)] box-content relative;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { UiFilterAutocompleteProvider } from '@ui/filter';
|
||||
import { isEqual } from 'lodash';
|
||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||
import { filter, first, map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from './article-search.store';
|
||||
import { FocusSearchboxEvent } from './focus-searchbox.event';
|
||||
import { ArticleSearchMainAutocompleteProvider } from './providers';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { FilterAutocompleteProvider } from 'apps/shared/components/filter/src/lib';
|
||||
import { isEqual } from 'lodash';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'page-article-search',
|
||||
@@ -15,9 +17,8 @@ import { ArticleSearchMainAutocompleteProvider } from './providers';
|
||||
styleUrls: ['article-search.component.scss'],
|
||||
providers: [
|
||||
FocusSearchboxEvent,
|
||||
ArticleSearchService,
|
||||
{
|
||||
provide: UiFilterAutocompleteProvider,
|
||||
provide: FilterAutocompleteProvider,
|
||||
useClass: ArticleSearchMainAutocompleteProvider,
|
||||
multi: true,
|
||||
},
|
||||
@@ -28,22 +29,16 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject();
|
||||
private _processId$: Observable<number>;
|
||||
|
||||
initialFilter$ = this._articleSearch.filter$.pipe(
|
||||
filter((filter) => !!filter),
|
||||
first()
|
||||
);
|
||||
get isTablet() {
|
||||
return this._environmentService.matchTablet();
|
||||
}
|
||||
|
||||
hasFilter$ = this._articleSearch.filter$.pipe(
|
||||
withLatestFrom(this.initialFilter$),
|
||||
map(([filter, initialFilter]) => !isEqual(filter?.getQueryParams(), initialFilter?.getQueryParams()))
|
||||
);
|
||||
|
||||
filterActive$ = new BehaviorSubject<boolean>(false);
|
||||
constructor(
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _router: Router,
|
||||
private _articleSearch: ArticleSearchService,
|
||||
private _activatedRoute: ActivatedRoute
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
private _environmentService: EnvironmentService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -60,12 +55,17 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
|
||||
.subscribe(([state, processId]) => {
|
||||
if (state.searchState === '') {
|
||||
const params = state.filter.getQueryParams();
|
||||
if (state.hits === 1) {
|
||||
const item = state.items.find((f) => f);
|
||||
this._router.navigate(['/kunde', processId, 'product', 'details', item.id]);
|
||||
this._navigationService.navigateToDetails({
|
||||
processId,
|
||||
itemId: item.id,
|
||||
queryParams: this.isTablet ? undefined : params,
|
||||
});
|
||||
} else {
|
||||
const params = state.filter.getQueryParams();
|
||||
this._router.navigate(['/kunde', processId, 'product', 'search', 'results'], {
|
||||
this._navigationService.navigateToResults({
|
||||
processId,
|
||||
queryParams: params,
|
||||
});
|
||||
}
|
||||
@@ -73,6 +73,20 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||
const clean = { ...params };
|
||||
|
||||
for (const key in clean) {
|
||||
if (Object.prototype.hasOwnProperty.call(clean, key)) {
|
||||
if (clean[key] == undefined) {
|
||||
delete clean[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clean;
|
||||
}
|
||||
|
||||
initProcessId() {
|
||||
this._processId$ = this._activatedRoute.parent.data.pipe(map((data) => Number(data.processId)));
|
||||
}
|
||||
@@ -96,7 +110,7 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
||||
await this._breadcrumb.addBreadcrumbIfNotExists({
|
||||
key: processId,
|
||||
name: 'Artikelsuche',
|
||||
path: `/kunde/${processId}/product`,
|
||||
path: this._navigationService.getArticleSearchBasePath(processId),
|
||||
params: queryParams,
|
||||
tags: ['catalog', 'main'],
|
||||
section: 'customer',
|
||||
|
||||
@@ -6,12 +6,13 @@ import { ArticleSearchComponent } from './article-search.component';
|
||||
import { SearchResultsModule } from './search-results/search-results.module';
|
||||
import { SearchMainModule } from './search-main/search-main.module';
|
||||
import { SearchFilterModule } from './search-filter/search-filter.module';
|
||||
import { ArticleSearchService } from './article-search.store';
|
||||
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, RouterModule, UiIconModule, SearchResultsModule, SearchMainModule, SearchFilterModule, SharedFilterOverlayModule],
|
||||
exports: [ArticleSearchComponent],
|
||||
declarations: [ArticleSearchComponent],
|
||||
providers: [],
|
||||
providers: [ArticleSearchService],
|
||||
})
|
||||
export class ArticleSearchModule {}
|
||||
|
||||
@@ -3,24 +3,31 @@ import { DomainCatalogService } from '@domain/catalog';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { debounceTime, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
|
||||
import { UiFilter } from '@ui/filter';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { ItemDTO, QueryTokenDTO } from '@swagger/cat';
|
||||
import { ItemDTO, QueryTokenDTO, UISettingsDTO } from '@swagger/cat';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { BranchDTO } from '@swagger/checkout';
|
||||
import { Filter } from 'apps/shared/components/filter/src/lib';
|
||||
|
||||
export interface ArticleSearchState {
|
||||
processId: number;
|
||||
filter: UiFilter;
|
||||
filter: Filter;
|
||||
searchState: '' | 'fetching' | 'empty' | 'error';
|
||||
items: ItemDTO[];
|
||||
hits: number;
|
||||
selectedBranch: BranchDTO;
|
||||
selectedItemIds: number[];
|
||||
defaultSettings?: UISettingsDTO;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
get defaultSettings() {
|
||||
return this.get((s) => s.defaultSettings);
|
||||
}
|
||||
|
||||
readonly defaultSettings$ = this.select((s) => s.defaultSettings);
|
||||
|
||||
get processId() {
|
||||
return this.get((s) => s.processId);
|
||||
}
|
||||
@@ -100,19 +107,19 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
}
|
||||
|
||||
async setDefaultFilter(defaultQueryParams?: Record<string, string>) {
|
||||
const filter = await this.catalog
|
||||
.getSettings()
|
||||
.pipe(map((settings) => UiFilter.create(settings)))
|
||||
.toPromise();
|
||||
const defaultSettings = await this.catalog.getSettings().toPromise();
|
||||
|
||||
const filter = Filter.create(defaultSettings);
|
||||
|
||||
if (!!defaultQueryParams) {
|
||||
filter?.fromQueryParams(defaultQueryParams);
|
||||
}
|
||||
|
||||
this.setFilter(filter);
|
||||
this.patchState({ defaultSettings });
|
||||
}
|
||||
|
||||
setFilter(filter: UiFilter) {
|
||||
setFilter(filter: Filter) {
|
||||
this.patchState({ filter });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import { UiFilterAutocomplete, UiFilterAutocompleteProvider, UiInput } from '@ui/filter';
|
||||
import { FilterAutocomplete, FilterAutocompleteProvider, FilterInput } from 'apps/shared/components/filter/src/lib';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class ArticleSearchMainAutocompleteProvider extends UiFilterAutocompleteProvider {
|
||||
export class ArticleSearchMainAutocompleteProvider extends FilterAutocompleteProvider {
|
||||
for = 'catalog';
|
||||
|
||||
constructor(private domainCatalogSearch: DomainCatalogService) {
|
||||
super();
|
||||
}
|
||||
|
||||
complete(input: UiInput): Observable<UiFilterAutocomplete[]> {
|
||||
complete(input: FilterInput): Observable<FilterAutocomplete[]> {
|
||||
const token = input?.parent?.parent?.getQueryToken();
|
||||
const filter = token?.filter;
|
||||
const type = Object.keys(token?.input).join(';');
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
<ng-container *ngIf="filter$ | async; let filter">
|
||||
<div class="catalog-search-filter-content">
|
||||
<button class="btn-close" type="button" (click)="close.emit()">
|
||||
<ui-icon icon="close" size="20px"></ui-icon>
|
||||
</button>
|
||||
<div class="w-full flex flex-row justify-end items-center">
|
||||
<button (click)="clearFilter(filter)" class="text-[#0556B4] mr-[0.8125rem]">Alle Filter entfernen</button>
|
||||
<button class="text-black p-4 outline-none border-none bg-transparent" type="button" (click)="closeFilter()">
|
||||
<ui-icon icon="close" size="15px"></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="catalog-search-filter-content-main">
|
||||
<h1 class="text-3xl font-bold text-center py-4">Filter</h1>
|
||||
<ui-filter
|
||||
<div class="catalog-search-filter-content-main -mt-12">
|
||||
<h1 class="text-2xl text-[1.625rem] font-bold text-center pt-6 pb-10">Filter</h1>
|
||||
<shared-filter
|
||||
[filter]="filter"
|
||||
[loading]="fetching$ | async"
|
||||
(search)="applyFilter(filter)"
|
||||
[hint]="searchboxHint$ | async"
|
||||
resizeInputOptionsToElement="page-article-search-filter .cta-wrapper"
|
||||
[scanner]="true"
|
||||
></ui-filter>
|
||||
></shared-filter>
|
||||
</div>
|
||||
|
||||
<div class="cta-wrapper">
|
||||
<button class="cta-reset-filter" (click)="resetFilter(filter)" [disabled]="fetching$ | async">
|
||||
Filter zurücksetzen
|
||||
</button>
|
||||
|
||||
<button class="cta-apply-filter" (click)="applyFilter(filter)" [disabled]="fetching$ | async">
|
||||
<ui-spinner [show]="fetching$ | async">
|
||||
Filter anwenden
|
||||
</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cta-wrapper">
|
||||
<button class="cta-reset-filter" (click)="resetFilter(filter)" [disabled]="fetching$ | async">
|
||||
Filter zurücksetzen
|
||||
</button>
|
||||
|
||||
<button class="cta-apply-filter" (click)="applyFilter(filter)" [disabled]="fetching$ | async">
|
||||
<ui-spinner [show]="fetching$ | async">
|
||||
Filter anwenden
|
||||
</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
:host {
|
||||
@apply block bg-glitter;
|
||||
@apply block bg-white h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||
}
|
||||
|
||||
.catalog-search-filter-content {
|
||||
@apply relative mx-auto p-4;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
@apply absolute text-cool-grey top-3 p-4 right-4 outline-none border-none bg-transparent;
|
||||
}
|
||||
|
||||
.catalog-search-filter-content-main {
|
||||
h1.title {
|
||||
@apply text-center;
|
||||
@@ -17,14 +13,12 @@
|
||||
}
|
||||
|
||||
.cta-wrapper {
|
||||
@apply fixed bottom-8 whitespace-nowrap;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
@apply text-center whitespace-nowrap absolute bottom-8 left-0 w-full;
|
||||
}
|
||||
|
||||
.cta-reset-filter,
|
||||
.cta-apply-filter {
|
||||
@apply text-lg font-bold px-6 py-3 rounded-full border-solid border-2 border-brand outline-none mx-2;
|
||||
@apply text-lg font-bold px-6 py-[0.85rem] rounded-full border-solid border-2 border-brand outline-none mx-2;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-inactive-branch cursor-not-allowed border-none text-white;
|
||||
@@ -38,3 +32,7 @@
|
||||
.cta-apply-filter {
|
||||
@apply text-white bg-brand;
|
||||
}
|
||||
|
||||
::ng-deep page-article-search-filter shared-filter shared-filter-input-group-main {
|
||||
@apply desktop:hidden px-16;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { UiFilter, UiFilterComponent } from '@ui/filter';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, take } from 'rxjs/operators';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { first, map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from '../article-search.store';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { Filter, FilterComponent } from 'apps/shared/components/filter/src/lib';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
|
||||
@Component({
|
||||
selector: 'page-article-search-filter',
|
||||
@@ -10,51 +15,104 @@ import { ArticleSearchService } from '../article-search.store';
|
||||
styleUrls: ['search-filter.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ArticleSearchFilterComponent implements OnInit {
|
||||
export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
@Output()
|
||||
close = new EventEmitter();
|
||||
|
||||
_processId$ = this._activatedRoute.parent.data.pipe(map((data) => Number(data.processId)));
|
||||
|
||||
fetching$: Observable<boolean>;
|
||||
|
||||
filter$: Observable<UiFilter>;
|
||||
filter$: Observable<Filter>;
|
||||
|
||||
searchboxHint$ = this.articleSearch.searchboxHint$;
|
||||
|
||||
@ViewChild(UiFilterComponent, { static: false })
|
||||
uiFilterComponent: UiFilterComponent;
|
||||
@ViewChild(FilterComponent, { static: false })
|
||||
uiFilterComponent: FilterComponent;
|
||||
|
||||
constructor(private articleSearch: ArticleSearchService) {}
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
private _onDestroy$ = new Subject();
|
||||
|
||||
constructor(
|
||||
private articleSearch: ArticleSearchService,
|
||||
private _environment: EnvironmentService,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
public application: ApplicationService,
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
private _breadcrumb: BreadcrumbService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.fetching$ = this.articleSearch.fetching$;
|
||||
this.filter$ = this.articleSearch.filter$.pipe(
|
||||
map((filter) => UiFilter.create(filter))
|
||||
// tap((filter) =>
|
||||
// filter.fromQueryParams({
|
||||
// main_qs: 'harry potter',
|
||||
// filter_format: 'eb;!hc',
|
||||
// filter_dbhwgr: '110;121',
|
||||
// main_author: 'author',
|
||||
// filter_region: '9780*|9781*;97884*',
|
||||
// 'filter_reading-age': '1-10',
|
||||
// main_stock: '1-',
|
||||
// })
|
||||
// )
|
||||
);
|
||||
this.filter$ = this.articleSearch.filter$.pipe(map((filter) => Filter.create(filter)));
|
||||
}
|
||||
|
||||
applyFilter(value: UiFilter) {
|
||||
ngOnDestroy(): void {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
async closeFilter(): Promise<void> {
|
||||
const processId = await this._processId$.pipe(first()).toPromise();
|
||||
const itemId = this._navigationService.getOutletParams(this._activatedRoute)?.right?.id;
|
||||
|
||||
if (this.isTablet) {
|
||||
return this.closeFilterTablet(processId);
|
||||
}
|
||||
|
||||
if (!itemId) {
|
||||
if (this._navigationService.getOutletLocations(this._activatedRoute)?.left === 'search') {
|
||||
return await this._navigationService.navigateToProductSearch({ processId, queryParamsHandling: 'preserve' });
|
||||
}
|
||||
return await this._navigationService.navigateToResults({ processId, queryParamsHandling: 'preserve' });
|
||||
} else {
|
||||
return await this._navigationService.navigateToDetails({ processId, itemId, queryParamsHandling: 'preserve' });
|
||||
}
|
||||
}
|
||||
|
||||
async closeFilterTablet(processId: number): Promise<void> {
|
||||
const latestBreadcrumb = await this._breadcrumb.getLastActivatedBreadcrumbByKey$(processId).pipe(first()).toPromise();
|
||||
if (latestBreadcrumb?.tags?.find((tag) => tag === 'results')) {
|
||||
return await this._navigationService.navigateToResults({ processId, queryParamsHandling: 'preserve' });
|
||||
} else {
|
||||
return await this._navigationService.navigateToProductSearch({ processId, queryParamsHandling: 'preserve' });
|
||||
}
|
||||
}
|
||||
|
||||
applyFilter(value: Filter) {
|
||||
this.uiFilterComponent?.cancelAutocomplete();
|
||||
this.articleSearch.setFilter(value);
|
||||
this.articleSearch.search({ clear: true });
|
||||
this.articleSearch.searchCompleted.pipe(take(1)).subscribe((s) => {
|
||||
if (s.searchState === '') {
|
||||
this.close.emit();
|
||||
}
|
||||
});
|
||||
this.articleSearch.searchCompleted
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
|
||||
.subscribe(([state, processId]) => {
|
||||
if (state.searchState === '') {
|
||||
const params = state.filter.getQueryParams();
|
||||
if (state.hits === 1 && this.isTablet) {
|
||||
const item = state.items.find((f) => f);
|
||||
this._navigationService.navigateToDetails({
|
||||
processId,
|
||||
itemId: item.id,
|
||||
queryParams: this.isTablet ? undefined : params,
|
||||
});
|
||||
} else if (this.isTablet) {
|
||||
this._navigationService.navigateToResults({
|
||||
processId,
|
||||
queryParams: params,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resetFilter(value: UiFilter) {
|
||||
clearFilter(value: Filter) {
|
||||
value.unselectAll();
|
||||
}
|
||||
|
||||
resetFilter(value: Filter) {
|
||||
const queryParams = { main_qs: value?.getQueryParams()?.main_qs || '' };
|
||||
this.articleSearch.setDefaultFilter(queryParams);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { UiFilterNextModule } from '@ui/filter';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiSpinnerModule } from '@ui/spinner';
|
||||
|
||||
import { ArticleSearchFilterComponent } from './search-filter.component';
|
||||
import { FilterNextModule } from 'apps/shared/components/filter/src/lib';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiFilterNextModule, UiIconModule, UiSpinnerModule],
|
||||
imports: [CommonModule, RouterModule, FilterNextModule, UiIconModule, UiSpinnerModule],
|
||||
exports: [ArticleSearchFilterComponent],
|
||||
declarations: [ArticleSearchFilterComponent],
|
||||
providers: [],
|
||||
|
||||
@@ -1,26 +1,50 @@
|
||||
<div class="card-search-article">
|
||||
<h1 class="title">Artikelsuche</h1>
|
||||
<p class="info">
|
||||
<div class="bg-white rounded py-10 px-4 text-center shadow-[0_-2px_24px_0_#dce2e9] h-full">
|
||||
<h1 class="text-2xl text-[1.625rem] font-bold mb-[0.375rem]">Artikelsuche</h1>
|
||||
<p class="text-lg mb-10">
|
||||
Welchen Artikel suchen Sie?
|
||||
</p>
|
||||
<ng-container *ngIf="filter$ | async; let filter">
|
||||
<ui-filter-filter-group-main [inputGroup]="filter?.filter | group: 'main'"></ui-filter-filter-group-main>
|
||||
<ui-filter-input-group-main
|
||||
[hint]="searchboxHint$ | async"
|
||||
[loading]="fetching$ | async"
|
||||
[inputGroup]="filter?.input | group: 'main'"
|
||||
(search)="search(filter)"
|
||||
[showDescription]="false"
|
||||
[scanner]="true"
|
||||
></ui-filter-input-group-main>
|
||||
<shared-filter-filter-group-main
|
||||
class="mb-8 w-full"
|
||||
*ngIf="!(isDesktop$ | async)"
|
||||
[inputGroup]="filter?.filter | group: 'main'"
|
||||
></shared-filter-filter-group-main>
|
||||
<div class="flex flex-row px-12 justify-center desktop:px-0">
|
||||
<shared-filter-input-group-main
|
||||
class="block w-full mr-3 desktop:mx-auto"
|
||||
[hint]="searchboxHint$ | async"
|
||||
[loading]="fetching$ | async"
|
||||
[inputGroup]="filter?.input | group: 'main'"
|
||||
(search)="search(filter)"
|
||||
[showDescription]="false"
|
||||
[scanner]="true"
|
||||
></shared-filter-input-group-main>
|
||||
<a
|
||||
*ngIf="!(isDesktop$ | async)"
|
||||
class="page-search-main__filter w-[6.75rem] h-14 rounded-card font-bold px-5 mb-4 text-lg bg-[#AEB7C1] flex flex-row flex-nowrap items-center justify-center"
|
||||
[class.active]="hasFilter$ | async"
|
||||
[routerLink]="filterRoute"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<ui-svg-icon class="mr-2" icon="filter-variant"></ui-svg-icon>
|
||||
Filter
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="recent-searches-wrapper">
|
||||
<h3 class="recent-searches-header">Deine letzten Suchanfragen</h3>
|
||||
<ul>
|
||||
<li class="recent-searches-items" *ngFor="let recentQuery of history$ | async">
|
||||
<button (click)="setQueryHistory(filter, recentQuery.friendlyName)">
|
||||
<ui-icon icon="search" size="15px"></ui-icon>
|
||||
<p>{{ recentQuery.friendlyName }}</p>
|
||||
<div class="flex flex-col items-start ml-12 desktop:ml-8 py-6 bg-white">
|
||||
<h3 class="text-sm font-bold mb-3">Deine letzten Suchanfragen</h3>
|
||||
<ul class="flex flex-col justify-start overflow-hidden items-start m-0 p-0 bg-white w-full">
|
||||
<li class="list-none pb-3" *ngFor="let recentQuery of history$ | async">
|
||||
<button
|
||||
class="flex flex-row items-center outline-none border-none bg-white text-black text-base m-0 p-0"
|
||||
(click)="setQueryHistory(filter, recentQuery.friendlyName)"
|
||||
>
|
||||
<ui-icon
|
||||
class="flex w-8 h-8 justify-center items-center mr-3 rounded-full text-black bg-[#edeff0]"
|
||||
icon="search"
|
||||
size="0.875rem"
|
||||
></ui-icon>
|
||||
<p class="m-0 p-0 whitespace-nowrap overflow-hidden overflow-ellipsis max-w-[25rem]">{{ recentQuery.friendlyName }}</p>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,71 +1,13 @@
|
||||
:host {
|
||||
@apply flex flex-col box-border;
|
||||
@apply flex flex-col box-border h-full;
|
||||
}
|
||||
|
||||
.title {
|
||||
@apply text-page-heading font-bold;
|
||||
}
|
||||
|
||||
.info {
|
||||
@apply text-2xl mt-1 mb-px-30;
|
||||
}
|
||||
|
||||
.filter-chips {
|
||||
@apply flex flex-row justify-center;
|
||||
}
|
||||
|
||||
.card-search-article {
|
||||
@apply bg-white rounded p-4 text-center;
|
||||
|
||||
box-shadow: 0 -2px 24px 0 #dce2e9;
|
||||
}
|
||||
|
||||
.card-search-article {
|
||||
min-height: calc(100vh - 380px);
|
||||
}
|
||||
|
||||
ui-filter-filter-group-main {
|
||||
@apply mb-8 w-full;
|
||||
.page-search-main__filter {
|
||||
&.active {
|
||||
@apply bg-[#596470] text-white ml-px-5;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep page-article-search-main ui-filter-filter-group-main .ui-filter-chip:not(.selected) {
|
||||
@apply bg-glitter;
|
||||
}
|
||||
|
||||
ui-filter-input-group-main {
|
||||
@apply block mx-auto;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.recent-searches-wrapper {
|
||||
@apply flex flex-col mx-auto items-start py-6 bg-white;
|
||||
width: 50%;
|
||||
z-index: 0;
|
||||
|
||||
.recent-searches-header {
|
||||
@apply text-sm font-bold mb-4;
|
||||
}
|
||||
|
||||
ul {
|
||||
@apply flex flex-col justify-start overflow-hidden items-start m-0 p-0 bg-white w-full;
|
||||
z-index: 0;
|
||||
|
||||
.recent-searches-items {
|
||||
@apply list-none pb-px-15;
|
||||
|
||||
button {
|
||||
@apply flex flex-row items-center outline-none border-none bg-white text-black text-base m-0 p-0;
|
||||
|
||||
ui-icon {
|
||||
@apply flex w-px-35 h-px-35 justify-center items-center mr-3 rounded-full text-black;
|
||||
background-color: #e6eff9;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply m-0 p-0 whitespace-nowrap overflow-hidden overflow-ellipsis;
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import { UiFilter, UiFilterInputGroupMainComponent } from '@ui/filter';
|
||||
import { combineLatest, NEVER, Subscription } from 'rxjs';
|
||||
import { catchError, debounceTime, first, switchMap } from 'rxjs/operators';
|
||||
import { catchError, debounceTime, first, switchMap, map, shareReplay } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from '../article-search.store';
|
||||
import { isEqual } from 'lodash';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/filter/src/lib';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'page-article-search-main',
|
||||
@@ -26,15 +28,39 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
|
||||
subscriptions = new Subscription();
|
||||
|
||||
@ViewChild(UiFilterInputGroupMainComponent, { static: false })
|
||||
uiInputGroupMain: UiFilterInputGroupMainComponent;
|
||||
hasFilter$ = combineLatest([this.searchService.filter$, this.searchService.defaultSettings$]).pipe(
|
||||
map(([filter, defaultFilter]) => {
|
||||
const filterQueryParams = filter?.getQueryParams();
|
||||
return !isEqual(this.resetQueryParamsQueryAndOrderBy(filterQueryParams), Filter.create(defaultFilter).getQueryParams());
|
||||
})
|
||||
);
|
||||
|
||||
@ViewChild(FilterInputGroupMainComponent, { static: false })
|
||||
sharedFilterInputGroupMain: FilterInputGroupMainComponent;
|
||||
|
||||
get isDesktop$() {
|
||||
return this._environment.matchDesktop$.pipe(
|
||||
map((state) => state?.matches),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
|
||||
get filterRoute() {
|
||||
const itemId = this._navigationService?.getOutletParams(this.route)?.right?.id;
|
||||
return this._navigationService.getArticleSearchResultsAndFilterPath({
|
||||
processId: this.application.activatedProcessId,
|
||||
itemId,
|
||||
});
|
||||
}
|
||||
|
||||
constructor(
|
||||
private searchService: ArticleSearchService,
|
||||
private catalog: DomainCatalogService,
|
||||
private route: ActivatedRoute,
|
||||
private application: ApplicationService,
|
||||
private breadcrumb: BreadcrumbService
|
||||
private breadcrumb: BreadcrumbService,
|
||||
private _environment: EnvironmentService,
|
||||
private _navigationService: ProductCatalogNavigationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -44,7 +70,7 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
.subscribe(async ([processId, queryParams]) => {
|
||||
const processChanged = processId !== this.searchService.processId;
|
||||
|
||||
if (!(this.searchService.filter instanceof UiFilter)) {
|
||||
if (!(this.searchService.filter instanceof Filter)) {
|
||||
await this.searchService.setDefaultFilter();
|
||||
}
|
||||
|
||||
@@ -57,6 +83,7 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
const cleanQueryParams = this.cleanupQueryParams(queryParams);
|
||||
|
||||
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams()))) {
|
||||
// Reset Filter on Product Search Shell Navigation click
|
||||
await this.searchService.setDefaultFilter(queryParams);
|
||||
}
|
||||
|
||||
@@ -85,16 +112,30 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
this.updateBreadcrumb(this.searchService.processId, this.searchService.filter.getQueryParams());
|
||||
}
|
||||
|
||||
search(filter: UiFilter) {
|
||||
this.uiInputGroupMain.cancelAutocomplete();
|
||||
search(filter: Filter) {
|
||||
this.sharedFilterInputGroupMain.cancelAutocomplete();
|
||||
this.searchService.setFilter(filter);
|
||||
this.searchService.search({ clear: true });
|
||||
}
|
||||
|
||||
setQueryHistory(filter: UiFilter, query: string) {
|
||||
setQueryHistory(filter: Filter, query: string) {
|
||||
filter.fromQueryParams({ main_qs: query });
|
||||
}
|
||||
|
||||
resetQueryParamsQueryAndOrderBy(params: Record<string, string> = {}) {
|
||||
const clean = { ...params };
|
||||
|
||||
for (const key in clean) {
|
||||
if (key === 'main_qs' || key?.includes('order_by')) {
|
||||
clean[key] = undefined;
|
||||
} else if (key?.includes('order_by')) {
|
||||
delete clean[key];
|
||||
}
|
||||
}
|
||||
|
||||
return clean;
|
||||
}
|
||||
|
||||
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||
const clean = { ...params };
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { UiFilterNextModule } from '@ui/filter';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { ArticleSearchMainComponent } from './search-main.component';
|
||||
import { FilterNextModule } from 'apps/shared/components/filter/src/lib';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiIconModule, UiFilterNextModule],
|
||||
imports: [CommonModule, RouterModule, UiIconModule, FilterNextModule],
|
||||
exports: [ArticleSearchMainComponent],
|
||||
declarations: [ArticleSearchMainComponent],
|
||||
providers: [],
|
||||
|
||||
@@ -1,17 +1,43 @@
|
||||
<div class="thumbnail animation"></div>
|
||||
<div class="col">
|
||||
<div class="author animation"></div>
|
||||
<div class="row">
|
||||
<div class="title animation"></div>
|
||||
<div class="price animation"></div>
|
||||
<ng-container *ngIf="!(mainOutletActive$ | async); else mainOutlet">
|
||||
<div class="bg-ucla-blue rounded-card w-[4.375rem] h-[5.625rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="flex flex-col flex-grow">
|
||||
<div class="h-4 bg-ucla-blue ml-4 mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="flex flex-row justify-between flex-grow">
|
||||
<div class="h-6 bg-ucla-blue ml-4 w-[12.5rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="h-6 bg-ucla-blue ml-4 w-[4.6875rem] animate-[load_1s_linear_infinite]"></div>
|
||||
</div>
|
||||
<div class="flex-grow"></div>
|
||||
<div class="flex flex-row justify-between flex-grow">
|
||||
<div class="h-4 bg-ucla-blue ml-4 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="h-4 bg-ucla-blue ml-4 w-[3.125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
</div>
|
||||
<div class="flex flex-row justify-between flex-grow">
|
||||
<div class="h-4 bg-ucla-blue ml-4 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="h-4 bg-ucla-blue ml-4 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space"></div>
|
||||
<div class="row">
|
||||
<div class="category animation"></div>
|
||||
<div class="stock animation"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #mainOutlet>
|
||||
<div class="bg-ucla-blue rounded-card w-[3rem] h-[4.125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="flex flex-col ml-4 w-[30%]">
|
||||
<div class="h-4 bg-ucla-blue mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="h-6 bg-ucla-blue mb-2 w-[12.5rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="h-6 bg-ucla-blue w-[12.5rem] animate-[load_1s_linear_infinite]"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="manufacturer animation"></div>
|
||||
<div class="ava animation"></div>
|
||||
<div class="flex flex-col ml-14 w-[30%]">
|
||||
<div class="h-4 bg-ucla-blue mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="h-4 bg-ucla-blue mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="h-4 bg-ucla-blue w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col ml-10 w-[20%]">
|
||||
<div class="h-4 bg-ucla-blue mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="h-4 bg-ucla-blue mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="h-4 bg-ucla-blue w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
</div>
|
||||
<div class="flex flex-col ml-2 w-[20%] items-end">
|
||||
<div class="h-4 bg-ucla-blue mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="h-4 bg-ucla-blue mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="h-4 bg-ucla-blue w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,61 +1,3 @@
|
||||
:host {
|
||||
@apply flex flex-row rounded-card bg-white mb-2 p-4;
|
||||
height: 187px;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
width: 70px;
|
||||
height: 90px;
|
||||
@apply bg-ucla-blue rounded-card;
|
||||
}
|
||||
|
||||
.space {
|
||||
@apply flex-grow;
|
||||
}
|
||||
|
||||
.col {
|
||||
@apply flex flex-col flex-grow;
|
||||
}
|
||||
|
||||
.row {
|
||||
@apply flex flex-row justify-between flex-grow;
|
||||
}
|
||||
|
||||
.author {
|
||||
width: 150px;
|
||||
@apply h-4 bg-ucla-blue ml-4 mb-2;
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 300px;
|
||||
@apply h-6 bg-ucla-blue ml-4;
|
||||
}
|
||||
|
||||
.price {
|
||||
width: 100px;
|
||||
@apply h-6 bg-ucla-blue ml-4;
|
||||
}
|
||||
|
||||
.category {
|
||||
width: 200px;
|
||||
@apply h-4 bg-ucla-blue ml-4;
|
||||
}
|
||||
|
||||
.manufacturer {
|
||||
width: 200px;
|
||||
@apply h-4 bg-ucla-blue ml-4;
|
||||
}
|
||||
|
||||
.stock {
|
||||
width: 75px;
|
||||
@apply h-4 bg-ucla-blue ml-4;
|
||||
}
|
||||
|
||||
.ava {
|
||||
width: 150px;
|
||||
@apply h-4 bg-ucla-blue ml-4;
|
||||
}
|
||||
|
||||
.animation {
|
||||
animation: load 1s linear infinite;
|
||||
@apply flex flex-row rounded-card bg-white mb-2 p-4 w-full h-[212px] desktop-small:h-[181px];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, HostBinding } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-search-result-item-loading',
|
||||
@@ -7,5 +10,13 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SearchResultItemLoadingComponent {
|
||||
constructor() {}
|
||||
get mainOutletActive$() {
|
||||
return this._navigationService?.mainOutletActive$(this._activatedRoute).pipe(shareReplay());
|
||||
}
|
||||
|
||||
constructor(private _navigationService: ProductCatalogNavigationService, private _activatedRoute: ActivatedRoute) {}
|
||||
|
||||
@HostBinding('style') get class() {
|
||||
return this._navigationService.mainOutletActive(this._activatedRoute) ? { height: '6.125rem' } : '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +1,122 @@
|
||||
<a class="product-list-result-content" [routerLink]="['/kunde', applicationService.activatedProcessId, 'product', 'details', item?.id]">
|
||||
<div class="item-thumbnail">
|
||||
<img loading="lazy" *ngIf="item?.imageId | thumbnailUrl; let thumbnailUrl" [src]="thumbnailUrl" [alt]="item?.product?.name" />
|
||||
</div>
|
||||
|
||||
<div class="item-contributors">
|
||||
<a
|
||||
*ngFor="let contributor of contributors; let last = last"
|
||||
[routerLink]="['/kunde', applicationService.activatedProcessId, 'product', 'search', 'results']"
|
||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||
(click)="$event?.stopPropagation()"
|
||||
>
|
||||
{{ contributor }}{{ last ? '' : ';' }}
|
||||
</a>
|
||||
<a
|
||||
class="page-search-result-item__item-card hover p-5 desktop-small:px-4 desktop-small:py-[0.625rem] h-[13.25rem] desktop-small:h-[11.3125rem] bg-white border border-solid border-transparent rounded-card"
|
||||
[class.page-search-result-item__item-card-main]="mainOutletActive$ | async"
|
||||
[routerLink]="detailsPath"
|
||||
[routerLinkActive]="!isTablet && !(mainOutletActive$ | async) ? 'active' : ''"
|
||||
[queryParamsHandling]="!isTablet ? 'preserve' : ''"
|
||||
>
|
||||
<div class="page-search-result-item__item-thumbnail text-center mr-4 w-[50px] h-[79px]">
|
||||
<img
|
||||
class="page-search-result-item__item-image w-[50px] h-[79px]"
|
||||
loading="lazy"
|
||||
*ngIf="item?.imageId | thumbnailUrl; let thumbnailUrl"
|
||||
[src]="thumbnailUrl"
|
||||
[alt]="item?.product?.name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="item-title"
|
||||
[class.xl]="item?.product?.name?.length >= 35"
|
||||
[class.lg]="item?.product?.name?.length >= 40"
|
||||
[class.md]="item?.product?.name?.length >= 50"
|
||||
[class.sm]="item?.product?.name?.length >= 60"
|
||||
[class.xs]="item?.product?.name?.length >= 100"
|
||||
class="page-search-result-item__item-grid-container"
|
||||
[class.page-search-result-item__item-grid-container-main]="mainOutletActive$ | async"
|
||||
>
|
||||
{{ item?.product?.name }}
|
||||
</div>
|
||||
<div
|
||||
class="page-search-result-item__item-contributors desktop-small:text-sm font-bold text-[#0556B4] text-ellipsis overflow-hidden max-w-[24rem] whitespace-nowrap"
|
||||
>
|
||||
<a
|
||||
*ngFor="let contributor of contributors; let last = last"
|
||||
[routerLink]="resultsPath"
|
||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||
(click)="$event?.stopPropagation()"
|
||||
>
|
||||
{{ contributor }}{{ last ? '' : ';' }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="item-price">
|
||||
{{ item?.catalogAvailability?.price?.value?.value | currency: 'EUR':'code' }}
|
||||
</div>
|
||||
<div
|
||||
class="page-search-result-item__item-title font-bold text-2xl"
|
||||
[class.text-xl]="item?.product?.name?.length >= 35 && isTablet"
|
||||
[class.text-lg]="item?.product?.name?.length >= 40 && isTablet"
|
||||
[class.text-md]="item?.product?.name?.length >= 50 && isTablet"
|
||||
[class.text-sm]="item?.product?.name?.length >= 60 || !isTablet"
|
||||
[class.text-xs]="item?.product?.name?.length >= 100 || (!isTablet && item?.product?.name?.length >= 70)"
|
||||
>
|
||||
{{ item?.product?.name }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="selectable" class="item-data-selector">
|
||||
<ui-select-bullet [ngModel]="selected" (ngModelChange)="setSelected($event)"></ui-select-bullet>
|
||||
</div>
|
||||
|
||||
<div class="item-stock z-dropdown" [uiOverlayTrigger]="tooltip" [overlayTriggerDisabled]="!(stockTooltipText$ | async)">
|
||||
<ng-container *ngIf="isOrderBranch$ | async">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<ui-icon icon="home" size="1em"></ui-icon>
|
||||
<span
|
||||
*ngIf="inStock$ | async; let stock"
|
||||
[class.skeleton]="stock.inStock === undefined"
|
||||
class="min-w-[1rem] text-right inline-block"
|
||||
>{{ stock?.inStock }}</span
|
||||
>
|
||||
<span>x</span>
|
||||
<div class="page-search-result-item__item-format desktop-small:text-sm">
|
||||
<div *ngIf="item?.product?.format && item?.product?.formatDetail" class="font-bold flex flex-row">
|
||||
<img
|
||||
class="mr-3"
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
loading="lazy"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
{{ item?.product?.formatDetail | substr: 25 }}
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!(isOrderBranch$ | async)">
|
||||
<div class="flex flex-row items-center justify-between z-dropdown">
|
||||
<ui-icon class="block" icon="home" size="1em"></ui-icon>
|
||||
<span class="min-w-[1rem] text-center inline-block">-</span>
|
||||
<span>x</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-8" [closeable]="true">
|
||||
{{ stockTooltipText$ | async }}
|
||||
</ui-tooltip>
|
||||
<!-- <div class="item-stock"><ui-icon icon="home" size="1em"></ui-icon> {{ item?.stockInfos | stockInfos }} x</div> -->
|
||||
</div>
|
||||
|
||||
<div class="item-ssc" [class.xs]="item?.catalogAvailability?.sscText?.length >= 60">
|
||||
{{ item?.catalogAvailability?.ssc }} - {{ item?.catalogAvailability?.sscText }}
|
||||
</div>
|
||||
<div class="page-search-result-item__item-manufacturer desktop-small:text-sm">
|
||||
{{ item?.product?.manufacturer | substr: 18 }} | {{ item?.product?.ean }}
|
||||
</div>
|
||||
|
||||
<div class="item-format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
loading="lazy"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
{{ item?.product?.formatDetail }}
|
||||
</div>
|
||||
<div class="page-search-result-item__item-misc desktop-small:text-sm">
|
||||
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
||||
{{ publicationDate }}
|
||||
</div>
|
||||
|
||||
<div class="item-misc">
|
||||
{{ item?.product?.manufacturer | substr: 18 }} | {{ item?.product?.ean }} <br />
|
||||
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
||||
{{ publicationDate }}
|
||||
<div class="page-search-result-item__item-price desktop-small:text-sm font-bold justify-self-end">
|
||||
{{ item?.catalogAvailability?.price?.value?.value | currency: 'EUR':'code' }}
|
||||
</div>
|
||||
|
||||
<div class="page-search-result-item__item-select-bullet justify-self-end">
|
||||
<input
|
||||
*ngIf="selectable"
|
||||
(click)="$event.stopPropagation()"
|
||||
[ngModel]="selected$ | async"
|
||||
(ngModelChange)="setSelected()"
|
||||
class="isa-select-bullet"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="page-search-result-item__item-stock desktop-small:text-sm font-bold z-dropdown justify-self-start"
|
||||
[class.justify-self-end]="!(mainOutletActive$ | async)"
|
||||
[uiOverlayTrigger]="tooltip"
|
||||
[overlayTriggerDisabled]="!(stockTooltipText$ | async)"
|
||||
>
|
||||
<ng-container *ngIf="isOrderBranch$ | async">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<ui-icon icon="home" size="1em"></ui-icon>
|
||||
<span
|
||||
*ngIf="inStock$ | async; let stock"
|
||||
[class.skeleton]="stock.inStock === undefined"
|
||||
class="min-w-[1rem] text-right inline-block"
|
||||
>{{ stock?.inStock }}</span
|
||||
>
|
||||
<span>x</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!(isOrderBranch$ | async)">
|
||||
<div class="flex flex-row items-center justify-between z-dropdown">
|
||||
<ui-icon class="block" icon="home" size="1em"></ui-icon>
|
||||
<span class="min-w-[1rem] text-center inline-block">-</span>
|
||||
<span>x</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-8" [closeable]="true">
|
||||
{{ stockTooltipText$ | async }}
|
||||
</ui-tooltip>
|
||||
|
||||
<div
|
||||
class="page-search-result-item__item-ssc desktop-small:text-sm justify-self-start"
|
||||
[class.justify-self-end]="!(mainOutletActive$ | async)"
|
||||
[class.xs]="item?.catalogAvailability?.sscText?.length >= 60"
|
||||
>
|
||||
<strong>{{ item?.catalogAvailability?.ssc }}</strong> -
|
||||
{{ !isTablet ? item?.catalogAvailability?.sscText : (item?.catalogAvailability?.sscText | substr: 18) }}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -1,113 +1,90 @@
|
||||
.product-list-result-content {
|
||||
@apply text-black no-underline grid;
|
||||
grid-template-columns: 102px 50% auto;
|
||||
grid-template-rows: auto;
|
||||
:host {
|
||||
@apply flex flex-col w-full h-[13.25rem] desktop-small:h-[11.3125rem];
|
||||
}
|
||||
|
||||
.page-search-result-item__item-card {
|
||||
@apply grid grid-flow-col;
|
||||
grid-template-columns: 3.9375rem auto;
|
||||
box-shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
|
||||
}
|
||||
|
||||
.page-search-result-item__item-grid-container {
|
||||
@apply grid grid-flow-row gap-[0.375rem];
|
||||
grid-template-areas:
|
||||
'item-thumbnail item-contributors item-contributors'
|
||||
'item-thumbnail item-title item-price'
|
||||
'item-thumbnail item-title item-data-selector'
|
||||
'item-thumbnail item-format item-stock'
|
||||
'item-thumbnail item-misc item-ssc';
|
||||
'contributors contributors contributors'
|
||||
'title title price'
|
||||
'title title price'
|
||||
'title title select'
|
||||
'format format select'
|
||||
'manufacturer manufacturer stock'
|
||||
'misc ssc ssc';
|
||||
}
|
||||
|
||||
.item-thumbnail {
|
||||
grid-area: item-thumbnail;
|
||||
width: 70px;
|
||||
@apply mr-8;
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 150px;
|
||||
@apply rounded-card shadow-cta;
|
||||
}
|
||||
}
|
||||
|
||||
.item-contributors {
|
||||
grid-area: item-contributors;
|
||||
height: 22px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 600px;
|
||||
white-space: nowrap;
|
||||
|
||||
a {
|
||||
@apply text-active-customer font-bold no-underline;
|
||||
}
|
||||
}
|
||||
|
||||
.item-title {
|
||||
grid-area: item-title;
|
||||
@apply font-bold text-2xl;
|
||||
height: 64px;
|
||||
max-height: 64px;
|
||||
}
|
||||
|
||||
.item-title.xl {
|
||||
@apply font-bold text-xl;
|
||||
}
|
||||
|
||||
.item-title.lg {
|
||||
@apply font-bold text-lg;
|
||||
}
|
||||
|
||||
.item-title.md {
|
||||
@apply font-bold text-base;
|
||||
}
|
||||
|
||||
.item-title.sm {
|
||||
@apply font-bold text-sm;
|
||||
}
|
||||
|
||||
.item-title.xs {
|
||||
@apply font-bold text-xs;
|
||||
}
|
||||
|
||||
.item-price {
|
||||
grid-area: item-price;
|
||||
@apply font-bold text-xl text-right;
|
||||
}
|
||||
|
||||
.item-format {
|
||||
grid-area: item-format;
|
||||
@apply flex flex-row items-center font-bold text-lg whitespace-nowrap;
|
||||
|
||||
img {
|
||||
@apply mr-2;
|
||||
}
|
||||
}
|
||||
|
||||
.item-stock {
|
||||
grid-area: item-stock;
|
||||
@apply flex flex-row justify-end items-baseline font-bold text-lg;
|
||||
|
||||
ui-icon {
|
||||
@apply text-active-customer mr-2;
|
||||
}
|
||||
}
|
||||
|
||||
.item-misc {
|
||||
grid-area: item-misc;
|
||||
}
|
||||
|
||||
.item-ssc {
|
||||
grid-area: item-ssc;
|
||||
@apply font-bold text-right;
|
||||
}
|
||||
|
||||
.item-ssc.xs {
|
||||
@apply font-bold text-xs;
|
||||
}
|
||||
|
||||
.item-data-selector {
|
||||
@apply w-full flex justify-end;
|
||||
grid-area: item-data-selector;
|
||||
}
|
||||
|
||||
ui-select-bullet {
|
||||
@apply p-4 -m-4 z-dropdown;
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) {
|
||||
.item-contributors {
|
||||
max-width: 780px;
|
||||
.page-search-result-item__item-grid-container-main {
|
||||
@apply gap-x-4;
|
||||
grid-template-rows: 1.3125rem 1.3125rem auto;
|
||||
grid-template-columns: 30% 30% 20% 8.8% auto;
|
||||
grid-template-areas:
|
||||
'contributors format stock price .'
|
||||
'title manufacturer ssc . select'
|
||||
'title misc . . .';
|
||||
}
|
||||
|
||||
.page-search-result-item__item-contributors {
|
||||
grid-area: contributors;
|
||||
}
|
||||
|
||||
.page-search-result-item__item-price {
|
||||
grid-area: price;
|
||||
}
|
||||
|
||||
.page-search-result-item__item-title {
|
||||
grid-area: title;
|
||||
}
|
||||
|
||||
.page-search-result-item__item-format {
|
||||
grid-area: format;
|
||||
}
|
||||
|
||||
.page-search-result-item__item-manufacturer {
|
||||
grid-area: manufacturer;
|
||||
}
|
||||
|
||||
.page-search-result-item__item-misc {
|
||||
grid-area: misc;
|
||||
}
|
||||
|
||||
.page-search-result-item__item-select-bullet {
|
||||
grid-area: select;
|
||||
}
|
||||
|
||||
.page-search-result-item__item-stock {
|
||||
grid-area: stock;
|
||||
}
|
||||
|
||||
.page-search-result-item__item-ssc {
|
||||
grid-area: ssc;
|
||||
}
|
||||
|
||||
.page-search-result-item__item-image {
|
||||
box-shadow: 0px 6px 18px rgba(0, 0, 0, 0.197935);
|
||||
}
|
||||
|
||||
.active,
|
||||
.hover:hover {
|
||||
@apply bg-[#D8DFE5] border border-solid border-[#0556B4];
|
||||
|
||||
.page-search-result-item__item-select-bullet {
|
||||
.isa-select-bullet::before {
|
||||
@apply bg-[#fff];
|
||||
}
|
||||
|
||||
.isa-select-bullet:checked:before {
|
||||
@apply bg-[#596470];
|
||||
}
|
||||
|
||||
.isa-select-bullet:hover::before {
|
||||
@apply bg-[#778490];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, HostListener, HostBinding } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { DomainAvailabilityService, DomainInStockService } from '@domain/availability';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { isEqual } from 'lodash';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { debounceTime, switchMap, map, shareReplay, filter } from 'rxjs/operators';
|
||||
import { debounceTime, switchMap, map, shareReplay, filter, first } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from '../article-search.store';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
export interface SearchResultItemComponentState {
|
||||
item?: ItemDTO;
|
||||
@@ -36,16 +39,7 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
|
||||
readonly item$ = this.select((s) => s.item);
|
||||
|
||||
@Input()
|
||||
get selected() {
|
||||
return this.get((s) => s.selected);
|
||||
}
|
||||
set selected(selected: boolean) {
|
||||
if (this.selected !== selected) {
|
||||
this.patchState({ selected });
|
||||
}
|
||||
}
|
||||
readonly selected$ = this.select((s) => s.selected);
|
||||
selected$ = this._articleSearchService.selectedItemIds$.pipe(map((selectedItemIds) => selectedItemIds.includes(this.item?.id)));
|
||||
|
||||
@Input()
|
||||
get selectable() {
|
||||
@@ -58,7 +52,7 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
}
|
||||
|
||||
@Output()
|
||||
selectedChange = new EventEmitter<boolean>();
|
||||
selectedChange = new EventEmitter<ItemDTO>();
|
||||
|
||||
get contributors() {
|
||||
return this.item?.product?.contributors?.split(';').map((val) => val.trim());
|
||||
@@ -77,6 +71,22 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
return '';
|
||||
}
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
get detailsPath() {
|
||||
return this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: this.item?.id });
|
||||
}
|
||||
|
||||
get resultsPath() {
|
||||
return this._navigationService.getArticleSearchResultsPath(this.applicationService.activatedProcessId);
|
||||
}
|
||||
|
||||
get mainOutletActive$() {
|
||||
return this._navigationService?.mainOutletActive$(this._activatedRoute).pipe(shareReplay());
|
||||
}
|
||||
|
||||
defaultBranch$ = this._availability.getDefaultBranch();
|
||||
|
||||
selectedBranchId$ = this.applicationService.activatedProcessId$.pipe(
|
||||
@@ -123,7 +133,10 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
private _articleSearchService: ArticleSearchService,
|
||||
public applicationService: ApplicationService,
|
||||
private _stockService: DomainInStockService,
|
||||
private _availability: DomainAvailabilityService
|
||||
private _availability: DomainAvailabilityService,
|
||||
private _environment: EnvironmentService,
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
private _activatedRoute: ActivatedRoute
|
||||
) {
|
||||
super({
|
||||
selected: false,
|
||||
@@ -131,7 +144,16 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
});
|
||||
}
|
||||
|
||||
setSelected(selected: boolean) {
|
||||
this._articleSearchService.setSelected({ selected, itemId: this.item?.id });
|
||||
setSelected() {
|
||||
const isSelected = this._articleSearchService.selectedItemIds.includes(this.item?.id);
|
||||
this._articleSearchService.setSelected({ selected: !isSelected, itemId: this.item?.id });
|
||||
|
||||
if (!this.isTablet) {
|
||||
this.selectedChange.emit(this.item);
|
||||
}
|
||||
}
|
||||
|
||||
@HostBinding('style') get class() {
|
||||
return this._navigationService.mainOutletActive(this._activatedRoute) ? { height: '6.125rem' } : '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,77 @@
|
||||
<div class="filter-wrapper">
|
||||
<div class="hits" *ngIf="hits$ | async; let hits">{{ hits }} Titel</div>
|
||||
<ui-order-by-filter [orderBy]="(filter$ | async)?.orderBy" (selectedOrderByChange)="search(); updateBreadcrumbs()"> </ui-order-by-filter>
|
||||
<div
|
||||
class="page-search-results__header bg-background-liste flex items-end justify-between"
|
||||
[class.pb-4]="!(mainOutletActive$ | async)"
|
||||
[class.flex-col]="!(mainOutletActive$ | async)"
|
||||
>
|
||||
<div class="flex flex-row w-full desktop-small:w-min" [class.desktop:w-full]="!(mainOutletActive$ | async)">
|
||||
<shared-filter-input-group-main
|
||||
*ngIf="filter$ | async; let filter"
|
||||
class="block mr-3 w-full desktop-small:w-[23.5rem]"
|
||||
[class.desktop:w-full]="!(mainOutletActive$ | async)"
|
||||
[hint]="searchboxHint$ | async"
|
||||
[loading]="fetching$ | async"
|
||||
[inputGroup]="filter?.input | group: 'main'"
|
||||
(search)="search(filter)"
|
||||
[showDescription]="false"
|
||||
[scanner]="true"
|
||||
></shared-filter-input-group-main>
|
||||
|
||||
<a
|
||||
class="page-search-results__filter w-[6.75rem] h-14 rounded-card font-bold px-5 mb-4 text-lg bg-[#AEB7C1] flex flex-row flex-nowrap items-center justify-center"
|
||||
[class.active]="hasFilter$ | async"
|
||||
[routerLink]="filterRoute"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<ui-svg-icon class="mr-2" icon="filter-variant"></ui-svg-icon>
|
||||
Filter
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="hits$ | async; let hits"
|
||||
class="page-search-results__items-count inline-flex flex-row items-center pr-5 text-sm"
|
||||
[class.mb-4]="mainOutletActive$ | async"
|
||||
>
|
||||
{{ hits ??
|
||||
0 }}
|
||||
Titel
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-search-results__order-by" [class.page-search-results__order-by-main]="mainOutletActive$ | async">
|
||||
<shared-order-by-filter
|
||||
[groupBy]="(mainOutletActive$ | async) ? [2, 2, 1, 1] : []"
|
||||
[orderBy]="(filter$ | async)?.orderBy"
|
||||
(selectedOrderByChange)="search(); updateBreadcrumbs()"
|
||||
>
|
||||
</shared-order-by-filter>
|
||||
</div>
|
||||
|
||||
<cdk-virtual-scroll-viewport
|
||||
#scrollContainer
|
||||
class="product-list scroll-bar scroll-bar-margin"
|
||||
[itemSize]="187"
|
||||
minBufferPx="1200"
|
||||
maxBufferPx="1200"
|
||||
class="product-list"
|
||||
[itemSize]="(mainOutletActive$ | async) ? 187 : 98"
|
||||
minBufferPx="2800"
|
||||
maxBufferPx="2800"
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<div class="product-list-result" *cdkVirtualFor="let item of results$ | async; trackBy: trackByItemId">
|
||||
<search-result-item
|
||||
[selected]="item | searchResultSelected: searchService.selectedItemIds"
|
||||
[selectable]="isSelectable(item)"
|
||||
[item]="item"
|
||||
></search-result-item>
|
||||
</div>
|
||||
<search-result-item
|
||||
class="page-search-results__result-item"
|
||||
[class.page-search-results__result-item-main]="mainOutletActive$ | async"
|
||||
*cdkVirtualFor="let item of results$ | async; trackBy: trackByItemId"
|
||||
(selectedChange)="addToCart($event)"
|
||||
[selectable]="isSelectable(item)"
|
||||
[item]="item"
|
||||
></search-result-item>
|
||||
<page-search-result-item-loading *ngIf="fetching$ | async"></page-search-result-item-loading>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
|
||||
<div class="actions">
|
||||
<div *ngIf="isTablet" class="actions z-fixed">
|
||||
<button
|
||||
[disabled]="loading$ | async"
|
||||
*ngIf="(selectedItemIds$ | async)?.length > 0"
|
||||
class="cta-cart cta-action-primary"
|
||||
(click)="addSelectedItemsToCart()"
|
||||
(click)="addToCart()"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
|
||||
@@ -1,34 +1,36 @@
|
||||
:host {
|
||||
@apply box-border grid;
|
||||
max-height: calc(100vh - 364px);
|
||||
height: 100vh;
|
||||
grid-template-rows: auto 1fr;
|
||||
@apply box-border grid h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||
grid-template-rows: auto auto 1fr;
|
||||
}
|
||||
|
||||
.product-list {
|
||||
@apply m-0 p-0 mt-2;
|
||||
@apply m-0 p-0 mt-px-2;
|
||||
}
|
||||
|
||||
.product-list-result {
|
||||
@apply list-none bg-white rounded-card p-4 mb-2;
|
||||
height: 187px;
|
||||
max-height: 187px;
|
||||
.page-search-results__result-item {
|
||||
@apply mb-px-10;
|
||||
}
|
||||
|
||||
.filter-wrapper {
|
||||
@apply block relative;
|
||||
.page-search-results__result-item-main {
|
||||
@apply mb-[5px];
|
||||
}
|
||||
|
||||
.hits {
|
||||
@apply text-inactive-branch font-semibold absolute top-2 right-0;
|
||||
}
|
||||
.page-search-results__order-by {
|
||||
@apply bg-white rounded-t-card px-6 desktop-small:px-8;
|
||||
}
|
||||
|
||||
ui-order-by-filter {
|
||||
@apply mx-auto;
|
||||
.page-search-results__order-by-main {
|
||||
@apply pl-[4.9375rem] px-4;
|
||||
}
|
||||
|
||||
.page-search-results__filter {
|
||||
&.active {
|
||||
@apply bg-[#596470] text-white ml-px-5;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply fixed bottom-28 inline-grid grid-flow-col gap-7;
|
||||
@apply fixed bottom-16 inline-grid grid-flow-col gap-7;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
@@ -44,3 +46,24 @@
|
||||
@apply bg-brand text-white;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep page-search-results .page-search-results__order-by-main shared-order-by-filter {
|
||||
@apply grid grid-flow-col justify-items-start gap-x-4 justify-start;
|
||||
grid-template-columns: 30% 30% 20% 8.8% auto;
|
||||
|
||||
.group {
|
||||
@apply desktop-small:justify-start;
|
||||
}
|
||||
|
||||
.group:last-child {
|
||||
@apply justify-self-end;
|
||||
|
||||
.order-by-filter-button {
|
||||
@apply ml-12 mr-0;
|
||||
}
|
||||
}
|
||||
|
||||
.order-by-filter-button {
|
||||
@apply ml-0 mr-12;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,20 @@ import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild, ViewC
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
import { AddToShoppingCartDTO } from '@swagger/checkout';
|
||||
import { UiFilter } from '@ui/filter';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { CacheService } from 'apps/core/cache/src/public-api';
|
||||
import { isEqual } from 'lodash';
|
||||
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
||||
import { debounceTime, first, map, switchMap } from 'rxjs/operators';
|
||||
import { debounceTime, first, map, shareReplay, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from '../article-search.store';
|
||||
import { AddedToCartModalComponent } from './added-to-cart-modal/added-to-cart-modal.component';
|
||||
import { SearchResultItemComponent } from './search-result-item.component';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/filter/src/lib';
|
||||
|
||||
@Component({
|
||||
selector: 'page-search-results',
|
||||
@@ -27,6 +29,9 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('scrollContainer', { static: true })
|
||||
scrollContainer: CdkVirtualScrollViewport;
|
||||
|
||||
@ViewChild(FilterInputGroupMainComponent, { static: false })
|
||||
sharedFilterInputGroupMain: FilterInputGroupMainComponent;
|
||||
|
||||
results$ = this.searchService.items$;
|
||||
fetching$ = this.searchService.fetching$;
|
||||
hits$ = this.searchService.hits$;
|
||||
@@ -35,6 +40,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
|
||||
selectedItemIds$ = this.searchService.selectedItemIds$;
|
||||
|
||||
searchboxHint$ = this.searchService.searchboxHint$;
|
||||
|
||||
selectedItems$ = combineLatest([this.results$, this.selectedItemIds$]).pipe(
|
||||
map(([items, selectedItemIds]) => {
|
||||
return items?.filter((item) => selectedItemIds?.find((selectedItemId) => item.id === selectedItemId));
|
||||
@@ -47,14 +54,39 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
|
||||
trackByItemId: TrackByFunction<ItemDTO> = (index, item) => item.id;
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
hasFilter$ = combineLatest([this.searchService.filter$, this.searchService.defaultSettings$]).pipe(
|
||||
map(([filter, defaultFilter]) => {
|
||||
const filterQueryParams = filter?.getQueryParams();
|
||||
return !isEqual(this.resetQueryParamsQueryAndOrderBy(filterQueryParams), Filter.create(defaultFilter).getQueryParams());
|
||||
})
|
||||
);
|
||||
|
||||
get filterRoute() {
|
||||
const itemId = this._navigationService?.getOutletParams(this.route)?.right?.id;
|
||||
return this._navigationService.getArticleSearchResultsAndFilterPath({
|
||||
processId: this.application.activatedProcessId,
|
||||
itemId,
|
||||
});
|
||||
}
|
||||
|
||||
get mainOutletActive$() {
|
||||
return this._navigationService?.mainOutletActive$(this.route).pipe(shareReplay());
|
||||
}
|
||||
|
||||
constructor(
|
||||
public searchService: ArticleSearchService,
|
||||
private route: ActivatedRoute,
|
||||
private application: ApplicationService,
|
||||
public application: ApplicationService,
|
||||
private breadcrumb: BreadcrumbService,
|
||||
private cache: CacheService,
|
||||
private _uiModal: UiModalService,
|
||||
private _checkoutService: DomainCheckoutService
|
||||
private _checkoutService: DomainCheckoutService,
|
||||
private _environment: EnvironmentService,
|
||||
private _navigationService: ProductCatalogNavigationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -72,7 +104,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
const branchChanged = selectedBranch?.id !== this.searchService?.selectedBranch?.id;
|
||||
|
||||
if (processChanged) {
|
||||
if (!!this.searchService.processId && this.searchService.filter instanceof UiFilter) {
|
||||
if (!!this.searchService.processId && this.searchService.filter instanceof Filter) {
|
||||
this.cacheCurrentData(
|
||||
this.searchService.processId,
|
||||
this.searchService.filter.getQueryParams(),
|
||||
@@ -87,7 +119,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
this.searchService.setBranch(selectedBranch);
|
||||
}
|
||||
|
||||
if (!(this.searchService.filter instanceof UiFilter)) {
|
||||
if (!(this.searchService.filter instanceof Filter)) {
|
||||
await this.searchService.setDefaultFilter();
|
||||
}
|
||||
|
||||
@@ -117,6 +149,27 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
await this.removeDetailsBreadcrumb(processId);
|
||||
})
|
||||
);
|
||||
|
||||
this.subscriptions.add(
|
||||
this.searchService.searchCompleted.pipe(withLatestFrom(this.application.activatedProcessId$)).subscribe(([state, processId]) => {
|
||||
if (state.searchState === '') {
|
||||
const params = state.filter.getQueryParams();
|
||||
if ((state.hits === 1 && this.isTablet) || (!this.isTablet && !this._navigationService.mainOutletActive(this.route))) {
|
||||
const item = state.items.find((f) => f);
|
||||
this._navigationService.navigateToDetails({
|
||||
processId,
|
||||
itemId: item.id,
|
||||
queryParams: this.isTablet ? undefined : params,
|
||||
});
|
||||
} else if (this.isTablet || this._navigationService.mainOutletActive(this.route)) {
|
||||
this._navigationService.navigateToResults({
|
||||
processId,
|
||||
queryParams: params,
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -127,7 +180,26 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
this.unselectAll();
|
||||
}
|
||||
|
||||
search() {
|
||||
resetQueryParamsQueryAndOrderBy(params: Record<string, string> = {}) {
|
||||
const clean = { ...params };
|
||||
|
||||
for (const key in clean) {
|
||||
if (key === 'main_qs') {
|
||||
clean[key] = undefined;
|
||||
} else if (key?.includes('order_by')) {
|
||||
delete clean[key];
|
||||
}
|
||||
}
|
||||
|
||||
return clean;
|
||||
}
|
||||
|
||||
search(filter?: Filter) {
|
||||
if (!!filter) {
|
||||
this.sharedFilterInputGroupMain.cancelAutocomplete();
|
||||
this.searchService.setFilter(filter);
|
||||
}
|
||||
|
||||
this.searchService.search({ clear: true });
|
||||
}
|
||||
|
||||
@@ -187,7 +259,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
await this.breadcrumb.addBreadcrumbIfNotExists({
|
||||
key: processId,
|
||||
name,
|
||||
path: `/kunde/${this.application.activatedProcessId}/product/search/results`,
|
||||
path: this._navigationService.getArticleSearchResultsPath(this.application.activatedProcessId),
|
||||
params: queryParams,
|
||||
section: 'customer',
|
||||
tags: ['catalog', 'filter', 'results'],
|
||||
@@ -250,29 +322,44 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
this.searchService.patchState({ selectedItemIds: [] });
|
||||
}
|
||||
|
||||
async addSelectedItemsToCart() {
|
||||
async addToCart(item?: ItemDTO) {
|
||||
this.loading$.next(true);
|
||||
const selectedItems = await this.selectedItems$.pipe(first()).toPromise();
|
||||
|
||||
if (!!item) {
|
||||
await this.addItemsToCart(item);
|
||||
} else {
|
||||
await this.addItemsToCart();
|
||||
}
|
||||
|
||||
this.loading$.next(false);
|
||||
}
|
||||
|
||||
private _createShoppingCartItem(item: ItemDTO): AddToShoppingCartDTO {
|
||||
return {
|
||||
quantity: 1,
|
||||
availability: {
|
||||
availabilityType: item?.catalogAvailability?.status,
|
||||
price: item?.catalogAvailability?.price,
|
||||
supplierProductNumber: item?.ids?.dig ? String(item?.ids?.dig) : item?.product?.supplierProductNumber,
|
||||
},
|
||||
product: {
|
||||
catalogProductNumber: String(item?.id),
|
||||
...item?.product,
|
||||
},
|
||||
itemType: item?.type,
|
||||
promotion: { points: item?.promoPoints },
|
||||
};
|
||||
}
|
||||
|
||||
async addItemsToCart(item?: ItemDTO) {
|
||||
const selectedItems = !item ? await this.selectedItems$.pipe(first()).toPromise() : [item];
|
||||
const items: AddToShoppingCartDTO[] = [];
|
||||
|
||||
const canAddItemsPayload = [];
|
||||
|
||||
for (const item of selectedItems) {
|
||||
const shoppingCartItem = this._createShoppingCartItem(item);
|
||||
const isDownload = item?.product?.format === 'EB' || item?.product?.format === 'DL';
|
||||
const shoppingCartItem: AddToShoppingCartDTO = {
|
||||
quantity: 1,
|
||||
availability: {
|
||||
availabilityType: item?.catalogAvailability?.status,
|
||||
price: item?.catalogAvailability?.price,
|
||||
supplierProductNumber: item?.ids?.dig ? String(item.ids?.dig) : item?.product?.supplierProductNumber,
|
||||
},
|
||||
product: {
|
||||
catalogProductNumber: String(item?.id),
|
||||
...item?.product,
|
||||
},
|
||||
itemType: item.type,
|
||||
promotion: { points: item?.promoPoints },
|
||||
};
|
||||
|
||||
if (isDownload) {
|
||||
shoppingCartItem.destination = { data: { target: 16 } };
|
||||
canAddItemsPayload.push({
|
||||
|
||||
@@ -8,7 +8,6 @@ import { UiCommonModule } from '@ui/common';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiSelectBulletModule } from '@ui/select-bullet';
|
||||
import { UiTooltipModule } from '@ui/tooltip';
|
||||
import { UiOrderByFilterModule } from 'apps/ui/filter/src/lib/next/order-by-filter/order-by-filter.module';
|
||||
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
|
||||
import { AddedToCartModalComponent } from './added-to-cart-modal/added-to-cart-modal.component';
|
||||
import { StockInfosPipe } from './order-by-filter/stick-infos.pipe';
|
||||
@@ -16,6 +15,9 @@ import { SearchResultItemLoadingComponent } from './search-result-item-loading.c
|
||||
import { SearchResultItemComponent } from './search-result-item.component';
|
||||
import { ArticleSearchResultsComponent } from './search-results.component';
|
||||
import { SearchResultSelectedPipe } from './selected/search-result-selected.pipe';
|
||||
import { FilterAutocompleteProvider, FilterNextModule, OrderByFilterModule } from 'apps/shared/components/filter/src/lib';
|
||||
import { FocusSearchboxEvent } from '../focus-searchbox.event';
|
||||
import { ArticleSearchMainAutocompleteProvider } from '../providers';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -27,9 +29,10 @@ import { SearchResultSelectedPipe } from './selected/search-result-selected.pipe
|
||||
UiIconModule,
|
||||
UiSelectBulletModule,
|
||||
UiSpinnerModule,
|
||||
UiOrderByFilterModule,
|
||||
OrderByFilterModule,
|
||||
ScrollingModule,
|
||||
UiTooltipModule,
|
||||
FilterNextModule,
|
||||
],
|
||||
exports: [ArticleSearchResultsComponent, SearchResultItemComponent],
|
||||
declarations: [
|
||||
@@ -40,6 +43,13 @@ import { SearchResultSelectedPipe } from './selected/search-result-selected.pipe
|
||||
SearchResultSelectedPipe,
|
||||
AddedToCartModalComponent,
|
||||
],
|
||||
providers: [],
|
||||
providers: [
|
||||
FocusSearchboxEvent,
|
||||
{
|
||||
provide: FilterAutocompleteProvider,
|
||||
useClass: ArticleSearchMainAutocompleteProvider,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class SearchResultsModule {}
|
||||
|
||||
@@ -2,10 +2,65 @@ import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { ArticleDetailsComponent } from './article-details/article-details.component';
|
||||
import { ArticleSearchComponent } from './article-search/article-search.component';
|
||||
import { ArticleSearchFilterComponent } from './article-search/search-filter/search-filter.component';
|
||||
import { ArticleSearchMainComponent } from './article-search/search-main/search-main.component';
|
||||
import { ArticleSearchResultsComponent } from './article-search/search-results/search-results.component';
|
||||
import { PageCatalogComponent } from './page-catalog.component';
|
||||
|
||||
const auxiliaryRoutes = [
|
||||
{
|
||||
path: 'search',
|
||||
component: ArticleSearchComponent,
|
||||
outlet: 'left',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: ArticleSearchMainComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'filter',
|
||||
component: ArticleSearchFilterComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
{
|
||||
path: 'filter/:id',
|
||||
component: ArticleSearchFilterComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
{
|
||||
path: 'results',
|
||||
component: ArticleSearchResultsComponent,
|
||||
outlet: 'left',
|
||||
},
|
||||
{
|
||||
path: 'results',
|
||||
component: ArticleSearchResultsComponent,
|
||||
outlet: 'main',
|
||||
},
|
||||
{
|
||||
path: 'results/:id',
|
||||
component: ArticleSearchResultsComponent,
|
||||
outlet: 'left',
|
||||
},
|
||||
{
|
||||
path: 'results/ean/:ean',
|
||||
component: ArticleSearchResultsComponent,
|
||||
outlet: 'left',
|
||||
},
|
||||
{
|
||||
path: 'details/ean/:ean',
|
||||
component: ArticleDetailsComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
{
|
||||
path: 'details/:id',
|
||||
component: ArticleDetailsComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
];
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
@@ -19,12 +74,16 @@ const routes: Routes = [
|
||||
path: '',
|
||||
component: ArticleSearchMainComponent,
|
||||
},
|
||||
{
|
||||
path: 'results',
|
||||
component: ArticleSearchResultsComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'results',
|
||||
component: ArticleSearchResultsComponent,
|
||||
},
|
||||
{
|
||||
path: 'filter',
|
||||
component: ArticleSearchFilterComponent,
|
||||
},
|
||||
{
|
||||
path: 'details/ean/:ean',
|
||||
component: ArticleDetailsComponent,
|
||||
@@ -33,6 +92,7 @@ const routes: Routes = [
|
||||
path: 'details/:id',
|
||||
component: ArticleDetailsComponent,
|
||||
},
|
||||
...auxiliaryRoutes,
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
|
||||
@@ -8,4 +8,25 @@
|
||||
>
|
||||
</shared-branch-selector>
|
||||
</shared-breadcrumb>
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
<ng-container *ngIf="routerEvents$ | async">
|
||||
<ng-container *ngIf="!(isDesktop$ | async); else desktop">
|
||||
<router-outlet></router-outlet>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #desktop>
|
||||
<ng-container *ngIf="showMainOutlet$ | async">
|
||||
<router-outlet name="main"></router-outlet>
|
||||
</ng-container>
|
||||
|
||||
<div class="grid grid-cols-[minmax(31rem,.5fr)_1fr] gap-6">
|
||||
<div *ngIf="showLeftOutlet$ | async" class="block">
|
||||
<router-outlet name="left"></router-outlet>
|
||||
</div>
|
||||
|
||||
<div *ngIf="showRightOutlet$ | async">
|
||||
<router-outlet name="right"></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
:host {
|
||||
@apply block relative;
|
||||
@apply block relative h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||
}
|
||||
|
||||
shell-breadcrumb {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
@@ -6,8 +7,8 @@ import { BranchSelectorComponent } from '@shared/components/branch-selector';
|
||||
import { BreadcrumbComponent } from '@shared/components/breadcrumb';
|
||||
import { BranchDTO } from '@swagger/checkout';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { BehaviorSubject, from, fromEvent, Observable, Subject } from 'rxjs';
|
||||
import { first, map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { fromEvent, Observable, Subject } from 'rxjs';
|
||||
import { first, map, shareReplay, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-catalog',
|
||||
@@ -28,16 +29,41 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
_onDestroy$ = new Subject<boolean>();
|
||||
|
||||
get isTablet$() {
|
||||
return this._environmentService.matchTablet$.pipe(
|
||||
map((state) => state.matches),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
|
||||
get isDesktop$() {
|
||||
return this._environmentService.matchDesktop$.pipe(
|
||||
map((state) => {
|
||||
return state.matches;
|
||||
}),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
|
||||
routerEvents$ = this._router.events.pipe(shareReplay());
|
||||
|
||||
showMainOutlet$ = this.routerEvents$.pipe(map((_) => !!this._activatedRoute?.children?.find((child) => child?.outlet === 'main')));
|
||||
|
||||
showLeftOutlet$ = this.routerEvents$.pipe(map((_) => !!this._activatedRoute?.children?.find((child) => child?.outlet === 'left')));
|
||||
|
||||
showRightOutlet$ = this.routerEvents$.pipe(map((_) => !!this._activatedRoute?.children?.find((child) => child?.outlet === 'right')));
|
||||
|
||||
constructor(
|
||||
public application: ApplicationService,
|
||||
private _uiModal: UiModalService,
|
||||
public auth: AuthService,
|
||||
private _environmentService: EnvironmentService,
|
||||
private _renderer: Renderer2
|
||||
private _renderer: Renderer2,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _router: Router
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
// this.auth.getClaims();
|
||||
this.activatedProcessId$ = this.application.activatedProcessId$.pipe(map((processId) => String(processId)));
|
||||
|
||||
this.selectedBranch$ = this.activatedProcessId$.pipe(switchMap((processId) => this.application.getSelectedBranch$(Number(processId))));
|
||||
@@ -46,19 +72,19 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (this._environmentService.isTablet()) {
|
||||
fromEvent(this.branchSelectorRef.nativeElement, 'focusin')
|
||||
.pipe(takeUntil(this._onDestroy$))
|
||||
.subscribe((_) => {
|
||||
fromEvent(this.branchSelectorRef.nativeElement, 'focusin')
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this.isTablet$))
|
||||
.subscribe(([_, isTablet]) => {
|
||||
if (isTablet) {
|
||||
this._renderer.setStyle(this.branchSelectorRef?.nativeElement, 'width', this.branchSelectorWidth);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
fromEvent(this.branchSelectorRef.nativeElement, 'focusout')
|
||||
.pipe(takeUntil(this._onDestroy$))
|
||||
.subscribe((_) => {
|
||||
this._renderer.removeStyle(this.branchSelectorRef?.nativeElement, 'width');
|
||||
});
|
||||
}
|
||||
fromEvent(this.branchSelectorRef.nativeElement, 'focusout')
|
||||
.pipe(takeUntil(this._onDestroy$))
|
||||
.subscribe((_) => {
|
||||
this._renderer.removeStyle(this.branchSelectorRef?.nativeElement, 'width');
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { DomainCustomerOrderService, DomainGoodsService, DomainOmsService, OrderItemsContext } from '@domain/oms';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { ListResponseArgsOfOrderItemListItemDTO, OrderItemListItemDTO, OrderItemProcessingStatusValue } from '@swagger/oms';
|
||||
@@ -63,13 +64,18 @@ export class CustomerOrderDetailsComponent extends ComponentStore<CustomerOrderD
|
||||
|
||||
private _onDestroy$ = new Subject();
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _domainGoodsInService: DomainCustomerOrderService,
|
||||
private _omsService: DomainOmsService,
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _router: Router,
|
||||
private _uiModal: UiModalService
|
||||
private _uiModal: UiModalService,
|
||||
private _environment: EnvironmentService
|
||||
) {
|
||||
super({
|
||||
fetching: false,
|
||||
@@ -187,19 +193,37 @@ export class CustomerOrderDetailsComponent extends ComponentStore<CustomerOrderD
|
||||
}
|
||||
|
||||
navigateToEditPage(orderItem: OrderItemListItemDTO) {
|
||||
// if (this.isTablet) {
|
||||
this._router.navigate([this.getEditPath(orderItem)], {
|
||||
queryParams: { orderNumber: orderItem.orderNumber, buyerNumber: orderItem.buyerNumber, archive: this.archive },
|
||||
queryParams: { buyerNumber: orderItem.buyerNumber, archive: this.archive },
|
||||
});
|
||||
// } else {
|
||||
// this._router.navigate(this.getEditPathDesktop(orderItem), {
|
||||
// queryParamsHandling: 'preserve',
|
||||
// queryParams: { buyerNumber: orderItem.buyerNumber, archive: this.archive },
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
navigateToDetailsPage(item: OrderItemListItemDTO) {
|
||||
// if (this.isTablet) {
|
||||
this._router.navigate([this.getDetailsPath(item)], {
|
||||
queryParams: { buyerNumber: item.buyerNumber, archive: this.archive },
|
||||
});
|
||||
// } else {
|
||||
// this._router.navigate(this.getDetailsPathDesktop(item), {
|
||||
// queryParamsHandling: 'preserve',
|
||||
// queryParams: { buyerNumber: item.buyerNumber, archive: this.archive },
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
navigateToLandingPage() {
|
||||
// if (this.isTablet) {
|
||||
this._router.navigate([`/kunde/${this.processId}/order`]);
|
||||
// } else {
|
||||
// this._router.navigate(['/kunde', this.processId, 'order', { outlets: { main: null, left: 'search', right: 'filter' } }]);
|
||||
// }
|
||||
}
|
||||
|
||||
async actionHandled(handler: { orderItemsContext: OrderItemsContext; command: string; navigation: 'details' | 'main' | 'reservation' }) {
|
||||
@@ -218,9 +242,65 @@ export class CustomerOrderDetailsComponent extends ComponentStore<CustomerOrderD
|
||||
: `/kunde/${this.processId}/order/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}`;
|
||||
}
|
||||
|
||||
// getDetailsPathDesktop(item: OrderItemListItemDTO) {
|
||||
// return item?.compartmentCode
|
||||
// ? [
|
||||
// '/kunde',
|
||||
// this.processId,
|
||||
// 'order',
|
||||
// {
|
||||
// outlets: {
|
||||
// main: null,
|
||||
// left: 'results',
|
||||
// right: ['details', 'compartment', encodeURIComponent(item?.compartmentCode), item?.processingStatus],
|
||||
// },
|
||||
// },
|
||||
// ]
|
||||
// : [
|
||||
// '/kunde',
|
||||
// this.processId,
|
||||
// 'order',
|
||||
// {
|
||||
// outlets: {
|
||||
// main: null,
|
||||
// left: 'results',
|
||||
// right: ['details', 'order', encodeURIComponent(item?.orderNumber), item?.processingStatus],
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
// }
|
||||
|
||||
getEditPath(item: OrderItemListItemDTO) {
|
||||
return item?.compartmentCode
|
||||
? `/kunde/${this.processId}/order/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}/edit`
|
||||
: `/kunde/${this.processId}/order/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}/edit`;
|
||||
}
|
||||
|
||||
// getEditPathDesktop(item: OrderItemListItemDTO) {
|
||||
// return item?.compartmentCode
|
||||
// ? [
|
||||
// '/kunde',
|
||||
// this.processId,
|
||||
// 'order',
|
||||
// {
|
||||
// outlets: {
|
||||
// main: null,
|
||||
// left: 'results',
|
||||
// right: ['details', 'compartment', encodeURIComponent(item?.compartmentCode), item?.processingStatus, 'edit'],
|
||||
// },
|
||||
// },
|
||||
// ]
|
||||
// : [
|
||||
// '/kunde',
|
||||
// this.processId,
|
||||
// 'order',
|
||||
// {
|
||||
// outlets: {
|
||||
// main: null,
|
||||
// left: 'results',
|
||||
// right: ['details', 'order', encodeURIComponent(item?.orderNumber), item?.processingStatus, 'edit'],
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { DomainCustomerOrderService, DomainGoodsService, DomainOmsService } from '@domain/oms';
|
||||
import { OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
@@ -48,12 +49,17 @@ export class CustomerOrderEditComponent implements OnInit {
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _domainGoodsInService: DomainCustomerOrderService,
|
||||
private _router: Router,
|
||||
private _uiModal: UiModalService
|
||||
private _uiModal: UiModalService,
|
||||
private _environment: EnvironmentService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -84,6 +90,8 @@ export class CustomerOrderEditComponent implements OnInit {
|
||||
const processingStatus = options?.processingStatus ? options.processingStatus : this._activatedRoute.snapshot.params.processingStatus;
|
||||
const buyerNumber = this._activatedRoute.snapshot.queryParams.buyerNumber;
|
||||
const archive = this._activatedRoute.snapshot.queryParams.archive;
|
||||
|
||||
// if (this.isTablet) {
|
||||
compartmentCode
|
||||
? this._router.navigate(
|
||||
[`/kunde/${this.processId}/order/details/compartment/${encodeURIComponent(compartmentCode)}/${processingStatus}`],
|
||||
@@ -92,6 +100,42 @@ export class CustomerOrderEditComponent implements OnInit {
|
||||
: this._router.navigate([`/kunde/${this.processId}/order/details/order/${encodeURIComponent(orderNumber)}/${processingStatus}`], {
|
||||
queryParams: { buyerNumber, archive },
|
||||
});
|
||||
// } else {
|
||||
// compartmentCode
|
||||
// ? this._router.navigate(
|
||||
// [
|
||||
// '/kunde',
|
||||
// this.processId,
|
||||
// 'order',
|
||||
// {
|
||||
// outlets: {
|
||||
// main: null,
|
||||
// left: 'results',
|
||||
// right: ['details', 'compartment', encodeURIComponent(compartmentCode), processingStatus],
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// { queryParamsHandling: 'preserve', queryParams: { buyerNumber, archive } }
|
||||
// )
|
||||
// : this._router.navigate(
|
||||
// [
|
||||
// '/kunde',
|
||||
// this.processId,
|
||||
// 'order',
|
||||
// {
|
||||
// outlets: {
|
||||
// main: null,
|
||||
// left: 'results',
|
||||
// right: ['details', 'order', encodeURIComponent(orderNumber), processingStatus],
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// {
|
||||
// queryParamsHandling: 'preserve',
|
||||
// queryParams: { buyerNumber, archive },
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
openModalIfItemsHaveDifferentCustomers(items: OrderItemListItemDTO[]) {
|
||||
|
||||
@@ -2,11 +2,70 @@ import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { CustomerOrderDetailsComponent, CustomerOrderDetailsModule } from './customer-order-details';
|
||||
import { CustomerOrderEditComponent } from './customer-order-edit';
|
||||
import { CustomerOrderSearchComponent, CustomerOrderSearchModule } from './customer-order-search';
|
||||
import { CustomerOrderSearchComponent, CustomerOrderSearchFilterComponent, CustomerOrderSearchModule } from './customer-order-search';
|
||||
import { CustomerOrderSearchMainComponent, CustomerOrderSearchMainModule } from './customer-order-search/search-main';
|
||||
import { CustomerOrderSearchResultsComponent, CustomerOrderSearchResultsModule } from './customer-order-search/search-results';
|
||||
import { CustomerOrderComponent } from './customer-order.component';
|
||||
|
||||
// const auxiliaryRoutes = [
|
||||
// {
|
||||
// path: 'search',
|
||||
// component: CustomerOrderSearchComponent,
|
||||
// outlet: 'left',
|
||||
// children: [
|
||||
// {
|
||||
// path: '',
|
||||
// component: CustomerOrderSearchMainComponent,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// path: 'filter',
|
||||
// component: CustomerOrderSearchFilterComponent,
|
||||
// outlet: 'right',
|
||||
// },
|
||||
// {
|
||||
// path: 'filter/:compartmentCode/:processingStatus',
|
||||
// component: CustomerOrderSearchFilterComponent,
|
||||
// outlet: 'right',
|
||||
// },
|
||||
// {
|
||||
// path: 'filter/:orderNumber/:processingStatus',
|
||||
// component: CustomerOrderSearchFilterComponent,
|
||||
// outlet: 'right',
|
||||
// },
|
||||
// {
|
||||
// path: 'results',
|
||||
// component: CustomerOrderSearchResultsComponent,
|
||||
// outlet: 'left',
|
||||
// },
|
||||
// {
|
||||
// path: 'results',
|
||||
// component: CustomerOrderSearchResultsComponent,
|
||||
// outlet: 'main',
|
||||
// },
|
||||
// {
|
||||
// path: 'details/compartment/:compartmentCode/:processingStatus',
|
||||
// component: CustomerOrderDetailsComponent,
|
||||
// outlet: 'right',
|
||||
// },
|
||||
// {
|
||||
// path: 'details/order/:orderNumber/:processingStatus',
|
||||
// component: CustomerOrderDetailsComponent,
|
||||
// outlet: 'right',
|
||||
// },
|
||||
// {
|
||||
// path: 'details/compartment/:compartmentCode/:processingStatus/edit',
|
||||
// component: CustomerOrderEditComponent,
|
||||
// outlet: 'right',
|
||||
// },
|
||||
// {
|
||||
// path: 'details/order/:orderNumber/:processingStatus/edit',
|
||||
// component: CustomerOrderEditComponent,
|
||||
// outlet: 'right',
|
||||
// },
|
||||
// ];
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
@@ -20,6 +79,10 @@ const routes: Routes = [
|
||||
{ path: 'results', component: CustomerOrderSearchResultsComponent },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'filter',
|
||||
component: CustomerOrderSearchFilterComponent,
|
||||
},
|
||||
{
|
||||
path: 'details/compartment/:compartmentCode/:processingStatus',
|
||||
component: CustomerOrderDetailsComponent,
|
||||
@@ -36,6 +99,7 @@ const routes: Routes = [
|
||||
path: 'details/order/:orderNumber/:processingStatus/edit',
|
||||
component: CustomerOrderEditComponent,
|
||||
},
|
||||
// ...auxiliaryRoutes,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
<div class="goods-out-search-filter-content">
|
||||
<button class="btn-close" type="button" (click)="close.emit()">
|
||||
<ui-icon icon="close" size="20px"></ui-icon>
|
||||
</button>
|
||||
<ng-container *ngIf="filter$ | async; let filter">
|
||||
<div class="goods-out-search-filter-content">
|
||||
<a class="btn-close" type="button" [routerLink]="closeFilterRouterPath" queryParamsHandling="preserve">
|
||||
<ui-icon icon="close" size="20px"></ui-icon>
|
||||
</a>
|
||||
|
||||
<div class="goods-out-search-filter-content-main">
|
||||
<h1 class="title">Filter</h1>
|
||||
<ui-filter
|
||||
[filter]="filter"
|
||||
[loading]="loading$ | async"
|
||||
(search)="applyFilter()"
|
||||
[hint]="message"
|
||||
resizeInputOptionsToElement="page-goods-out-search-filter .cta-wrapper"
|
||||
[scanner]="true"
|
||||
>
|
||||
<page-order-branch-id-input *uiFilterCustomInput="'order_branch_id'; let input" [input]="input"> </page-order-branch-id-input>
|
||||
</ui-filter>
|
||||
<div class="goods-out-search-filter-content-main">
|
||||
<h1 class="title">Filter</h1>
|
||||
<ui-filter
|
||||
[filter]="filter"
|
||||
[loading]="loading$ | async"
|
||||
(search)="applyFilter(filter)"
|
||||
[hint]="message"
|
||||
resizeInputOptionsToElement="page-goods-out-search-filter .cta-wrapper"
|
||||
[scanner]="true"
|
||||
>
|
||||
<page-order-branch-id-input *uiFilterCustomInput="'order_branch_id'; let input" [input]="input"> </page-order-branch-id-input>
|
||||
</ui-filter>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cta-wrapper">
|
||||
<button class="cta-reset-filter" (click)="resetFilter()" [disabled]="loading$ | async">
|
||||
Filter zurücksetzen
|
||||
</button>
|
||||
<div class="cta-wrapper">
|
||||
<button class="cta-reset-filter" (click)="resetFilter(filter)" [disabled]="loading$ | async">
|
||||
Filter zurücksetzen
|
||||
</button>
|
||||
|
||||
<button class="cta-apply-filter" (click)="applyFilter()" [disabled]="loading$ | async">
|
||||
<ui-spinner [show]="loading$ | async">
|
||||
Filter anwenden
|
||||
</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
<button class="cta-apply-filter" (click)="applyFilter(filter)" [disabled]="loading$ | async">
|
||||
<ui-spinner [show]="loading$ | async">
|
||||
Filter anwenden
|
||||
</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -22,9 +22,7 @@
|
||||
}
|
||||
|
||||
.cta-wrapper {
|
||||
@apply fixed bottom-8 whitespace-nowrap;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
@apply text-center whitespace-nowrap;
|
||||
}
|
||||
|
||||
.cta-reset-filter,
|
||||
|
||||
@@ -9,13 +9,15 @@ import {
|
||||
Input,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { UiFilter, UiFilterComponent } from '@ui/filter';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { take, takeUntil } from 'rxjs/operators';
|
||||
import { map, take, takeUntil } from 'rxjs/operators';
|
||||
import { CustomerOrderSearchStore } from '../customer-order-search.store';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { ApplicationService } from '@core/application';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-search-filter',
|
||||
@@ -27,12 +29,14 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
@Output()
|
||||
close = new EventEmitter();
|
||||
|
||||
filter: UiFilter;
|
||||
|
||||
loading$: Observable<boolean>;
|
||||
|
||||
message: string;
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
@Input()
|
||||
processId: number;
|
||||
|
||||
@@ -41,11 +45,57 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(UiFilterComponent, { static: false })
|
||||
uiFilter: UiFilterComponent;
|
||||
|
||||
filter$: Observable<UiFilter>;
|
||||
|
||||
get leftOutletLocation(): string {
|
||||
return this._activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'left')?.snapshot?.routeConfig?.path ?? '';
|
||||
}
|
||||
|
||||
get rightOutletParams(): Params {
|
||||
return this._activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'right')?.snapshot?.params;
|
||||
}
|
||||
|
||||
get closeFilterRouterPath() {
|
||||
// if (!this.isTablet) {
|
||||
// if (this.leftOutletLocation.includes('results')) {
|
||||
// const params = this.rightOutletParams;
|
||||
// const orderNumber = params?.orderNumber;
|
||||
// const compartmentCode = params?.compartmentCode;
|
||||
// const processingStatus = params?.processingStatus;
|
||||
// return orderNumber
|
||||
// ? [
|
||||
// '/kunde',
|
||||
// this.application.activatedProcessId,
|
||||
// 'order',
|
||||
// { outlets: { main: null, left: 'results', right: ['details', 'order', orderNumber, processingStatus] } },
|
||||
// ]
|
||||
// : [
|
||||
// '/kunde',
|
||||
// this.application.activatedProcessId,
|
||||
// 'order',
|
||||
// {
|
||||
// outlets: {
|
||||
// main: null,
|
||||
// left: 'results',
|
||||
// right: ['details', 'compartment', compartmentCode, processingStatus],
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
// }
|
||||
// return ['/kunde', this.application.activatedProcessId, 'order', { outlets: { main: null, left: 'search', right: 'filter' } }];
|
||||
// } else {
|
||||
return ['/kunde', this.application.activatedProcessId, 'order', 'results'];
|
||||
// }
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _goodsOutSearchStore: CustomerOrderSearchStore,
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _cdr: ChangeDetectorRef,
|
||||
private _router: Router
|
||||
private _router: Router,
|
||||
private _environment: EnvironmentService,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
public application: ApplicationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -59,23 +109,23 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private _initSettings() {
|
||||
this.filter = UiFilter.create(this._goodsOutSearchStore.filter);
|
||||
this.filter$ = this._goodsOutSearchStore.filter$.pipe(map((filter) => UiFilter.create(filter)));
|
||||
}
|
||||
|
||||
private _initLoading$() {
|
||||
this.loading$ = this._goodsOutSearchStore.fetching$;
|
||||
}
|
||||
|
||||
async resetFilter() {
|
||||
const queryParams = { main_qs: this.filter?.getQueryParams()?.main_qs || '' };
|
||||
async resetFilter(filter: UiFilter) {
|
||||
const queryParams = { main_qs: filter?.getQueryParams()?.main_qs || '' };
|
||||
this._goodsOutSearchStore.resetFilter(queryParams);
|
||||
this._initSettings();
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
|
||||
async applyFilter() {
|
||||
async applyFilter(filter: UiFilter) {
|
||||
this.uiFilter?.cancelAutocomplete();
|
||||
this._goodsOutSearchStore.setFilter(this.filter);
|
||||
this._goodsOutSearchStore.setFilter(filter);
|
||||
this.message = undefined;
|
||||
|
||||
await this.updateQueryParams();
|
||||
@@ -84,6 +134,7 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
if (result.results.error) {
|
||||
} else {
|
||||
if (result.results.hits > 0) {
|
||||
// if (this.isTablet) {
|
||||
if (result.results.hits === 1) {
|
||||
const orderItem = result.results.result[0];
|
||||
this._router.navigate([this.getDetailsPath(orderItem)]);
|
||||
@@ -92,6 +143,18 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
queryParams: this._goodsOutSearchStore.filter.getQueryParams(),
|
||||
});
|
||||
}
|
||||
// } else {
|
||||
// const orderItem = result.results.result[0];
|
||||
// if (result.results.hits === 1) {
|
||||
// this._router.navigate(this.getDetailsPathDesktop(orderItem, this.processId), {
|
||||
// queryParams: this._goodsOutSearchStore.filter.getQueryParams(),
|
||||
// });
|
||||
// } else {
|
||||
// this._router.navigate(this.getDetailsPathDesktop(orderItem, this.processId), {
|
||||
// queryParams: this._goodsOutSearchStore.filter.getQueryParams(),
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
this.close.emit();
|
||||
} else {
|
||||
@@ -126,4 +189,32 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
? `/kunde/${this.processId}/order/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}`
|
||||
: `/kunde/${this.processId}/order/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}`;
|
||||
}
|
||||
|
||||
// getDetailsPathDesktop(item: OrderItemListItemDTO, processId: number) {
|
||||
// return item?.compartmentCode
|
||||
// ? [
|
||||
// '/kunde',
|
||||
// processId,
|
||||
// 'order',
|
||||
// {
|
||||
// outlets: {
|
||||
// main: null,
|
||||
// left: 'results',
|
||||
// right: ['details', 'compartment', encodeURIComponent(item?.compartmentCode), item?.processingStatus],
|
||||
// },
|
||||
// },
|
||||
// ]
|
||||
// : [
|
||||
// '/kunde',
|
||||
// processId,
|
||||
// 'order',
|
||||
// {
|
||||
// outlets: {
|
||||
// main: null,
|
||||
// left: 'results',
|
||||
// right: ['details', 'order', encodeURIComponent(item?.orderNumber), item?.processingStatus],
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -25,18 +25,31 @@ export class OrderBranchIdInputComponent extends AbstractUiFilterInputDirective
|
||||
ngOnInit() {
|
||||
this.control.setValue({ id: Number(this.value) });
|
||||
|
||||
const uiInputChangesSub = this.uiInput?.changes?.subscribe((changes) => {
|
||||
const controlValue = this.control?.value?.id;
|
||||
const changesValue = Number(changes?.target?.value);
|
||||
if (controlValue !== changesValue) {
|
||||
this.control.setValue(changesValue && !isNaN(changesValue) ? { id: changesValue } : undefined);
|
||||
}
|
||||
});
|
||||
|
||||
const onInputChangeSub = this.onUiInputChange$.subscribe((input) => {
|
||||
if (this.control.value !== input.value) {
|
||||
this.control.setValue(input.value ? { id: Number(input.value) } : undefined);
|
||||
const controlValue = this.control?.value?.id;
|
||||
const inputValue = Number(input?.value);
|
||||
if (controlValue !== inputValue) {
|
||||
this.control.setValue(inputValue && !isNaN(inputValue) ? { id: inputValue } : undefined);
|
||||
}
|
||||
});
|
||||
|
||||
const onControlValueChangeSub = this.control.valueChanges.subscribe((value) => {
|
||||
if (this.value !== value) {
|
||||
this.setValue(value ? String(value?.id) : undefined);
|
||||
if (!value) {
|
||||
this.setValue(undefined);
|
||||
} else if (this.value !== String(value?.id)) {
|
||||
this.setValue(String(value?.id));
|
||||
}
|
||||
});
|
||||
|
||||
this._subscriptions.add(uiInputChangesSub);
|
||||
this._subscriptions.add(onInputChangeSub);
|
||||
this._subscriptions.add(onControlValueChangeSub);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="flex flex-row justify-end mb-4">
|
||||
<!-- <div *ngIf="isTablet" class="flex flex-row justify-end mb-4">
|
||||
<button class="filter" [class.active]="hasFilter$ | async" (click)="shellFilterOverlay.open()">
|
||||
<ui-icon size="20px" icon="filter_alit"></ui-icon>
|
||||
<span class="label">Filter</span>
|
||||
</button>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { combineLatest, Subject } from 'rxjs';
|
||||
import { map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { CustomerOrderSearchStore } from './customer-order-search.store';
|
||||
import { CustomerOrderSearchMainAutocompleteProvider } from './providers/customer-order-search-main-autocomplete.provider';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-search',
|
||||
@@ -15,7 +16,6 @@ import { CustomerOrderSearchMainAutocompleteProvider } from './providers/custome
|
||||
styleUrls: ['customer-order-search.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
CustomerOrderSearchStore,
|
||||
{
|
||||
provide: UiFilterAutocompleteProvider,
|
||||
useClass: CustomerOrderSearchMainAutocompleteProvider,
|
||||
@@ -32,28 +32,25 @@ import { CustomerOrderSearchMainAutocompleteProvider } from './providers/custome
|
||||
export class CustomerOrderSearchComponent implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject();
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
hasFilter$ = combineLatest([this._goodsOutSearchStore.filter$, this._goodsOutSearchStore.defaultSettings$]).pipe(
|
||||
map(([filter, defaultFilter]) => !isEqual(filter?.getQueryParams(), UiFilter.create(defaultFilter).getQueryParams()))
|
||||
);
|
||||
|
||||
processId$ = this._activatedRoute.data.pipe(map((data) => +data.processId));
|
||||
processId$ = this._activatedRoute.parent.data.pipe(map((data) => +data.processId));
|
||||
|
||||
constructor(
|
||||
private _goodsOutSearchStore: CustomerOrderSearchStore,
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _activatedRoute: ActivatedRoute
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _environment: EnvironmentService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.processId$.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._activatedRoute.queryParams)).subscribe(([processId]) => {
|
||||
// if (params && Object.keys(params).length === 0) {
|
||||
// console.log('params is empty');
|
||||
// this._goodsOutSearchStore.setQueryParams(params);
|
||||
// this._goodsOutSearchStore.loadSettings();
|
||||
// } else {
|
||||
// // this._goodsOutSearchStore.resetFilter(params);
|
||||
// }
|
||||
|
||||
this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
key: processId,
|
||||
name: 'Kundenbestellung',
|
||||
|
||||
@@ -8,6 +8,7 @@ import { RouterModule } from '@angular/router';
|
||||
import { UiFilterNextModule } from '@ui/filter';
|
||||
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
|
||||
import { UiSpinnerModule } from '@ui/spinner';
|
||||
import { CustomerOrderSearchStore } from './customer-order-search.store';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -20,6 +21,7 @@ import { UiSpinnerModule } from '@ui/spinner';
|
||||
OrderBranchIdInputComponent,
|
||||
],
|
||||
exports: [CustomerOrderSearchComponent],
|
||||
providers: [CustomerOrderSearchStore],
|
||||
declarations: [CustomerOrderSearchComponent, CustomerOrderSearchFilterComponent],
|
||||
})
|
||||
export class CustomerOrderSearchModule {}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
}
|
||||
|
||||
.search-main {
|
||||
@apply bg-white text-center rounded-t-card py-5 shadow-card;
|
||||
@apply bg-white text-center rounded-t-card p-5 shadow-card;
|
||||
height: calc(100vh - 380px);
|
||||
|
||||
.search-main-title {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { debounce, isEqual } from 'lodash';
|
||||
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
||||
import { debounceTime, first, map, withLatestFrom } from 'rxjs/operators';
|
||||
import { CustomerOrderSearchStore } from '../customer-order-search.store';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-search-main',
|
||||
@@ -28,10 +29,14 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
|
||||
private _subscriptions = new Subscription();
|
||||
|
||||
get processId() {
|
||||
return +this._activatedRoute.snapshot.data.processId;
|
||||
return +this._activatedRoute.parent.parent.snapshot.data.processId;
|
||||
}
|
||||
|
||||
processId$ = this._activatedRoute.data.pipe(map((data) => +data.processId));
|
||||
processId$ = this._activatedRoute.parent.parent.data.pipe(map((data) => +data.processId));
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
@ViewChild(UiFilterInputGroupMainComponent, { static: false })
|
||||
filterInputGroup: UiFilterInputGroupMainComponent;
|
||||
@@ -41,7 +46,8 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
|
||||
private _cdr: ChangeDetectorRef,
|
||||
private _router: Router,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _breadcrumb: BreadcrumbService
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _environment: EnvironmentService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -106,14 +112,29 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
|
||||
if (result.results.error) {
|
||||
} else {
|
||||
if (result.results.hits > 0) {
|
||||
// if (this.isTablet) {
|
||||
if (result.results.hits === 1) {
|
||||
const orderItem = result.results.result[0];
|
||||
this._router.navigate([this.getDetailsPath(orderItem, this.processId)]);
|
||||
this._router.navigate([this.getDetailsPath(orderItem, this.processId)], {
|
||||
queryParams: this._goodsOutSearchStore.filter.getQueryParams(),
|
||||
});
|
||||
} else {
|
||||
this._router.navigate(['/kunde', this.processId, 'order', 'results'], {
|
||||
queryParams: this._goodsOutSearchStore.filter.getQueryParams(),
|
||||
});
|
||||
}
|
||||
// } else {
|
||||
// const orderItem = result.results.result[0];
|
||||
// if (result.results.hits === 1) {
|
||||
// this._router.navigate(this.getDetailsPathDesktop(orderItem, this.processId), {
|
||||
// queryParams: this._goodsOutSearchStore.filter.getQueryParams(),
|
||||
// });
|
||||
// } else {
|
||||
// this._router.navigate(this.getDetailsPathDesktop(orderItem, this.processId), {
|
||||
// queryParams: this._goodsOutSearchStore.filter.getQueryParams(),
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
} else {
|
||||
this.message = 'keine Suchergebnisse';
|
||||
}
|
||||
@@ -149,6 +170,34 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
|
||||
: `/kunde/${processId}/order/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}`;
|
||||
}
|
||||
|
||||
// getDetailsPathDesktop(item: OrderItemListItemDTO, processId: number) {
|
||||
// return item?.compartmentCode
|
||||
// ? [
|
||||
// '/kunde',
|
||||
// processId,
|
||||
// 'order',
|
||||
// {
|
||||
// outlets: {
|
||||
// main: null,
|
||||
// left: 'results',
|
||||
// right: ['details', 'compartment', encodeURIComponent(item?.compartmentCode), item?.processingStatus],
|
||||
// },
|
||||
// },
|
||||
// ]
|
||||
// : [
|
||||
// '/kunde',
|
||||
// processId,
|
||||
// 'order',
|
||||
// {
|
||||
// outlets: {
|
||||
// main: null,
|
||||
// left: 'results',
|
||||
// right: ['details', 'order', encodeURIComponent(item?.orderNumber), item?.processingStatus],
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
// }
|
||||
|
||||
queryChangeDebounce = debounce(async () => {
|
||||
this.queryChanged$.next(true);
|
||||
await this.updateQueryParams(this.processId);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<a
|
||||
class="page-customer-order-item__item-card p-5 desktop:p-px-10 h-[212px] desktop:h-[181px] bg-white rounded-card"
|
||||
[routerLink]="detailsLink"
|
||||
[queryParams]="queryParams"
|
||||
[queryParamsHandling]="!isTablet ? 'preserve' : ''"
|
||||
>
|
||||
{{ item?.product?.name }}
|
||||
<!-- <div class="page-search-result-item__item-thumbnail text-center mr-4 w-[50px] h-[79px]">
|
||||
<img
|
||||
class="page-search-result-item__item-image w-[50px] h-[79px]"
|
||||
loading="lazy"
|
||||
*ngIf="item?.imageId | thumbnailUrl; let thumbnailUrl"
|
||||
[src]="thumbnailUrl"
|
||||
[alt]="item?.product?.name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid-container-test">
|
||||
<div
|
||||
class="page-search-result-item__item-title font-bold text-2xl"
|
||||
[class.text-xl]="item?.product?.name?.length >= 35"
|
||||
[class.text-lg]="item?.product?.name?.length >= 40"
|
||||
[class.text-md]="item?.product?.name?.length >= 50"
|
||||
[class.text-sm]="item?.product?.name?.length >= 60 || !isTablet"
|
||||
[class.text-xs]="item?.product?.name?.length >= 100 || (!isTablet && item?.product?.name?.length >= 70)"
|
||||
>
|
||||
{{ item?.product?.name }}
|
||||
</div>
|
||||
|
||||
<div class="page-search-result-item__item-special-comment font-bold text-sm">
|
||||
{{ item?.specialComment }}
|
||||
</div>
|
||||
|
||||
<div class="page-search-result-item__item-select-bullet justify-self-end">
|
||||
<input
|
||||
*ngIf="selectable"
|
||||
(click)="$event.stopPropagation()"
|
||||
[ngModel]="selected$ | async"
|
||||
(ngModelChange)="setSelected()"
|
||||
class="isa-select-bullet"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div> -->
|
||||
</a>
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply flex flex-col w-full h-[212px] desktop:h-[181px] bg-white;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { isEqual } from 'lodash';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { CustomerOrderSearchStore } from '../customer-order-search.store';
|
||||
|
||||
export interface CustomerOrderItemComponentState {
|
||||
item?: OrderItemListItemDTO;
|
||||
selected: boolean;
|
||||
selectable: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-item',
|
||||
templateUrl: 'customer-order-item.component.html',
|
||||
styleUrls: ['customer-order-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerOrderItemComponent extends ComponentStore<CustomerOrderItemComponentState> implements OnInit {
|
||||
@Input()
|
||||
get item() {
|
||||
return this.get((s) => s.item);
|
||||
}
|
||||
set item(item: OrderItemListItemDTO) {
|
||||
if (!isEqual(this.item, item)) {
|
||||
this.patchState({ item });
|
||||
}
|
||||
}
|
||||
|
||||
readonly item$ = this.select((s) => s.item);
|
||||
|
||||
// selected$ = this._articleSearchService.selectedItemIds$.pipe(map((selectedItemIds) => selectedItemIds.includes(this.item?.id)));
|
||||
|
||||
@Input()
|
||||
selected: boolean;
|
||||
|
||||
@Input()
|
||||
get selectable() {
|
||||
return this.get((s) => s.selectable);
|
||||
}
|
||||
set selectable(selectable: boolean) {
|
||||
if (this.selectable !== selectable) {
|
||||
this.patchState({ selectable });
|
||||
}
|
||||
}
|
||||
|
||||
@Output()
|
||||
selectedChange = new EventEmitter<boolean>();
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
get queryParams() {
|
||||
const compartmentCode = this.item?.compartmentCode;
|
||||
const archive = !!this._customerOrderStore.filter?.getQueryParams()?.main_archive || false;
|
||||
|
||||
if (compartmentCode) {
|
||||
return {
|
||||
buyerNumber: this.item?.buyerNumber,
|
||||
archive,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
archive,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
get detailsLink() {
|
||||
const orderNumber = this.item?.orderNumber;
|
||||
const processingStatus = this.item?.processingStatus;
|
||||
const compartmentCode = this.item?.compartmentCode;
|
||||
// if (this.isTablet) {
|
||||
if (compartmentCode) {
|
||||
return [
|
||||
`/kunde/${this._applicationService.activatedProcessId}/order/details/compartment/${encodeURIComponent(
|
||||
compartmentCode
|
||||
)}/${processingStatus}`,
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
`/kunde/${this._applicationService.activatedProcessId}/order/details/order/${encodeURIComponent(orderNumber)}/${processingStatus}`,
|
||||
];
|
||||
}
|
||||
// } else {
|
||||
// if (compartmentCode) {
|
||||
// return [
|
||||
// '/kunde',
|
||||
// this._applicationService.activatedProcessId,
|
||||
// 'order',
|
||||
// {
|
||||
// outlets: {
|
||||
// main: null,
|
||||
// left: 'results',
|
||||
// right: ['details', 'compartment', encodeURIComponent(compartmentCode), processingStatus],
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
// } else {
|
||||
// return [
|
||||
// '/kunde',
|
||||
// this._applicationService.activatedProcessId,
|
||||
// 'order',
|
||||
// { outlets: { main: null, left: 'results', right: ['details', 'order', encodeURIComponent(orderNumber), processingStatus] } },
|
||||
// ];
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _environment: EnvironmentService,
|
||||
private _customerOrderStore: CustomerOrderSearchStore,
|
||||
private _applicationService: ApplicationService
|
||||
) {
|
||||
super({ selected: false, selectable: false });
|
||||
}
|
||||
|
||||
ngOnInit() {}
|
||||
}
|
||||
@@ -1,7 +1,28 @@
|
||||
<div class="hits">{{ hits$ | async }} Titel</div>
|
||||
<div class="page-customer-order-search-results__header bg-background-liste flex flex-col items-end pb-4">
|
||||
<a
|
||||
[class.active]="hasFilter$ | async"
|
||||
class="page-customer-order-search-results__filter h-14 rounded-card font-bold px-5 mb-4 text-lg bg-cadet-blue flex flex-row flex-nowrap items-center justify-center"
|
||||
[routerLink]="filterRoute"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<ui-svg-icon class="mr-2" icon="filter-variant"></ui-svg-icon>
|
||||
Filter
|
||||
</a>
|
||||
|
||||
<div
|
||||
*ngIf="hits$ | async; let hits"
|
||||
class="page-customer-order-search-results__items-count inline-flex flex-row items-center pr-5 text-sm"
|
||||
>
|
||||
{{ hits ??
|
||||
0 }}
|
||||
Titel
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ui-scroll-container
|
||||
*ngIf="!(listEmpty$ | async); else emptyMessage"
|
||||
[loading]="loading$ | async"
|
||||
[showScrollbar]="false"
|
||||
[showScrollArrow]="false"
|
||||
(reachEnd)="loadMore()"
|
||||
[deltaEnd]="150"
|
||||
[itemLength]="itemLength$ | async"
|
||||
@@ -10,6 +31,22 @@
|
||||
>
|
||||
<ng-container *ngIf="processId$ | async; let processId">
|
||||
<shared-goods-in-out-order-group *ngFor="let bueryNumberGroup of items$ | async | groupBy: byBuyerNumberFn">
|
||||
<!-- <div class="page-customer-order-search__items-list" *ngFor="let bueryNumberGroup of items$ | async | groupBy: byBuyerNumberFn">
|
||||
<ng-container *ngIf="bueryNumberGroup.items[0]; let firstItem">
|
||||
<div class="page-customer-order-search__item-header-group bg-white text-xl rounded-t-card p-4 font-bold mb-px-2">
|
||||
<h3 class="mt-0 mb-4">
|
||||
{{ firstItem?.organisation }}
|
||||
<ng-container *ngIf="!!firstItem?.organisation && (!!firstItem?.firstName || !!firstItem?.lastName)"> - </ng-container>
|
||||
{{ firstItem?.lastName }}
|
||||
{{ firstItem?.firstName }}
|
||||
</h3>
|
||||
<h4 class="mt-0 mb-0">
|
||||
{{ firstItem?.buyerNumber }}
|
||||
ans lager (nicht abgeholt) bezahlt
|
||||
</h4>
|
||||
</div>
|
||||
</ng-container> -->
|
||||
|
||||
<ng-container *ngFor="let orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn; let lastOrderNumber = last">
|
||||
<ng-container
|
||||
*ngFor="let processingStatusGroup of orderNumberGroup.items | groupBy: byProcessingStatusFn; let lastProcessingStatus = last"
|
||||
@@ -26,6 +63,17 @@
|
||||
[selected]="item | goodsOutItemSelected: selectedOrderItemSubsetIds"
|
||||
(selectedChange)="setSelectedItem(item, $event)"
|
||||
></shared-goods-in-out-order-group-item>
|
||||
|
||||
<!-- <page-customer-order-item
|
||||
*ngFor="let item of compartmentCodeGroup.items; let firstItem = first; trackBy: trackByFn"
|
||||
class="mb-px-10"
|
||||
[selectable]="item | goodsOutItemSelectable: selectionRules:selectedItems"
|
||||
[selected]="item | goodsOutItemSelected: selectedOrderItemSubsetIds"
|
||||
[item]="item"
|
||||
(click)="navigateToDetails(processId, item)"
|
||||
(selectedChange)="setSelectedItem(item, $event)"
|
||||
></page-customer-order-item> -->
|
||||
|
||||
<div class="divider" *ngIf="!lastCompartmentCode"></div>
|
||||
</ng-container>
|
||||
<div class="divider" *ngIf="!lastProcessingStatus"></div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// :host {
|
||||
// @apply box-border block h-[100vh] max-h-[calc(100vh-364px)] desktop:max-h-[calc(100vh-300px)];
|
||||
// }
|
||||
|
||||
:host {
|
||||
@apply block relative;
|
||||
}
|
||||
|
||||
.hits {
|
||||
@apply text-active-customer text-right mb-3 font-semibold text-base;
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
@apply bg-white text-center font-semibold text-inactive-customer py-10 rounded-card;
|
||||
}
|
||||
@@ -18,6 +18,10 @@ shared-goods-in-out-order-group-item {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
||||
// page-customer-order-item {
|
||||
// @apply cursor-pointer;
|
||||
// }
|
||||
|
||||
.actions {
|
||||
@apply fixed bottom-28 inline-grid grid-flow-col gap-7;
|
||||
left: 50%;
|
||||
@@ -40,6 +44,10 @@ shared-goods-in-out-order-group-item {
|
||||
}
|
||||
}
|
||||
|
||||
// ::ng-deep page-customer-order-search-results ui-scroll-container .scroll-container {
|
||||
// max-height: calc(100vh - 25.5rem) !important;
|
||||
// }
|
||||
|
||||
::ng-deep .desktop page-goods-out-search-results ui-scroll-container {
|
||||
.scrollbar-gap::-webkit-scrollbar-track {
|
||||
margin-bottom: 7.25rem;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild, TrackByFunction } from '@angular/core';
|
||||
import { debounceTime, first, map, shareReplay, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { debounceTime, filter, first, map, shareReplay, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { KeyValueDTOOfStringAndString, OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ActivatedRoute, ChildrenOutletContexts, Params, Router } from '@angular/router';
|
||||
import { CustomerOrderSearchStore } from '../customer-order-search.store';
|
||||
import { BehaviorSubject, combineLatest, Observable, Subject, Subscription } from 'rxjs';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
@@ -11,6 +11,8 @@ import { OrderItemsContext } from '@domain/oms';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { UiScrollContainerComponent } from '@ui/scroll-container';
|
||||
import { UiFilter } from '@ui/filter';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
export interface CustomerOrderSearchResultsState {
|
||||
selectedOrderItemSubsetIds: number[];
|
||||
@@ -80,13 +82,51 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
|
||||
scrollTo: number;
|
||||
|
||||
filter$ = this._goodsOutSearchStore.filter$;
|
||||
|
||||
initialFilter$ = this.filter$.pipe(
|
||||
filter((filter) => !!filter),
|
||||
first()
|
||||
);
|
||||
|
||||
hasFilter$ = this.filter$.pipe(
|
||||
withLatestFrom(this.initialFilter$),
|
||||
map(([filter, initialFilter]) => !isEqual(filter?.getQueryParams(), initialFilter?.getQueryParams()))
|
||||
);
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
get rightOutletParams(): Params {
|
||||
return this._activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'right')?.snapshot?.params;
|
||||
}
|
||||
|
||||
get filterRoute() {
|
||||
const processId = this._activatedRoute?.parent?.snapshot?.data?.processId;
|
||||
// if (!this.isTablet) {
|
||||
// const orderNumber = this.rightOutletParams?.orderNumber;
|
||||
// const compartmentCode = this.rightOutletParams?.compartmentCode;
|
||||
// const processingStatus = this.rightOutletParams?.processingStatus;
|
||||
// return [
|
||||
// '/kunde',
|
||||
// processId,
|
||||
// 'order',
|
||||
// { outlets: { main: null, left: 'results', right: ['filter', orderNumber ?? compartmentCode, processingStatus] } },
|
||||
// ];
|
||||
// } else {
|
||||
return ['/kunde', processId, 'order', 'filter'];
|
||||
// }
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _goodsOutSearchStore: CustomerOrderSearchStore,
|
||||
private _router: Router,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _commandService: CommandService,
|
||||
private _modal: UiModalService
|
||||
private _modal: UiModalService,
|
||||
private _environment: EnvironmentService
|
||||
) {
|
||||
super({
|
||||
selectedOrderItemSubsetIds: [],
|
||||
@@ -263,6 +303,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
const compartmentCode = orderItem.compartmentCode;
|
||||
const archive = !!this._goodsOutSearchStore.filter?.getQueryParams()?.main_archive || false;
|
||||
|
||||
// if (this.isTablet) {
|
||||
if (compartmentCode) {
|
||||
this._router.navigate([`/kunde/${processId}/order/details/compartment/${encodeURIComponent(compartmentCode)}/${processingStatus}`], {
|
||||
queryParams: {
|
||||
@@ -277,6 +318,46 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
},
|
||||
});
|
||||
}
|
||||
// } else {
|
||||
// if (compartmentCode) {
|
||||
// this._router.navigate(
|
||||
// [
|
||||
// '/kunde',
|
||||
// processId,
|
||||
// 'order',
|
||||
// {
|
||||
// outlets: {
|
||||
// main: null,
|
||||
// left: 'results',
|
||||
// right: ['details', 'compartment', encodeURIComponent(compartmentCode), processingStatus],
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// {
|
||||
// queryParamsHandling: 'preserve',
|
||||
// queryParams: {
|
||||
// buyerNumber: orderItem.buyerNumber,
|
||||
// archive,
|
||||
// },
|
||||
// }
|
||||
// );
|
||||
// } else {
|
||||
// this._router.navigate(
|
||||
// [
|
||||
// '/kunde',
|
||||
// processId,
|
||||
// 'order',
|
||||
// { outlets: { main: null, left: 'results', right: ['details', 'order', encodeURIComponent(orderNumber), processingStatus] } },
|
||||
// ],
|
||||
// {
|
||||
// queryParamsHandling: 'preserve',
|
||||
// queryParams: {
|
||||
// archive,
|
||||
// },
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
setSelectedItem(item: OrderItemListItemDTO, selected: boolean) {
|
||||
|
||||
@@ -8,10 +8,18 @@ import { CustomerOrderItemSelectablePipe } from './customer-order-item-selectabl
|
||||
import { CustomerOrderItemSelectedPipe } from './customer-order-item-selectede.pipe';
|
||||
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
|
||||
import { UiScrollContainerModule } from '@ui/scroll-container';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { CustomerOrderItemComponent } from './customer-order-item.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, GoodsInOutOrderGroupModule, UiCommonModule, UiSpinnerModule, UiScrollContainerModule],
|
||||
exports: [CustomerOrderSearchResultsComponent],
|
||||
declarations: [CustomerOrderSearchResultsComponent, CustomerOrderItemSelectablePipe, CustomerOrderItemSelectedPipe],
|
||||
imports: [CommonModule, RouterModule, GoodsInOutOrderGroupModule, UiCommonModule, UiIconModule, UiSpinnerModule, UiScrollContainerModule],
|
||||
exports: [CustomerOrderSearchResultsComponent, CustomerOrderItemComponent],
|
||||
declarations: [
|
||||
CustomerOrderSearchResultsComponent,
|
||||
CustomerOrderItemSelectablePipe,
|
||||
CustomerOrderItemSelectedPipe,
|
||||
CustomerOrderItemComponent,
|
||||
],
|
||||
})
|
||||
export class CustomerOrderSearchResultsModule {}
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from './customer-order-item-selectable.pipe';
|
||||
export * from './customer-order-item-selectede.pipe';
|
||||
export * from './customer-order-search-results.component';
|
||||
export * from './customer-order-search-results.module';
|
||||
export * from './customer-order-item.component';
|
||||
|
||||
@@ -8,4 +8,19 @@
|
||||
</shared-branch-selector>
|
||||
</shared-breadcrumb>
|
||||
|
||||
<!-- <ng-container *ngIf="isTablet; else desktop"> -->
|
||||
<router-outlet></router-outlet>
|
||||
<!-- </ng-container>
|
||||
|
||||
<ng-template #desktop>
|
||||
<router-outlet name="main"></router-outlet>
|
||||
<div class="grid desktop:grid-cols-[31rem_auto]">
|
||||
<div class="mr-6 hidden desktop:block">
|
||||
<router-outlet name="left"></router-outlet>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<router-outlet name="right"></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template> -->
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { BranchDTO } from '@swagger/checkout';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -19,6 +20,10 @@ export class CustomerOrderComponent implements OnInit {
|
||||
|
||||
selectedBranch$: Observable<BranchDTO>;
|
||||
|
||||
get isTablet() {
|
||||
return this._environmentService.matchTablet();
|
||||
}
|
||||
|
||||
onCustomerOrderDetailsPage$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
@@ -26,6 +31,7 @@ export class CustomerOrderComponent implements OnInit {
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _uiModal: UiModalService,
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _environmentService: EnvironmentService,
|
||||
public auth: AuthService
|
||||
) {}
|
||||
|
||||
|
||||
@@ -10,7 +10,15 @@
|
||||
</button>
|
||||
</div> -->
|
||||
|
||||
<input #autofocus="uiAutofocus" autofocus type="text" [formControl]="control" (keydown.enter)="searchTasks()" placeholder="Suchen" />
|
||||
<input
|
||||
id="searchbox"
|
||||
#autofocus="uiAutofocus"
|
||||
autofocus
|
||||
type="text"
|
||||
[formControl]="control"
|
||||
(keydown.enter)="searchTasks()"
|
||||
placeholder="Suchen"
|
||||
/>
|
||||
|
||||
<div class="actions">
|
||||
<button class="clear" (click)="clearSearch(); autofocus.focus()" *ngIf="control.value.length">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
}
|
||||
|
||||
::ng-deep shared-branch-selector ui-autocomplete .ui-autocomplete-output-wrapper {
|
||||
@apply overflow-hidden overflow-y-auto max-w-content rounded-b-md;
|
||||
@apply overflow-hidden overflow-y-auto rounded-b-md;
|
||||
max-height: 350px;
|
||||
width: 100%;
|
||||
left: unset;
|
||||
@@ -28,6 +28,26 @@
|
||||
button {
|
||||
@apply py-2;
|
||||
}
|
||||
|
||||
@screen desktop-small {
|
||||
@apply max-w-screen-desktop-small;
|
||||
}
|
||||
|
||||
@screen desktop {
|
||||
@apply max-w-screen-desktop;
|
||||
}
|
||||
|
||||
@screen desktop-large {
|
||||
@apply max-w-screen-desktop-large;
|
||||
}
|
||||
|
||||
@screen desktop-x-large {
|
||||
@apply max-w-screen-desktop-x-large;
|
||||
}
|
||||
|
||||
@screen desktop-xx-large {
|
||||
@apply max-w-screen-desktop-xx-large;
|
||||
}
|
||||
}
|
||||
|
||||
.shared-branch-selector-autocomplete-option.shared-branch-selector-selected {
|
||||
|
||||
@@ -207,14 +207,18 @@ export class BranchSelectorComponent implements OnInit, OnDestroy, AfterViewInit
|
||||
|
||||
clear() {
|
||||
this.setBranch();
|
||||
this.emitValues();
|
||||
this.complete.next('');
|
||||
this.onChange(undefined);
|
||||
this.onTouched();
|
||||
this.valueChange.emit(undefined);
|
||||
}
|
||||
|
||||
emitValues(branch?: BranchDTO) {
|
||||
this.onChange(branch);
|
||||
this.onTouched();
|
||||
this.valueChange.emit(branch);
|
||||
if (!!branch) {
|
||||
this.onChange(branch);
|
||||
this.onTouched();
|
||||
this.valueChange.emit(branch);
|
||||
}
|
||||
}
|
||||
|
||||
closeAndClearAutocomplete() {
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
div {
|
||||
@apply z-fixed mx-auto;
|
||||
height: calc(100vh - 8.375rem);
|
||||
@apply w-content;
|
||||
background-color: var(--bg-color);
|
||||
|
||||
@screen desktop-small {
|
||||
@apply max-w-screen-desktop-small;
|
||||
}
|
||||
|
||||
@screen desktop {
|
||||
@apply max-w-screen-desktop;
|
||||
}
|
||||
|
||||
@screen desktop-large {
|
||||
@apply max-w-screen-desktop-large;
|
||||
}
|
||||
|
||||
@screen desktop-x-large {
|
||||
@apply max-w-screen-desktop-x-large;
|
||||
}
|
||||
|
||||
@screen desktop-xx-large {
|
||||
@apply max-w-screen-desktop-xx-large;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div>
|
||||
<div class="inputs overflow-y-auto">
|
||||
<div class="inputs">
|
||||
<button
|
||||
class="ui-input"
|
||||
class="ui-input border border-solid border-[#EDEFF0]"
|
||||
type="button"
|
||||
*ngFor="let input of uiInputGroup?.input"
|
||||
[class.active]="activeInput === input"
|
||||
@@ -11,7 +11,12 @@
|
||||
<div class="grow">
|
||||
{{ input?.label }}
|
||||
</div>
|
||||
<ui-icon icon="arrow_head" size="1rem"></ui-icon>
|
||||
<span class="w-[14px] h-[14px] flex items-center justify-center text-2xl text-[#596470]" *ngIf="activeInput !== input"
|
||||
><ui-svg-icon icon="add"></ui-svg-icon
|
||||
></span>
|
||||
<span class="w-[14px] h-[14px] flex items-center justify-center text-2xl text-black" *ngIf="activeInput === input"
|
||||
><ui-svg-icon class="mr-[1px]" icon="remove"></ui-svg-icon
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,34 +1,19 @@
|
||||
:host {
|
||||
@apply grid grid-flow-col gap-2;
|
||||
@apply grid grid-flow-col;
|
||||
grid-template-columns: 240px 1fr;
|
||||
}
|
||||
|
||||
ui-icon {
|
||||
@apply transition transform text-cool-grey;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
@apply grid grid-flow-row gap-2;
|
||||
max-height: calc(100vh - 377px);
|
||||
|
||||
.ui-input {
|
||||
@apply flex flex-row items-center border-none outline-none p-4 font-bold text-base bg-white text-black text-left rounded-card transition transform;
|
||||
}
|
||||
|
||||
.has-options {
|
||||
@apply text-white bg-active-customer;
|
||||
|
||||
ui-icon {
|
||||
@apply text-white;
|
||||
}
|
||||
@apply flex flex-row items-center outline-none p-4 text-lg bg-white text-black text-left rounded-card transition transform mr-2;
|
||||
box-shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
|
||||
}
|
||||
|
||||
.ui-input.active {
|
||||
@apply text-black bg-white pr-6 -mr-2 rounded-r-none;
|
||||
|
||||
ui-icon {
|
||||
@apply rotate-180 text-cool-grey;
|
||||
}
|
||||
@apply text-black bg-white font-bold pr-6 mr-0 rounded-r-none border-none;
|
||||
box-shadow: 0px 6px 24px rgba(206, 212, 219, 0.8);
|
||||
}
|
||||
|
||||
.space-right {
|
||||
@@ -40,6 +25,13 @@ ui-icon {
|
||||
}
|
||||
}
|
||||
|
||||
.has-options {
|
||||
@apply text-black bg-white font-bold;
|
||||
span {
|
||||
@apply text-black;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep ui-filter-filter-group-filter .input-options-wrapper {
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<ui-searchbox
|
||||
<shared-searchbox
|
||||
class="w-full"
|
||||
[placeholder]="uiInput?.placeholder"
|
||||
[query]="uiInput?.value"
|
||||
(queryChange)="onQueryChange($event)"
|
||||
@@ -14,7 +15,7 @@
|
||||
{{ item.display }}
|
||||
</button>
|
||||
</ui-autocomplete>
|
||||
</ui-searchbox>
|
||||
</shared-searchbox>
|
||||
<ng-container *ngIf="showDescription && uiInput?.description">
|
||||
<button [uiOverlayTrigger]="tooltipContent" #tooltip="uiOverlayTrigger" class="info-tooltip-button" type="button">
|
||||
i
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
}
|
||||
|
||||
.info-tooltip-button {
|
||||
@apply border-font-customer border-2 border-solid bg-white rounded-md text-base font-bold absolute;
|
||||
@apply border-font-customer ml-4 border-2 border-solid bg-white rounded-md text-base font-bold absolute;
|
||||
border-style: outset;
|
||||
width: 31px;
|
||||
height: 31px;
|
||||
top: 14px;
|
||||
right: -45px;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ import { CommonModule } from '@angular/common';
|
||||
|
||||
import { FilterInputGroupMainComponent } from './filter-input-group-main.component';
|
||||
import { UiAutocompleteModule } from '@ui/autocomplete';
|
||||
import { UiSearchboxNextModule } from '@ui/searchbox';
|
||||
import { UiTooltipModule } from 'apps/ui/tooltip/src/public-api';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { SearchboxModule } from '@shared/components/searchbox';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiCommonModule, UiSearchboxNextModule, UiAutocompleteModule, UiTooltipModule],
|
||||
imports: [CommonModule, UiCommonModule, SearchboxModule, UiAutocompleteModule, UiTooltipModule],
|
||||
exports: [FilterInputGroupMainComponent],
|
||||
declarations: [FilterInputGroupMainComponent],
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<ui-filter-filter-group-main [inputGroup]="uiFilter?.filter | group: 'main'"></ui-filter-filter-group-main>
|
||||
<ui-filter-input-group-main
|
||||
<shared-filter-filter-group-main [inputGroup]="uiFilter?.filter | group: 'main'"></shared-filter-filter-group-main>
|
||||
<shared-filter-input-group-main
|
||||
*ngIf="uiFilter?.input | group: 'main'; let inputGroupMain"
|
||||
[inputGroup]="inputGroupMain"
|
||||
(search)="emitSearch($event)"
|
||||
[loading]="loading"
|
||||
[hint]="hint"
|
||||
[scanner]="scanner"
|
||||
></ui-filter-input-group-main>
|
||||
<ui-filter-filter-group-filter [inputGroup]="uiFilter?.filter | group: 'filter'"></ui-filter-filter-group-filter>
|
||||
></shared-filter-input-group-main>
|
||||
<shared-filter-filter-group-filter [inputGroup]="uiFilter?.filter | group: 'filter'"></shared-filter-filter-group-filter>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row gap-8;
|
||||
@apply grid grid-flow-row gap-[1.875rem];
|
||||
}
|
||||
|
||||
ui-filter-input-group-main {
|
||||
shared-filter-input-group-main {
|
||||
@apply mx-auto w-full;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
@@ -7,11 +6,6 @@ import {
|
||||
SimpleChanges,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ElementRef,
|
||||
Renderer2,
|
||||
Inject,
|
||||
AfterViewInit,
|
||||
HostListener,
|
||||
ViewChild,
|
||||
ContentChildren,
|
||||
QueryList,
|
||||
@@ -26,7 +20,7 @@ import { IFilter, Filter } from './tree/filter';
|
||||
styleUrls: ['filter.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FilterComponent implements OnChanges, AfterViewInit {
|
||||
export class FilterComponent implements OnChanges {
|
||||
@Input()
|
||||
loading: boolean;
|
||||
|
||||
@@ -39,9 +33,6 @@ export class FilterComponent implements OnChanges, AfterViewInit {
|
||||
@Input()
|
||||
filter: IFilter;
|
||||
|
||||
@Input()
|
||||
resizeInputOptionsToElement: string | HTMLElement;
|
||||
|
||||
@Input()
|
||||
scanner = false;
|
||||
|
||||
@@ -55,7 +46,7 @@ export class FilterComponent implements OnChanges, AfterViewInit {
|
||||
return this.filter instanceof Filter && this.filter;
|
||||
}
|
||||
|
||||
constructor(private elementRef: ElementRef, private renderer: Renderer2, @Inject(DOCUMENT) private document: Document) {}
|
||||
constructor() {}
|
||||
|
||||
ngOnChanges({ filter }: SimpleChanges): void {
|
||||
if (filter) {
|
||||
@@ -65,36 +56,6 @@ export class FilterComponent implements OnChanges, AfterViewInit {
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => this.resizeInputOptions(), 500);
|
||||
}
|
||||
|
||||
getHtmlElement(element: string | HTMLElement): HTMLElement {
|
||||
return typeof element === 'string' ? this.document.querySelector(element) : element;
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
@HostListener('window:click', ['$event'])
|
||||
resizeInputOptions() {
|
||||
if (this.resizeInputOptionsToElement) {
|
||||
const inputOptions = this.elementRef.nativeElement.querySelector('.input-options');
|
||||
const targetElement = this.getHtmlElement(this.resizeInputOptionsToElement);
|
||||
|
||||
// set the max-height of the input-options to the height of the actions, depending on the distance between the two
|
||||
if (inputOptions && targetElement) {
|
||||
const distanceBetweenElements = this.getDistanceBetweenElements(inputOptions, targetElement) - 15;
|
||||
this.renderer.setStyle(inputOptions, 'max-height', `${distanceBetweenElements}px`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDistanceBetweenElements(element1: HTMLElement, element2: HTMLElement) {
|
||||
const rect1 = element1.getBoundingClientRect();
|
||||
const rect2 = element2.getBoundingClientRect();
|
||||
|
||||
return Math.abs(rect1.top - rect2.top);
|
||||
}
|
||||
|
||||
emitSearch(query: string) {
|
||||
setTimeout(() => {
|
||||
this.search.emit(query);
|
||||
|
||||
@@ -5,5 +5,5 @@ export * from './providers';
|
||||
export * from './shared';
|
||||
export * from './testing';
|
||||
export * from './tree';
|
||||
export * from './ui-filter.component';
|
||||
export * from './ui-filter.module';
|
||||
export * from './filter.component';
|
||||
export * from './filter.module';
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
<div class="order-by-filter-button-wrapper">
|
||||
<button
|
||||
[attr.data-label]="label"
|
||||
class="order-by-filter-button"
|
||||
type="button"
|
||||
*ngFor="let label of orderByKeys"
|
||||
(click)="setActive(label)"
|
||||
>
|
||||
<span>
|
||||
{{ label }}
|
||||
</span>
|
||||
<ui-icon
|
||||
[class.asc]="label === activeOrderBy?.label && !activeOrderBy.desc"
|
||||
[class.desc]="label === activeOrderBy?.label && activeOrderBy.desc"
|
||||
icon="arrow"
|
||||
size="14px"
|
||||
></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
<ng-container *ngIf="orderByKeys">
|
||||
<div class="group w-full flex flex-row justify-between" *ngFor="let group of groupedOrderByKeys">
|
||||
<button
|
||||
*ngFor="let label of group.keys"
|
||||
[attr.data-label]="label"
|
||||
class="order-by-filter-button"
|
||||
type="button"
|
||||
(click)="setActive(label)"
|
||||
>
|
||||
<span>
|
||||
{{ label }}
|
||||
</span>
|
||||
<ui-icon
|
||||
[class.asc]="label === activeOrderBy?.label && !activeOrderBy.desc"
|
||||
[class.desc]="label === activeOrderBy?.label && activeOrderBy.desc"
|
||||
icon="arrow"
|
||||
size="10px"
|
||||
></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,28 +1,23 @@
|
||||
:host {
|
||||
@apply box-border flex justify-center items-center;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.order-by-filter-button-wrapper {
|
||||
@apply flex flex-row items-center justify-center;
|
||||
@apply box-border flex flex-row items-center justify-center h-[3.3125rem] w-full;
|
||||
}
|
||||
|
||||
.order-by-filter-button {
|
||||
@apply bg-transparent outline-none border-none text-regular font-bold flex flex-row justify-center items-center m-0 py-2 px-2;
|
||||
@apply bg-transparent outline-none border-none text-regular font-bold flex flex-row justify-center items-center;
|
||||
}
|
||||
|
||||
::ng-deep .tablet ui-order-by-filter .order-by-filter-button {
|
||||
::ng-deep .tablet shared-order-by-filter .order-by-filter-button {
|
||||
@apply mx-0;
|
||||
}
|
||||
|
||||
ui-icon {
|
||||
@apply hidden transform ml-2 rounded-full p-1;
|
||||
@apply hidden transform ml-1 rounded-full p-1 bg-[#AEB7C1] h-[20px] w-[20px] items-center justify-center;
|
||||
transition: 250ms all ease-in-out;
|
||||
}
|
||||
|
||||
ui-icon.asc,
|
||||
ui-icon.desc {
|
||||
@apply flex rounded-full visible text-white;
|
||||
@apply flex rounded-full visible text-black;
|
||||
}
|
||||
|
||||
ui-icon.asc {
|
||||
@@ -32,23 +27,3 @@ ui-icon.asc {
|
||||
ui-icon.desc {
|
||||
@apply rotate-90;
|
||||
}
|
||||
|
||||
::ng-deep .customer ui-order-by-filter {
|
||||
ui-icon {
|
||||
@apply bg-active-customer;
|
||||
}
|
||||
|
||||
.order-by-filter-button {
|
||||
@apply text-active-customer;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .branch ui-order-by-filter {
|
||||
ui-icon {
|
||||
@apply bg-active-branch;
|
||||
}
|
||||
|
||||
.order-by-filter-button {
|
||||
@apply text-active-branch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, ChangeDetectorRef, Output, EventEmitter, OnInit } from '@angular/core';
|
||||
import { UiOrderBy } from '@ui/filter';
|
||||
import { OrderBy } from '../tree';
|
||||
|
||||
@Component({
|
||||
selector: 'shared-order-by-filter',
|
||||
@@ -9,15 +9,48 @@ import { UiOrderBy } from '@ui/filter';
|
||||
})
|
||||
export class OrderByFilterComponent implements OnInit {
|
||||
@Input()
|
||||
orderBy: UiOrderBy[];
|
||||
orderBy: OrderBy[];
|
||||
|
||||
@Input()
|
||||
groupBy?: number[] = []; // example input: [2, 1, 2] should result in: [[item, item], [item], [item, item]]
|
||||
|
||||
@Output()
|
||||
selectedOrderByChange = new EventEmitter<UiOrderBy>();
|
||||
selectedOrderByChange = new EventEmitter<OrderBy>();
|
||||
|
||||
get orderByKeys() {
|
||||
return this.orderBy?.map((ob) => ob.label).filter((key, idx, self) => self.indexOf(key) === idx);
|
||||
}
|
||||
|
||||
get groupedOrderByKeys(): Array<{ group: number; keys: string[] }> {
|
||||
let orderByKeys = this.orderByKeys; // copy
|
||||
const grouped: Array<{ group: number; keys: string[] }> = [];
|
||||
|
||||
if (this.groupBy?.length > 1) {
|
||||
// group keys based on given input groupBy array structure
|
||||
for (let [outerIndex, group] of this.groupBy?.entries()) {
|
||||
let item = {
|
||||
group: outerIndex,
|
||||
keys: [],
|
||||
};
|
||||
|
||||
for (let [innerIndex, key] of orderByKeys.entries()) {
|
||||
if (innerIndex < group) {
|
||||
item.keys.push(key);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter already grouped keys from initial array
|
||||
orderByKeys = orderByKeys.filter((key) => !item.keys.find((itemKey) => itemKey === key));
|
||||
grouped.push(item);
|
||||
}
|
||||
} else {
|
||||
grouped.push({ group: 1, keys: this.orderByKeys });
|
||||
}
|
||||
return grouped;
|
||||
}
|
||||
|
||||
get activeOrderBy() {
|
||||
return this.orderBy?.find((f) => f.selected);
|
||||
}
|
||||
@@ -31,7 +64,7 @@ export class OrderByFilterComponent implements OnInit {
|
||||
setActive(orderBy: string) {
|
||||
const active = this.activeOrderBy;
|
||||
const orderBys = this.orderBy?.filter((f) => f.label === orderBy);
|
||||
let next: UiOrderBy;
|
||||
let next: OrderBy;
|
||||
|
||||
if (orderBys?.length) {
|
||||
if (active?.label !== orderBy) {
|
||||
|
||||
@@ -3,19 +3,9 @@
|
||||
}
|
||||
|
||||
button.ui-filter-chip {
|
||||
@apply grid grid-flow-col gap-2 items-center rounded-full text-base px-4 py-3 bg-white text-inactive-customer border-none font-bold;
|
||||
}
|
||||
|
||||
/** styling branch bereich **/
|
||||
::ng-deep .branch ui-filter-input-chip button.ui-filter-chip {
|
||||
@apply text-inactive-branch;
|
||||
@apply grid grid-flow-col gap-2 items-center rounded-full text-base px-4 py-3 bg-[#EDEFF0] border-none font-semibold;
|
||||
}
|
||||
|
||||
button.ui-filter-chip.selected {
|
||||
@apply bg-active-customer text-white;
|
||||
}
|
||||
|
||||
/** styling branch bereich **/
|
||||
::ng-deep .branch ui-filter-input-chip button.ui-filter-chip.selected {
|
||||
@apply bg-active-branch;
|
||||
@apply bg-[#596470] text-white;
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
</button>
|
||||
</div>
|
||||
<ng-container *ngIf="uiOption?.expanded">
|
||||
<ui-input-option-bool class="ml-10" *ngFor="let subOption of uiOption?.values" [option]="subOption"></ui-input-option-bool>
|
||||
<shared-input-option-bool class="ml-10" *ngFor="let subOption of uiOption?.values" [option]="subOption"></shared-input-option-bool>
|
||||
</ng-container>
|
||||
|
||||
@@ -18,6 +18,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep ui-input-option-bool ui-checkbox ui-icon {
|
||||
::ng-deep shared-input-option-bool ui-checkbox ui-icon {
|
||||
@apply text-cool-grey;
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
::ng-deep ui-input-option-date-range ui-datepicker.dp-left {
|
||||
::ng-deep shared-input-option-date-range ui-datepicker.dp-left {
|
||||
.dp {
|
||||
left: 102px;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep ui-input-option-date-range ui-datepicker.dp-right {
|
||||
::ng-deep shared-input-option-date-range ui-datepicker.dp-right {
|
||||
.dp {
|
||||
right: -6px;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,5 @@
|
||||
}
|
||||
|
||||
ui-form-control input {
|
||||
max-width: 200px;
|
||||
@apply px-0 rounded-none;
|
||||
@apply px-0 rounded-none max-w-[12rem];
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
</button>
|
||||
</div>
|
||||
<ng-container *ngIf="uiOption?.expanded">
|
||||
<ui-input-option-tri-state *ngFor="let subOption of uiOption?.values" [option]="subOption"></ui-input-option-tri-state>
|
||||
<shared-input-option-tri-state *ngFor="let subOption of uiOption?.values" [option]="subOption"></shared-input-option-tri-state>
|
||||
</ng-container>
|
||||
|
||||
@@ -10,7 +10,7 @@ ui-switch {
|
||||
@apply px-4 py-2 flex flex-row justify-between items-center;
|
||||
|
||||
.btn-expand {
|
||||
@apply border-none outline-none bg-transparent text-cool-grey;
|
||||
@apply border-none outline-none bg-transparent text-[#0556B4];
|
||||
|
||||
ui-icon {
|
||||
@apply transition-all transform rotate-90;
|
||||
@@ -22,6 +22,6 @@ ui-switch {
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep ui-input-option-bool ui-checkbox ui-icon {
|
||||
@apply text-cool-grey;
|
||||
::ng-deep shared-input-option-bool ui-checkbox ui-icon {
|
||||
@apply text-[#0556B4];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="input-options-wrapper">
|
||||
<div class="input-options-wrapper h-[calc(100vh-34.5rem)] desktop:h-[calc(100vh-28rem)]">
|
||||
<div class="hidden-overflow">
|
||||
<div class="input-options-header" [class.header-shadow]="scrollPersantage > 0">
|
||||
<button type="button" (click)="setSelected(undefined)">
|
||||
@@ -13,30 +13,31 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-options-wrapper">
|
||||
<div class="input-options-wrapper h-[calc(100vh-38.5rem)] desktop:h-[calc(100vh-32rem)]">
|
||||
<p class="input-desription">
|
||||
{{ uiInputOptions?.parent?.description }}
|
||||
</p>
|
||||
<ng-container *ngIf="uiInputOptions?.parent?.type === 2 || uiInputOptions?.parent?.type === 4">
|
||||
<div class="input-options" #inputOptionsConainter (scroll)="markForCheck()">
|
||||
<ng-container *ngIf="uiInputOptions?.parent?.type === 2">
|
||||
<ui-input-option-bool
|
||||
<shared-input-option-bool
|
||||
*ngFor="let option of uiInputOptions?.values"
|
||||
[option]="option"
|
||||
(optionChange)="optionChange($event)"
|
||||
></ui-input-option-bool>
|
||||
></shared-input-option-bool>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="uiInputOptions?.parent?.type === 4">
|
||||
<ui-input-option-tri-state *ngFor="let option of uiInputOptions?.values" [option]="option"> </ui-input-option-tri-state>
|
||||
<shared-input-option-tri-state *ngFor="let option of uiInputOptions?.values" [option]="option"> </shared-input-option-tri-state>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="w-full h-[6rem]"></div>
|
||||
<button class="cta-scroll" [class.up]="scrollPersantage > 20" *ngIf="scrollable" (click)="scroll(20)">
|
||||
<ui-icon icon="arrow" size="20px"></ui-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
<ui-input-option-date-range *ngIf="uiInputOptions?.parent?.type === 128" [options]="uiInputOptions?.values">
|
||||
</ui-input-option-date-range>
|
||||
<ui-input-option-number-range *ngIf="uiInputOptions?.parent?.type === 4096" [options]="uiInputOptions?.values">
|
||||
</ui-input-option-number-range>
|
||||
<shared-input-option-date-range *ngIf="uiInputOptions?.parent?.type === 128" [options]="uiInputOptions?.values">
|
||||
</shared-input-option-date-range>
|
||||
<shared-input-option-number-range *ngIf="uiInputOptions?.parent?.type === 4096" [options]="uiInputOptions?.values">
|
||||
</shared-input-option-number-range>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
:host {
|
||||
@apply block;
|
||||
box-shadow: 0px 6px 24px rgba(206, 212, 219, 0.8);
|
||||
}
|
||||
|
||||
.input-desription {
|
||||
@@ -19,7 +20,7 @@
|
||||
@apply grid grid-flow-col justify-end items-center mb-2;
|
||||
|
||||
button {
|
||||
@apply bg-transparent p-4 text-base outline-none border-none font-semibold text-inactive-customer;
|
||||
@apply bg-transparent p-4 text-base outline-none border-none text-[#0556B4];
|
||||
}
|
||||
|
||||
&.header-shadow {
|
||||
@@ -36,7 +37,8 @@
|
||||
}
|
||||
|
||||
.cta-scroll {
|
||||
@apply absolute bottom-4 right-4 shadow-cta border-none outline-none bg-white w-10 h-10 rounded-full flex flex-row flex-nowrap items-center justify-center text-active-customer;
|
||||
@apply absolute bottom-4 right-4 border-none outline-none bg-white w-[3.625rem] h-[3.625rem] rounded-full flex flex-row flex-nowrap items-center justify-center text-[#596470];
|
||||
box-shadow: 0px 0px 20px rgba(89, 100, 112, 0.5);
|
||||
|
||||
ui-icon {
|
||||
@apply transition-transform transform rotate-90;
|
||||
@@ -46,15 +48,3 @@
|
||||
@apply -rotate-90;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .branch ui-filter-input-options {
|
||||
.cta-scroll {
|
||||
@apply text-cool-grey;
|
||||
}
|
||||
|
||||
.input-options-header {
|
||||
button {
|
||||
@apply text-cool-grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,12 @@ export class FilterInputTextComponent extends AbstractUiFilterInputDirective imp
|
||||
this.control.setValue(this.value);
|
||||
this.updateValidator();
|
||||
|
||||
const uiInputChangesSub = this.uiInput?.changes?.subscribe((changes) => {
|
||||
if (this.control.value !== changes?.target?.value) this.control.setValue(changes?.target?.value);
|
||||
|
||||
this.updateValidator();
|
||||
});
|
||||
|
||||
const onInputChangeSub = this.onUiInputChange$.subscribe((input) => {
|
||||
if (this.control.value !== input.value) this.control.setValue(input.value);
|
||||
|
||||
@@ -32,6 +38,7 @@ export class FilterInputTextComponent extends AbstractUiFilterInputDirective imp
|
||||
if (this.value !== value) this.setValue(value);
|
||||
});
|
||||
|
||||
this._subscriptions.add(uiInputChangesSub);
|
||||
this._subscriptions.add(onInputChangeSub);
|
||||
this._subscriptions.add(onControlValueChangeSub);
|
||||
}
|
||||
|
||||
@@ -201,6 +201,21 @@ export class Filter implements IFilter {
|
||||
return target;
|
||||
}
|
||||
|
||||
unselectAll() {
|
||||
this.filter?.forEach((uiInputGroup) =>
|
||||
uiInputGroup?.input?.forEach((uiInput) => {
|
||||
uiInput?.setSelected(undefined);
|
||||
uiInput?.setValue(undefined);
|
||||
})
|
||||
);
|
||||
this.input?.forEach((uiInputGroup) =>
|
||||
uiInputGroup?.input?.forEach((uiInput) => {
|
||||
uiInput?.setSelected(undefined);
|
||||
uiInput?.setValue(undefined);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
static getQueryParamsFromQueryTokenDTO(queryToken: QueryTokenDTO): Record<string, string> {
|
||||
const queryParams: Record<string, string> = {};
|
||||
|
||||
|
||||
@@ -11,10 +11,7 @@
|
||||
}
|
||||
|
||||
.goods-in-out-order-details-action-wrapper {
|
||||
@apply fixed bottom-28 inline-flex flex-row-reverse flex-wrap justify-center items-center;
|
||||
width: 80%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
@apply fixed bottom-28 left-[50%] inline-flex desktop:flex translate-x-[-50%] desktop:translate-x-[15%] w-[80%] desktop:w-fit flex-row-reverse flex-wrap justify-center items-center;
|
||||
|
||||
.cta-action {
|
||||
@apply border-2 border-solid border-brand rounded-full py-3 px-6 font-bold text-lg outline-none self-end whitespace-nowrap m-2;
|
||||
|
||||
@@ -64,7 +64,7 @@ input {
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply flex justify-center items-center fixed left-0 right-0;
|
||||
@apply flex justify-center items-center desktop:translate-x-[60%] desktop:w-fit fixed left-0 right-0 desktop:left-[unset] desktop:right-[unset];
|
||||
bottom: 110px;
|
||||
|
||||
.cta-close {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<div class="searchbox-input-wrapper">
|
||||
<input
|
||||
id="searchbox"
|
||||
class="searchbox-input"
|
||||
#input
|
||||
type="text"
|
||||
|
||||
6
apps/shared/services/ng-package.json
Normal file
6
apps/shared/services/ng-package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
15
apps/shared/services/src/lib/base-navigation.service.ts
Normal file
15
apps/shared/services/src/lib/base-navigation.service.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NavigationDetails, OutletLocations, OutletParams } from './defs';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export abstract class BaseNavigationService {
|
||||
constructor() {}
|
||||
|
||||
abstract getOutletLocations(activatedRoute: ActivatedRoute): OutletLocations;
|
||||
abstract getOutletParams(activatedRoute: ActivatedRoute): OutletParams;
|
||||
abstract mainOutletActive$(activatedRoute: ActivatedRoute): Observable<boolean>;
|
||||
abstract mainOutletActive(activatedRoute: ActivatedRoute): boolean;
|
||||
protected abstract _navigateTo(navigation: NavigationDetails): Promise<boolean>;
|
||||
}
|
||||
5
apps/shared/services/src/lib/defs/index.ts
Normal file
5
apps/shared/services/src/lib/defs/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// start:ng42.barrel
|
||||
export * from './navigation-details.model';
|
||||
export * from './outlet-locations.model';
|
||||
export * from './outlet-params.model';
|
||||
// end:ng42.barrel
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface NavigationDetails {
|
||||
routerLink: any[];
|
||||
queryParams?: Record<string, string>;
|
||||
queryParamsHandling?: 'merge' | 'preserve' | '';
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface OutletLocations {
|
||||
main: string;
|
||||
left: string;
|
||||
right: string;
|
||||
}
|
||||
7
apps/shared/services/src/lib/defs/outlet-params.model.ts
Normal file
7
apps/shared/services/src/lib/defs/outlet-params.model.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Params } from '@angular/router';
|
||||
|
||||
export interface OutletParams {
|
||||
main: Params;
|
||||
left: Params;
|
||||
right: Params;
|
||||
}
|
||||
16
apps/shared/services/src/lib/navigation.service.spec.ts
Normal file
16
apps/shared/services/src/lib/navigation.service.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NavigationService } from './navigation.service';
|
||||
|
||||
describe('NavigationService', () => {
|
||||
let service: NavigationService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(NavigationService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
50
apps/shared/services/src/lib/navigation.service.ts
Normal file
50
apps/shared/services/src/lib/navigation.service.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { NavigationDetails, OutletLocations, OutletParams } from './defs';
|
||||
import { BaseNavigationService } from './base-navigation.service';
|
||||
import { Observable, combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class NavigationService extends BaseNavigationService {
|
||||
constructor(private _router: Router, private _environment: EnvironmentService) {
|
||||
super();
|
||||
}
|
||||
|
||||
getOutletLocations(activatedRoute: ActivatedRoute): OutletLocations {
|
||||
return {
|
||||
main: activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'main')?.snapshot?.routeConfig?.path,
|
||||
left: activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'left')?.snapshot?.routeConfig?.path,
|
||||
right: activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'right')?.snapshot?.routeConfig?.path,
|
||||
};
|
||||
}
|
||||
|
||||
getOutletParams(activatedRoute: ActivatedRoute): OutletParams {
|
||||
return {
|
||||
main: activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'main')?.snapshot?.params,
|
||||
left: activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'left')?.snapshot?.params,
|
||||
right: activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'right')?.snapshot?.params,
|
||||
};
|
||||
}
|
||||
|
||||
mainOutletActive$(activatedRoute: ActivatedRoute): Observable<boolean> {
|
||||
return combineLatest([this._environment.matchDesktopSmall$, this._environment.matchDesktop$]).pipe(
|
||||
map(
|
||||
([desktopSmallState, desktopState]) =>
|
||||
!!this.getOutletLocations(activatedRoute)?.main && (desktopSmallState.matches || desktopState.matches)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
mainOutletActive(activatedRoute: ActivatedRoute): boolean {
|
||||
return !!this.getOutletLocations(activatedRoute)?.main && (this._environment.matchDesktopSmall() || this._environment.matchDesktop());
|
||||
}
|
||||
|
||||
protected async _navigateTo(navigation: NavigationDetails): Promise<boolean> {
|
||||
return await this._router.navigate(navigation.routerLink, {
|
||||
queryParams: navigation?.queryParams ?? {},
|
||||
queryParamsHandling: navigation?.queryParamsHandling ?? '',
|
||||
});
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user