Merged PR 1457: #3524 #3599 #3598 Responsive Design History

#3524 #3599 #3598 Responsive Design History
This commit is contained in:
Nino Righi
2022-12-19 17:07:43 +00:00
parent e4410fe2c7
commit dfab7e3540
49 changed files with 21159 additions and 458 deletions

View File

@@ -2746,6 +2746,39 @@
} }
} }
} }
},
"@shared/history": {
"projectType": "library",
"root": "apps/shared/history",
"sourceRoot": "apps/shared/history/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/shared/history/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shared/history/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/shared/history/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shared/history/tsconfig.spec.json",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
} }
} }
} }

View File

@@ -10,6 +10,7 @@ import {
CustomerDTO, CustomerDTO,
CustomerInfoDTO, CustomerInfoDTO,
CustomerService, CustomerService,
HistoryDTO,
InputDTO, InputDTO,
KeyValueDTOOfStringAndString, KeyValueDTOOfStringAndString,
ListResponseArgsOfCustomerInfoDTO, ListResponseArgsOfCustomerInfoDTO,
@@ -18,7 +19,6 @@ import {
PayerDTO, PayerDTO,
PayerService, PayerService,
ResponseArgsOfIEnumerableOfBonusCardInfoDTO, ResponseArgsOfIEnumerableOfBonusCardInfoDTO,
ResponseArgsOfIEnumerableOfHistoryDTO,
ShippingAddressDTO, ShippingAddressDTO,
ShippingAddressService, ShippingAddressService,
} from '@swagger/crm'; } from '@swagger/crm';
@@ -524,7 +524,7 @@ export class CrmCustomerService {
return this.customerService.CustomerGetBonuscards(customerId); return this.customerService.CustomerGetBonuscards(customerId);
} }
getCustomerHistory(customerId: number): Observable<ResponseArgsOfIEnumerableOfHistoryDTO> { getCustomerHistory(customerId: number): Observable<HistoryDTO[]> {
return this.customerService.CustomerGetCustomerHistory({ customerId }); return this.customerService.CustomerGetCustomerHistory({ customerId }).pipe(map((response) => response?.result));
} }
} }

View File

