Merge branch 'develop' into release/1.5

This commit is contained in:
Lorenz Hilpert
2021-09-23 17:33:29 +02:00
66 changed files with 633 additions and 160 deletions

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { fromEvent } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { map, take, tap } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
@@ -8,16 +8,17 @@ import { map, take } from 'rxjs/operators';
export class EnvironmentService {
constructor() {}
/** Returns whether app is used within native container*/
isNative(): Promise<boolean> {
return fromEvent(window, 'message')
.pipe(
map((evt: MessageEvent) => evt.data),
map((data) => data.status === 'INIT'),
take(1)
)
.toPromise();
}
// /** Returns whether app is used within native container*/
// isNative(): Promise<boolean> {
// return fromEvent(window, 'message')
// .pipe(
// map((evt: MessageEvent) => evt.data),
// tap(console.log.bind(window)),
// map((data) => data.status === 'INIT'),
// take(1)
// )
// .toPromise();
// }
/** Returns whether current device is mobile phone or tablet */
async isMobile(): Promise<boolean> {

View File

@@ -45,6 +45,8 @@ export class ReOrderActionHandler extends ActionHandler<OrderItemsContext> {
context = await this._command.handleCommand('NOTAVAILABLE', context);
updatedItems.push(...context.items);
}
} else {
return data;
}
}

View File

@@ -1,3 +1,3 @@
export type ProcessingStatusType = 'Approved' | 'InProcess' | 'Completed' | 'Overdue' | 'Archived' | 'Uncompleted';
export type ProcessingStatusType = 'Approved' | 'InProcess' | 'Completed' | 'Overdue' | 'Archived' | 'Uncompleted' | 'Removed';
export type ProcessingStatusList = ProcessingStatusType[];

View File

@@ -168,6 +168,10 @@ export class DomainTaskCalendarService {
processingStatusList.push('Archived');
}
if (!!(processingStatus & 32)) {
processingStatusList.push('Removed');
}
if (!!(processingStatus & 64)) {
processingStatusList.push('Uncompleted');
}

View File

