mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merged PR 1185: #2982 #2983 #3028 #3031 Remission Liste Caching, Scrollposition, Loader, Virtual Scroll Viewport
#2982 #2983 #3028 #3031 Remission Liste Caching, Scrollposition, Loader, Virtual Scroll Viewport
This commit is contained in:
committed by
Lorenz Hilpert
parent
c4ed8d0648
commit
f657a088d4
2
apps/core/cache/src/lib/cache.service.ts
vendored
2
apps/core/cache/src/lib/cache.service.ts
vendored
@@ -51,7 +51,7 @@ export class CacheService {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
private delete(token: Object, from: 'session' | 'persist' = 'session') {
|
||||
delete(token: Object, from: 'session' | 'persist' = 'session') {
|
||||
if (from === 'session') {
|
||||
sessionStorage.removeItem(this.getKey(token));
|
||||
} else if (from === 'persist') {
|
||||
|
||||
@@ -209,7 +209,7 @@ export class DomainRemissionService {
|
||||
.pipe(
|
||||
map((res) => {
|
||||
const o = items.map((item) => {
|
||||
const stockInfo = res.result.find((stockInfo) => stockInfo.itemId === +item.dto.product.catalogProductNumber);
|
||||
const stockInfo = res?.result?.find((stockInfo) => stockInfo.itemId === +item.dto.product.catalogProductNumber);
|
||||
|
||||
if (!stockInfo) {
|
||||
const defaultStockData = {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// start:ng42.barrel
|
||||
export * from './remission-list.component';
|
||||
export * from './remission-list-item-loading';
|
||||
// end:ng42.barrel
|
||||
|
||||
@@ -27,6 +27,7 @@ export class RemissionFilterComponent implements OnDestroy {
|
||||
|
||||
applyFilter(filter: UiFilter) {
|
||||
this.store.setFilter(filter);
|
||||
this.store._filterChange$.next(true);
|
||||
this.close.emit();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
// start:ng42.barrel
|
||||
export * from './remission-list-item-loading.component';
|
||||
// end:ng42.barrel
|
||||
@@ -0,0 +1,47 @@
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="contributors animation"></div>
|
||||
<div class="product-group animation"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="title-large animation"></div>
|
||||
<div class="department animation"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="title animation"></div>
|
||||
<div class="department animation"></div>
|
||||
</div>
|
||||
<div class="space"></div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="thumbnail animation"></div>
|
||||
<div class="col ml-4">
|
||||
<div class="row align-left">
|
||||
<div class="left animation"></div>
|
||||
<div class="right-small animation"></div>
|
||||
</div>
|
||||
<div class="row align-left">
|
||||
<div class="left animation"></div>
|
||||
<div class="right-small animation"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="left-small animation"></div>
|
||||
<div class="right animation"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="left-small animation"></div>
|
||||
<div class="right animation"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div></div>
|
||||
<div class="right animation"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space"></div>
|
||||
<div class="row">
|
||||
<div></div>
|
||||
<div class="right-extra-small animation"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,92 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row gap-2 p-4 bg-white rounded-card w-full;
|
||||
height: 368px;
|
||||
max-height: 368px;
|
||||
}
|
||||
|
||||
.animation {
|
||||
@apply bg-ucla-blue h-6;
|
||||
animation: load 1s linear infinite;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
width: 70px;
|
||||
@apply rounded-card h-px-100;
|
||||
}
|
||||
|
||||
.space {
|
||||
@apply flex-grow h-2;
|
||||
}
|
||||
|
||||
.col {
|
||||
@apply flex flex-col flex-grow;
|
||||
}
|
||||
|
||||
.row {
|
||||
@apply flex flex-row justify-between flex-grow;
|
||||
|
||||
.align-left {
|
||||
@apply justify-start;
|
||||
|
||||
.left {
|
||||
margin-right: 10%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.author {
|
||||
width: 17.5%;
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.title-large {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.contributors {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.product-group {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.department {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.left {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.left-small {
|
||||
text-align: left;
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.right-small {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.right-extra-small {
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
@keyframes load {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
30% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'page-remission-list-item-loading',
|
||||
templateUrl: 'remission-list-item-loading.component.html',
|
||||
styleUrls: ['remission-list-item-loading.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class RemissionListItemLoadingComponent {
|
||||
constructor() {}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { RemissionListItemLoadingComponent } from './remission-list-item-loading.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
exports: [RemissionListItemLoadingComponent],
|
||||
declarations: [RemissionListItemLoadingComponent],
|
||||
providers: [],
|
||||
})
|
||||
export class RemissionListItemLoadingModule {}
|
||||
@@ -1,8 +1,23 @@
|
||||
<div class="grid grid-flow-col gap-8 justify-between items-start">
|
||||
<h3 class="font-bold text-xl">{{ item.title }}</h3>
|
||||
<span class="mt-px-2 whitespace-nowrap overflow-hidden overflow-ellipsis w-36 text-active-branch"
|
||||
<div class="item-header-details grid grid-flow-col justify-between w-full">
|
||||
<div class="contributors text-black font-bold overflow-hidden text-base overflow-ellipsis whitespace-nowrap">
|
||||
<a [class.mr-2]="!last" *ngFor="let contributor of contributors; let last = last">{{ last ? contributor : contributor + ';' }}</a>
|
||||
</div>
|
||||
<h3
|
||||
class="title font-bold text-2xl"
|
||||
[class.xl]="item?.dto?.product?.name?.length >= 35"
|
||||
[class.lg]="item?.dto?.product?.name?.length >= 40"
|
||||
[class.md]="item?.dto?.product?.name?.length >= 50"
|
||||
[class.sm]="item?.dto?.product?.name?.length >= 60"
|
||||
[class.xs]="item?.dto?.product?.name?.length >= 100"
|
||||
>
|
||||
{{ item.title }}
|
||||
</h3>
|
||||
<span class="product-group block text-right whitespace-nowrap overflow-hidden overflow-ellipsis text-active-branch"
|
||||
>{{ item.productGroup }}:{{ item.productGroup | productGroup }}</span
|
||||
>
|
||||
<div class="department block text-right text-inactive-branch overflow-hidden overflow-ellipsis whitespace-nowrap">
|
||||
{{ item.department }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="!!returnDto && (item?.dto?.descendantOf?.enabled || item?.dto?.impediment)"
|
||||
@@ -62,22 +77,16 @@
|
||||
<div class="font-bold">{{ item.remissionReason }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-flow-row gap-1 justify-self-end">
|
||||
<div class="text-right text-inactive-branch overflow-hidden overflow-ellipsis whitespace-nowrap">
|
||||
{{ item.department }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!!returnDto; else removeItem">
|
||||
<div class="grid grid-flow-col justify-self-end gap-2">
|
||||
<button *ngIf="enableChangeRemissionQuantity" class="text-brand text-lg font-bold px-6 py-3" (click)="addProductToShippingDocument()">
|
||||
<div class="grid grid-flow-col justify-self-end gap-2 items-center">
|
||||
<button *ngIf="enableChangeRemissionQuantity" class="text-brand text-base font-bold px-6 py-3" (click)="addProductToShippingDocument()">
|
||||
Remi-Menge / Platzierung ändern
|
||||
</button>
|
||||
<button
|
||||
*ngIf="enableToRemit"
|
||||
class="bg-white border-brand border-solid border-2 text-brand font-bold text-lg px-6 py-3 rounded-full"
|
||||
class="bg-white border-brand border-solid border-2 text-brand font-bold text-base px-6 py-3 rounded-full h-12"
|
||||
(click)="remit()"
|
||||
>
|
||||
Remittieren
|
||||
@@ -87,7 +96,7 @@
|
||||
|
||||
<ng-template #removeItem>
|
||||
<div *ngIf="item?.dto?.source === 'manually-added'" class="grid grid-flow-col justify-self-end gap-2">
|
||||
<button class="text-brand text-lg font-bold px-6 py-3" (click)="removeReturnItem()">
|
||||
<button class="text-brand text-base font-bold px-6 py-3" (click)="removeReturnItem()">
|
||||
Entfernen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,55 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row gap-2 p-4 bg-white rounded-card;
|
||||
@apply grid grid-flow-row gap-2 p-4 bg-white rounded-card w-full;
|
||||
height: 368px;
|
||||
max-height: 368px;
|
||||
}
|
||||
|
||||
.item-header-details {
|
||||
grid-template-columns: auto 9rem;
|
||||
grid-template-rows: auto;
|
||||
grid-template-areas:
|
||||
'contributors product-group'
|
||||
'title department';
|
||||
}
|
||||
|
||||
.contributors {
|
||||
grid-area: contributors;
|
||||
max-width: 95%;
|
||||
}
|
||||
|
||||
.title {
|
||||
grid-area: title;
|
||||
max-width: 95%;
|
||||
}
|
||||
|
||||
.title.xl {
|
||||
@apply font-bold text-xl;
|
||||
}
|
||||
|
||||
.title.lg {
|
||||
@apply font-bold text-lg;
|
||||
}
|
||||
|
||||
.title.md {
|
||||
@apply font-bold text-base;
|
||||
}
|
||||
|
||||
.title.sm {
|
||||
@apply font-bold text-sm;
|
||||
}
|
||||
|
||||
.title.xs {
|
||||
@apply font-bold text-xs;
|
||||
}
|
||||
|
||||
.product-group {
|
||||
grid-area: product-group;
|
||||
}
|
||||
|
||||
.department {
|
||||
grid-area: department;
|
||||
}
|
||||
|
||||
.product-data-grid {
|
||||
grid-template-columns: 90px 190px 340px auto;
|
||||
grid-template-columns: 90px 190px auto;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ export class RemissionListItemComponent implements OnDestroy {
|
||||
@Input()
|
||||
returnDto: ReturnDTO;
|
||||
|
||||
get contributors() {
|
||||
return this.item?.dto?.product?.contributors?.split(';').map((val) => val.trim());
|
||||
}
|
||||
|
||||
get firstReceipt() {
|
||||
return this.returnDto?.receipts?.find((_) => true)?.data;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { CacheService } from '@core/cache';
|
||||
import { Config } from '@core/config';
|
||||
import { DomainRemissionService, RemissionListItem } from '@domain/remission';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { SupplierDTO } from '@swagger/remi';
|
||||
import { UiFilter } from '@ui/filter';
|
||||
import { combineLatest, Observable, Subject } from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
|
||||
import { debounceTime, filter, switchMap, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
|
||||
|
||||
export interface RemissionState {
|
||||
@@ -23,10 +27,26 @@ export interface RemissionState {
|
||||
export class RemissionListComponentStore extends ComponentStore<RemissionState> implements OnDestroy {
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
private _searchCompleted = new Subject<RemissionState>();
|
||||
private _searchCompleted = new BehaviorSubject<RemissionState>({
|
||||
suppliers: [],
|
||||
sources: [],
|
||||
hits: 0,
|
||||
items: [],
|
||||
requiredCapacities: undefined,
|
||||
});
|
||||
|
||||
searchCompleted = this._searchCompleted.asObservable();
|
||||
|
||||
_sourceOrSupplierChange$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
_filterChange$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
scroll = new Subject<number>();
|
||||
|
||||
get processId() {
|
||||
return this._config.get('process.ids.remission');
|
||||
}
|
||||
|
||||
get suppliers() {
|
||||
return this.get((s) => s.suppliers);
|
||||
}
|
||||
@@ -103,7 +123,14 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
|
||||
return this.select((s) => s.searchOptions);
|
||||
}
|
||||
|
||||
constructor(private readonly _domainRemissionService: DomainRemissionService) {
|
||||
constructor(
|
||||
private readonly _domainRemissionService: DomainRemissionService,
|
||||
private readonly _config: Config,
|
||||
private readonly _cache: CacheService,
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
private readonly _router: Router,
|
||||
private readonly _breadcrumb: BreadcrumbService
|
||||
) {
|
||||
super({
|
||||
suppliers: [],
|
||||
sources: [],
|
||||
@@ -111,7 +138,6 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
|
||||
items: [],
|
||||
requiredCapacities: undefined,
|
||||
});
|
||||
|
||||
this.loadSuppliers();
|
||||
this.loadSources();
|
||||
this.initLoadFilter();
|
||||
@@ -132,9 +158,14 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
|
||||
}
|
||||
|
||||
initSearch() {
|
||||
combineLatest([this.selectedSource$, this.selectedSupplier$, this.filter$])
|
||||
combineLatest([this._sourceOrSupplierChange$, this._filterChange$, this._activatedRoute.queryParams])
|
||||
.pipe(debounceTime(500), takeUntil(this._onDestroy$))
|
||||
.subscribe(() => this.search({}));
|
||||
.subscribe(([change, filter, queryParams]) => {
|
||||
const data = this.getCachedData(queryParams);
|
||||
if (change || data.items?.length === 0 || filter) {
|
||||
this.search({ newSearch: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// tslint:disable: member-ordering
|
||||
@@ -204,6 +235,7 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
|
||||
settings?.filter?.forEach((filter) => (filter.input = filter.input?.filter((input) => input.options?.values?.length > 0)));
|
||||
|
||||
this.setFilter(UiFilter.create(settings));
|
||||
this.items ? this.setFetching(false) : '';
|
||||
},
|
||||
(err) => {}
|
||||
)
|
||||
@@ -260,7 +292,6 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
|
||||
...state,
|
||||
suppliers,
|
||||
selectedSupplierId: state.selectedSupplierId ?? suppliers[0]?.id,
|
||||
items: [],
|
||||
}));
|
||||
|
||||
setSelectedSupplierId = this.updater<number>((state, supplierId) => {
|
||||
@@ -270,7 +301,6 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
|
||||
return {
|
||||
...state,
|
||||
selectedSupplierId: supplierId ?? state.selectedSupplierId ?? state.suppliers[0]?.id,
|
||||
items: [],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -279,7 +309,6 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
|
||||
...state,
|
||||
sources,
|
||||
selectedSource: state.selectedSource ? sources.find((source) => source === state.selectedSource) : sources[0],
|
||||
items: [],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -291,7 +320,6 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
|
||||
setFilter = this.updater<UiFilter>((state, filter) => ({
|
||||
...state,
|
||||
filter,
|
||||
items: [],
|
||||
}));
|
||||
|
||||
setItems = this.updater<RemissionListItem[]>((state, items) => ({
|
||||
@@ -299,6 +327,11 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
|
||||
items,
|
||||
}));
|
||||
|
||||
setHits = this.updater<number>((state, hits) => ({
|
||||
...state,
|
||||
hits,
|
||||
}));
|
||||
|
||||
removeItem = this.updater<RemissionListItem>((state, item) => ({
|
||||
...state,
|
||||
items: state.items.filter((i) => i.dto?.id !== item.dto?.id),
|
||||
@@ -309,4 +342,59 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
|
||||
requiredCapacities,
|
||||
fetching: false,
|
||||
}));
|
||||
|
||||
cacheCurrentData(params: Record<string, string> = {}) {
|
||||
const qparams = this.cleanupQueryParams({ ...params, processId: String(this.processId) });
|
||||
|
||||
this._cache.set(qparams, {
|
||||
items: this.items,
|
||||
hits: this.hits,
|
||||
});
|
||||
}
|
||||
|
||||
getCachedData(params: Record<string, string> = {}) {
|
||||
const qparams = this.cleanupQueryParams({ ...params, processId: String(this.processId) });
|
||||
|
||||
return (
|
||||
this._cache.get<{
|
||||
items: RemissionListItem[];
|
||||
hits: number;
|
||||
}>(qparams) || { items: [], hits: 0 }
|
||||
);
|
||||
}
|
||||
|
||||
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||
const clean = { ...params };
|
||||
delete clean['scroll_position'];
|
||||
|
||||
for (const key in clean) {
|
||||
if (Object.prototype.hasOwnProperty.call(clean, key)) {
|
||||
if (clean[key] == undefined) {
|
||||
delete clean[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clean;
|
||||
}
|
||||
|
||||
setSupplier(supplier: SupplierDTO) {
|
||||
this._router.navigate([], {
|
||||
queryParams: {
|
||||
supplier: supplier.id,
|
||||
},
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
this._sourceOrSupplierChange$.next(true);
|
||||
}
|
||||
|
||||
setSource(source: string) {
|
||||
this._router.navigate([], {
|
||||
queryParams: {
|
||||
source,
|
||||
},
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
this._sourceOrSupplierChange$.next(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,92 +7,78 @@
|
||||
<page-remission-filter *ngIf="shellFilterOverlay.isOpen" (close)="shellFilterOverlay.close()"></page-remission-filter>
|
||||
</shell-filter-overlay>
|
||||
|
||||
<ui-scroll-container
|
||||
[loading]="(fetching$ | async) && (items$ | async).length > 0"
|
||||
(reachEnd)="loadMore()"
|
||||
[deltaEnd]="150"
|
||||
[itemLength]="(items$ | async).length"
|
||||
class="scroll-container"
|
||||
[showScrollbar]="false"
|
||||
[containerHeight]="'17'"
|
||||
<cdk-virtual-scroll-viewport
|
||||
#scrollContainer
|
||||
class="remission-list scroll-bar"
|
||||
[itemSize]="368"
|
||||
minBufferPx="1200"
|
||||
maxBufferPx="1200"
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<h1 class="text-2xl font-bold text-center">Remission</h1>
|
||||
<div class="text-center">
|
||||
<p class="text-xl mt-4">
|
||||
Wählen Sie den Bereich aus dem <br />
|
||||
Sie Artikel remittieren möchten.
|
||||
</p>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-center">Remission</h1>
|
||||
<div class="text-center">
|
||||
<p class="text-xl mt-4">
|
||||
Wählen Sie den Bereich aus dem <br />
|
||||
Sie Artikel remittieren möchten.
|
||||
</p>
|
||||
|
||||
<div class="inline-flex flex-row bg-white rounded-md mt-4">
|
||||
<button
|
||||
class="w-48 py-2 bg-white text-black rounded-md font-bold"
|
||||
type="button"
|
||||
*ngFor="let source of sources$ | async"
|
||||
[class.bg-active-branch]="(selectedSource$ | async) === source"
|
||||
[class.text-white]="(selectedSource$ | async) === source"
|
||||
(click)="setSource(source)"
|
||||
>
|
||||
{{ source }}
|
||||
</button>
|
||||
</div>
|
||||
<br />
|
||||
<div class="inline-flex flex-row bg-white rounded-md mt-4">
|
||||
<button
|
||||
class="w-48 py-2 bg-white text-black rounded-md font-bold"
|
||||
type="button"
|
||||
*ngFor="let supplier of filteredSuppliers$ | async"
|
||||
[class.bg-active-branch]="(selectedSupplierId$ | async) === supplier.id"
|
||||
[class.text-white]="(selectedSupplierId$ | async) === supplier.id"
|
||||
(click)="setSupplier(supplier)"
|
||||
>
|
||||
{{ supplier?.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p *ngIf="!(return$ | async)" class="text-center mt-4">
|
||||
<a class="text-brand font-bold text-xl px-4 py-3" routerLink="../add-product">
|
||||
Artikel hinzufügen
|
||||
</a>
|
||||
<button type="button" class="text-brand font-bold text-xl px-4 py-3" routerLink="../shipping-documents">
|
||||
Warenbegleitscheine <span *ngIf="returnCount$ | async; let returnCount">({{ returnCount }})</span>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<h2 *ngIf="return$ | async" class="text-center font-bold text-2xl my-4">Wanne befüllen</h2>
|
||||
|
||||
<div class="mt-4 grid grid-flow-row gap-2" *ngIf="(selectedSource$ | async) === 'Abteilungsremission'">
|
||||
<page-required-capacities [requiredCapacities]="requiredCapacities$ | async"></page-required-capacities>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<div>
|
||||
<page-shared-shipping-document-details
|
||||
[@shippingDocument]="showContent$ | async"
|
||||
*ngIf="return$ | async; let returnDto"
|
||||
[return]="returnDto"
|
||||
canRemoveItems="true"
|
||||
showAddToRemiHint="true"
|
||||
(deleted)="shippingDocumentDeleted()"
|
||||
(itemDeleted)="reloadReturn()"
|
||||
></page-shared-shipping-document-details>
|
||||
</div>
|
||||
|
||||
<div *ngIf="items$ | async; let items" [@remissionList]="showContent$ | async">
|
||||
<p class="text-right mt-2 text-active-branch font-bold">{{ (hits$ | async) || 0 }} Titel</p>
|
||||
<div class="mt-4 grid grid-flow-row gap-2">
|
||||
<page-remission-list-item *ngFor="let item of items" [item]="item" [returnDto]="return$ | async"></page-remission-list-item>
|
||||
<div class="inline-flex flex-row bg-white rounded-md mt-4">
|
||||
<button
|
||||
class="w-48 py-2 bg-white text-black rounded-md font-bold"
|
||||
type="button"
|
||||
*ngFor="let source of sources$ | async"
|
||||
[class.bg-active-branch]="(selectedSource$ | async) === source"
|
||||
[class.text-white]="(selectedSource$ | async) === source"
|
||||
(click)="setSource(source)"
|
||||
>
|
||||
{{ source }}
|
||||
</button>
|
||||
</div>
|
||||
<br />
|
||||
<div class="inline-flex flex-row bg-white rounded-md mt-4">
|
||||
<button
|
||||
class="w-48 py-2 bg-white text-black rounded-md font-bold"
|
||||
type="button"
|
||||
*ngFor="let supplier of filteredSuppliers$ | async"
|
||||
[class.bg-active-branch]="(selectedSupplierId$ | async) === supplier.id"
|
||||
[class.text-white]="(selectedSupplierId$ | async) === supplier.id"
|
||||
(click)="setSupplier(supplier)"
|
||||
>
|
||||
{{ supplier?.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p *ngIf="!(return$ | async)" class="text-center mt-4">
|
||||
<a class="text-brand font-bold text-xl px-4 py-3" routerLink="../add-product">
|
||||
Artikel hinzufügen
|
||||
</a>
|
||||
<button type="button" class="text-brand font-bold text-xl px-4 py-3" routerLink="../shipping-documents">
|
||||
Warenbegleitscheine <span *ngIf="returnCount$ | async; let returnCount">({{ returnCount ?? 0 }})</span>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<h2 *ngIf="return$ | async" class="text-center font-bold text-2xl my-4">Wanne befüllen</h2>
|
||||
|
||||
<div class="mt-4 grid grid-flow-row gap-2" *ngIf="(selectedSource$ | async) === 'Abteilungsremission'">
|
||||
<page-required-capacities [requiredCapacities]="requiredCapacities$ | async"></page-required-capacities>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<p class="text-right my-2 text-active-branch font-bold">{{ (hits$ | async) || 0 }} Titel</p>
|
||||
<div class="remission-list-result" *cdkVirtualFor="let item of items$ | async" cdkVirtualForTrackBy="trackByItemId">
|
||||
<page-remission-list-item [item]="item" [returnDto]="return$ | async"></page-remission-list-item>
|
||||
</div>
|
||||
<page-remission-list-item-loading *ngIf="fetching$ | async"></page-remission-list-item-loading>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-px-100"></div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
|
||||
<ui-spinner *ngIf="fetching$ | async; let fetching" class="text-center mt-8" [show]="fetching"></ui-spinner>
|
||||
</ui-scroll-container>
|
||||
|
||||
<div class="actions" [ngSwitch]="showShippingDocument$ | async">
|
||||
<ng-container *ngSwitchCase="false">
|
||||
<div class="actions">
|
||||
<ng-container>
|
||||
<a
|
||||
@action
|
||||
*ngIf="showStartRemissionAction$ | async"
|
||||
routerLink="../create"
|
||||
[queryParams]="{ supplierId: selectedSupplierId$ | async }"
|
||||
@@ -103,30 +89,16 @@
|
||||
</a>
|
||||
|
||||
<a
|
||||
@action
|
||||
*ngIf="return$ | async"
|
||||
[routerLink]="[]"
|
||||
fragment="shipping-document"
|
||||
(click)="reloadReturn()"
|
||||
routerLink="../shipping-document"
|
||||
class="flex items-center bg-white font-bold text-lg px-6 py-3 rounded-full shadow-cta"
|
||||
>
|
||||
Zum Warenbegleitschein
|
||||
<ui-icon class="ml-2" icon="arrow_head" size="16px"></ui-icon>
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="true">
|
||||
<a @action [routerLink]="[]" class="flex items-center bg-white font-bold text-lg px-6 py-3 rounded-full shadow-cta whitespace-nowrap">
|
||||
<ui-icon class="mr-2" icon="arrow_head" size="16px" rotate="180deg"></ui-icon>
|
||||
Warenbegleitschein befüllen
|
||||
</a>
|
||||
<button
|
||||
@action
|
||||
[routerLink]="['..', 'finish-shipping-document', firstReceiptId$ | async]"
|
||||
[disabled]="finishShippingDocumentDisabled$ | async"
|
||||
class="bg-brand text-white font-bold text-lg px-6 py-3 rounded-full shadow-cta whitespace-nowrap"
|
||||
>
|
||||
Wanne abschließen
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<button class="cta-scroll" *ngIf="showScrollArrow$ | async" (click)="scrollTop(0)">
|
||||
<ui-icon icon="arrow" size="20px"></ui-icon>
|
||||
</button>
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
:host {
|
||||
@apply flex flex-col w-full box-content relative;
|
||||
max-height: calc(100vh - 284px);
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.remission-list {
|
||||
@apply m-0 p-0 mt-2 h-full;
|
||||
}
|
||||
|
||||
.remission-list-result {
|
||||
@apply list-none bg-white rounded-card mb-2;
|
||||
height: 368px;
|
||||
max-height: 368px;
|
||||
}
|
||||
|
||||
.filter {
|
||||
@@ -16,6 +28,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply fixed inline-grid grid-flow-col gap-7;
|
||||
bottom: 7.25rem;
|
||||
@@ -27,10 +43,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep page-remission-list ui-scroll-container .cta-scroll {
|
||||
bottom: 38px !important;
|
||||
}
|
||||
.cta-scroll {
|
||||
@apply absolute shadow-cta border-none outline-none bg-white w-12 h-12 rounded-full grid items-center justify-center;
|
||||
bottom: 26px;
|
||||
right: 5%;
|
||||
|
||||
::ng-deep page-remission-list ui-scroll-container .scroll-container {
|
||||
display: block !important;
|
||||
ui-icon {
|
||||
@apply transition-transform transform -rotate-90 text-active-branch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild, ViewChildren, QueryList } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { Config } from '@core/config';
|
||||
import { DomainRemissionService } from '@domain/remission';
|
||||
import { RemissionListItem } from '@domain/remission';
|
||||
import { SupplierDTO } from '@swagger/remi';
|
||||
import { Subject, Observable, combineLatest } from 'rxjs';
|
||||
import { map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { Subject, combineLatest, BehaviorSubject } from 'rxjs';
|
||||
import { debounceTime, first, map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { RemissionListItemComponent } from './remission-list-item';
|
||||
import { RemissionListComponentStore } from './remission-list.component-store';
|
||||
import { RemissionComponentStore } from './remission.component-store';
|
||||
|
||||
@@ -17,74 +17,14 @@ import { RemissionComponentStore } from './remission.component-store';
|
||||
styleUrls: ['remission-list.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [RemissionListComponentStore, RemissionComponentStore],
|
||||
animations: [
|
||||
trigger('shippingDocument', [
|
||||
state(
|
||||
'shipping-document',
|
||||
style({
|
||||
opacity: 1,
|
||||
transform: 'translateX(0)',
|
||||
})
|
||||
),
|
||||
state(
|
||||
'remission-list',
|
||||
style({
|
||||
opacity: 0,
|
||||
transform: 'translateX(100%)',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
})
|
||||
),
|
||||
transition('shipping-document => remission-list', [animate('500ms ease-in-out')]),
|
||||
transition('remission-list => shipping-document', [animate('500ms 500ms ease-in-out')]),
|
||||
]),
|
||||
trigger('remissionList', [
|
||||
state(
|
||||
'shipping-document',
|
||||
style({
|
||||
opacity: 0,
|
||||
transform: 'translateX(-100%)',
|
||||
display: 'none',
|
||||
})
|
||||
),
|
||||
state(
|
||||
'remission-list',
|
||||
style({
|
||||
opacity: 1,
|
||||
transform: 'translateX(0)',
|
||||
})
|
||||
),
|
||||
transition('shipping-document => remission-list', [animate('500ms 500ms ease-in-out')]),
|
||||
transition('remission-list => shipping-document', [animate('500ms ease-in-out')]),
|
||||
]),
|
||||
trigger('action', [
|
||||
transition(':enter', [
|
||||
style({
|
||||
opacity: 0,
|
||||
transform: 'translateY(-100%)',
|
||||
}),
|
||||
animate('500ms 500ms ease-in-out'),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class RemissionListComponent implements OnInit, OnDestroy {
|
||||
get processId() {
|
||||
return this._config.get('process.ids.remission');
|
||||
}
|
||||
|
||||
@ViewChild('shippingDocument', { read: ElementRef })
|
||||
shippingDocumentElementRef: ElementRef;
|
||||
|
||||
@ViewChild('remissionList', { read: ElementRef })
|
||||
remissionListElementRef: ElementRef;
|
||||
@ViewChildren(RemissionListItemComponent) listItems: QueryList<RemissionListItemComponent>;
|
||||
@ViewChild('scrollContainer', { static: true })
|
||||
scrollContainer: CdkVirtualScrollViewport;
|
||||
|
||||
private _onDestroy$ = new Subject();
|
||||
|
||||
returnCount$: Observable<number>;
|
||||
|
||||
get suppliers$() {
|
||||
return this._remissionListStore.suppliers$;
|
||||
}
|
||||
@@ -125,24 +65,14 @@ export class RemissionListComponent implements OnInit, OnDestroy {
|
||||
return this._remissionStore.return$;
|
||||
}
|
||||
|
||||
get firstReceiptId$() {
|
||||
return this.return$.pipe(map((returnDto) => returnDto?.receipts?.find((_) => true)?.data?.id));
|
||||
get returnCount$() {
|
||||
return this._remissionStore.returnCount$;
|
||||
}
|
||||
|
||||
showShippingDocument$ = this._activatedRoute.fragment.pipe(map((fragment) => fragment === 'shipping-document'));
|
||||
|
||||
showContent$ = this._activatedRoute.fragment.pipe(
|
||||
map((fragment) => (fragment === 'shipping-document' ? 'shipping-document' : 'remission-list'))
|
||||
);
|
||||
|
||||
showStartRemissionAction$ = combineLatest([this.fetching$, this.hits$, this.return$]).pipe(
|
||||
map(([fetching, hits, r]) => !fetching && hits > 0 && !r)
|
||||
);
|
||||
|
||||
finishShippingDocumentDisabled$ = this.return$.pipe(
|
||||
map((returnDto) => returnDto?.receipts?.find((_) => true)?.data?.items?.length === 0)
|
||||
);
|
||||
|
||||
filteredSuppliers$ = combineLatest([this._remissionStore.uncompleted$, this.suppliers$, this.selectedSupplier$]).pipe(
|
||||
map(([uncompleted, suppliers, selectedSupplier]) => {
|
||||
if (!uncompleted) {
|
||||
@@ -152,32 +82,38 @@ export class RemissionListComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
);
|
||||
|
||||
trackByItemId = (item: RemissionListItem) => item?.dto?.id;
|
||||
|
||||
showScrollArrow$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
constructor(
|
||||
private readonly _remissionListStore: RemissionListComponentStore,
|
||||
private readonly _remissionStore: RemissionComponentStore,
|
||||
private readonly _remissionService: DomainRemissionService,
|
||||
private readonly _router: Router,
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
private readonly _breadcrumb: BreadcrumbService,
|
||||
private readonly _config: Config,
|
||||
private readonly _applicationService: ApplicationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.removeBreadcrumbs();
|
||||
|
||||
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$)).subscribe((params) => {
|
||||
const { supplier, source } = params;
|
||||
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$), debounceTime(0)).subscribe(async (queryParams) => {
|
||||
const { supplier, source } = queryParams;
|
||||
if (supplier) {
|
||||
this._remissionListStore.setSelectedSupplierId(+supplier);
|
||||
}
|
||||
if (source) {
|
||||
this._remissionListStore.setSelectedSource(source);
|
||||
|
||||
if (source === 'Abteilungsremission') {
|
||||
this._remissionListStore.loadRequiredCapacities();
|
||||
}
|
||||
}
|
||||
|
||||
const data = this._remissionListStore.getCachedData(queryParams);
|
||||
if (data.items?.length !== 0) {
|
||||
this._remissionListStore.setItems(data.items);
|
||||
this._remissionListStore.setHits(data.hits);
|
||||
this.scrollTop(Number(queryParams?.scroll_position ?? 0));
|
||||
}
|
||||
|
||||
await this.updateBreadcrumb(queryParams);
|
||||
await this.removeBreadcrumbs();
|
||||
});
|
||||
|
||||
this._remissionListStore.filter$
|
||||
@@ -188,28 +124,22 @@ export class RemissionListComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
|
||||
this.returnCount$ = this._remissionService
|
||||
.getReturns({
|
||||
returncompleted: false,
|
||||
})
|
||||
.pipe(map((items) => items?.length || 0));
|
||||
|
||||
this._activatedRoute.params.pipe(takeUntil(this._onDestroy$)).subscribe((params) => {
|
||||
const id = +params.returnId;
|
||||
if (!!id) {
|
||||
this._remissionStore.getReturn(id);
|
||||
}
|
||||
});
|
||||
|
||||
this._remissionStore.return$.pipe(takeUntil(this._onDestroy$)).subscribe((r) => {
|
||||
if (!!r) {
|
||||
this._remissionListStore.setSelectedSupplierId(r.supplier.id);
|
||||
} else {
|
||||
this._remissionStore.getReturns(false);
|
||||
}
|
||||
});
|
||||
|
||||
this._remissionStore.getReturnCompleted.pipe(takeUntil(this._onDestroy$)).subscribe((returnDto) => {
|
||||
if (!!returnDto) {
|
||||
this._remissionListStore.setSelectedSupplierId(returnDto.supplier.id);
|
||||
}
|
||||
|
||||
if (!returnDto || !!returnDto.completed) {
|
||||
this._applicationService.patchProcessData(this.processId, {
|
||||
this._applicationService.patchProcessData(this._remissionListStore.processId, {
|
||||
active: undefined,
|
||||
});
|
||||
this._router.navigate(['./'], { relativeTo: this._activatedRoute.parent });
|
||||
@@ -217,49 +147,76 @@ export class RemissionListComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
async ngOnDestroy() {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
|
||||
if (!!this._remissionStore.return) {
|
||||
const params = await this._activatedRoute.queryParams.pipe(first()).toPromise();
|
||||
this._remissionListStore.cacheCurrentData(params);
|
||||
await this.updateBreadcrumb(params);
|
||||
}
|
||||
}
|
||||
|
||||
setSupplier(supplier: SupplierDTO) {
|
||||
this._router.navigate([], {
|
||||
queryParams: {
|
||||
supplier: supplier.id,
|
||||
},
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
async updateBreadcrumb(queryParams: Record<string, string> = {}) {
|
||||
const scroll_position = this.scrollContainer.measureScrollOffset('top');
|
||||
const returnId = this._activatedRoute?.snapshot?.params?.returnId;
|
||||
|
||||
const crumbs = await this._breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this._remissionListStore.processId, ['remission'])
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
const params = { ...queryParams, scroll_position };
|
||||
for (const crumb of crumbs) {
|
||||
this._breadcrumb.patchBreadcrumb(crumb.id, {
|
||||
params,
|
||||
path: `/filiale/remission/${returnId ? returnId + '/' : ''}list`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setSource(source: string) {
|
||||
this._router.navigate([], {
|
||||
queryParams: {
|
||||
source,
|
||||
},
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
async scrolledIndexChange(index: number) {
|
||||
const items = await this.items$.pipe(first()).toPromise();
|
||||
const hits = await this.hits$.pipe(first()).toPromise();
|
||||
|
||||
removeBreadcrumbs() {
|
||||
this._breadcrumb.removeBreadcrumbsByKeyAndTags(this.processId, ['remission', 'shipping-documents']);
|
||||
this._breadcrumb.removeBreadcrumbsByKeyAndTags(this.processId, ['remission', 'add-product']);
|
||||
this._breadcrumb.removeBreadcrumbsByKeyAndTags(this.processId, ['remission', 'create']);
|
||||
this._breadcrumb.removeBreadcrumbsByKeyAndTags(this.processId, ['remission', 'finish-shipping-document']);
|
||||
this._breadcrumb.removeBreadcrumbsByKeyAndTags(this.processId, ['remission', 'finish-remission']);
|
||||
}
|
||||
if (index > 0) {
|
||||
this.showScrollArrow$.next(true);
|
||||
} else {
|
||||
this.showScrollArrow$.next(false);
|
||||
}
|
||||
|
||||
if (items.length >= hits) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
if (this._activatedRoute.snapshot.fragment === 'shipping-document') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._remissionListStore.hits > this._remissionListStore.items.length && !this._remissionListStore.fetching) {
|
||||
if (index >= items.length - 10 && items.length - 10 > 0 && !this._remissionListStore.fetching) {
|
||||
this._remissionListStore.search({});
|
||||
}
|
||||
}
|
||||
|
||||
reloadReturn() {
|
||||
this._remissionStore.reloadReturn();
|
||||
scrollTop(scrollPos: number) {
|
||||
setTimeout(() => this.scrollContainer.scrollTo({ top: scrollPos, behavior: 'smooth' }), 0);
|
||||
}
|
||||
|
||||
setSupplier(supplier: SupplierDTO) {
|
||||
this._remissionListStore.setSupplier(supplier);
|
||||
}
|
||||
|
||||
setSource(source: string) {
|
||||
this._remissionListStore.setSource(source);
|
||||
}
|
||||
|
||||
async removeBreadcrumbs() {
|
||||
await this._breadcrumb.removeBreadcrumbsByKeyAndTags(this._remissionListStore.processId, ['remission', 'shipping-documents']);
|
||||
await this._breadcrumb.removeBreadcrumbsByKeyAndTags(this._remissionListStore.processId, ['remission', 'add-product']);
|
||||
await this._breadcrumb.removeBreadcrumbsByKeyAndTags(this._remissionListStore.processId, ['remission', 'create']);
|
||||
await this._breadcrumb.removeBreadcrumbsByKeyAndTags(this._remissionListStore.processId, ['remission', 'finish-shipping-document']);
|
||||
await this._breadcrumb.removeBreadcrumbsByKeyAndTags(this._remissionListStore.processId, ['remission', 'finish-remission']);
|
||||
}
|
||||
|
||||
shippingDocumentDeleted() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ScrollingModule } from '@angular/cdk/scrolling';
|
||||
import { RemissionListComponent } from './remission-list.component';
|
||||
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
@@ -8,9 +8,8 @@ import { RemissionFilterModule } from './remission-filter/remission-filter.modul
|
||||
import { RemissionListItemModule } from './remission-list-item';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { RequiredCapacitiesModule } from '../required-capacities/required-capacities.module';
|
||||
import { UiSpinnerModule } from '@ui/spinner';
|
||||
import { UiScrollContainerModule } from '@ui/scroll-container';
|
||||
import { SharedShippingDocumentDetailsModule } from '../shared/shipping-document-details/shipping-document-details.module';
|
||||
import { RemissionListItemLoadingModule } from './remission-list-item-loading/remission-list-item-loading.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -21,9 +20,9 @@ import { SharedShippingDocumentDetailsModule } from '../shared/shipping-document
|
||||
RemissionListItemModule,
|
||||
RouterModule,
|
||||
RequiredCapacitiesModule,
|
||||
UiSpinnerModule,
|
||||
UiScrollContainerModule,
|
||||
SharedShippingDocumentDetailsModule,
|
||||
RemissionListItemLoadingModule,
|
||||
ScrollingModule,
|
||||
],
|
||||
exports: [RemissionListComponent],
|
||||
declarations: [RemissionListComponent],
|
||||
|
||||
@@ -3,10 +3,11 @@ import { DomainRemissionService } from '@domain/remission';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { ReturnDTO } from '@swagger/remi';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
|
||||
export interface RemissionComponentStoreState {
|
||||
return?: ReturnDTO;
|
||||
returnCount?: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -21,6 +22,14 @@ export class RemissionComponentStore extends ComponentStore<RemissionComponentSt
|
||||
return this.select((s) => s.return);
|
||||
}
|
||||
|
||||
get returnCount() {
|
||||
return this.get((s) => s.returnCount);
|
||||
}
|
||||
|
||||
get returnCount$() {
|
||||
return this.select((s) => s.returnCount);
|
||||
}
|
||||
|
||||
get uncompleted() {
|
||||
return this.get((s) => !!s?.return && !s?.return?.completed);
|
||||
}
|
||||
@@ -48,15 +57,25 @@ export class RemissionComponentStore extends ComponentStore<RemissionComponentSt
|
||||
)
|
||||
);
|
||||
|
||||
getReturns = this.effect((returncompleted$?: Observable<boolean>) =>
|
||||
returncompleted$.pipe(
|
||||
switchMap((completed) => this._domainRemissionService.getReturns({ returncompleted: completed })),
|
||||
tapResponse(
|
||||
(r: ReturnDTO[]) => {
|
||||
this.setReturnCount(r?.length);
|
||||
},
|
||||
(err) => {}
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
setReturn = this.updater<ReturnDTO>((state, r) => ({
|
||||
...state,
|
||||
return: r,
|
||||
}));
|
||||
|
||||
reloadReturn = this.effect(($) =>
|
||||
$.pipe(
|
||||
withLatestFrom(this.return$),
|
||||
tap(([_, returnDto]) => this.getReturn(returnDto.id))
|
||||
)
|
||||
);
|
||||
setReturnCount = this.updater<number>((state, returnCount) => ({
|
||||
...state,
|
||||
returnCount,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ const routes: Routes = [
|
||||
path: ':returnId/list',
|
||||
component: RemissionListComponent,
|
||||
},
|
||||
{
|
||||
path: ':returnId/shipping-document',
|
||||
component: ShippingDocumentDetailsComponent,
|
||||
},
|
||||
{
|
||||
path: 'add-product',
|
||||
component: AddProductComponent,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { DomainRemissionService } from '@domain/remission';
|
||||
import { ReceiptItemDTO, ReturnDTO } from '@swagger/remi';
|
||||
import { UiErrorModalComponent, UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
@@ -9,7 +9,7 @@ import { UiErrorModalComponent, UiMessageModalComponent, UiModalService } from '
|
||||
styleUrls: ['shipping-document-details.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SharedShippingDocumentDetailsComponent implements OnInit {
|
||||
export class SharedShippingDocumentDetailsComponent {
|
||||
@Input()
|
||||
return: ReturnDTO;
|
||||
|
||||
@@ -43,8 +43,6 @@ export class SharedShippingDocumentDetailsComponent implements OnInit {
|
||||
|
||||
constructor(private _remissionService: DomainRemissionService, private _modal: UiModalService) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
async deleteReturn() {
|
||||
if (this.itemsCount > 0) {
|
||||
const modal = this._modal.open({
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<page-shared-shipping-document-details
|
||||
*ngIf="return$ | async; let return"
|
||||
[return]="return"
|
||||
[canRemoveItems]="remissionStarted$ | async"
|
||||
[showAddToRemiHint]="remissionStarted$ | async"
|
||||
(deleted)="deleted($event)"
|
||||
(itemDeleted)="reloadReturn()"
|
||||
></page-shared-shipping-document-details>
|
||||
|
||||
<ng-container *ngIf="notCompleted$ | async">
|
||||
@@ -20,3 +23,23 @@
|
||||
</a>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="actions">
|
||||
<ng-container *ngIf="remissionStarted$ | async">
|
||||
<a
|
||||
[routerLink]="['/filiale', 'remission', returnId$ | async, 'list']"
|
||||
[queryParams]="scrollPosition$ | async"
|
||||
class="flex items-center bg-white font-bold text-lg px-6 py-3 rounded-full shadow-cta whitespace-nowrap"
|
||||
>
|
||||
<ui-icon class="mr-2" icon="arrow_head" size="16px" rotate="180deg"></ui-icon>
|
||||
Warenbegleitschein befüllen
|
||||
</a>
|
||||
<button
|
||||
[routerLink]="['..', 'finish-shipping-document', firstReceiptId$ | async]"
|
||||
[disabled]="finishShippingDocumentDisabled$ | async"
|
||||
class="bg-brand text-white font-bold text-lg px-6 py-3 rounded-full shadow-cta whitespace-nowrap"
|
||||
>
|
||||
Wanne abschließen
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
:host {
|
||||
@apply block;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply fixed inline-grid grid-flow-col gap-7;
|
||||
bottom: 7.25rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
button:disabled {
|
||||
@apply cursor-not-allowed bg-inactive-branch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { CacheService } from '@core/cache';
|
||||
import { Config } from '@core/config';
|
||||
import { DomainRemissionService } from '@domain/remission';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { ReturnDTO } from '@swagger/remi';
|
||||
import { NEVER, Observable } from 'rxjs';
|
||||
import { catchError, filter, map, shareReplay, switchMap, tap } from 'rxjs/operators';
|
||||
import { catchError, filter, map, shareReplay, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { ShortReceiptNumberPipe } from '../../pipes/short-receipt-number.pipe';
|
||||
|
||||
export interface ShippingDocumentDetailsState {
|
||||
remissionStarted?: boolean;
|
||||
return: ReturnDTO;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page-shipping-document-details',
|
||||
templateUrl: 'shipping-document-details.component.html',
|
||||
@@ -15,8 +22,10 @@ import { ShortReceiptNumberPipe } from '../../pipes/short-receipt-number.pipe';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [ShortReceiptNumberPipe],
|
||||
})
|
||||
export class ShippingDocumentDetailsComponent implements OnInit {
|
||||
return$: Observable<ReturnDTO>;
|
||||
export class ShippingDocumentDetailsComponent extends ComponentStore<ShippingDocumentDetailsState> implements OnInit {
|
||||
get processId() {
|
||||
return this._config.get('process.ids.remission');
|
||||
}
|
||||
|
||||
notCompleted$: Observable<boolean>;
|
||||
|
||||
@@ -24,40 +33,78 @@ export class ShippingDocumentDetailsComponent implements OnInit {
|
||||
|
||||
hasItems$: Observable<boolean>;
|
||||
|
||||
firstReceiptId$: Observable<number>;
|
||||
|
||||
finishShippingDocumentDisabled$: Observable<boolean>;
|
||||
|
||||
get remissionStarted() {
|
||||
return this.get((s) => s.remissionStarted);
|
||||
}
|
||||
|
||||
get remissionStarted$() {
|
||||
return this.select((s) => s.remissionStarted).pipe(shareReplay());
|
||||
}
|
||||
|
||||
get return() {
|
||||
return this.get((s) => s.return);
|
||||
}
|
||||
|
||||
get return$() {
|
||||
return this.select((s) => s.return).pipe(shareReplay());
|
||||
}
|
||||
|
||||
scrollPosition$ = this._breadcrumb.getBreadcrumbsByKeyAndTags$(this.processId, ['remission']).pipe(
|
||||
map((crumbs) => {
|
||||
const crumbParams = crumbs?.find((_) => true)?.params;
|
||||
|
||||
if (crumbParams && Object.keys(crumbParams)?.find((key) => key === 'scroll_position')) {
|
||||
return { scroll_position: crumbParams['scroll_position'] ?? 0 };
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
constructor(
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _remissionService: DomainRemissionService,
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _config: Config,
|
||||
private _shortReceiptNumberPipe: ShortReceiptNumberPipe,
|
||||
private _router: Router
|
||||
) {}
|
||||
private _router: Router,
|
||||
private _cache: CacheService
|
||||
) {
|
||||
super({ return: undefined });
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.return$ = this._activatedRoute.params.pipe(
|
||||
map((params) => params?.id),
|
||||
filter((id) => !!id),
|
||||
switchMap((id) =>
|
||||
this._remissionService.getReturn(id).pipe(
|
||||
catchError(() => {
|
||||
this.navigateBack();
|
||||
return NEVER;
|
||||
})
|
||||
)
|
||||
),
|
||||
tap((returnDto) => this.addOrUpdateBreadcrumbIfNotExists(returnDto)),
|
||||
shareReplay(1)
|
||||
this.removeBreadcrumbs();
|
||||
this.getReturn();
|
||||
|
||||
this.finishShippingDocumentDisabled$ = this.return$.pipe(
|
||||
map((returnDto) => returnDto?.receipts?.find((_) => true)?.data?.items?.length === 0)
|
||||
);
|
||||
|
||||
this.notCompleted$ = this.return$.pipe(map((returnDto) => !returnDto.completed));
|
||||
this.firstReceiptId$ = this.return$.pipe(map((returnDto) => returnDto?.receipts?.find((_) => true)?.data?.id));
|
||||
|
||||
this.notCompleted$ = this.return$.pipe(map((returnDto) => !returnDto?.completed && !this.remissionStarted));
|
||||
|
||||
this.returnId$ = this.return$.pipe(map((returnDto) => returnDto?.id));
|
||||
|
||||
this.hasItems$ = this.return$.pipe(map((returnDto) => returnDto?.receipts?.find((_) => true)?.data?.items?.length > 0));
|
||||
}
|
||||
|
||||
removeBreadcrumbs() {
|
||||
this._breadcrumb.removeBreadcrumbsByKeyAndTags(this.processId, ['remission', 'add-product']);
|
||||
this._breadcrumb.removeBreadcrumbsByKeyAndTags(this.processId, ['remission', 'create']);
|
||||
this._breadcrumb.removeBreadcrumbsByKeyAndTags(this.processId, ['remission', 'finish-shipping-document']);
|
||||
this._breadcrumb.removeBreadcrumbsByKeyAndTags(this.processId, ['remission', 'finish-remission']);
|
||||
}
|
||||
|
||||
navigateBack() {
|
||||
this._router.navigate(['..'], { relativeTo: this._activatedRoute });
|
||||
this.remissionStarted
|
||||
? this._router.navigate(['../list'], { relativeTo: this._activatedRoute })
|
||||
: this._router.navigate(['..'], { relativeTo: this._activatedRoute });
|
||||
}
|
||||
|
||||
async addOrUpdateBreadcrumbIfNotExists(returnDto: ReturnDTO) {
|
||||
@@ -65,9 +112,11 @@ export class ShippingDocumentDetailsComponent implements OnInit {
|
||||
if (packageNumber) {
|
||||
const shortPackageNumber = this._shortReceiptNumberPipe.transform(packageNumber);
|
||||
this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
key: this._config.get('process.ids.remission'),
|
||||
name: `#${shortPackageNumber}`,
|
||||
path: `/filiale/remission/shipping-documents/${returnDto.id}`,
|
||||
key: this.processId,
|
||||
name: `${this.remissionStarted ? `Warenbegleitschein #${shortPackageNumber}` : `#${shortPackageNumber}`}`,
|
||||
path: this.remissionStarted
|
||||
? `/filiale/remission/${returnDto.id}/shipping-document`
|
||||
: `/filiale/remission/shipping-documents/${returnDto.id}`,
|
||||
section: 'branch',
|
||||
tags: ['remission', 'shipping-documents', 'details'],
|
||||
});
|
||||
@@ -77,4 +126,44 @@ export class ShippingDocumentDetailsComponent implements OnInit {
|
||||
deleted() {
|
||||
this.navigateBack();
|
||||
}
|
||||
|
||||
reloadReturn = this.effect(($) =>
|
||||
$.pipe(
|
||||
tap((_) => {
|
||||
this.getReturn();
|
||||
this._cache.delete({ processId: String(this.processId) });
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
getReturn = this.effect(($) =>
|
||||
$.pipe(
|
||||
withLatestFrom(this._activatedRoute.params),
|
||||
tap(([_, params]) =>
|
||||
params?.id ? this.patchState({ remissionStarted: false }) : params?.returnId ? this.patchState({ remissionStarted: true }) : ''
|
||||
),
|
||||
map(([_, params]) => params?.id ?? params?.returnId),
|
||||
filter((id) => !!id),
|
||||
switchMap((id) => this._remissionService.getReturn(id)),
|
||||
tapResponse(
|
||||
(r: ReturnDTO) => {
|
||||
this.setReturn(r);
|
||||
this.addOrUpdateBreadcrumbIfNotExists(r);
|
||||
},
|
||||
(err) => {
|
||||
this.navigateBack();
|
||||
return NEVER;
|
||||
}
|
||||
),
|
||||
catchError(() => {
|
||||
this.navigateBack();
|
||||
return NEVER;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
setReturn = this.updater<ReturnDTO>((state, r) => ({
|
||||
...state,
|
||||
return: r,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ import { ShippingDocumentListComponent } from './shipping-document-list.componen
|
||||
import { SharedShippingDocumentDetailsModule } from '../shared/shipping-document-details/shipping-document-details.module';
|
||||
import { ShippingDocumentDetailsComponent } from './shipping-document-details/shipping-document-details.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, RemissionPipeModule, SharedShippingDocumentDetailsModule, RouterModule],
|
||||
imports: [CommonModule, RemissionPipeModule, SharedShippingDocumentDetailsModule, UiIconModule, RouterModule],
|
||||
exports: [],
|
||||
declarations: [ShippingDocumentListComponent, ShippingDocumentListItemComponent, ShippingDocumentDetailsComponent],
|
||||
providers: [],
|
||||
|
||||
Reference in New Issue
Block a user