@@ -19,29 +19,29 @@ body {
background: var(--bg-color); background: var(--bg-color);
} }
::-webkit-scrollbar { @layer base {
width: 0; // remove scrollbar space ::-webkit-scrollbar {
background: transparent; // optional: just make scrollbar invisible */ width: 0; // remove scrollbar space
} background: transparent; // optional: just make scrollbar invisible */
}
.desktop .scroll-bar::-webkit-scrollbar, .desktop .scroll-bar::-webkit-scrollbar {
.desktop pdf-viewer ::-webkit-scrollbar { @apply w-3;
width: 12px; background-color: transparent;
background-color: transparent; }
}
.desktop .scroll-bar::-webkit-scrollbar-thumb, .desktop .scroll-bar::-webkit-scrollbar-track {
.desktop pdf-viewer ::-webkit-scrollbar-thumb { @apply my-4;
border-radius: 10px; -webkit-box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.1);
-webkit-box-shadow: inset 0 0 6px rgb(0 0 0 / 10%); border-radius: 10px;
background-color: var(--scrollbar-color); background-color: white;
} }
.desktop .scroll-bar::-webkit-scrollbar-track, .desktop .scroll-bar::-webkit-scrollbar-thumb {
.desktop pdf-viewer ::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);
-webkit-box-shadow: inset 0 0 4px rgb(0 0 0 / 10%); border-radius: 10px;
border-radius: 10px; background-color: var(--scrollbar-color);
background-color: white; }
} }
@keyframes load { @keyframes load {

View File

@@ -0,0 +1,8 @@
import { HistoryDTO as CrmHistoryDTO } from '@swagger/crm';
import { HistoryDTO as OmsHistoryDTO } from '@swagger/oms';
export interface HistoryData {
customerNumber: string;
customerName: string;
history: CrmHistoryDTO[] | OmsHistoryDTO[];
}

View File

@@ -1,58 +1,13 @@
<div class="wrapper"> <div class="wrapper -mx-4 px-4">
<div class="customer"> <div class="bg-white -mx-4 px-7 flex flex-row py-5">
<div class="row"> <div class="flex flex-row flex-grow">
<span class="label">Kundenname</span> <span class="mr-3">Kundenname</span>
<strong>{{ customerName }}</strong> <strong>{{ customerName }}</strong>
</div> </div>
<div class="row"> <div class="flex flex-row flex-grow">
<span class="label">Kundennummer</span> <span class="mr-3">Kundennummer</span>
<strong *ngIf="ref.data.mode === 'customer'">{{ customer?.customerNumber }}</strong> <strong>{{ customerNumber }}</strong>
<strong *ngIf="ref.data.mode === 'goods'">{{ orderItemListItem?.buyerNumber }}</strong>
<strong *ngIf="ref.data.mode === 'order'">{{ order?.buyer.buyerNumber }}</strong>
</div>
</div>
<hr />
<div class="scroll-container">
<div *ngFor="let history of history$ | async; first as isFirst; last as isLast" class="content-wrapper">
<div class="log-entry" [class.last]="isLast" [class.first]="isFirst">
<span class="timeline-dot"></span>
<div class="row details header">
<div *ngIf="history.changed">{{ history.changed | date }} | {{ history.changed | date: 'shortTime' }} Uhr</div>
<div *ngIf="history.location">| {{ history.location }}</div>
<div *ngIf="history.changedBy">| {{ history.changedBy }}</div>
</div>
<div class="row details title">
<ng-container *ngIf="ref?.data?.mode === 'goods' || ref?.data?.mode === 'order'; else customer">
Status der Bestellung wurde geändert
</ng-container>
<ng-template #customer>
Kundendaten wurden geändert
</ng-template>
</div>
<br />
<div *ngFor="let values of history?.values" class="row details changes">
<p>{{ values.caption }}:</p>
<p class="changes-gap">
<strong>{{ values.value }} </strong>(neu)
</p>
<p *ngIf="values.previousValue">
<strong>{{ values.previousValue }} </strong>(alt)
</p>
</div>
</div>
</div>
<div *ngIf="spinner$ | async" class="loading-spinner">
<ui-spinner [show]="true"></ui-spinner>
</div>
<div *ngIf="historyError$ | async">
<h3 class="error">
Abruf der Historie fehlgeschlagen.
</h3>
</div>
<div *ngIf="historyEmpty$ | async">
<h3 class="error">
Keine Einträge in der Historie gefunden.
</h3>
</div> </div>
</div> </div>
<shared-history-list [history]="history"> </shared-history-list>
</div> </div>

View File

@@ -1,133 +1,7 @@
$offset: 10px;
$line-top-offset: 3px;
$dot-size: 16px;
$dot-size-lg: 20px;
$border-size: 1px;
$border-size-cover: 2px;
:host { :host {
@apply bg-white;
min-height: 500px; min-height: 500px;
} }
.row { .wrapper {
@apply w-full flex flex-row flex-grow items-center px-2 my-2; background-color: #f5f7fa;
.label {
width: 150px;
}
}
hr {
height: 2px;
@apply bg-disabled-customer my-6;
margin-left: -1rem;
width: calc(100% + 2rem);
}
.error {
@apply text-warning text-center;
}
.loading-spinner {
@apply p-8;
}
.scroll-container {
@apply overflow-y-scroll overflow-x-hidden mr-10 mb-8;
max-height: 800px;
}
.scroll-container::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.1);
border-radius: 10px;
background-color: #e1ebf5;
}
.scroll-container::-webkit-scrollbar {
width: 12px;
background-color: white;
}
.scroll-container::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);
background-color: #0f2942;
}
.content-wrapper {
@apply flex flex-col pl-2 pr-9 mb-10;
}
.details {
@apply my-0;
}
.title {
@apply font-bold text-lg;
}
.changes {
@apply flex flex-row my-0;
p {
@apply m-0;
}
.changes-gap {
@apply mx-1;
}
}
.log-entry {
position: relative;
// calculate offset relative to container offset (22px)
padding-left: calc(70px - 22px);
&:before {
content: '';
position: absolute;
height: calc(100% + 40px);
width: 1px;
top: $line-top-offset;
left: $offset;
border: $border-size solid #e1ebf5;
background: #e1ebf5;
}
&.last:before {
height: 30%;
border: $border-size-cover solid #fff;
background: #fff;
left: calc(#{$offset} - #{$border-size-cover});
}
.timeline-dot {
position: absolute;
top: 0;
left: $offset;
background: #fff;
width: 2px;
&:before {
content: '';
position: absolute;
background: #e1ebf5;
top: $line-top-offset;
left: calc((#{$dot-size} - #{$line-top-offset}) / -2);
border-radius: 50%;
width: $dot-size;
height: $dot-size;
}
}
&.first {
.timeline-dot:before {
width: $dot-size-lg;
height: $dot-size-lg;
background: #be8100;
left: calc(#{$dot-size} / -2);
}
}
} }

View File

@@ -0,0 +1,60 @@
import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator';
import { SharedHistoryListComponent, SharedHistoryListModule } from '@shared/history';
import { HistoryDTO } from '@swagger/crm';
import { UiModalRef } from '@ui/modal';
import { HistoryData } from './history-data';
import { HistoryModalComponent } from './history.component';
describe('HistoryModalComponent', () => {
let spectator: Spectator<HistoryModalComponent>;
let modalRefMock: jasmine.SpyObj<UiModalRef<void, HistoryData>>;
const createComponent = createComponentFactory({
component: HistoryModalComponent,
imports: [SharedHistoryListModule],
declarations: [],
providers: [mockProvider(UiModalRef, { data: {} as HistoryData })],
});
beforeEach(() => {
spectator = createComponent();
modalRefMock = spectator.inject(UiModalRef);
});
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
describe('customerName', () => {
it('should get the customer name from ref.data', () => {
const customerName = 'Test';
spectator.component.ref.data.customerName = customerName;
expect(spectator.component.customerName).toBe(customerName);
});
});
describe('customerNumber', () => {
it('should get the customer number from ref.data', () => {
const customerNumber = '12345';
spectator.component.ref.data.customerNumber = customerNumber;
expect(spectator.component.customerNumber).toBe(customerNumber);
});
});
describe('history', () => {
it('should get the history from ref.data', () => {
const history: HistoryDTO[] = [{ id: 1 }];
spectator.component.ref.data.history = history;
expect(spectator.component.history).toBe(history);
});
});
describe('template', () => {
it('should render shared-history-list and set Input History', () => {
const history: HistoryDTO[] = [{ id: 1 }, { id: 2 }];
spyOnProperty(spectator.component, 'history', 'get').and.returnValue(history);
spectator.detectComponentChanges();
expect(spectator.query(SharedHistoryListComponent).history).toEqual(history);
});
});
});

View File

@@ -1,13 +1,6 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { CrmCustomerService } from '@domain/crm';
import { DomainOmsService } from '@domain/oms';
import { CustomerDTO } from '@swagger/crm';
import { HistoryDTO as CrmHistoryDTO } from '@swagger/crm';
import { HistoryDTO as OmsHistoryDTO, OrderDTO } from '@swagger/oms';
import { OrderItemListItemDTO } from '@swagger/oms';
import { UiModalRef } from '@ui/modal'; import { UiModalRef } from '@ui/modal';
import { BehaviorSubject, Observable, of } from 'rxjs'; import { HistoryData } from './history-data';
import { catchError, map } from 'rxjs/operators';
@Component({ @Component({
selector: 'lib-history', selector: 'lib-history',
@@ -15,100 +8,18 @@ import { catchError, map } from 'rxjs/operators';
styleUrls: ['history.component.scss'], styleUrls: ['history.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class HistoryComponent implements OnInit { export class HistoryModalComponent {
historyError$ = new BehaviorSubject<boolean>(false);
historyEmpty$ = new BehaviorSubject<boolean>(false);
spinner$ = new BehaviorSubject<boolean>(true);
history$: Observable<Array<CrmHistoryDTO | OmsHistoryDTO>>;
get customerName() { get customerName() {
if (this.ref.data.mode === 'customer' || this.ref.data.mode === 'goods') { return this.ref.data.customerName;
const data = this.ref.data.item as CustomerDTO;
return [data?.organisation?.name ?? data?.organisation, data?.lastName, data?.firstName].filter((i) => !!i).join(', ');
}
if (this.ref.data.mode === 'order') {
const data = this.ref.data.item as { order: OrderDTO; orderItemSubsetId: number };
return [data?.order?.buyer?.organisation, data?.order?.buyer?.lastName, data?.order?.buyer?.firstName].filter((i) => !!i).join(', ');
}
return '';
} }
constructor( get customerNumber() {
public ref: UiModalRef< return this.ref.data.customerNumber;
void,
{ mode: 'goods' | 'customer' | 'order'; item: CustomerDTO | OrderItemListItemDTO | { order: OrderDTO; orderItemSubsetId: number } }
>,
private customerService: CrmCustomerService,
private omsService: DomainOmsService
) {}
get customer() {
return this.ref.data.item as CustomerDTO;
} }
get orderItemListItem() { get history() {
return this.ref.data.item as OrderItemListItemDTO; return this.ref.data.history;
} }
get order() { constructor(public ref: UiModalRef<void, HistoryData>) {}
return (this.ref.data.item as any)?.order as OrderDTO;
}
ngOnInit() {
switch (this.ref.data.mode) {
case 'customer':
this.history$ = this.customerService.getCustomerHistory((this.ref.data.item as CustomerDTO).id).pipe(
map((response) => {
this.spinner$.next(false);
if (response.result.length === 0) {
this.historyEmpty$.next(true);
}
return response.result;
}),
catchError(() => {
this.historyError$.next(true);
this.spinner$.next(false);
return of<CrmHistoryDTO[]>([]);
})
);
break;
case 'goods':
this.history$ = this.omsService.getHistory((this.ref.data.item as OrderItemListItemDTO).orderItemSubsetId).pipe(
map((history) => {
this.spinner$.next(false);
if (history.length === 0) {
this.historyEmpty$.next(true);
}
return history;
}),
catchError(() => {
this.historyError$.next(true);
this.spinner$.next(false);
return of([]);
})
);
break;
case 'order':
this.history$ = this.omsService
.getHistory((this.ref.data.item as { order: OrderDTO; orderItemSubsetId: number }).orderItemSubsetId)
.pipe(
map((history) => {
this.spinner$.next(false);
if (history.length === 0) {
this.historyEmpty$.next(true);
}
return history;
}),
catchError(() => {
this.historyError$.next(true);
this.spinner$.next(false);
return of([]);
})
);
break;
}
}
} }

View File

@@ -1,13 +1,12 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { UiIconModule } from '@ui/icon'; import { SharedHistoryListModule } from '@shared/history';
import { UiModalModule } from '@ui/modal'; import { UiModalModule } from '@ui/modal';
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module'; import { HistoryModalComponent } from './history.component';
import { HistoryComponent } from './history.component';
@NgModule({ @NgModule({
declarations: [HistoryComponent], declarations: [HistoryModalComponent],
imports: [CommonModule, UiModalModule, UiIconModule, UiSpinnerModule], imports: [CommonModule, UiModalModule, SharedHistoryListModule],
exports: [HistoryComponent], exports: [HistoryModalComponent],
}) })
export class HistoryModule {} export class HistoryModule {}

View File

@@ -3,4 +3,5 @@
*/ */
export * from './lib/history.module'; export * from './lib/history.module';
export * from './lib/history-data';
export * from './lib/history.component'; export * from './lib/history.component';

View File

@@ -44,9 +44,3 @@
@apply bg-brand text-white; @apply bg-brand text-white;
} }
} }
::ng-deep .desktop page-search-results {
.scroll-bar-margin::-webkit-scrollbar-track {
margin-bottom: 0.5rem;
}
}

View File

@@ -12,8 +12,10 @@
<div class="card-customer-details"> <div class="card-customer-details">
<div class="header-container"> <div class="header-container">
<h1 class="title">{{ (customerType$ | async) === 'b2b' ? 'Firmendetails' : 'Kundendetails' }}</h1> <h1 class="title">{{ (customerType$ | async) === 'b2b' ? 'Firmendetails' : 'Kundendetails' }}</h1>
<button (click)="openHistory()" class="button-customer-history"> <button [disabled]="fetchHistory$ | async" (click)="openHistory()" class="button-customer-history">
Historie <ui-spinner [show]="fetchHistory$ | async">
Historie
</ui-spinner>
</button> </button>
</div> </div>
<p class="info">Sind Ihre {{ (customerType$ | async) === 'b2b' ? 'Firmendaten' : 'Kundendaten' }} korrekt?</p> <p class="info">Sind Ihre {{ (customerType$ | async) === 'b2b' ? 'Firmendaten' : 'Kundendaten' }} korrekt?</p>

View File

@@ -1,11 +1,11 @@
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { Component, ChangeDetectionStrategy, OnInit, ChangeDetectorRef } from '@angular/core'; import { Component, ChangeDetectionStrategy, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application'; import { ApplicationService } from '@core/application';
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb'; import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
import { DomainCheckoutService } from '@domain/checkout'; import { DomainCheckoutService } from '@domain/checkout';
import { AddressHelper, AssignedPayerHelper, CrmCustomerService, ShippingAddressHelper } from '@domain/crm'; import { AddressHelper, AssignedPayerHelper, CrmCustomerService, ShippingAddressHelper } from '@domain/crm';
import { HistoryComponent } from '@modal/history'; import { HistoryModalComponent, HistoryData } from '@modal/history';
import { import {
BuyerDTO, BuyerDTO,
NotificationChannel, NotificationChannel,
@@ -20,11 +20,11 @@ import {
ShippingAddressDTO as CrmShippingAddressDTO, ShippingAddressDTO as CrmShippingAddressDTO,
ShippingAddressDTO, ShippingAddressDTO,
} from '@swagger/crm'; } from '@swagger/crm';
import { UiMessageModalComponent, UiModalService } from '@ui/modal'; import { UiErrorModalComponent, UiMessageModalComponent, UiModalService } from '@ui/modal';
import { isResponseArgs } from '@utils/object'; import { isResponseArgs } from '@utils/object';
import { isBoolean } from 'lodash'; import { isBoolean } from 'lodash';
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs'; import { BehaviorSubject, combineLatest, Observable, of, Subject } from 'rxjs';
import { catchError, first, map, shareReplay, switchMap, tap } from 'rxjs/operators'; import { catchError, first, take, map, shareReplay, switchMap, tap, takeUntil } from 'rxjs/operators';
import { CantAddCustomerToCartModalComponent } from '../modals/cant-add-customer-to-cart-modal/cant-add-customer-to-cart.component'; import { CantAddCustomerToCartModalComponent } from '../modals/cant-add-customer-to-cart-modal/cant-add-customer-to-cart.component';
import { CantAddCustomerToCartData } from '../modals/cant-add-customer-to-cart-modal/cant-add-customer-to-cart.data'; import { CantAddCustomerToCartData } from '../modals/cant-add-customer-to-cart-modal/cant-add-customer-to-cart.data';
import { CantSelectGuestModalComponent } from '../modals/cant-select-guest/cant-select-guest-modal.component'; import { CantSelectGuestModalComponent } from '../modals/cant-select-guest/cant-select-guest-modal.component';
@@ -35,7 +35,7 @@ import { CantSelectGuestModalComponent } from '../modals/cant-select-guest/cant-
styleUrls: ['customer-details.component.scss'], styleUrls: ['customer-details.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class CustomerDetailsComponent implements OnInit { export class CustomerDetailsComponent implements OnInit, OnDestroy {
customerId$: Observable<number>; customerId$: Observable<number>;
customer$: Observable<CustomerDTO>; customer$: Observable<CustomerDTO>;
@@ -62,6 +62,7 @@ export class CustomerDetailsComponent implements OnInit {
fetchShippingAdresses$ = new BehaviorSubject<boolean>(false); fetchShippingAdresses$ = new BehaviorSubject<boolean>(false);
fetchAssignedPayers$ = new BehaviorSubject<boolean>(false); fetchAssignedPayers$ = new BehaviorSubject<boolean>(false);
fetchHistory$ = new BehaviorSubject<boolean>(false);
showSpinner: boolean; showSpinner: boolean;
continueDisabled$ = combineLatest([this.fetchShippingAdresses$, this.fetchAssignedPayers$]).pipe( continueDisabled$ = combineLatest([this.fetchShippingAdresses$, this.fetchAssignedPayers$]).pipe(
@@ -70,6 +71,8 @@ export class CustomerDetailsComponent implements OnInit {
private currentBreadcrumb: Breadcrumb; private currentBreadcrumb: Breadcrumb;
private _onDestroy$ = new Subject();
constructor( constructor(
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private customerDetailsService: CrmCustomerService, private customerDetailsService: CrmCustomerService,
@@ -182,6 +185,11 @@ export class CustomerDetailsComponent implements OnInit {
this.createBreadcrumb(); this.createBreadcrumb();
} }
ngOnDestroy(): void {
this._onDestroy$.next();
this._onDestroy$.complete();
}
async createBreadcrumb() { async createBreadcrumb() {
const customerId = await this.customerId$.pipe(first()).toPromise(); const customerId = await this.customerId$.pipe(first()).toPromise();
@@ -214,16 +222,50 @@ export class CustomerDetailsComponent implements OnInit {
} }
async openHistory() { async openHistory() {
const customer = await this.customer$.pipe(first()).toPromise(); this.fetchHistory$.next(true);
this.modal.open({ try {
content: HistoryComponent, const customer = await this.customer$.pipe(first()).toPromise();
title: 'Historie', const customerName = [customer.organisation?.name ?? customer.organisation?.name, customer.lastName, customer.firstName]
config: { showScrollbarY: false }, .filter((i) => !!i)
data: { .join(', ');
mode: 'customer', const customerNumber = customer.customerNumber;
item: customer, const history = await this.customerDetailsService
}, .getCustomerHistory(customer.id)
}); .pipe(takeUntil(this._onDestroy$), take(1))
.toPromise();
const data: HistoryData = {
customerName,
customerNumber,
history: history,
};
if (!!history) {
if (history.length > 0) {
this.modal.open<void, HistoryData>({
content: HistoryModalComponent,
title: 'Historie',
config: { showScrollbarY: false, padding: false },
data,
});
} else if (history.length === 0) {
this.modal.open({
content: UiMessageModalComponent,
title: 'Keine Einträge in der Historie gefunden',
data: { message: `Es konnten keine Einträge für ${customerName} mit der Kundennummer ${customerNumber} gefunden werden.` },
});
}
}
} catch (error) {
const errorModalRef = this.modal.open({
content: UiErrorModalComponent,
title: 'Abruf der Historie fehlgeschlagen',
data: error,
});
await errorModalRef.afterClosed$.toPromise();
} finally {
this.fetchHistory$.next(false);
}
} }
async continue() { async continue() {

View File

@@ -138,7 +138,11 @@
</ng-container> </ng-container>
<div class="history-wrapper"> <div class="history-wrapper">
<button class="cta-history" (click)="openHistory(subsetItem.id)">Historie</button> <button [disabled]="fetchHistory$ | async" class="cta-history" (click)="openHistory(subsetItem.id)">
<ui-spinner [show]="fetchHistory$ | async">
Historie
</ui-spinner>
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,16 +1,18 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit, OnDestroy } from '@angular/core';
import { ProductImageService } from '@cdn/product-image'; import { ProductImageService } from '@cdn/product-image';
import { HistoryComponent } from '@modal/history'; import { DomainOmsService } from '@domain/oms';
import { HistoryModalComponent, HistoryData } from '@modal/history';
import { OrderDTO, OrderItemDTO, OrderItemSubsetDTO } from '@swagger/oms'; import { OrderDTO, OrderItemDTO, OrderItemSubsetDTO } from '@swagger/oms';
import { UiModalService } from '@ui/modal'; import { UiErrorModalComponent, UiMessageModalComponent, UiModalService } from '@ui/modal';
import { Observable, of } from 'rxjs'; import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
import { takeUntil, take } from 'rxjs/operators';
@Component({ @Component({
selector: 'page-customer-order-item-card', selector: 'page-customer-order-item-card',
templateUrl: 'customer-order-item-card.component.html', templateUrl: 'customer-order-item-card.component.html',
styleUrls: ['./customer-order-item-card.component.scss'], styleUrls: ['./customer-order-item-card.component.scss'],
}) })
export class CustomerOrderItemCardComponent implements OnInit { export class CustomerOrderItemCardComponent implements OnInit, OnDestroy {
@Input() @Input()
order: OrderDTO; order: OrderDTO;
@@ -25,7 +27,10 @@ export class CustomerOrderItemCardComponent implements OnInit {
return this.orderItem.subsetItems.map((subsetItem) => subsetItem.data); return this.orderItem.subsetItems.map((subsetItem) => subsetItem.data);
} }
constructor(private imageService: ProductImageService, private _modal: UiModalService) {} fetchHistory$ = new BehaviorSubject<boolean>(false);
private _onDestroy$ = new Subject();
constructor(private imageService: ProductImageService, private _modal: UiModalService, private _omsService: DomainOmsService) {}
ngOnInit() { ngOnInit() {
this.expand = new Array(this.subsetItems.length); this.expand = new Array(this.subsetItems.length);
@@ -38,6 +43,11 @@ export class CustomerOrderItemCardComponent implements OnInit {
); );
} }
ngOnDestroy(): void {
this._onDestroy$.next();
this._onDestroy$.complete();
}
ssc(subsetItem: OrderItemSubsetDTO) { ssc(subsetItem: OrderItemSubsetDTO) {
if (subsetItem.ssc) { if (subsetItem.ssc) {
return `${subsetItem.ssc} - ${subsetItem.sscText}`; return `${subsetItem.ssc} - ${subsetItem.sscText}`;
@@ -46,18 +56,48 @@ export class CustomerOrderItemCardComponent implements OnInit {
} }
} }
openHistory(orderItemSubsetId: number) { async openHistory(orderItemSubsetId: number) {
this._modal.open({ this.fetchHistory$.next(true);
content: HistoryComponent, try {
title: 'Historie', const customerName = [
config: { showScrollbarY: false }, this.order.buyer?.organisation?.name ?? this.order.buyer?.organisation?.name,
data: { this.order.buyer?.lastName,
mode: 'order', this.order.buyer?.firstName,
item: { ]
order: this.order, .filter((i) => !!i)
orderItemSubsetId, .join(', ');
}, const history = await this._omsService.getHistory(orderItemSubsetId).pipe(takeUntil(this._onDestroy$), take(1)).toPromise();
}, if (!!history) {
}); if (history.length > 0) {
const data: HistoryData = {
customerName,
customerNumber: String(orderItemSubsetId),
history: history,
};
this._modal.open({
content: HistoryModalComponent,
title: 'Historie',
config: { showScrollbarY: false, padding: false },
data,
});
} else if (history.length === 0) {
this._modal.open({
content: UiMessageModalComponent,
title: 'Keine Einträge in der Historie gefunden',
data: { message: `Es konnten keine Einträge für ${customerName} mit der Bestellnummer ${orderItemSubsetId} gefunden werden.` },
});
}
}
} catch (error) {
const errorModalRef = this._modal.open({
content: UiErrorModalComponent,
title: 'Abruf der Historie fehlgeschlagen',
data: error,
});
await errorModalRef.afterClosed$.toPromise();
} finally {
this.fetchHistory$.next(false);
}
} }
} }

View File

@@ -1,6 +1,7 @@
<h5>Seite {{ page }} / {{ totalPages }}</h5> <h5>Seite {{ page }} / {{ totalPages }}</h5>
<pdf-viewer <pdf-viewer
class="scroll-bar"
[src]="objectURL" [src]="objectURL"
[render-text]="true" [render-text]="true"
[(page)]="page" [(page)]="page"

View File

@@ -151,7 +151,11 @@
</button> </button>
</ng-container> </ng-container>
<div class="history-wrapper"> <div class="history-wrapper">
<button class="cta-history" (click)="openHistory()">Historie</button> <button [disabled]="fetchHistory$ | async" class="cta-history" (click)="openHistory()">
<ui-spinner [show]="fetchHistory$ | async">
Historie
</ui-spinner>
</button>
</div> </div>
<ui-select-bullet *ngIf="selectable$ | async" [ngModel]="selected$ | async" (ngModelChange)="setSelected($event)"> </ui-select-bullet> <ui-select-bullet *ngIf="selectable$ | async" [ngModel]="selected$ | async" (ngModelChange)="setSelected($event)"> </ui-select-bullet>

View File

@@ -12,13 +12,13 @@ import {
} from '@angular/core'; } from '@angular/core';
import { UntypedFormControl } from '@angular/forms'; import { UntypedFormControl } from '@angular/forms';
import { DomainOmsService, DomainReceiptService } from '@domain/oms'; import { DomainOmsService, DomainReceiptService } from '@domain/oms';
import { HistoryComponent } from '@modal/history'; import { HistoryModalComponent, HistoryData } from '@modal/history';
import { ComponentStore, tapResponse } from '@ngrx/component-store'; import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { OrderDTO, OrderItemListItemDTO, ReceiptDTO, ReceiptType } from '@swagger/oms'; import { OrderDTO, OrderItemListItemDTO, ReceiptDTO, ReceiptType } from '@swagger/oms';
import { UiModalService } from '@ui/modal'; import { UiErrorModalComponent, UiMessageModalComponent, UiModalService } from '@ui/modal';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { combineLatest, NEVER } from 'rxjs'; import { BehaviorSubject, combineLatest, NEVER, Subject } from 'rxjs';
import { catchError, filter, first, map, switchMap, withLatestFrom } from 'rxjs/operators'; import { catchError, filter, first, map, switchMap, withLatestFrom, takeUntil, take } from 'rxjs/operators';
import { SharedGoodsInOutOrderDetailsStore } from '../goods-in-out-order-details.store'; import { SharedGoodsInOutOrderDetailsStore } from '../goods-in-out-order-details.store';
export interface SharedGoodsInOutOrderDetailsItemComponentState { export interface SharedGoodsInOutOrderDetailsItemComponentState {
@@ -141,6 +141,9 @@ export class SharedGoodsInOutOrderDetailsItemComponent extends ComponentStore<Sh
more$ = this.select((s) => s.more); more$ = this.select((s) => s.more);
fetchHistory$ = new BehaviorSubject<boolean>(false);
private _onDestroy$ = new Subject();
constructor( constructor(
private _host: SharedGoodsInOutOrderDetailsStore, private _host: SharedGoodsInOutOrderDetailsStore,
private _domainReceiptService: DomainReceiptService, private _domainReceiptService: DomainReceiptService,
@@ -158,6 +161,8 @@ export class SharedGoodsInOutOrderDetailsItemComponent extends ComponentStore<Sh
ngOnDestroy() { ngOnDestroy() {
// Remove Prev OrderItem from selected list // Remove Prev OrderItem from selected list
this._host.selectOrderItem(this.orderItem, false); this._host.selectOrderItem(this.orderItem, false);
this._onDestroy$.next();
this._onDestroy$.complete();
} }
loadReceipts = this.effect(($) => loadReceipts = this.effect(($) =>
@@ -206,16 +211,49 @@ export class SharedGoodsInOutOrderDetailsItemComponent extends ComponentStore<Sh
this.patchState({ more }); this.patchState({ more });
} }
openHistory() { async openHistory() {
this._modal.open({ this.fetchHistory$.next(true);
content: HistoryComponent, try {
title: 'Historie', const customerName = [this.orderItem?.organisation ?? this.orderItem.organisation, this.orderItem.lastName, this.orderItem.firstName]
config: { showScrollbarY: false }, .filter((i) => !!i)
data: { .join(', ');
mode: 'goods', const customerNumber = this.orderItem.buyerNumber;
item: this.orderItem, const history = await this._omsService
}, .getHistory(this.orderItem.orderItemSubsetId)
}); .pipe(takeUntil(this._onDestroy$), take(1))
.toPromise();
if (!!history) {
if (history.length > 0) {
const data: HistoryData = {
customerName,
customerNumber,
history: history,
};
this._modal.open({
content: HistoryModalComponent,
title: 'Historie',
config: { showScrollbarY: false, padding: false },
data,
});
} else if (history.length === 0) {
this._modal.open({
content: UiMessageModalComponent,
title: 'Keine Einträge in der Historie gefunden',
data: { message: `Es konnten keine Einträge für ${customerName} mit der Kundennummer ${customerNumber} gefunden werden.` },
});
}
}
} catch (error) {
const errorModalRef = this._modal.open({
content: UiErrorModalComponent,
title: 'Abruf der Historie fehlgeschlagen',
data: error,
});
await errorModalRef.afterClosed$.toPromise();
} finally {
this.fetchHistory$.next(false);
}
} }
setSelected(selected: boolean) { setSelected(selected: boolean) {

View File

@@ -0,0 +1,25 @@
# History
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.0.0.
## Code scaffolding
Run `ng generate component component-name --project history` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project history`.
> Note: Don't forget to add `--project history` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build history` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build history`, go to the dist folder `cd dist/history` and run `npm publish`.
## Running unit tests
Run `ng test history` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View File

@@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/shared/history",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "@shared/history",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^15.0.0",
"@angular/core": "^15.0.0"
},
"dependencies": {
"tslib": "^2.3.0"
}
}

View File

@@ -0,0 +1,10 @@
<div class="scroll-container scroll-bar pt-5 overflow-y-scroll overflow-x-hidden mr-2">
<div *ngFor="let logEntry of history; first as isFirst; last as isLast" class="flex flex-col pl-2 pr-7 mb-10">
<div class="log-entry relative pl-7" [class.last]="isLast" [class.first]="isFirst">
<span class="timeline-dot" [class.last]="isLast && !isFirst"></span>
<span class="timeline-dot" [class.bottom-square]="isLast && isFirst"></span>
<shared-history-log-entry [historyLogEntryData]="logEntry"></shared-history-log-entry>
</div>
</div>
</div>

View File

@@ -0,0 +1,84 @@
:host {
// Dot Timeline
--timeline-bar-left-offset: 6px;
--timeline-bar-dots-top-offset: 5px;
--timeline-bar-dot-size: 14px;
--timeline-bar-line-width: 1px;
min-height: 500px;
}
.wrapper {
background-color: #f5f7fa;
}
.scroll-container {
max-height: 800px;
}
.log-entry {
&:before {
content: '';
position: absolute;
height: calc(100% + 40px);
width: 1px;
top: var(--timeline-bar-dots-top-offset);
left: var(--timeline-bar-left-offset);
border: var(--timeline-bar-line-width) solid #aeb7c1;
background: #aeb7c1;
}
&.last:before {
@apply bg-white;
height: 100%;
border: var(--timeline-bar-line-width) solid #aeb7c1;
left: var(--timeline-bar-left-offset);
top: unset;
}
&.last.first:before {
top: var(--timeline-bar-dots-top-offset);
height: 97%;
}
.timeline-dot {
@apply absolute top-0 bg-white;
left: var(--timeline-bar-left-offset);
width: 2px;
&:before {
@apply absolute;
content: '';
background: #aeb7c1;
top: var(--timeline-bar-dots-top-offset);
left: calc((-1 * var(--timeline-bar-dots-top-offset)) - 1px);
border-radius: 50%;
width: var(--timeline-bar-dot-size);
height: var(--timeline-bar-dot-size);
}
&.last:before,
&.bottom-square:before {
@apply rounded-none;
}
&.last,
&.bottom-square {
top: unset;
bottom: 18px;
}
}
&.first {
.timeline-dot:before {
@apply bg-black;
width: var(--timeline-bar-dot-size);
height: var(--timeline-bar-dot-size);
left: calc((var(--timeline-bar-dot-size) / -2) + 1px);
}
.timeline-dot.bottom-square:before {
background: #aeb7c1;
}
}
}

View File

@@ -0,0 +1,70 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator';
import { SharedHistoryListComponent, SharedHistoryLogEntryComponent, SharedHistoryLogEntryModule } from '@shared/history';
import { HistoryDTO } from '@swagger/crm';
describe('SharedHistoryListComponent', () => {
let spectator: Spectator<SharedHistoryListComponent>;
const createComponent = createComponentFactory({
component: SharedHistoryListComponent,
imports: [SharedHistoryLogEntryModule],
declarations: [],
});
beforeEach(() => {
spectator = createComponent();
});
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
describe('history', () => {
it('should render log-entry if history length <= 0', () => {
const history = [];
spectator.setInput('history', history);
expect(spectator.query('.log-entry')).not.toBeVisible();
});
it('should render log-entry if history length > 0', () => {
const history = [{}, {}];
spectator.setInput('history', history);
expect(spectator.query('.log-entry')).toBeVisible();
});
it('should add class last to last log-entry', () => {
const history = [{}, {}];
spectator.setInput('history', history);
expect(spectator.queryLast('.log-entry')).toHaveClass('last');
});
it('should add class first to first log-entry', () => {
const history = [{}, {}];
spectator.setInput('history', history);
expect(spectator.queryAll('.log-entry')[0]).toHaveClass('first');
});
it('should add class last to timeline-dot if it is the last log-entry and not the first', () => {
const history = [{}, {}];
spectator.setInput('history', history);
expect(spectator.queryLast('.log-entry').querySelector('.timeline-dot')).toHaveClass('last');
});
it('should add class bottom-square if it is the last and first log-entry at the same time', () => {
const history = [{}];
spectator.setInput('history', history);
expect(spectator.queryLast('.timeline-dot')).toHaveClass('bottom-square');
});
});
describe('template', () => {
it('should render shared-history-log-entry and set Input historyLogEntryData', () => {
const history: HistoryDTO[] = [];
const historyLogEntryData: HistoryDTO = { id: 1 };
history.push(historyLogEntryData);
spectator.setInput('history', history);
spectator.detectComponentChanges();
expect(spectator.query(SharedHistoryLogEntryComponent).historyLogEntryData).toEqual(historyLogEntryData);
});
});
});

View File

@@ -0,0 +1,14 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { HistoryDTO as CrmHistoryDTO } from '@swagger/crm';
import { HistoryDTO as OmsHistoryDTO } from '@swagger/oms';
@Component({
selector: 'shared-history-list',
templateUrl: 'history-list.component.html',
styleUrls: ['history-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SharedHistoryListComponent {
@Input() history: CrmHistoryDTO[] | OmsHistoryDTO[];
constructor() {}
}

View File

@@ -0,0 +1,13 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SharedHistoryListComponent } from './history-list.component';
import { SharedHistoryLogEntryModule } from './history-log-entry';
@NgModule({
imports: [CommonModule, SharedHistoryLogEntryModule],
exports: [SharedHistoryListComponent],
declarations: [SharedHistoryListComponent],
providers: [],
})
export class SharedHistoryListModule {}

View File

@@ -0,0 +1,7 @@
<p class="px-6 caption">{{ historyLogEntryValues?.caption }}</p>
<p class="pr-6 previous-value">
{{ historyLogEntryValues?.previousValue }}
</p>
<p class="pr-6 value">
{{ historyLogEntryValues?.value }}
</p>

View File

@@ -0,0 +1,4 @@
:host {
@apply grid grid-flow-col py-2;
grid-template-columns: 1fr 1fr 1fr;
}

View File

@@ -0,0 +1,48 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator';
import { SharedHistoryLogEntryValuesComponent } from '@shared/history';
import { DiffDTO as CrmDiffDTO } from '@swagger/crm';
import { DiffDTO as OmsDiffDTO } from '@swagger/oms';
describe('SharedHistoryLogEntryValuesComponent', () => {
let spectator: Spectator<SharedHistoryLogEntryValuesComponent>;
let historyValuesMock: CrmDiffDTO | OmsDiffDTO;
const createComponent = createComponentFactory({
component: SharedHistoryLogEntryValuesComponent,
imports: [],
declarations: [],
});
beforeEach(() => {
spectator = createComponent();
});
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
describe('values', () => {
beforeEach(() => {
historyValuesMock = {
caption: 'test',
previousValue: 'previous',
value: 'next',
};
});
it('should render caption if caption exist', () => {
spectator.setInput('historyLogEntryValues', historyValuesMock);
expect(spectator.query('.caption')).toHaveText(historyValuesMock.caption);
});
it('should render previousValue if previousValue exist', () => {
spectator.setInput('historyLogEntryValues', historyValuesMock);
expect(spectator.query('.previous-value')).toHaveText(historyValuesMock.previousValue);
});
it('should render value if value exist', () => {
spectator.setInput('historyLogEntryValues', historyValuesMock);
expect(spectator.query('.value')).toHaveText(historyValuesMock.value);
});
});
});

View File

@@ -0,0 +1,14 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { DiffDTO as CrmDiffDTO } from '@swagger/crm';
import { DiffDTO as OmsDiffDTO } from '@swagger/oms';
@Component({
selector: 'shared-history-log-entry-values',
templateUrl: 'history-log-entry-values.component.html',
styleUrls: ['history-log-entry-values.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SharedHistoryLogEntryValuesComponent {
@Input() historyLogEntryValues: CrmDiffDTO | OmsDiffDTO;
constructor() {}
}

View File

@@ -0,0 +1,3 @@
// start:ng42.barrel
export * from './history-log-entry-values.component';
// end:ng42.barrel

View File

@@ -0,0 +1,46 @@
<div class="w-full flex flex-row flex-grow mb-2">
<ng-container *ngIf="historyLogEntryData?.changed; else created">
<div class="flex flex-row font-bold mr-6">
<div class="flex flex-row whitespace-nowrap changed-timestamp">
{{ historyLogEntryData.changed | date }} <span class="separator font-normal">|</span>
{{ historyLogEntryData.changed | date: 'shortTime' }} Uhr
</div>
</div>
<div class="flex flex-row">
<div class="flex flex-row changed-by" *ngIf="historyLogEntryData.changedBy">
Geändert von {{ historyLogEntryData.changedBy }}
<span *ngIf="historyLogEntryData.changedAt" class="separator">|</span>
</div>
<div class="flex flex-row changed-at" *ngIf="historyLogEntryData.changedAt">{{ historyLogEntryData.changedAt }}</div>
</div>
</ng-container>
</div>
<div class="details-container">
<div class="details-header">
<strong class="px-6 log-entry-name">{{ historyLogEntryData?.name ?? 'Geändertes Feld'}}</strong>
<div *ngIf="historyLogEntryData?.values?.length > 0" class="pr-6"><strong class="old-value">Alter Wert</strong></div>
<div *ngIf="historyLogEntryData?.values?.length > 0" class="pr-6"><strong class="new-value">Neuer Wert</strong></div>
</div>
<div class="details-values" [class.first]="isFirst" *ngFor="let values of historyLogEntryData?.values; first as isFirst">
<shared-history-log-entry-values [historyLogEntryValues]="values"></shared-history-log-entry-values>
</div>
</div>
<ng-template #created>
<ng-container *ngIf="historyLogEntryData?.created">
<div class="flex flex-row font-bold mr-6">
<div class="flex flex-row whitespace-nowrap created-timestamp">
{{ historyLogEntryData.created | date }} <span class="separator font-normal">|</span>
{{ historyLogEntryData.created | date: 'shortTime' }} Uhr
</div>
</div>
<div class="flex flex-row">
<div class="flex flex-row created-by" *ngIf="historyLogEntryData.createdBy">
Angelegt von {{ historyLogEntryData.createdBy }}
<span *ngIf="historyLogEntryData.createdAt" class="separator">|</span>
</div>
<div class="flex flex-row created-at" *ngIf="historyLogEntryData.createdAt">{{ historyLogEntryData.createdAt }}</div>
</div>
</ng-container>
</ng-template>

View File

@@ -0,0 +1,36 @@
.separator {
@apply -mt-px-2;
color: #aeb7c1;
}
.details-container {
@apply bg-white flex flex-col break-all;
box-shadow: 0px 6px 24px rgba(206, 212, 219, 0.8);
border-radius: 0px 5px 5px 5px;
.details-header {
@apply grid grid-flow-col py-3;
grid-template-columns: 1fr 1fr 1fr;
.old-value {
@apply rounded-card text-white py-1 px-4;
background-color: #89949e;
}
.new-value {
@apply rounded-card text-white py-1 px-4;
background-color: #134563;
}
}
.details-values {
&:nth-child(even) {
background-color: #f5f7fa;
}
}
.first {
@apply border-t-2 border-solid;
border-color: #d8dfe5;
}
}

View File

@@ -0,0 +1,145 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator';
import { SharedHistoryLogEntryComponent, SharedHistoryLogEntryValuesComponent } from '@shared/history';
import { DiffDTO, HistoryDTO as CrmHistoryDTO, HistoryDTO } from '@swagger/crm';
import { HistoryDTO as OmsHistoryDTO } from '@swagger/oms';
import { DatePipe } from '@angular/common';
import { registerLocaleData } from '@angular/common';
import localeDe from '@angular/common/locales/de';
import localeDeExtra from '@angular/common/locales/extra/de';
import { LOCALE_ID } from '@angular/core';
registerLocaleData(localeDe, 'de-DE', localeDeExtra);
describe('SharedHistoryLogEntryComponent', () => {
let spectator: Spectator<SharedHistoryLogEntryComponent>;
let historyMock: CrmHistoryDTO | OmsHistoryDTO;
const datePipe = new DatePipe('de-DE');
const createComponent = createComponentFactory({
component: SharedHistoryLogEntryComponent,
imports: [],
declarations: [SharedHistoryLogEntryValuesComponent],
providers: [{ provide: LOCALE_ID, useValue: 'de-DE' }],
});
beforeEach(() => {
spectator = createComponent();
});
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
describe('log-entry', () => {
beforeEach(() => {
historyMock = {};
});
describe('type changed', () => {
beforeEach(() => {
historyMock = {
changed: '2022-11-16T14:49:35.6895',
changedAt: 'test-system',
changedBy: 'test-user',
};
});
it('should not render created log-entry', () => {
expect(spectator.query('.created-timestamp')).not.toBeVisible();
expect(spectator.query('.created-by')).not.toBeVisible();
expect(spectator.query('.created-at')).not.toBeVisible();
});
it('should render changed-timestamp', () => {
const date = datePipe.transform(historyMock.changed);
const time = datePipe.transform(historyMock.changed, 'shortTime');
const changedTimestamp = date + ' | ' + time + ' Uhr';
spectator.setInput('historyLogEntryData', historyMock);
expect(spectator.query('.changed-timestamp')).toHaveText(changedTimestamp);
});
it('should render changed-by', () => {
const changedBy = historyMock.changedBy + ' |';
spectator.setInput('historyLogEntryData', historyMock);
expect(spectator.query('.changed-by')).toHaveText(changedBy);
});
it('should render changed-at', () => {
const changedAt = historyMock.changedAt;
spectator.setInput('historyLogEntryData', historyMock);
expect(spectator.query('.changed-at')).toHaveText(changedAt);
});
});
describe('type created', () => {
beforeEach(() => {
historyMock = {
created: '2022-11-16T14:49:35.6895',
createdAt: 'test-system',
createdBy: 'test-user',
};
});
it('should not render changed log-entry', () => {
expect(spectator.query('.changed-timestamp')).not.toBeVisible();
expect(spectator.query('.changed-by')).not.toBeVisible();
expect(spectator.query('.changed-at')).not.toBeVisible();
});
it('should render created-timestamp', () => {
const date = datePipe.transform(historyMock.created);
const time = datePipe.transform(historyMock.created, 'shortTime');
const createdTimestamp = date + ' | ' + time + ' Uhr';
spectator.setInput('historyLogEntryData', historyMock);
expect(spectator.query('.created-timestamp')).toHaveText(createdTimestamp);
});
it('should render created-by', () => {
const createdBy = historyMock.createdBy + ' |';
spectator.setInput('historyLogEntryData', historyMock);
expect(spectator.query('.created-by')).toHaveText(createdBy);
});
it('should render created-at', () => {
const createdAt = historyMock.createdAt;
spectator.setInput('historyLogEntryData', historyMock);
expect(spectator.query('.created-at')).toHaveText(createdAt);
});
});
it('should render log-entry-name if available', () => {
historyMock = {
name: 'test-name',
};
spectator.setInput('historyLogEntryData', historyMock);
expect(spectator.query('.log-entry-name')).toHaveText('test-name');
});
it('should instead render Geändertes Feld if log-entry-name is not available', () => {
spectator.setInput('historyLogEntryData', historyMock);
expect(spectator.query('.log-entry-name')).toHaveText('Geändertes Feld');
});
it('should not render old and new value if no log-entry-values are available', () => {
spectator.setInput('historyLogEntryData', historyMock);
expect(spectator.query('.old-value')).not.toBeVisible();
expect(spectator.query('.new-value')).not.toBeVisible();
});
it('should render shared-history-log-entry-values if log-entry-values are available', () => {
historyMock = {
values: [{}, {}],
};
spectator.setInput('historyLogEntryData', historyMock);
expect(spectator.query('shared-history-log-entry-values')).toBeVisible();
});
it('should render shared-history-log-entry-values and set Input historyLogEntryValues', () => {
const historyLogEntryValues: DiffDTO = { value: 'Test' };
const history: HistoryDTO = { id: 1, values: [historyLogEntryValues] };
spectator.setInput('historyLogEntryData', history);
spectator.detectComponentChanges();
expect(spectator.query(SharedHistoryLogEntryValuesComponent).historyLogEntryValues).toEqual(historyLogEntryValues);
});
});
});

View File

@@ -0,0 +1,14 @@
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
import { HistoryDTO as CrmHistoryDTO } from '@swagger/crm';
import { HistoryDTO as OmsHistoryDTO } from '@swagger/oms';
@Component({
selector: 'shared-history-log-entry',
templateUrl: 'history-log-entry.component.html',
styleUrls: ['history-log-entry.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SharedHistoryLogEntryComponent {
@Input() historyLogEntryData: CrmHistoryDTO | OmsHistoryDTO;
constructor() {}
}

View File

@@ -0,0 +1,12 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SharedHistoryLogEntryValuesComponent } from './history-log-entry-values';
import { SharedHistoryLogEntryComponent } from './history-log-entry.component';
@NgModule({
imports: [CommonModule],
exports: [SharedHistoryLogEntryComponent, SharedHistoryLogEntryValuesComponent],
declarations: [SharedHistoryLogEntryComponent, SharedHistoryLogEntryValuesComponent],
providers: [],
})
export class SharedHistoryLogEntryModule {}

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './history-log-entry.component';
export * from './history-log-entry.module';
export * from './history-log-entry-values';
// end:ng42.barrel

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './history-list.component';
export * from './history-list.module';
export * from './history-log-entry';
// end:ng42.barrel

View File

@@ -0,0 +1,5 @@
/*
* Public API Surface of history
*/
export * from './lib';

View File

@@ -0,0 +1,14 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/lib",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": []
},
"exclude": [
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"compilationMode": "partial"
}
}

View File

@@ -0,0 +1,14 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -2,8 +2,12 @@
import { DiffDTO } from './diff-dto'; import { DiffDTO } from './diff-dto';
export interface HistoryDTO { export interface HistoryDTO {
changed?: string; changed?: string;
changedAt?: string;
changedBy?: string; changedBy?: string;
changeset?: number; changeset?: number;
created?: string;
createdAt?: string;
createdBy?: string;
description?: string; description?: string;
histDate?: string; histDate?: string;
historyset?: number; historyset?: number;

View File

@@ -2,8 +2,12 @@
import { DiffDTO } from './diff-dto'; import { DiffDTO } from './diff-dto';
export interface HistoryDTO { export interface HistoryDTO {
changed?: string; changed?: string;
changedAt?: string;
changedBy?: string; changedBy?: string;
changeset?: number; changeset?: number;
created?: string;
createdAt?: string;
createdBy?: string;
description?: string; description?: string;
histDate?: string; histDate?: string;
historyset?: number; historyset?: number;

View File

@@ -5,4 +5,5 @@ export class UiModalConfig extends OverlayConfig {
backdropClose?: boolean; backdropClose?: boolean;
showScrollbarX?: boolean; showScrollbarX?: boolean;
showScrollbarY?: boolean; showScrollbarY?: boolean;
padding?: boolean;
} }

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, OnInit, TemplateRef, Type, ChangeDetectorRef, OnDestroy } from '@angular/core'; import { Component, ChangeDetectionStrategy, OnInit, TemplateRef, Type, ChangeDetectorRef, OnDestroy, HostBinding } from '@angular/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { UiModalRef } from './defs/modal-ref'; import { UiModalRef } from './defs/modal-ref';
@@ -55,4 +55,8 @@ export class UiModalComponent implements OnInit, OnDestroy {
ngOnDestroy(): void { ngOnDestroy(): void {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
@HostBinding('style') get class() {
return !this.ref?.config?.padding ? 'padding: 0px' : '';
}
} }

View File

@@ -29,6 +29,7 @@ export class UiModalService {
showScrollbarX: false, showScrollbarX: false,
showScrollbarY: true, showScrollbarY: true,
canClose: true, canClose: true,
padding: true,
...config, ...config,
}); });

20282
package-lock.json generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -147,6 +147,9 @@
"@shell/process": [ "@shell/process": [
"apps/shell/process/src/public-api.ts" "apps/shell/process/src/public-api.ts"
], ],
"@shared/history": [
"apps/shared/history/src/public-api.ts"
],
"@ui/toggle": [ "@ui/toggle": [
"apps/ui/toggle/src/public-api.ts" "apps/ui/toggle/src/public-api.ts"
], ],