@@ -1,5 +1,4 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core';
import { Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { CommandService } from '@core/command';
import { OrderItemsContext } from '@domain/oms';
@@ -64,12 +63,7 @@ export class GoodsInCleanupListComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject<void>();
constructor(
private _breadcrumb: BreadcrumbService,
private _store: GoodsInCleanupListStore,
private _commandService: CommandService,
private _router: Router
) {}
constructor(private _breadcrumb: BreadcrumbService, private _store: GoodsInCleanupListStore, private _commandService: CommandService) {}
ngOnInit(): void {
this.initInitialSearch();
@@ -94,11 +88,7 @@ export class GoodsInCleanupListComponent implements OnInit, OnDestroy {
initInitialSearch() {
if (this._store.hits === 0) {
this._store.searchResult$.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
if (result.hits === 0) {
await this._router.navigate(['/goods/in']);
} else {
await this.updateBreadcrumb();
}
await this.updateBreadcrumb();
});
}

View File

@@ -3,9 +3,10 @@ import { CommandService } from '@core/command';
import { DomainOmsService } from '@domain/oms';
import { ComponentStore } from '@ngrx/component-store';
import { OrderItemListItemDTO } from '@swagger/oms';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { isEqual } from 'lodash';
import { Subject } from 'rxjs';
import { catchError, first, shareReplay, switchMap, takeUntil } from 'rxjs/operators';
import { catchError, first, shareReplay, switchMap } from 'rxjs/operators';
interface GoodsInListItemComponentState {
item: OrderItemListItemDTO;
@@ -114,7 +115,7 @@ export class GoodsInListItemComponent extends ComponentStore<GoodsInListItemComp
private _onDestroy$ = new Subject();
constructor(private _omsService: DomainOmsService, private _command: CommandService) {
constructor(private _omsService: DomainOmsService, private _command: CommandService, private _modal: UiModalService) {
super({
item: undefined,
editSsc: false,
@@ -153,7 +154,14 @@ export class GoodsInListItemComponent extends ComponentStore<GoodsInListItemComp
}
async showReorderModal() {
await this._command.handleCommand('REORDER', { items: [this.item] });
this.refresh.emit();
try {
await this._command.handleCommand('REORDER', { items: [this.item] });
this.refresh.emit();
} catch (e) {
this._modal.open({
content: UiErrorModalComponent,
data: e,
});
}
}
}

View File

@@ -1,13 +1,26 @@
<div class="list-count">{{ hits$ | async }} Titel</div>
<ui-scroll-container [loading]="loading$ | async" (reachEnd)="loadMore()" [deltaEnd]="150" [itemLength]="itemLength$ | async">
<ui-scroll-container
*ngIf="!(listEmpty$ | async); else emptyMessage"
[loading]="loading$ | async"
(reachEnd)="loadMore()"
[deltaEnd]="150"
[itemLength]="itemLength$ | async"
>
<ng-container *ngFor="let item of items$ | async">
<goods-in-list-item [item]="item" [editSsc]="editSsc" (refresh)="refreshList()"></goods-in-list-item>
<hr />
</ng-container>
</ui-scroll-container>
<div class="actions">
<ng-template #emptyMessage>
<div class="empty-message">
Momentan steht kein Bestellposten zur Nachbearbeitung aus.<br />
Alle Bestellposten sind ordnungsgemäß bearbeitet.
</div>
</ng-template>
<div *ngIf="!(listEmpty$ | async)" class="actions">
<button *ngIf="!editSsc" class="cta-edit-ssc cta-action-primary" (click)="editSsc = true">
Meldenummern vergeben
</button>

View File

@@ -10,6 +10,10 @@
@apply bg-white;
}
.empty-message {
@apply bg-white text-center font-semibold text-inactive-branch py-10 rounded-card;
}
hr {
@apply bg-branch;
height: 2px;

View File

@@ -1,5 +1,4 @@
import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core';
import { Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainOmsService } from '@domain/oms';
import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
@@ -25,6 +24,11 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
loading$ = this._store.loading$.pipe(shareReplay());
listEmpty$ = combineLatest([this.loading$, this.hits$]).pipe(
map(([loading, hits]) => !loading && hits === 0),
shareReplay()
);
editSsc: boolean;
editSscDisabled$: Observable<boolean>;
@@ -33,12 +37,7 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
private _onDestroy$ = new Subject();
constructor(
private _breadcrumb: BreadcrumbService,
private _domainOmsService: DomainOmsService,
private _store: GoodsInListStore,
private _router: Router
) {}
constructor(private _breadcrumb: BreadcrumbService, private _domainOmsService: DomainOmsService, private _store: GoodsInListStore) {}
ngOnInit() {
this.initInitialSearch();
@@ -67,19 +66,16 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
}
registerEditSscDisabled() {
this.editSscDisabled$ = combineLatest(this.listItems.map((item) => item.sscInvalid$)).pipe(
map((items) => items.includes(true) || this.listItems.filter((i) => i.sscChanged).length === 0)
);
this.editSscDisabled$ = combineLatest([
combineLatest(this.listItems.map((item) => item.sscInvalid$)),
combineLatest(this.listItems.map((item) => item.sscChanged$)),
]).pipe(map(([sscInvalid, sscChanged]) => sscInvalid.includes(true) || sscChanged.every((item) => !item)));
}
initInitialSearch() {
if (this._store.hits === 0) {
this._store.searchResult$.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
if (result.hits === 0) {
await this._router.navigate(['/goods/in']);
} else {
await this.updateBreadcrumb();
}
await this.updateBreadcrumb();
});
}

View File

@@ -118,13 +118,9 @@ export class GoodsInReservationComponent implements OnInit, OnDestroy {
initInitialSearch() {
if (this._store.hits === 0) {
this._store.searchResult$.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
if (result.hits === 0) {
await this._router.navigate(['/goods/in']);
} else {
await this.updateBreadcrumb();
if (result.hits === 1) {
this.navigateToDetails(result.result[0]);
}
await this.updateBreadcrumb();
if (result.hits === 1) {
this.navigateToDetails(result.result[0]);
}
});
}

View File

@@ -15,7 +15,7 @@
.search-main {
@apply bg-white text-center rounded-t-card py-5 shadow-card;
height: calc(100vh - 500px);
height: calc(100vh - 495px);
.search-main-title {
@apply text-2xl font-bold;

View File

@@ -1,5 +1,11 @@
<div class="hits">{{ hits$ | async }} Titel</div>
<ui-scroll-container [loading]="loading$ | async" (reachEnd)="loadMore()" [deltaEnd]="150" [itemLength]="(items$ | async).length">
<ui-scroll-container
*ngIf="!(listEmpty$ | async); else emptyMessage"
[loading]="loading$ | async"
(reachEnd)="loadMore()"
[deltaEnd]="150"
[itemLength]="(items$ | async).length"
>
<shared-goods-in-out-order-group *ngFor="let bueryNumberGroup of items$ | async | groupBy: byBuyerNumberFn">
<ng-container *ngFor="let orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn; let lastOrderNumber = last">
<ng-container
@@ -23,3 +29,10 @@
</ng-container>
</shared-goods-in-out-order-group>
</ui-scroll-container>
<ng-template #emptyMessage>
<div class="empty-message">
Es sind im Moment keine Bestellposten vorhanden,<br />
die bearbeitet werden können.
</div>
</ng-template>

View File

@@ -6,6 +6,10 @@
@apply text-active-branch text-right mb-3 font-semibold text-base;
}
.empty-message {
@apply bg-white text-center font-semibold text-inactive-branch py-10 rounded-card;
}
.divider {
height: 2px;
}

View File

@@ -1,9 +1,9 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
import { first, shareReplay, takeUntil } from 'rxjs/operators';
import { first, map, shareReplay, takeUntil } from 'rxjs/operators';
import { OrderItemListItemDTO } from '@swagger/oms';
import { ActivatedRoute, Router } from '@angular/router';
import { GoodsInSearchStore } from '../goods-in-search.store';
import { Subject } from 'rxjs';
import { combineLatest, Subject } from 'rxjs';
import { BreadcrumbService } from '@core/breadcrumb';
@Component({
@@ -19,6 +19,11 @@ export class GoodsInSearchResultsComponent implements OnInit, OnDestroy {
loading$ = this._goodsInSearchStore.fetching$.pipe(shareReplay());
listEmpty$ = combineLatest([this.loading$, this.hits$]).pipe(
map(([loading, hits]) => !loading && hits === 0),
shareReplay()
);
byBuyerNumberFn = (item: OrderItemListItemDTO) => item.buyerNumber;
byOrderNumberFn = (item: OrderItemListItemDTO) => item.orderNumber;

View File

@@ -1,5 +1,11 @@
<div class="hits">{{ hits$ | async }} Titel</div>
<ui-scroll-container [loading]="loading$ | async" (reachEnd)="loadMore()" [deltaEnd]="150" [itemLength]="itemLength$ | async">
<ui-scroll-container
*ngIf="!(listEmpty$ | async); else emptyMessage"
[loading]="loading$ | async"
(reachEnd)="loadMore()"
[deltaEnd]="150"
[itemLength]="itemLength$ | async"
>
<shared-goods-in-out-order-group *ngFor="let bueryNumberGroup of items$ | async | groupBy: byBuyerNumberFn">
<ng-container *ngFor="let orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn; let lastOrderNumber = last">
<ng-container
@@ -26,6 +32,13 @@
</shared-goods-in-out-order-group>
</ui-scroll-container>
<ng-template #emptyMessage>
<div class="empty-message">
Es sind im Moment keine Bestellposten vorhanden,<br />
die bearbeitet werden können.
</div>
</ng-template>
<div class="actions" *ngIf="actions$ | async; let actions">
<button
[disabled]="(loadingFetchedActionButton$ | async) || (loading$ | async)"

View File

@@ -6,6 +6,10 @@
@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;
}
.divider {
height: 2px;
}

View File

@@ -29,6 +29,11 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
loading$ = this._goodsOutSearchStore.fetching$.pipe(shareReplay());
loadingFetchedActionButton$ = new BehaviorSubject<boolean>(false);
listEmpty$ = combineLatest([this.loading$, this.hits$]).pipe(
map(([loading, hits]) => !loading && hits === 0),
shareReplay()
);
selectedOrderItemSubsetIds$ = this.select((s) => s.selectedOrderItemSubsetIds);
get selectedOrderItemSubsetIds() {

View File

@@ -1,5 +1,9 @@
:host {
@apply flex flex-col;
&.Removed {
@apply opacity-30;
}
}
.info-header {

View File

@@ -1,6 +1,6 @@
import { Clipboard } from '@angular/cdk/clipboard';
import { DatePipe } from '@angular/common';
import { Component, ChangeDetectionStrategy, Input, OnChanges, SimpleChanges, Optional } from '@angular/core';
import { Component, ChangeDetectionStrategy, Input, OnChanges, SimpleChanges, Optional, HostBinding } from '@angular/core';
import { DomainTaskCalendarService } from '@domain/task-calendar';
import { FileDTO } from '@swagger/checkout';
import { DisplayInfoDTO } from '@swagger/eis';
@@ -108,6 +108,11 @@ export class TaskInfoComponent implements OnChanges {
)
);
@HostBinding('class')
get statusClass() {
return this.domainTaskCalendarService.getProcessingStatusList(this.info);
}
constructor(
private dateAdapter: DateAdapter,
private datePipe: DatePipe,

View File

@@ -32,3 +32,21 @@
</ng-container>
</div>
</ng-container>
<ng-container *ngIf="isToday$ | async">
<div class="task-list ongoing-list" *ngIf="ongoingItems$ | async; let ongoingItems">
<div class="head-row">
<div class="head-row-title">
<h4>Laufende Aufgaben</h4>
</div>
<div class="head-row-info">
<span class="muted"> {{ ongoingItems?.length }} Aufgaben </span>
</div>
</div>
<hr />
<ng-container *ngFor="let item of ongoingItems">
<page-task-list-item [item]="item" (click)="select.emit(item)"></page-task-list-item>
<hr />
</ng-container>
</div>
</ng-container>

View File

@@ -42,3 +42,11 @@
@apply opacity-30;
}
}
.task-list ::ng-deep page-task-list-item.Removed {
.date,
.shirt-size,
.task-content {
@apply opacity-30 line-through;
}
}

View File

@@ -55,13 +55,35 @@ export class TaskListComponent {
}
return false;
});
}),
map((list) => list.sort(this.moveRemovedToEnd.bind(this)))
);
ongoingItems$ = combineLatest([this.items$, this.selected$]).pipe(
map(([items, selectedDate]) => {
// Filter Aufgaben die vor dem aktuellen Tag gestartet sind und nicht überfällig oder abgeschlossen sind
const list = items.filter((item) => {
const type = this.domainTaskCalendarService.getInfoType(item);
const processingStatus = this.domainTaskCalendarService.getProcessingStatusList(item);
if (
type === 'Task' &&
processingStatus.includes('InProcess') &&
!processingStatus.includes('Overdue') &&
!processingStatus.includes('Completed')
) {
return new Date(item.taskDate) < this.today;
}
return false;
});
return this.sort(list, ['Overdue', 'InProcess', 'Approved', 'Completed']);
})
);
selectedItems$ = combineLatest([this.items$, this.selected$]).pipe(
map(([items, date]) => items.filter((item) => this.dateAdapter.equals(this.domainTaskCalendarService.getDisplayInfoDate(item), date))),
// Sortierung der aufgaben nach Rot => Gelb => Grau => Grün
map((list) => this.sort(list, ['Overdue', 'InProcess', 'Approved', 'Completed']))
map((list) => this.sort(list, ['Overdue', 'InProcess', 'Approved', 'Completed', 'Removed'])),
map((list) => list.sort(this.moveRemovedToEnd.bind(this)))
);
@Output()
@@ -75,6 +97,21 @@ export class TaskListComponent {
private domainPrinterService: DomainPrinterService
) {}
moveRemovedToEnd(a: DisplayInfoDTO, b: DisplayInfoDTO) {
const statusA = this.domainTaskCalendarService.getProcessingStatusList(a)?.includes('Removed');
const statusB = this.domainTaskCalendarService.getProcessingStatusList(b)?.includes('Removed');
if (statusA && statusB) {
return 0;
} else if (statusA && !statusB) {
return 1;
} else if (!statusA && statusB) {
return -1;
}
return 0;
}
/**
* Returns an Array of DisplayInfoDTO, sorted by ProcessingStatus
* Ignores Overdue if Task is already Completed
@@ -85,8 +122,9 @@ export class TaskListComponent {
*/
sort(items: DisplayInfoDTO[], order: ProcessingStatusList) {
let result = [...items];
const reversedOrder = [...order].reverse();
for (const status of [...order].reverse()) {
for (const status of reversedOrder) {
result = result?.sort((a, b) => {
const statusA = this.domainTaskCalendarService.getProcessingStatusList(a);
const statusB = this.domainTaskCalendarService.getProcessingStatusList(b);

View File

@@ -4,4 +4,10 @@
<ui-icon *ngIf="info?.updateComment" icon="refresh" size="22px"></ui-icon>
</div>
<page-task-info [info]="info"></page-task-info>
<button type="button" (click)="close()"><ui-icon icon="close" size="16px"></ui-icon></button>
<button class="btn-close" type="button" (click)="close()"><ui-icon icon="close" size="16px"></ui-icon></button>
<div class="actions">
<button *ngIf="showOpenSuccessorCta$ | async" class="btn-cta" type="button" (click)="close(info.successor?.id)">
Zur neuen Aufgabe
</button>
</div>

View File

@@ -1,5 +1,11 @@
:host {
@apply flex flex-col relative;
&.Removed {
h1 {
@apply opacity-30 line-through;
}
}
}
.header {
@@ -18,10 +24,18 @@ h3 {
@apply absolute m-0 -top-4 p-4 pl-0 text-regular text-active-branch uppercase;
}
button {
.btn-close {
@apply absolute -top-4 -right-4 p-4 outline-none border-none bg-transparent;
ui-icon {
@apply text-inactive-branch;
}
}
.actions {
@apply text-center mb-5 mt-6 sticky bottom-1;
.btn-cta {
@apply bg-brand text-white font-bold text-lg outline-none border-none rounded-full px-6 py-3;
}
}

View File

@@ -1,7 +1,9 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Component, ChangeDetectionStrategy, HostBinding } from '@angular/core';
import { DomainTaskCalendarService } from '@domain/task-calendar';
import { DisplayInfoDTO } from '@swagger/eis';
import { UiModalRef } from '@ui/modal';
import { Subscription } from 'rxjs';
import { combineLatest, ReplaySubject } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
@Component({
selector: 'page-info-modal',
@@ -12,11 +14,31 @@ import { Subscription } from 'rxjs';
export class InfoModalComponent {
info: DisplayInfoDTO;
constructor(private modalRef: UiModalRef<DisplayInfoDTO>) {
this.info = this.modalRef.data;
info$ = new ReplaySubject<DisplayInfoDTO>();
processingStatus$ = this.info$.pipe(
map((info) => this.domainTaskCalendarService.getProcessingStatusList(info)),
shareReplay()
);
showOpenSuccessorCta$ = combineLatest([this.processingStatus$, this.info$]).pipe(
map(([processingStatus, info]) => processingStatus.includes('Removed') && !!info.successor?.id)
);
@HostBinding('class')
get statusClass() {
return this.domainTaskCalendarService.getProcessingStatusList(this.info);
}
close() {
this.modalRef.close();
constructor(
private modalRef: UiModalRef<{ successorId: number }, DisplayInfoDTO>,
private domainTaskCalendarService: DomainTaskCalendarService
) {
this.info = this.modalRef.data;
this.info$.next(this.info);
}
close(successorId: number = undefined) {
this.modalRef.close({ successorId });
}
}

View File

@@ -4,4 +4,10 @@
<ui-icon *ngIf="info?.updateComment" icon="refresh" size="22px"></ui-icon>
</div>
<page-task-info [info]="info" showTaskDate="true"></page-task-info>
<button type="button" (click)="close()"><ui-icon icon="close" size="16px"></ui-icon></button>
<button class="btn-close" type="button" (click)="close()"><ui-icon icon="close" size="16px"></ui-icon></button>
<div class="actions">
<button *ngIf="showOpenSuccessorCta$ | async" class="btn-cta" type="button" (click)="close(info.successor?.id)">
Zur neuen Aufgabe
</button>
</div>

View File

@@ -1,5 +1,11 @@
:host {
@apply flex flex-col relative;
&.Removed {
h1 {
@apply opacity-30 line-through;
}
}
}
.header {
@@ -18,10 +24,18 @@ h3 {
@apply absolute m-0 -top-4 p-4 pl-0 text-regular text-active-branch uppercase;
}
button {
.btn-close {
@apply absolute top-0 right-0 outline-none border-none bg-transparent;
ui-icon {
@apply text-inactive-branch;
}
}
.actions {
@apply text-center mb-5 mt-6 sticky bottom-1;
.btn-cta {
@apply bg-brand text-white font-bold text-lg outline-none border-none rounded-full px-6 py-3;
}
}

View File

@@ -1,6 +1,9 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, HostBinding } from '@angular/core';
import { DomainTaskCalendarService } from '@domain/task-calendar';
import { DisplayInfoDTO } from '@swagger/eis';
import { UiModalRef } from '@ui/modal';
import { combineLatest, ReplaySubject } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
@Component({
selector: 'page-preinfo-modal',
@@ -11,11 +14,31 @@ import { UiModalRef } from '@ui/modal';
export class PreInfoModalComponent {
info: DisplayInfoDTO;
constructor(private modalRef: UiModalRef<DisplayInfoDTO>) {
this.info = this.modalRef.data;
info$ = new ReplaySubject<DisplayInfoDTO>();
processingStatus$ = this.info$.pipe(
map((info) => this.domainTaskCalendarService.getProcessingStatusList(info)),
shareReplay()
);
showOpenSuccessorCta$ = combineLatest([this.processingStatus$, this.info$]).pipe(
map(([processingStatus, info]) => processingStatus.includes('Removed') && !!info.successor?.id)
);
@HostBinding('class')
get statusClass() {
return this.domainTaskCalendarService.getProcessingStatusList(this.info);
}
close() {
this.modalRef.close();
constructor(
private modalRef: UiModalRef<{ successorId: number }, DisplayInfoDTO>,
private domainTaskCalendarService: DomainTaskCalendarService
) {
this.info = this.modalRef.data;
this.info$.next(this.info);
}
close(successorId: number = undefined) {
this.modalRef.close({ successorId });
}
}

View File

@@ -25,6 +25,9 @@
<button *ngIf="showCompleteEditCta$ | async" class="btn-cta" type="button" (click)="completeEdit()" [disabled]="editDisabled$ | async">
Bearbeitung abschließen
</button>
<button *ngIf="showOpenSuccessorCta$ | async" class="btn-cta" type="button" (click)="close(info.successor?.id)">
Zur neuen Aufgabe
</button>
<div *ngIf="showCameraCta$ | async" [disabled]="editDisabled$ | async">
<ng-container>
<div class="camera-hint">Bitte fotografieren Sie die Präsentation, um die<br />Aufgabe abschließen zu können</div>

View File

@@ -1,5 +1,14 @@
:host {
@apply flex flex-col relative;
&.Removed {
.header {
@apply opacity-30 line-through;
}
.status {
@apply opacity-30;
}
}
}
.header {

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, ElementRef, ViewChild } from '@angular/core';
import { ChangeDetectionStrategy, Component, ElementRef, HostBinding, ViewChild } from '@angular/core';
import { DomainTaskCalendarService } from '@domain/task-calendar';
import { DisplayInfoDTO } from '@swagger/eis';
import { DateAdapter } from '@ui/common';
@@ -6,7 +6,7 @@ import { UiMessageModalComponent, UiModalRef, UiModalService } from '@ui/modal';
import { isNullOrUndefined } from '@utils/common';
import { NativeContainerService } from 'native-container';
import { combineLatest, ReplaySubject } from 'rxjs';
import { map, tap, shareReplay, switchMap } from 'rxjs/operators';
import { map, shareReplay, switchMap } from 'rxjs/operators';
import { CameraCaptureModalComponent } from '../camera/camera-capture-modal.component';
@Component({
@@ -44,6 +44,11 @@ export class TaskModalComponent {
})
);
@HostBinding('class')
get statusClass() {
return this.domainTaskCalendarService.getProcessingStatusList(this.info);
}
editDisabled$ = this.info$.pipe(
map((info) => {
if (info.publicationDate && info.taskDate && info.taskOverdueDate) {
@@ -65,22 +70,37 @@ export class TaskModalComponent {
showStartEditCta$ = this.processingStatus$.pipe(
map(
(processingStatus) =>
!processingStatus.includes('Uncompleted') && !processingStatus.includes('InProcess') && !processingStatus.includes('Completed')
!processingStatus.includes('Uncompleted') &&
!processingStatus.includes('InProcess') &&
!processingStatus.includes('Completed') &&
!processingStatus.includes('Removed')
)
);
showCompleteEditCta$ = combineLatest([this.processingStatus$, this.info$]).pipe(
map(([processingStatus, info]) => processingStatus.includes('InProcess') && !info.requiresImageOnConfirmation)
map(
([processingStatus, info]) =>
processingStatus.includes('InProcess') && !info.requiresImageOnConfirmation && !processingStatus.includes('Removed')
)
);
showCameraCta$ = combineLatest([this.processingStatus$, this.info$]).pipe(
map(([processingStatus, info]) => processingStatus.includes('InProcess') && info.requiresImageOnConfirmation)
map(
([processingStatus, info]) =>
processingStatus.includes('InProcess') && info.requiresImageOnConfirmation && !processingStatus.includes('Removed')
)
);
showResetEditCta$ = this.processingStatus$.pipe(map((processingStatus) => processingStatus.includes('Completed')));
showResetEditCta$ = this.processingStatus$.pipe(
map((processingStatus) => processingStatus.includes('Completed') && !processingStatus.includes('Removed'))
);
showOpenSuccessorCta$ = combineLatest([this.processingStatus$, this.info$]).pipe(
map(([processingStatus, info]) => processingStatus.includes('Removed') && !!info.successor?.id)
);
constructor(
private modalRef: UiModalRef<DisplayInfoDTO>,
private modalRef: UiModalRef<{ successorId: number }, DisplayInfoDTO>,
private domainTaskCalendarService: DomainTaskCalendarService,
private uiModal: UiModalService,
private nativeContainer: NativeContainerService,
@@ -90,8 +110,8 @@ export class TaskModalComponent {
this.info$.next(this.info);
}
close() {
this.modalRef.close();
close(successorId: number = undefined) {
this.modalRef.close({ successorId });
}
async startEdit() {

View File

@@ -5,10 +5,10 @@ import { DisplayInfoDTO, InputDTO, ResponseArgsOfIEnumerableOfInputDTO } from '@
import { CalendarIndicator } from '@ui/calendar';
import { DateAdapter } from '@ui/common';
import { Filter, FilterOption, SelectFilter, UiFilterMappingService } from '@ui/filter';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { UiMessageModalComponent, UiModalRef, UiModalResult, UiModalService } from '@ui/modal';
import { clone } from 'lodash';
import { combineLatest, Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap, withLatestFrom } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { debounceTime, map, switchMap, withLatestFrom } from 'rxjs/operators';
import { InfoModalComponent } from './modals/info/info-modal.component';
import { PreInfoModalComponent } from './modals/preinfo/preinfo-modal.component';
import { TaskModalComponent } from './modals/task/task-modal.component';
@@ -172,36 +172,45 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
open(displayInfoDTO: DisplayInfoDTO) {
const type = this.domainTaskCalendarService.getInfoType(displayInfoDTO);
let taskModalRef: UiModalRef<{ successorId: number }, DisplayInfoDTO>;
switch (type) {
case 'Info':
this.uiModal.open({
taskModalRef = this.uiModal.open({
content: InfoModalComponent,
data: displayInfoDTO,
});
break;
case 'PreInfo':
this.uiModal.open({
taskModalRef = this.uiModal.open({
content: PreInfoModalComponent,
data: displayInfoDTO,
});
break;
case 'Task':
const taskModalRef = this.uiModal.open<string, DisplayInfoDTO>({
taskModalRef = this.uiModal.open({
content: TaskModalComponent,
data: displayInfoDTO,
});
taskModalRef.afterClosed$.subscribe({
complete: () => this.loadItems(),
});
break;
}
// TODO:
// Logik welcher Dialog soll angezeigt werden
taskModalRef?.afterClosed$.subscribe({
next: async (result: UiModalResult<{ successorId: number }>) => {
if (result.data?.successorId) {
const info = await this.domainTaskCalendarService
.getInfoById({ infoId: result.data.successorId })
.pipe(map((res) => res.result))
.toPromise();
if (info) {
this.open(info);
}
}
},
complete: () => this.loadItems(),
});
}
mapInputArrayToFilterArray(source: InputDTO[]): SelectFilter[] {

View File

@@ -32,7 +32,7 @@ import { ReloadFormState } from '../actions/forms.actions';
import { FILIALE_LANDING_PAGE } from '../../utils/app.constants';
import { Injectable } from '@angular/core';
export const SYNC_DATA_VERSION = 220;
export const SYNC_DATA_VERSION = 222;
export class AppStateModel {
currentProcesssId: number;

View File

@@ -33,5 +33,9 @@
></app-remission-to-top-to-bottom-actions>
</div>
</ng-container>
<div class="empty-hits" *ngIf="data.productsData && data.productsData.hits === 0">
Es gibt im Moment keine Artikel, die <br />
remittiert werden müssen.
</div>
</div>
</div>

View File

@@ -34,3 +34,7 @@
}
}
}
.empty-hits {
@apply mt-16 text-center text-active-branch text-lg;
}

View File

@@ -279,11 +279,6 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy {
};
startRemission() {
try {
throw new Error('[Remission Create] Start Remission');
} catch (error) {
console.error(error);
}
this.currentRemissionProcess$
.pipe(
switchMap((process) =>
@@ -299,11 +294,7 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy {
}
openStartRemissionDialog() {
if (this.isIPad) {
this.startRemission();
} else {
this.remissionStartDialog.openDialog();
}
this.startRemission();
}
navigateToStartedRemission() {

View File

@@ -0,0 +1,22 @@
<button class="close-btn" (click)="ref.close()">
<ui-icon icon="close" size="21px"></ui-icon>
</button>
<h1>
Ist Ihre
{{ isNative ? 'Wannennummer' : 'Packstück-ID' }}
korrekt oder möchten <br />
Sie diese erneut scannen?
</h1>
<h3>
{{ ref.data }}
</h3>
<div class="actions-wrapper">
<button type="button">
Erneut Scannen
</button>
<button type="button" class="primary">
Nummer korrekt
</button>
</div>

View File

@@ -0,0 +1,28 @@
:host {
@apply block box-content relative;
}
.close-btn {
@apply absolute -top-2 right-0 text-ucla-blue border-none bg-transparent;
}
h1 {
@apply text-xl font-bold text-center;
}
h3 {
@apply text-3xl font-bold text-center;
}
.actions-wrapper {
@apply grid grid-flow-col items-center content-center gap-4 mx-auto mb-4;
width: fit-content;
button {
@apply border-solid border-brand border-2 rounded-full px-6 py-3 bg-white text-brand text-cta-l font-bold;
&.primary {
@apply bg-brand text-white;
}
}
}

View File

@@ -0,0 +1,17 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { UiModalRef } from '@ui/modal';
import { NativeContainerService } from 'native-container';
@Component({
selector: 'app-confirm.receipt-number-modal',
templateUrl: 'confirm-receipt-number-modal.component.html',
styleUrls: ['confirm-receipt-number-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ConfirmReceiptNumberModalComponent {
get isNative() {
return this._nativeContainer.isUiWebview().isNative;
}
constructor(public ref: UiModalRef<boolean, string>, private _nativeContainer: NativeContainerService) {}
}

View File

@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ConfirmReceiptNumberModalComponent } from './confirm-receipt-number-modal.component';
import { UiIconModule } from '@ui/icon';
@NgModule({
imports: [CommonModule, UiIconModule],
exports: [ConfirmReceiptNumberModalComponent],
declarations: [ConfirmReceiptNumberModalComponent],
})
export class ConfirmReceiptNumberModalModule {}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './confirm-receipt-number-modal.component';
export * from './confirm-receipt-number-modal.module';
// end:ng42.barrel

View File

@@ -14,6 +14,11 @@
*ngIf="showScanningButton$ | async"
(scan)="shippingDocumentScanned($event)"
></lib-remission-shipping-document-scanner>
<ui-searchbox
*ngIf="showSearchbox$ | async"
placeholder="Packstück-ID scannen"
(search)="shippingDocumentScanned($event)"
></ui-searchbox>
</div>
</div>

View File

@@ -41,3 +41,8 @@
margin-top: 25px;
}
}
ui-searchbox {
@apply ml-4;
width: 425px;
}

View File

@@ -12,6 +12,7 @@ import { SetRemissionShippingDocument } from 'apps/sales/src/app/core/store/acti
import { NativeContainerService } from 'shared/public_api';
import { HttpErrorResponse } from '@angular/common/http';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { ConfirmReceiptNumberModalComponent } from './confirm-receipt-number-modal/confirm-receipt-number-modal.component';
@Component({
selector: 'app-shipping-document-creation',
@@ -22,6 +23,8 @@ import { UiMessageModalComponent, UiModalService } from '@ui/modal';
export class ShippingDocumentCreationComponent implements OnInit {
showScanningButton$: Observable<boolean>;
showSearchbox$: Observable<boolean>;
@Select(RemissionSelectors.getRemissionShippingDocument)
shippingDocument$: Observable<ShippingDocument>;
@Select(RemissionSelectors.getRemissionShippingDocumentstatus)
@@ -47,15 +50,21 @@ export class ShippingDocumentCreationComponent implements OnInit {
this.showScanningButton$ = this.enableScan$.pipe(
map((allowScan) => !!allowScan && this.appService.isIPadEnv() && this.nativeService.isUiWebview())
);
this.showSearchbox$ = this.showScanningButton$.pipe(map((s) => !s));
}
shippingDocumentWithGeneratedId() {
this.createShippingDocument();
}
shippingDocumentScanned(receiptNumber: string) {
if (receiptNumber) {
this.createShippingDocument(receiptNumber);
async shippingDocumentScanned(receiptNumber: string) {
if (receiptNumber && receiptNumber.length > 3) {
const result = await this.modal.open({ content: ConfirmReceiptNumberModalComponent, data: receiptNumber }).afterClosed$.toPromise();
if (result.data) {
this.createShippingDocument(receiptNumber);
}
} else {
this.invalidBarcodeDialog.openDialog();
}

View File

@@ -83,6 +83,8 @@ import { RemissionShippingDocumentContainerComponent } from './pages/remission-l
import { RemissionScrollButtonComponent } from './components/remission-scroll-button';
import { ShippingDocumentActionsComponent } from './pages/remission-list-started/shipping-document-container/shipping-document-actions';
import { RemissionListStartActionComponent } from './components/remission-list-actions/remission-list-start';
import { UiSearchboxNextModule } from '@ui/searchbox';
import { ConfirmReceiptNumberModalModule } from './pages/remission-list-started/shipping-document-creation/confirm-receipt-number-modal/confirm-receipt-number-modal.module';
@NgModule({
declarations: [
@@ -154,6 +156,8 @@ import { RemissionListStartActionComponent } from './components/remission-list-a
TabSelectionModule,
FilterLoaderModule,
RemissionUeberlaufCapacitiesModule,
UiSearchboxNextModule,
ConfirmReceiptNumberModalModule,
],
})
export class RemissionClientModule {}

View File

@@ -27,5 +27,5 @@ export const ProcessingStatusNameMap = new Map<number, { value: string; disabled
[33554432, { value: 'reserviert', disabled: true }],
[67108864, { value: 'zusammengestellt', disabled: true }],
[134217728, { value: 'verpackt', disabled: true }],
[268435456, { value: 'Lieferantenbestellung ausführen', disabled: true }],
[268435456, { value: 'Lieferschein', disabled: true }],
]);

View File

@@ -71,7 +71,7 @@ export class SharedGoodsInOutOrderDetailsCoversComponent extends ComponentStore<
map(
(orderItems) =>
orderItems
?.filter((a) => !!a.readyForPickUp)
?.filter((a) => !!a.readyForPickUp && a.processingStatus === 128)
?.sort((a, b) => new Date(b.readyForPickUp).getTime() - new Date(a.readyForPickUp).getTime())
?.find((_) => true)?.compartmentCode
)

View File

@@ -66,7 +66,7 @@
{{ orderItem?.processingStatus | processingStatus }}
<ui-icon [rotate]="dropdown.opened ? '270deg' : '90deg'" icon="arrow_head"></ui-icon>
</button>
<ui-dropdown #statusDropdown yPosition="below" xPosition="after">
<ui-dropdown #statusDropdown yPosition="below" xPosition="after" [xOffset]="8">
<button uiDropdownItem *ngFor="let action of statusActions$ | async" (click)="handleAction(action)">
{{ action.label }}
</button>
@@ -126,7 +126,7 @@
</strong>
<ui-icon [rotate]="deadlineDropdownTrigger.opened ? '270deg' : '90deg'" icon="arrow_head"></ui-icon>
</button>
<ui-dropdown #deadlineDropdown yPosition="below" xPosition="after" class="deadline-select">
<ui-dropdown #deadlineDropdown yPosition="below" xPosition="after" class="deadline-select" [xOffset]="8">
<button uiDropdownItem *ngFor="let dl of pickupDeadlines$ | async | keyvalue" (click)="updatePickupDeadline(dl.value)">
{{ dl.key }}
</button>
@@ -153,6 +153,7 @@
#uiDatepicker
yPosition="below"
xPosition="after"
[xOffset]="8"
[min]="minDateDatepicker"
[disabledDaysOfWeek]="[0]"
[selected]="orderItem?.estimatedShippingDate"

View File

@@ -77,7 +77,7 @@
.cta-status-dropdown,
.cta-datepicker,
.cta-pickup-deadline {
@apply flex flex-row border-none outline-none text-base font-bold bg-transparent items-center;
@apply flex flex-row border-none outline-none text-base font-bold bg-transparent items-center px-0 mx-0;
ui-icon {
@apply text-inactive-customer ml-2;

View File

@@ -61,6 +61,9 @@ button {
input:disabled {
@apply text-warning font-bold;
// ipad color fix
-webkit-text-fill-color: rgb(190, 129, 0);
opacity: 1;
}
button {

View File

@@ -1,37 +1,6 @@
import { Injectable, Pipe, PipeTransform } from '@angular/core';
import { OrderItemProcessingStatusValue } from '@swagger/oms';
const ProcessingStatusNameMap = new Map<number, { value: string; disabled: boolean }>([
[1, { value: 'neu', disabled: true }],
[2, { value: '', disabled: true }],
[4, { value: '', disabled: true }],
[8, { value: 'geparkt', disabled: true }],
[16, { value: 'bestellt', disabled: false }],
[32, { value: 'Vorbereitung Versand', disabled: true }],
[64, { value: 'versendet', disabled: false }],
[128, { value: 'eingetroffen', disabled: false }],
[256, { value: 'abgeholt', disabled: false }],
[512, { value: 'storniert (Kunde)', disabled: false }],
[1024, { value: 'storniert', disabled: false }],
[2048, { value: 'storniert (Lieferant)', disabled: false }],
[4096, { value: 'nicht lieferbar', disabled: false }],
[8192, { value: 'nachbestellt', disabled: true }],
[16384, { value: 'zurückgegeben', disabled: true }],
[32768, { value: 'steht zum Download zur Verfügung', disabled: true }],
[65536, { value: 'downloaded', disabled: true }],
[131072, { value: 'nicht abgeholt', disabled: true }],
[262144, { value: 'ans Lager (nicht abgeholt)', disabled: false }],
[524288, { value: 'angefragt', disabled: false }],
[1048576, { value: 'weitergeleitet intern', disabled: false }],
[2097152, { value: 'überfällig', disabled: true }],
[4194304, { value: 'zugestellt', disabled: false }],
[8388608, { value: 'Lieferant ermittelt', disabled: false }],
[16777216, { value: 'derzeit nicht lieferbar', disabled: false }],
[33554432, { value: 'reserviert', disabled: true }],
[67108864, { value: 'zusammengestellt', disabled: true }],
[134217728, { value: 'verpackt', disabled: true }],
[268435456, { value: 'Lieferantenbestellung ausführen', disabled: true }],
]);
import { ProcessingStatusNameMap } from '../constants/processing-status-name.map';
@Pipe({
name: 'processingStatus',

View File

@@ -5,6 +5,6 @@ import { Pipe, PipeTransform } from '@angular/core';
})
export class ReplacePipe implements PipeTransform {
transform(value: string, replaceStr: string = '', replaceWith: string = ''): string {
return value?.replace(replaceStr, replaceWith);
return value?.replace(new RegExp(replaceStr, 'gi'), replaceWith);
}
}

View File

@@ -82,6 +82,8 @@ export class UiDatepickerTriggerDirective implements OnInit, OnDestroy, OnChange
this.close();
}
this.updatePositionStrategy();
this.datepickerRef = this.overlayRef.attach(dropdownPortal);
this.datepicker.close = () => this.close();
@@ -104,6 +106,10 @@ export class UiDatepickerTriggerDirective implements OnInit, OnDestroy, OnChange
this.subscription.add(this.overlayRef.backdropClick().subscribe(() => this.close()));
}
updatePositionStrategy() {
this.overlayRef.updatePositionStrategy(this.getPositionStrategy());
}
getPositionStrategy() {
let [originX, originFallbackX]: HorizontalConnectionPos[] = [this.datepicker.xPosition === 'before' ? 'start' : 'end', 'start'];
@@ -111,17 +117,19 @@ export class UiDatepickerTriggerDirective implements OnInit, OnDestroy, OnChange
let [overlayY, originFallbackY]: VerticalConnectionPos[] = [originY === 'bottom' ? 'top' : 'bottom', overlayFallbackY];
let [overlayX, overlayFallbackX] = [originX, originFallbackX];
let offsetY = 0;
let offsetY = this.datepicker.yOffset ?? 0;
let offsetX = this.datepicker.xOffset ?? 0;
return this.overlayPositionBuilder.flexibleConnectedTo(this.elementRef).withPositions([
{ originX, originY, overlayX, overlayY, offsetY },
{ originX: originFallbackX, originY, overlayX: overlayFallbackX, overlayY, offsetY },
{ originX, originY, overlayX, overlayY, offsetY, offsetX },
{ originX: originFallbackX, originY, overlayX: overlayFallbackX, overlayY, offsetY, offsetX },
{
originX,
originY: originFallbackY,
overlayX,
overlayY: overlayFallbackY,
offsetY: -offsetY,
offsetX: -offsetX,
},
{
originX: originFallbackX,
@@ -129,6 +137,7 @@ export class UiDatepickerTriggerDirective implements OnInit, OnDestroy, OnChange
overlayX: overlayFallbackX,
overlayY: overlayFallbackY,
offsetY: -offsetY,
offsetX: -offsetX,
},
]);
}

View File

@@ -38,6 +38,12 @@ export class UiDatepickerComponent<TDate = any> extends Datepicker<TDate> implem
@Input()
yPosition: DatepickerPositionY;
@Input()
xOffset = 0;
@Input()
yOffset = 0;
get classList() {
return ['dp', `x-position-${this.xPosition}`, `y-position-${this.yPosition}`];
}

View File

@@ -76,6 +76,8 @@ export class UiDropdownTriggerDirective implements OnInit, OnDestroy, OnChanges
this.close();
}
this.updatePositionStrategy();
this.dropdownRef = this.overlayRef.attach(dropdownPortal);
this.dropdown.close = () => {
@@ -104,6 +106,10 @@ export class UiDropdownTriggerDirective implements OnInit, OnDestroy, OnChanges
.subscribe(() => this.close());
}
updatePositionStrategy() {
this.overlayRef.updatePositionStrategy(this.getPositionStrategy());
}
getPositionStrategy() {
let [originX, originFallbackX]: HorizontalConnectionPos[] = [this.dropdown.xPosition === 'before' ? 'start' : 'end', 'start'];
@@ -111,17 +117,20 @@ export class UiDropdownTriggerDirective implements OnInit, OnDestroy, OnChanges
let [overlayY, originFallbackY]: VerticalConnectionPos[] = [originY === 'bottom' ? 'top' : 'bottom', overlayFallbackY];
let [overlayX, overlayFallbackX] = [originX, originFallbackX];
let offsetY = 0;
let offsetY = this.dropdown.yOffset ?? 0;
let offsetX = this.dropdown.xOffset ?? 0;
return this.overlayPositionBuilder.flexibleConnectedTo(this.elementRef).withPositions([
{ originX, originY, overlayX, overlayY, offsetY },
{ originX: originFallbackX, originY, overlayX: overlayFallbackX, overlayY, offsetY },
{ originX, originY, overlayX, overlayY, offsetY, offsetX },
{ originX: originFallbackX, originY, overlayX: overlayFallbackX, overlayY, offsetY, offsetX },
{
originX,
originY: originFallbackY,
overlayX,
overlayY: overlayFallbackY,
offsetY: -offsetY,
offsetX: -offsetX,
},
{
originX: originFallbackX,
@@ -129,6 +138,7 @@ export class UiDropdownTriggerDirective implements OnInit, OnDestroy, OnChanges
overlayX: overlayFallbackX,
overlayY: overlayFallbackY,
offsetY: -offsetY,
offsetX: -offsetX,
},
]);
}

View File

@@ -17,6 +17,12 @@ export class UiDropdownComponent implements OnInit {
@Input()
yPosition: DropdownPositionY;
@Input()
xOffset = 0;
@Input()
yOffset = 0;
close = () => {};
get classList() {

View File

@@ -23,6 +23,7 @@
[ngModel]="uiStartOption?.value"
(save)="uiStartOption?.setValue($event)"
>
<ng-container #content>Übernehmen</ng-container>
</ui-datepicker>
</div>
<div *ngIf="uiStopOption" class="option">
@@ -49,6 +50,7 @@
[ngModel]="uiStopOption?.value"
(save)="uiStopOption?.setValue($event)"
>
<ng-container #content>Übernehmen</ng-container>
</ui-datepicker>
</div>
</div>

View File

@@ -167,6 +167,7 @@ export class UiOption implements IUiOption {
trySetFromValue(value: string) {
const type = this.getParentInput()?.type;
switch (type) {
case UiInputType.Bool:
case UiInputType.InputSelector:
@@ -179,6 +180,9 @@ export class UiOption implements IUiOption {
case UiInputType.IntegerRange:
this.trySetFromNumberRange(value);
break;
case UiInputType.DateRange:
this.trySetFromDateRange(value);
break;
}
}
@@ -191,6 +195,32 @@ export class UiOption implements IUiOption {
}
}
trySetFromDateRange(value: string) {
let split: string[] = [];
if (value) {
if (value?.includes('"-"')) {
split = value.split('"-"');
} else if (value?.includes('-"')) {
split = value.split('-"');
} else if (value?.includes('"-')) {
split = value.split('"-');
}
}
if (this.key === 'start') {
if (split[0]) {
split[0] = split[0].replace(new RegExp('"', 'g'), '');
}
this.setValue(split[0]);
} else if (this.key === 'stop') {
if (split[1]) {
split[1] = split[1].replace(new RegExp('"', 'g'), '');
}
this.setValue(split[1]);
}
}
trySetBoolFromValue(value: string) {
const valueSplits = value?.split(';');
if (valueSplits.includes(this.value)) {

View File

@@ -1,9 +1,8 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UiLoaderComponent } from './loader.component';
import { UiButtonLoaderComponent, UiLoaderComponent } from './loader.component';
import { UiIconModule } from '@ui/icon';
import { UiButtonLoaderComponent } from '@ui/loader';
@NgModule({
imports: [CommonModule, UiIconModule],

View File

@@ -0,0 +1,18 @@
:host {
@apply flex flex-col;
}
.title {
@apply text-xl text-center;
}
.message {
@apply text-base text-center whitespace-pre-line break-words;
}
.actions {
@apply text-center;
button {
@apply px-5 py-3 bg-brand text-white text-cta-l rounded-full border-none outline-none;
}
}

View File

@@ -0,0 +1,54 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { isResponseArgs } from '@utils/object';
import { isEmpty } from 'lodash';
import { UiModalRef } from '../defs';
@Component({
selector: 'ui-error-modal',
template: `
<h1 class="title">Es ist ein Fehler aufgetreten</h1>
<p class="message">
{{ errorMessage }}
</p>
<div class="actions">
<button (click)="modalRef.close()">OK</button>
</div>
`,
styleUrls: ['./error-modal.component.scss'],
})
export class UiErrorModalComponent implements OnInit {
get errorMessage() {
if (this.error instanceof HttpErrorResponse) {
if (isResponseArgs(this.error?.error)) {
return (
this.getMessageFromInvalidProperties(this.error?.error?.invalidProperties) || this.error?.error?.message || this.error?.message
);
}
return this.getMessageFromHttpErrorResponse(this.error);
}
return this.error?.message;
}
get error() {
return this.modalRef.data;
}
constructor(public modalRef: UiModalRef<undefined, Error>) {}
ngOnInit() {}
getMessageFromInvalidProperties(invalidProperties: Record<string, string>): string {
if (!isEmpty(invalidProperties)) {
return Object.values(invalidProperties).join('\n');
}
}
getMessageFromHttpErrorResponse(error: HttpErrorResponse) {
return `HTTP Status: ${error.status} - ${error.statusText}
Type: ${error.type}
URL: ${error.url}
Message: ${error.message}
`;
}
}

View File

@@ -0,0 +1,3 @@
// start:ng42.barrel
export * from './error-modal.component';
// end:ng42.barrel

View File

@@ -5,4 +5,5 @@ export * from './debug-modal/debug-modal.component';
export * from './message-modal.component';
export * from './modal.module';
export * from './modal.service';
export * from './error-modal';
// end:ng42.barrel

View File

@@ -5,5 +5,5 @@
}
::ng-deep .cdk-overlay-pane.modal-overflow ui-modal {
@apply overflow-auto;
@apply overflow-x-hidden overflow-y-scroll;
}

View File

@@ -7,7 +7,7 @@
}
.quantity-control-wrapper {
@apply relative w-10;
@apply relative w-11;
&.open {
@apply px-4;
@@ -23,7 +23,7 @@ button.action {
}
button.current-quantity {
@apply flex flex-row justify-between items-center bg-white text-base outline-none border-none text-left font-bold w-full;
@apply flex flex-row justify-between items-center bg-white text-base outline-none border-none text-left font-bold w-full px-0 mx-0;
ui-icon {
@apply ml-2;

View File

@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { UiIconModule } from '@ui/icon';
import { UiLoaderModule } from '@ui/loader';
import { UiContentLoaderComponent } from '@ui/scroll-container';
import { UiContentLoaderComponent } from './content-loader';
import { UiScrollContainerDirective } from './directives';
import { UiScrollContainerComponent } from './scroll-container.component';
import { UiSkeletonLoaderComponent } from './skeleton-loader';