mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Merge branch 'feature/rd-customer' into develop
This commit is contained in:
@@ -2,8 +2,8 @@ import { NgModule } from '@angular/core';
|
||||
import { ProductImagePipe } from './product-image.pipe';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ProductImagePipe],
|
||||
imports: [],
|
||||
declarations: [],
|
||||
imports: [ProductImagePipe],
|
||||
exports: [ProductImagePipe],
|
||||
})
|
||||
export class ProductImageModule {}
|
||||
|
||||
@@ -3,6 +3,8 @@ import { ProductImageService } from './product-image.service';
|
||||
|
||||
@Pipe({
|
||||
name: 'productImage',
|
||||
standalone: true,
|
||||
pure: true,
|
||||
})
|
||||
export class ProductImagePipe implements PipeTransform {
|
||||
constructor(private imageService: ProductImageService) {}
|
||||
|
||||
@@ -71,12 +71,12 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'customer',
|
||||
loadChildren: () => import('@page/customer').then((m) => m.PageCustomerModule),
|
||||
loadChildren: () => import('@page/customer-rd').then((m) => m.CustomerModule),
|
||||
canActivate: [CanActivateCustomerGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/customer',
|
||||
loadChildren: () => import('@page/customer').then((m) => m.PageCustomerModule),
|
||||
loadChildren: () => import('@page/customer-rd').then((m) => m.CustomerModule),
|
||||
canActivate: [CanActivateCustomerWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
|
||||
@@ -243,10 +243,19 @@
|
||||
"name": "arrow-drop-up",
|
||||
"data": "m280-400 200-201 200 201H280Z",
|
||||
"viewBox": "0 -960 960 960"
|
||||
},
|
||||
{
|
||||
"name": "refresh",
|
||||
"data": "M480-160q-133 0-226.5-93.5T160-480q0-133 93.5-226.5T480-800q85 0 149 34.5T740-671v-129h60v254H546v-60h168q-38-60-97-97t-137-37q-109 0-184.5 75.5T220-480q0 109 75.5 184.5T480-220q83 0 152-47.5T728-393h62q-29 105-115 169t-195 64Z",
|
||||
"viewBox": "0 -960 960 960"
|
||||
}
|
||||
|
||||
],
|
||||
"aliases": [
|
||||
{
|
||||
"alias": "barcode-scanner",
|
||||
"name": "barcode-scan"
|
||||
},
|
||||
{
|
||||
"alias": "d-account",
|
||||
"name": "account"
|
||||
@@ -255,6 +264,30 @@
|
||||
"alias": "d-no-account",
|
||||
"name": "package-variant-closed"
|
||||
},
|
||||
{
|
||||
"alias": "Rücklage",
|
||||
"name": "isa-shopping-bag"
|
||||
},
|
||||
{
|
||||
"alias": "Abholung",
|
||||
"name": "isa-box-out"
|
||||
},
|
||||
{
|
||||
"alias": "Versand",
|
||||
"name": "isa-truck"
|
||||
},
|
||||
{
|
||||
"alias": "DIG-Versand",
|
||||
"name": "isa-truck"
|
||||
},
|
||||
{
|
||||
"alias": "B2B-Versand",
|
||||
"name": "isa-b2b-truck"
|
||||
},
|
||||
{
|
||||
"alias": "Download",
|
||||
"name": "isa-download"
|
||||
},
|
||||
{
|
||||
"name": "isa-audio",
|
||||
"alias": "AU"
|
||||
|
||||
@@ -67,3 +67,18 @@
|
||||
@apply block bg-gray-300 h-6;
|
||||
animation: load 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.input-control {
|
||||
@apply rounded border border-solid border-[#AEB7C1] px-4 py-[1.125rem] outline-none;
|
||||
}
|
||||
|
||||
// .input-control:focus,
|
||||
// .input-control:not(:placeholder-shown) {
|
||||
// @apply bg-white;
|
||||
// }
|
||||
|
||||
.input-control.ng-touched.ng-invalid {
|
||||
@apply border-brand;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<span class="text-[22px] font-bold"> {{ customer?.lastName }} {{ customer?.firstName }} </span>
|
||||
<span class="text-[22px] font-bold"> {{ customerName }} </span>
|
||||
<div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-row items-center">
|
||||
|
||||
@@ -15,5 +15,19 @@ export class CustomerResultListItemFullComponent {
|
||||
@Input()
|
||||
customer: CustomerInfoDTO;
|
||||
|
||||
get customerName() {
|
||||
if (!this.customer) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const isB2b = this.customer.features?.some((f) => f.key === 'b2b') && this.customer.organisation?.name;
|
||||
|
||||
if (!isB2b) {
|
||||
return `${this.customer.lastName} ${this.customer.firstName}`;
|
||||
} else {
|
||||
return `${this.customer.organisation.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
:host {
|
||||
@apply flex flex-col bg-surface text-surface-content h-[11.313rem] mb-[0.625rem] p-4;
|
||||
}
|
||||
|
||||
:host.active {
|
||||
@apply border border-solid border-[#0556B4] bg-[#D8DFE5];
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-between grow">
|
||||
<span class="text-[22px] font-bold"> {{ customer?.lastName }} {{ customer?.firstName }} </span>
|
||||
<span class="text-[22px] font-bold"> {{ customerName }} </span>
|
||||
<div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-row items-center">
|
||||
|
||||
@@ -15,5 +15,19 @@ export class CustomerResultListItemComponent {
|
||||
@Input()
|
||||
customer: CustomerInfoDTO;
|
||||
|
||||
get customerName() {
|
||||
if (!this.customer) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const isB2b = this.customer.features?.some((f) => f.key === 'b2b') && this.customer.organisation?.name;
|
||||
|
||||
if (!isB2b) {
|
||||
return `${this.customer.lastName} ${this.customer.firstName}`;
|
||||
} else {
|
||||
return `${this.customer.organisation.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,35 @@
|
||||
<cdk-virtual-scroll-viewport itemSize="98" class="h-[calc(100vh-18.875rem)]" *ngIf="!compact">
|
||||
<cdk-virtual-scroll-viewport
|
||||
itemSize="98"
|
||||
class="h-[calc(100vh-18.875rem)]"
|
||||
*ngIf="!compact"
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<a
|
||||
*cdkVirtualFor="let customer of customers$ | async; trackBy: trackByFn"
|
||||
*cdkVirtualFor="let customer of customers; trackBy: trackByFn"
|
||||
[routerLink]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.path"
|
||||
[queryParams]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
routerLinkActive
|
||||
#rla="routerLinkActive"
|
||||
>
|
||||
<page-customer-result-list-item-full [customer]="customer"></page-customer-result-list-item-full>
|
||||
<page-customer-result-list-item-full [class.active]="rla.isActive" [customer]="customer"></page-customer-result-list-item-full>
|
||||
</a>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
|
||||
<cdk-virtual-scroll-viewport itemSize="98" class="h-[calc(100vh-20.75rem)]" *ngIf="compact">
|
||||
<cdk-virtual-scroll-viewport
|
||||
itemSize="98"
|
||||
class="h-[calc(100vh-20.75rem)]"
|
||||
*ngIf="compact"
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<a
|
||||
*cdkVirtualFor="let customer of customers$ | async; trackBy: trackByFn"
|
||||
*cdkVirtualFor="let customer of customers; trackBy: trackByFn"
|
||||
[routerLink]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.path"
|
||||
[queryParams]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
routerLinkActive
|
||||
#rla="routerLinkActive"
|
||||
>
|
||||
<page-customer-result-list-item [customer]="customer"></page-customer-result-list-item>
|
||||
<page-customer-result-list-item [class.active]="rla.isActive" [customer]="customer"></page-customer-result-list-item>
|
||||
</a>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, ViewChild } from '@angular/core';
|
||||
import { CustomerInfoDTO } from '@swagger/crm';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { BooleanInput, NumberInput, coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';
|
||||
import { CustomerSearchNavigation } from '../../navigations';
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-result-list',
|
||||
@@ -21,7 +20,8 @@ export class CustomerResultListComponent {
|
||||
this._compact = coerceBooleanProperty(value);
|
||||
}
|
||||
|
||||
customers$ = this._customerService.getCustomers('Lorenz Hilpert').pipe(map((res) => res.result));
|
||||
@Input()
|
||||
customers: CustomerInfoDTO[];
|
||||
|
||||
@Input()
|
||||
selected: CustomerInfoDTO;
|
||||
@@ -29,6 +29,9 @@ export class CustomerResultListComponent {
|
||||
@Output()
|
||||
selectedChange = new EventEmitter<CustomerInfoDTO>();
|
||||
|
||||
@Output()
|
||||
endReached = new EventEmitter<void>();
|
||||
|
||||
private _processId: NumberInput;
|
||||
@Input()
|
||||
get processId() {
|
||||
@@ -40,5 +43,19 @@ export class CustomerResultListComponent {
|
||||
|
||||
trackByFn = (_: number, item: CustomerInfoDTO) => item?.id;
|
||||
|
||||
constructor(private _customerService: CrmCustomerService, public customerSearchNavigation: CustomerSearchNavigation) {}
|
||||
@ViewChild(CdkVirtualScrollViewport, { static: false })
|
||||
viewport: CdkVirtualScrollViewport;
|
||||
|
||||
@Input()
|
||||
set scrollIndex(index: number) {
|
||||
this.viewport?.scrollToIndex(index);
|
||||
}
|
||||
|
||||
constructor(public customerSearchNavigation: CustomerSearchNavigation) {}
|
||||
|
||||
scrolledIndexChange(index: number) {
|
||||
if (index && this.customers.length <= this.viewport?.getRenderedRange()?.end) {
|
||||
this.endReached.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<ui-checkbox [formControl]="control" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly">
|
||||
<shared-checkbox [formControl]="control" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly">
|
||||
Bitte unterschreiben Sie die Teilnahmebedingungen im Kundenkartenformular, um die Prämiennutzung zu ermöglichen.
|
||||
{{ requiredMark ? '*' : '' }}
|
||||
</ui-checkbox>
|
||||
</shared-checkbox>
|
||||
|
||||
@@ -2,12 +2,11 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { AcceptAGBFormBlockComponent } from './accept-agb-form-block.component';
|
||||
import { UiCheckboxModule } from '@ui/checkbox';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { CheckboxComponent } from '@shared/components/checkbox';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiCommonModule, UiCheckboxModule, ReactiveFormsModule],
|
||||
imports: [CommonModule, CheckboxComponent, ReactiveFormsModule],
|
||||
exports: [AcceptAGBFormBlockComponent],
|
||||
declarations: [AcceptAGBFormBlockComponent],
|
||||
})
|
||||
|
||||
@@ -1,25 +1,62 @@
|
||||
<ng-container [formGroup]="control">
|
||||
<ui-form-control label="Straße" [requiredMark]="requiredMarks.includes('street') ? '*' : ''">
|
||||
<input uiInput type="text" formControlName="street" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Hausnummer" [requiredMark]="requiredMarks.includes('streetNumber') ? '*' : ''">
|
||||
<input uiInput type="text" formControlName="streetNumber" [tabindex]="tabIndexStart + 1" [readonly]="readonly" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="PLZ" [requiredMark]="requiredMarks.includes('zipCode') ? '*' : ''">
|
||||
<input uiInput type="text" formControlName="zipCode" [tabindex]="tabIndexStart + 2" [readonly]="readonly" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Ort" [requiredMark]="requiredMarks.includes('city') ? '*' : ''">
|
||||
<input uiInput type="text" formControlName="city" [tabindex]="tabIndexStart + 3" [readonly]="readonly" />
|
||||
</ui-form-control>
|
||||
<shared-form-control label="Straße">
|
||||
<input
|
||||
placeholder="Straße"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="street"
|
||||
[tabindex]="tabIndexStart"
|
||||
[autofocus]="focusAfterInit"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<shared-form-control label="Hausnummer">
|
||||
<input
|
||||
placeholder="Hausnummer"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="streetNumber"
|
||||
[tabindex]="tabIndexStart + 1"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<shared-form-control label="PLZ">
|
||||
<input
|
||||
placeholder="PLZ"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="zipCode"
|
||||
[tabindex]="tabIndexStart + 2"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<shared-form-control label="Ort">
|
||||
<input
|
||||
placeholder="Ort"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="city"
|
||||
[tabindex]="tabIndexStart + 3"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
|
||||
<ui-form-control class="col-span-2" label="Adresszusatz" [clearable]="false" [requiredMark]="requiredMarks.includes('info') ? '*' : ''">
|
||||
<input uiInput type="text" formControlName="info" [tabindex]="tabIndexStart + 4" [readonly]="readonly" />
|
||||
</ui-form-control>
|
||||
<shared-form-control class="col-span-2" label="Adresszusatz">
|
||||
<input
|
||||
placeholder="Adresszusatz"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="info"
|
||||
[tabindex]="tabIndexStart + 4"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
|
||||
<ui-form-control class="col-span-2" label="Land" [clearable]="true" [requiredMark]="requiredMarks.includes('country') ? '*' : ''">
|
||||
<ui-select formControlName="country" [tabindex]="tabIndexStart + 5" [readonly]="readonly">
|
||||
<ui-select-option *ngFor="let country of countries || (countries$ | async)" [label]="country.name" [value]="country.isO3166_A_3">
|
||||
</ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
<shared-form-control class="col-span-2" label="Land">
|
||||
<shared-select placeholder="Land" formControlName="country" [tabindex]="tabIndexStart + 5" [readonly]="readonly">
|
||||
<shared-select-option *ngFor="let country of countries || (countries$ | async)" [value]="country.isO3166_A_3">
|
||||
{{ country.name }}
|
||||
</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
</ng-container>
|
||||
|
||||
@@ -2,14 +2,12 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { AddressFormBlockComponent } from './address-form-block.component';
|
||||
import { UiFormControlModule } from '@ui/form-control';
|
||||
import { UiInputModule } from '@ui/input';
|
||||
import { UiSelectModule } from '@ui/select';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { FormControlComponent } from '@shared/components/form-control';
|
||||
import { SelectModule } from '@shared/components/select';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiCommonModule, UiFormControlModule, UiInputModule, UiSelectModule, ReactiveFormsModule],
|
||||
imports: [CommonModule, ReactiveFormsModule, FormControlComponent, SelectModule],
|
||||
exports: [AddressFormBlockComponent],
|
||||
declarations: [AddressFormBlockComponent],
|
||||
})
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
<ui-form-control label="Geburtsdatum (TT.MM.JJJJ)" [requiredMark]="requiredMark ? '*' : ''">
|
||||
<input uiDateInput type="text" [formControl]="control" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly" />
|
||||
</ui-form-control>
|
||||
<shared-form-control label="Geburtsdatum (TT.MM.JJJJ)">
|
||||
<input
|
||||
placeholder="Geburtsdatum"
|
||||
class="input-control"
|
||||
uiDateInput
|
||||
type="text"
|
||||
[formControl]="control"
|
||||
[tabindex]="tabIndexStart"
|
||||
[autofocus]="focusAfterInit"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
|
||||
@@ -2,13 +2,11 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { BirthDateFormBlockComponent } from './birth-date-form-block.component';
|
||||
import { UiFormControlModule } from '@ui/form-control';
|
||||
import { UiInputModule } from '@ui/input';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { FormControlComponent } from '@shared/components/form-control';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, ReactiveFormsModule, UiFormControlModule, UiInputModule, UiCommonModule],
|
||||
imports: [CommonModule, ReactiveFormsModule, FormControlComponent],
|
||||
exports: [BirthDateFormBlockComponent],
|
||||
declarations: [BirthDateFormBlockComponent],
|
||||
})
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<ui-checkbox [formControl]="deviatingAddress" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly">
|
||||
<shared-checkbox
|
||||
class="mb-4"
|
||||
[formControl]="deviatingAddress"
|
||||
[tabindex]="tabIndexStart"
|
||||
[autofocus]="focusAfterInit"
|
||||
[readonly]="readonly"
|
||||
>
|
||||
<ng-content></ng-content>
|
||||
</ui-checkbox>
|
||||
</shared-checkbox>
|
||||
<div class="address-block" *ngIf="control.value.deviatingAddress">
|
||||
<div class="wrapper">
|
||||
<app-organisation-form-block
|
||||
|
||||
@@ -2,26 +2,24 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { DeviatingAddressFormBlockComponent } from './deviating-address-form-block.component';
|
||||
import { UiCheckboxModule } from '@ui/checkbox';
|
||||
import { AddressFormBlockModule } from '../address';
|
||||
import { NameFormBlockModule } from '../name';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { OrganisationFormBlockModule } from '../organisation';
|
||||
import { EmailFormBlockModule } from '../email';
|
||||
import { PhoneNumbersFormBlockModule } from '../phone-numbers';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { CheckboxComponent } from '@shared/components/checkbox';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
UiCheckboxModule,
|
||||
NameFormBlockModule,
|
||||
AddressFormBlockModule,
|
||||
OrganisationFormBlockModule,
|
||||
EmailFormBlockModule,
|
||||
PhoneNumbersFormBlockModule,
|
||||
UiCommonModule,
|
||||
CheckboxComponent,
|
||||
],
|
||||
exports: [DeviatingAddressFormBlockComponent],
|
||||
declarations: [DeviatingAddressFormBlockComponent],
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
<ui-form-control label="E-Mail" [requiredMark]="requiredMark ? '*' : ''">
|
||||
<input uiInput type="mail" [formControl]="control" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly" />
|
||||
</ui-form-control>
|
||||
<shared-form-control label="E-Mail">
|
||||
<input
|
||||
placeholder="E-Mail"
|
||||
type="mail"
|
||||
class="input-control"
|
||||
[formControl]="control"
|
||||
[tabindex]="tabIndexStart"
|
||||
[autofocus]="focusAfterInit"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
|
||||
@@ -3,12 +3,10 @@ import { CommonModule } from '@angular/common';
|
||||
|
||||
import { EmailFormBlockComponent } from './email-form-block.component';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiFormControlModule } from '@ui/form-control';
|
||||
import { UiInputModule } from '@ui/input';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { FormControlComponent } from '@shared/components/form-control';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiCommonModule, ReactiveFormsModule, UiFormControlModule, UiInputModule],
|
||||
imports: [CommonModule, ReactiveFormsModule, FormControlComponent],
|
||||
exports: [EmailFormBlockComponent],
|
||||
declarations: [EmailFormBlockComponent],
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="interests-description">Geben Sie Interessen an, um Ihre persönlichen Kontoangaben zu verfeinern.</div>
|
||||
<div class="interests-wrapper" [formGroup]="control">
|
||||
<ui-checkbox
|
||||
<shared-checkbox
|
||||
*ngFor="let pair of interests | keyvalue; let idx = index"
|
||||
[formControlName]="pair.key"
|
||||
[tabindex]="tabIndexStart + idx"
|
||||
@@ -8,5 +8,5 @@
|
||||
[readonly]="readonly"
|
||||
>
|
||||
{{ pair.value }}
|
||||
</ui-checkbox>
|
||||
</shared-checkbox>
|
||||
</div>
|
||||
|
||||
@@ -2,12 +2,11 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { InterestsFormBlockComponent } from './interests-form-block.component';
|
||||
import { UiCheckboxModule } from '@ui/checkbox';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { CheckboxComponent } from '@shared/components/checkbox';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiCommonModule, ReactiveFormsModule, UiCheckboxModule],
|
||||
imports: [CommonModule, ReactiveFormsModule, CheckboxComponent],
|
||||
exports: [InterestsFormBlockComponent],
|
||||
declarations: [InterestsFormBlockComponent],
|
||||
})
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<ng-container [formGroup]="control">
|
||||
<label class="grid grid-flow-row gap-1">
|
||||
<span>Anrede</span>
|
||||
|
||||
<shared-select formControlName="gender" placeholder="Anrede" [readonly]="readonly" [tabindex]="tabIndexStart">
|
||||
<shared-form-control label="Anrede">
|
||||
<shared-select
|
||||
formControlName="gender"
|
||||
placeholder="Anrede"
|
||||
[readonly]="readonly"
|
||||
[tabindex]="tabIndexStart"
|
||||
[autofocus]="focusAfterInit"
|
||||
>
|
||||
<shared-select-option [value]="2">Herr</shared-select-option>
|
||||
<shared-select-option [value]="4">Frau</shared-select-option>
|
||||
</shared-select>
|
||||
</label>
|
||||
|
||||
<label class="grid grid-flow-row gap-1">
|
||||
<span>Titel</span>
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Titel">
|
||||
<shared-select formControlName="title" placeholder="Titel" [readonly]="readonly" [tabindex]="tabIndexStart + 1">
|
||||
<shared-select-option value="Dipl.-Ing.">Dipl.-Ing.</shared-select-option>
|
||||
<shared-select-option value="Dr.">Dr.</shared-select-option>
|
||||
@@ -19,29 +21,27 @@
|
||||
<shared-select-option value="Prof. Dr.">Prof. Dr.</shared-select-option>
|
||||
<shared-select-option value="RA">RA</shared-select-option>
|
||||
</shared-select>
|
||||
</label>
|
||||
</shared-form-control>
|
||||
|
||||
<!-- <ui-form-control [clearable]="!readonly" label="Anrede" [requiredMark]="requiredMarks.includes('gender') ? '*' : ''">
|
||||
<ui-select formControlName="gender" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly">
|
||||
<ui-select-option [value]="2" label="Herr"></ui-select-option>
|
||||
<ui-select-option [value]="4" label="Frau"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control> -->
|
||||
<!-- <ui-form-control [clearable]="!readonly" label="Titel" [requiredMark]="requiredMarks.includes('title') ? '*' : ''">
|
||||
<ui-select formControlName="title" [tabindex]="tabIndexStart + 1" [readonly]="readonly">
|
||||
<ui-select-option value="Dipl.-Ing." label="Dipl.-Ing."></ui-select-option>
|
||||
<ui-select-option value="Dr." label="Dr."></ui-select-option>
|
||||
<ui-select-option value="Dr. med." label="Dr. med."></ui-select-option>
|
||||
<ui-select-option value="Prof." label="Prof."></ui-select-option>
|
||||
<ui-select-option value="Prof. Dr." label="Prof. Dr."></ui-select-option>
|
||||
<ui-select-option value="RA" label="RA"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control> -->
|
||||
<shared-form-control label="Nachname">
|
||||
<input
|
||||
class="input-control"
|
||||
placeholder="Nachname"
|
||||
type="text"
|
||||
formControlName="lastName"
|
||||
[tabindex]="tabIndexStart + 2"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
|
||||
<ui-form-control label="Nachname" [requiredMark]="requiredMarks.includes('firstName') ? '*' : ''">
|
||||
<input uiInput type="text" formControlName="lastName" [tabindex]="tabIndexStart + 2" [readonly]="readonly" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Vorname" [requiredMark]="requiredMarks.includes('lastName') ? '*' : ''">
|
||||
<input uiInput type="text" formControlName="firstName" [tabindex]="tabIndexStart + 3" [readonly]="readonly" />
|
||||
</ui-form-control>
|
||||
<shared-form-control label="Vorname">
|
||||
<input
|
||||
class="input-control"
|
||||
placeholder="Vorname"
|
||||
type="text"
|
||||
formControlName="firstName"
|
||||
[tabindex]="tabIndexStart + 3"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
</ng-container>
|
||||
|
||||
@@ -2,14 +2,12 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { NameFormBlockComponent } from './name-form-block.component';
|
||||
import { UiFormControlModule } from '@ui/form-control';
|
||||
import { UiInputModule } from '@ui/input';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { SelectModule } from '@shared/components/select';
|
||||
import { FormControlComponent } from '@shared/components/form-control';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiCommonModule, ReactiveFormsModule, UiFormControlModule, UiInputModule, SelectModule],
|
||||
imports: [CommonModule, ReactiveFormsModule, SelectModule, FormControlComponent],
|
||||
exports: [NameFormBlockComponent],
|
||||
declarations: [NameFormBlockComponent],
|
||||
})
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<ui-checkbox [formControl]="control" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly">
|
||||
<shared-checkbox [formControl]="control" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly">
|
||||
Erhalten Sie 250 Lesepunkte mit der Anmeldung zum Newsletter und eine Geburtstagsüberraschung
|
||||
</ui-checkbox>
|
||||
</shared-checkbox>
|
||||
|
||||
@@ -2,12 +2,11 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { NewsletterFormBlockComponent } from './newsletter-form-block.component';
|
||||
import { UiCheckboxModule } from '@ui/checkbox';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { CheckboxComponent } from '@shared/components/checkbox';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiCommonModule, ReactiveFormsModule, UiCheckboxModule],
|
||||
imports: [CommonModule, ReactiveFormsModule, CheckboxComponent],
|
||||
exports: [NewsletterFormBlockComponent],
|
||||
declarations: [NewsletterFormBlockComponent],
|
||||
})
|
||||
|
||||
@@ -1,13 +1,35 @@
|
||||
<ng-container [formGroup]="control">
|
||||
<ui-form-control class="col-span-2" label="Firmenname" [requiredMark]="requiredMarks.includes('name') ? '*' : ''">
|
||||
<input uiInput type="text" formControlName="name" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly" />
|
||||
</ui-form-control>
|
||||
<shared-form-control class="col-span-2" label="Firmenname">
|
||||
<input
|
||||
placeholder="Firmenname"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="name"
|
||||
[tabindex]="tabIndexStart"
|
||||
[autofocus]="focusAfterInit"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<ng-container *ngIf="appearence === 'default'">
|
||||
<ui-form-control label="Abteilung" [requiredMark]="requiredMarks.includes('department') ? '*' : ''">
|
||||
<input uiInput type="text" formControlName="department" [tabindex]="tabIndexStart + 1" [readonly]="readonly" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="USt-ID" [requiredMark]="requiredMarks.includes('vatId') ? '*' : ''">
|
||||
<input uiInput type="text" formControlName="vatId" [tabindex]="tabIndexStart + 2" [readonly]="readonly" />
|
||||
</ui-form-control>
|
||||
<shared-form-control label="Abteilung">
|
||||
<input
|
||||
placeholder="Abteilung"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="department"
|
||||
[tabindex]="tabIndexStart + 1"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<shared-form-control label="USt-ID">
|
||||
<input
|
||||
placeholder="USt-ID"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="vatId"
|
||||
[tabindex]="tabIndexStart + 2"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
@@ -2,13 +2,11 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { OrganisationFormBlockComponent } from './organisation-form-block.component';
|
||||
import { UiFormControlModule } from '@ui/form-control';
|
||||
import { UiInputModule } from '@ui/input';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { FormControlComponent } from '@shared/components/form-control';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiCommonModule, ReactiveFormsModule, UiFormControlModule, UiInputModule],
|
||||
imports: [CommonModule, ReactiveFormsModule, FormControlComponent],
|
||||
exports: [OrganisationFormBlockComponent],
|
||||
declarations: [OrganisationFormBlockComponent],
|
||||
})
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
<ui-form-control label="Kundenkartencode" requiredMark="*" class="flex-grow">
|
||||
<input uiInput type="text" [formControl]="control" [tabindex]="tabIndexStart" [readonly]="readonly" [autofocus]="focusAfterInit" />
|
||||
</ui-form-control>
|
||||
<shared-form-control label="Kundenkartencode" class="flex-grow">
|
||||
<input
|
||||
placeholder="Kundenkartencode"
|
||||
class="input-control"
|
||||
type="text"
|
||||
[formControl]="control"
|
||||
[tabindex]="tabIndexStart"
|
||||
[readonly]="readonly"
|
||||
[autofocus]="focusAfterInit"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<button type="button" *ngIf="!readonly && canScan()" (click)="scan()">
|
||||
<shared-icon icon="barcode-scan" [size]="32"></shared-icon>
|
||||
</button>
|
||||
|
||||
@@ -3,13 +3,11 @@ import { CommonModule } from '@angular/common';
|
||||
|
||||
import { P4mNumberFormBlockComponent } from './p4m-number-form-block.component';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiFormControlModule } from '@ui/form-control';
|
||||
import { UiInputModule } from '@ui/input';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
import { FormControlComponent } from '@shared/components/form-control';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiCommonModule, ReactiveFormsModule, UiFormControlModule, UiInputModule, IconComponent],
|
||||
imports: [CommonModule, ReactiveFormsModule, FormControlComponent, IconComponent],
|
||||
exports: [P4mNumberFormBlockComponent],
|
||||
declarations: [P4mNumberFormBlockComponent],
|
||||
})
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
<ng-container [formGroup]="control">
|
||||
<ui-form-control label="Festnetznummer" [requiredMark]="requiredMarks.includes('phone') ? '*' : ''">
|
||||
<input uiInput type="tel" formControlName="phone" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Mobilnummer" [clearable]="false" [requiredMark]="requiredMarks.includes('mobile') ? '*' : ''">
|
||||
<input uiInput type="tel" formControlName="mobile" [tabindex]="tabIndexStart + 1" [readonly]="readonly" />
|
||||
</ui-form-control>
|
||||
<shared-form-control label="Festnetznummer">
|
||||
<input
|
||||
placeholder="Festnetznummer"
|
||||
class="input-control"
|
||||
type="tel"
|
||||
formControlName="phone"
|
||||
[tabindex]="tabIndexStart"
|
||||
[autofocus]="focusAfterInit"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<shared-form-control label="Mobilnummer">
|
||||
<input
|
||||
placeholder="Mobilnummer"
|
||||
class="input-control"
|
||||
type="tel"
|
||||
formControlName="mobile"
|
||||
[tabindex]="tabIndexStart + 1"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { UiFormControlModule } from '@ui/form-control';
|
||||
import { UiInputModule } from '@ui/input';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { PhoneNumbersFormBlockComponent } from './phone-numbers-form-block.component';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { FormControlComponent } from '@shared/components/form-control';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiCommonModule, ReactiveFormsModule, UiFormControlModule, UiInputModule],
|
||||
imports: [CommonModule, ReactiveFormsModule, FormControlComponent],
|
||||
exports: [PhoneNumbersFormBlockComponent],
|
||||
declarations: [PhoneNumbersFormBlockComponent],
|
||||
})
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './kundenkarte.component';
|
||||
@@ -0,0 +1,38 @@
|
||||
<div class="wrapper text-center" [@cardFlip]="state" (@cardFlip.done)="flipAnimationDone($event)">
|
||||
<div *ngIf="cardDetails" class="card-main">
|
||||
<div class="icons text-brand">
|
||||
<button *ngIf="isCustomerCard && frontside" class="icon-barcode" (click)="flipCard()">
|
||||
<shared-icon [size]="35" icon="barcode-scanner"></shared-icon>
|
||||
</button>
|
||||
<button *ngIf="isCustomerCard && !frontside" class="icon-back" (click)="flipCard()">
|
||||
<shared-icon [size]="35" icon="refresh"></shared-icon>
|
||||
</button>
|
||||
<!-- <div *ngIf="!isCustomerCard" class="icon-delete"><ui-icon (click)="onDeletePartnerCard()" size="25px" icon="trash"></ui-icon></div> -->
|
||||
</div>
|
||||
<div class="headline">
|
||||
<p *ngIf="isCustomerCard && frontside">Ihre Lesepunkte</p>
|
||||
<p *ngIf="isCustomerCard && !frontside">Kartennummer</p>
|
||||
<p *ngIf="!isCustomerCard">Partnerkartennummer</p>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div *ngIf="!isCustomerCard || (isCustomerCard && !frontside)" class="card-number">{{ cardDetails.code }}</div>
|
||||
<div *ngIf="isCustomerCard && frontside" class="points">{{ cardDetails.totalPoints | number }}</div>
|
||||
</div>
|
||||
<div class="barcode-button">
|
||||
<div *ngIf="!isCustomerCard || (isCustomerCard && !frontside)" class="barcode-field">
|
||||
<img class="barcode" src="/assets/images/barcode.png" alt="Barcode" />
|
||||
</div>
|
||||
<div *ngIf="isCustomerCard && frontside">
|
||||
<button class="button" (click)="onRewardShop()">Zum Prämienshop</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-bottom">
|
||||
<div *ngIf="!isCustomerCard || (isCustomerCard && !frontside)" class="customer-name">
|
||||
<p>{{ cardDetails.firstName }} {{ cardDetails.lastName }}</p>
|
||||
</div>
|
||||
<div *ngIf="isCustomerCard && frontside" class="logo ml-2">
|
||||
<img class="logo-picture" src="/assets/images/Hugendubel_Logo.png" alt="Hugendubel Logo" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,91 @@
|
||||
.wrapper {
|
||||
@apply box-border rounded-customerCard relative flex flex-col;
|
||||
width: 421px;
|
||||
height: 266px;
|
||||
margin-bottom: 42px;
|
||||
box-shadow: 0px 21px 64px 0px #4e4e4e;
|
||||
perspective: 1000px;
|
||||
}
|
||||
|
||||
ui-icon {
|
||||
color: #f70400;
|
||||
}
|
||||
|
||||
.icons {
|
||||
@apply absolute;
|
||||
right: 21px;
|
||||
top: 19px;
|
||||
}
|
||||
|
||||
.headline {
|
||||
@apply text-wild-blue-yonder mt-px-15 text-p3 font-bold;
|
||||
}
|
||||
|
||||
.card-number,
|
||||
.points {
|
||||
@apply text-white -mt-px-15 font-bold;
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
|
||||
.card-number {
|
||||
@apply -mt-px-10 text-4xl;
|
||||
}
|
||||
|
||||
.button {
|
||||
@apply border-none outline-none bg-brand text-white font-bold text-p1 px-px-25 py-px-15 rounded-full mt-px-15;
|
||||
}
|
||||
|
||||
.barcode-field {
|
||||
@apply bg-white mt-px-10 box-border;
|
||||
width: 201px;
|
||||
height: 80px;
|
||||
|
||||
img {
|
||||
@apply mx-auto;
|
||||
}
|
||||
}
|
||||
|
||||
.barcode {
|
||||
@apply mt-px-15;
|
||||
}
|
||||
|
||||
.barcode-button {
|
||||
@apply box-border w-full flex flex-col items-center justify-items-center;
|
||||
}
|
||||
|
||||
.customer-name {
|
||||
@apply w-full text-left font-bold ml-px-20;
|
||||
}
|
||||
|
||||
.logo {
|
||||
@apply w-full text-right;
|
||||
margin-top: 13px;
|
||||
margin-right: 16px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.logo-picture {
|
||||
width: 101px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.card-main {
|
||||
@apply box-border rounded-t-customerCard flex flex-col bg-onyx;
|
||||
width: 421px;
|
||||
height: 217px;
|
||||
transition: transform 0.8s;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
.card-bottom {
|
||||
@apply box-border rounded-b-customerCard flex flex-row items-center text-black;
|
||||
width: 421px;
|
||||
height: 49px;
|
||||
transition: transform 0.8s;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
.icon-barcode,
|
||||
.icon-back {
|
||||
@apply bg-transparent border-none outline-none;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { DecimalPipe, NgIf } from '@angular/common';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
import { BonusCardInfoDTO } from '@swagger/crm';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-kundenkarte',
|
||||
templateUrl: 'kundenkarte.component.html',
|
||||
styleUrls: ['kundenkarte.component.scss'],
|
||||
standalone: true,
|
||||
imports: [IconComponent, NgIf, DecimalPipe],
|
||||
animations: [
|
||||
trigger('cardFlip', [
|
||||
state(
|
||||
'front',
|
||||
style({
|
||||
transform: 'rotateY(0deg)',
|
||||
})
|
||||
),
|
||||
state(
|
||||
'back',
|
||||
style({
|
||||
transform: 'rotateY(0deg)',
|
||||
})
|
||||
),
|
||||
state(
|
||||
'flip',
|
||||
style({
|
||||
transform: 'rotateY(90deg)',
|
||||
})
|
||||
),
|
||||
transition('front <=> flip', [animate('400ms')]),
|
||||
transition('back <=> flip', [animate('400ms')]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class KundenkarteComponent implements OnInit {
|
||||
@Input() cardDetails: BonusCardInfoDTO;
|
||||
@Input() isCustomerCard: boolean;
|
||||
frontside: boolean;
|
||||
state: 'front' | 'flip' | 'back' = 'front';
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {
|
||||
this.frontside = true;
|
||||
}
|
||||
|
||||
onRewardShop(): void {}
|
||||
|
||||
onDeletePartnerCard(): void {}
|
||||
|
||||
flipCard() {
|
||||
this.state = 'flip';
|
||||
}
|
||||
|
||||
flipAnimationDone(event: any) {
|
||||
if (event.fromState === 'front') {
|
||||
this.frontside = false;
|
||||
this.state = 'back';
|
||||
} else if (event.fromState === 'back') {
|
||||
this.frontside = true;
|
||||
this.state = 'front';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ app-newsletter-form-block,
|
||||
app-accept-agb-form-block,
|
||||
app-interests-form-block,
|
||||
app-deviating-address-form-block {
|
||||
@apply mt-8;
|
||||
@apply mt-4;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Component, ChangeDetectionStrategy, ViewChild } from '@angular/core';
|
||||
import { ValidatorFn, Validators } from '@angular/forms';
|
||||
import { CustomerDTO } from '@swagger/crm';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { DeviatingAddressFormBlockComponent } from '../../components/form-blocks';
|
||||
import { AddressFormBlockComponent, AddressFormBlockData } from '../../components/form-blocks/address';
|
||||
import { NameFormBlockData } from '../../components/form-blocks/name/name-form-block-data';
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
</app-accept-agb-form-block>
|
||||
|
||||
<app-newsletter-form-block
|
||||
class="mb-4"
|
||||
#newsletterBlock
|
||||
[tabIndexStart]="p4mBlock.tabIndexEnd + 1"
|
||||
(onInit)="addFormBlock('newsletter', $event)"
|
||||
|
||||
@@ -35,6 +35,7 @@ export class CreateStoreCustomerComponent extends AbstractCreateCustomer {
|
||||
nameRequiredMarks: (keyof NameFormBlockData)[] = ['gender', 'firstName', 'lastName'];
|
||||
|
||||
nameValidationFns: Record<string, ValidatorFn[]> = {
|
||||
title: [],
|
||||
gender: [Validators.required, Validators.min(1)],
|
||||
firstName: [Validators.required],
|
||||
lastName: [Validators.required],
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
</app-accept-agb-form-block>
|
||||
|
||||
<app-newsletter-form-block
|
||||
class="mb-4"
|
||||
#newsletterBlock
|
||||
[tabIndexStart]="p4mBlock.tabIndexEnd + 1"
|
||||
(onInit)="addFormBlock('newsletter', $event)"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
:host {
|
||||
@apply block bg-surface text-surface-content rounded px-6 py-8;
|
||||
}
|
||||
|
||||
form {
|
||||
@apply grid grid-cols-2 gap-x-8;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<div class="flex flex-row justify-end -mt-4 -mr-2">
|
||||
<a
|
||||
*ngIf="detailsRoute$ | async; let detailsRoute"
|
||||
[routerLink]="detailsRoute.path"
|
||||
[queryParams]="detailsRoute.urlTree.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
<shared-icon icon="close" [size]="32"></shared-icon>
|
||||
</a>
|
||||
</div>
|
||||
<h1 class="text-2xl text-center font-bold mb-6">Rechnungsadresse hinzufügen</h1>
|
||||
|
||||
<form [formGroup]="formGroup" (ngSubmit)="save()">
|
||||
<shared-form-control label="Anrede">
|
||||
<shared-select formControlName="gender" placeholder="Anrede" tabindex="1" [autofocus]="true">
|
||||
<shared-select-option [value]="2">Herr</shared-select-option>
|
||||
<shared-select-option [value]="4">Frau</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Titel">
|
||||
<shared-select formControlName="title" placeholder="Titel" tabindex="2">
|
||||
<shared-select-option value="Dipl.-Ing.">Dipl.-Ing.</shared-select-option>
|
||||
<shared-select-option value="Dr.">Dr.</shared-select-option>
|
||||
<shared-select-option value="Dr. med.">Dr. med.</shared-select-option>
|
||||
<shared-select-option value="Prof.">Prof.</shared-select-option>
|
||||
<shared-select-option value="Prof. Dr.">Prof. Dr.</shared-select-option>
|
||||
<shared-select-option value="RA">RA</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Nachname">
|
||||
<input class="input-control" placeholder="Nachname" type="text" formControlName="lastName" tabindex="3" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Vorname">
|
||||
<input class="input-control" placeholder="Vorname" type="text" formControlName="firstName" tabindex="4" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Firma" class="col-span-2">
|
||||
<input class="input-control" placeholder="Firma" type="text" formControlName="organisation" tabindex="5" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Straße">
|
||||
<input class="input-control" placeholder="Straße" type="text" formControlName="street" tabindex="6" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Hausnummer">
|
||||
<input class="input-control" placeholder="Hausnummer" type="text" formControlName="streetNumber" tabindex="7" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="PLZ">
|
||||
<input class="input-control" placeholder="PLZ" type="text" formControlName="zipCode" tabindex="8" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Ort">
|
||||
<input class="input-control" placeholder="Ort" type="text" formControlName="city" tabindex="9" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Adresszusatz" class="col-span-2">
|
||||
<input class="input-control" placeholder="Adresszusatz" type="text" formControlName="info" tabindex="10" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control class="col-span-2" label="Land">
|
||||
<shared-select placeholder="Land" formControlName="country" tabindex="11">
|
||||
<shared-select-option *ngFor="let country of countries$ | async" [value]="country.isO3166_A_3">
|
||||
{{ country.name }}
|
||||
</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
<div class="text-center col-span-2">
|
||||
<shared-checkbox>Diese Rechnungsadresse als Standard Adresse festlegen</shared-checkbox>
|
||||
</div>
|
||||
<div class="mt-6 text-center col-span-2">
|
||||
<button
|
||||
[disabled]="formGroup.invalid || formGroup.disabled"
|
||||
type="submit"
|
||||
class="px-5 py-3 font-bold text-lg rounded-full bg-brand text-white"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,108 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { CheckboxComponent } from '@shared/components/checkbox';
|
||||
import { FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { SelectModule } from '@shared/components/select';
|
||||
import { FormControlComponent } from '@shared/components/form-control';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { AddressDTO, Gender, PayerDTO } from '@swagger/crm';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
||||
import { AddressSelectionModalService } from '@shared/modals/address-selection-modal';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { CustomerSearchNavigation } from '../../navigations';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'page-add-billing-address-main-view',
|
||||
templateUrl: 'add-billing-address-main-view.component.html',
|
||||
styleUrls: ['add-billing-address-main-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'page-add-billing-address-main-view' },
|
||||
standalone: true,
|
||||
imports: [
|
||||
IconComponent,
|
||||
RouterLink,
|
||||
AsyncPipe,
|
||||
NgIf,
|
||||
NgForOf,
|
||||
ReactiveFormsModule,
|
||||
SelectModule,
|
||||
FormControlComponent,
|
||||
CheckboxComponent,
|
||||
],
|
||||
})
|
||||
export class AddBillingAddressMainViewComponent {
|
||||
formGroup = new FormGroup({
|
||||
gender: new FormControl<Gender>(0, [Validators.required, Validators.min(1)]),
|
||||
title: new FormControl<string>(undefined),
|
||||
firstName: new FormControl<string>(undefined, [Validators.required]),
|
||||
lastName: new FormControl<string>(undefined, [Validators.required]),
|
||||
organisation: new FormControl<string>(undefined),
|
||||
street: new FormControl<string>(undefined, [Validators.required]),
|
||||
streetNumber: new FormControl<string>(undefined, [Validators.required]),
|
||||
zipCode: new FormControl<string>(undefined, [Validators.required]),
|
||||
city: new FormControl<string>(undefined, [Validators.required]),
|
||||
country: new FormControl<string>('DEU', [Validators.required]),
|
||||
info: new FormControl<string>(undefined),
|
||||
isDefault: new FormControl<boolean>(false),
|
||||
});
|
||||
|
||||
countries$ = this._customerService.getCountries().pipe(map((res) => res.result));
|
||||
|
||||
detailsRoute$ = combineLatest([this._store.processId$, this._store.customerId$]).pipe(
|
||||
map(([processId, customerId]) => this._navigation.detailsRoute({ processId, customerId }))
|
||||
);
|
||||
|
||||
constructor(
|
||||
private _customerService: CrmCustomerService,
|
||||
private _addressSelection: AddressSelectionModalService,
|
||||
private _store: CustomerSearchStore,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
) {}
|
||||
|
||||
async save() {
|
||||
if (this.formGroup.invalid) {
|
||||
this.formGroup.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.formGroup.disable();
|
||||
|
||||
const formData = this.formGroup.value;
|
||||
|
||||
const address: AddressDTO = {
|
||||
street: formData.street,
|
||||
streetNumber: formData.streetNumber,
|
||||
zipCode: formData.zipCode,
|
||||
city: formData.city,
|
||||
country: formData.country,
|
||||
info: formData.info,
|
||||
};
|
||||
|
||||
const addressValidationResult = await this._addressSelection.validateAddress(address);
|
||||
|
||||
if (addressValidationResult === undefined) {
|
||||
this.formGroup.enable();
|
||||
return;
|
||||
}
|
||||
|
||||
const payer: PayerDTO = {
|
||||
gender: formData.gender,
|
||||
title: formData.title,
|
||||
firstName: formData.firstName,
|
||||
lastName: formData.lastName,
|
||||
organisation: formData.organisation ? { name: formData.organisation } : undefined,
|
||||
address: addressValidationResult,
|
||||
};
|
||||
|
||||
const result = await this._customerService.createPayer(this._store.customerId, payer, formData.isDefault);
|
||||
|
||||
this._navigation.navigateToDetails({ processId: this._store.processId, customerId: this._store.customerId });
|
||||
} catch (error) {
|
||||
this.formGroup.enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
:host {
|
||||
@apply block bg-surface text-surface-content rounded px-6 py-8;
|
||||
}
|
||||
|
||||
form {
|
||||
@apply grid grid-cols-2 gap-x-8;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<div class="flex flex-row justify-end -mt-4 -mr-2">
|
||||
<a
|
||||
*ngIf="detailsRoute$ | async; let detailsRoute"
|
||||
[routerLink]="detailsRoute.path"
|
||||
[queryParams]="detailsRoute.urlTree.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
<shared-icon icon="close" [size]="32"></shared-icon>
|
||||
</a>
|
||||
</div>
|
||||
<h1 class="text-2xl text-center font-bold mb-6">Lieferadresse hinzufügen</h1>
|
||||
|
||||
<form [formGroup]="formGroup" (ngSubmit)="save()">
|
||||
<ng-container *ngIf="isBusinessKonto$ | async">
|
||||
<shared-form-control label="Firma" class="col-span-2">
|
||||
<input class="input-control" placeholder="Firma" type="text" formControlName="organisation" tabindex="1" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Abteilung">
|
||||
<input class="input-control" placeholder="Abteilung" type="text" formControlName="department" tabindex="2" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="USt-ID">
|
||||
<input class="input-control" placeholder="Abteilung" type="text" formControlName="vatId" tabindex="3" />
|
||||
</shared-form-control>
|
||||
</ng-container>
|
||||
|
||||
<shared-form-control label="Anrede">
|
||||
<shared-select formControlName="gender" placeholder="Anrede" tabindex="4" [autofocus]="true">
|
||||
<shared-select-option [value]="2">Herr</shared-select-option>
|
||||
<shared-select-option [value]="4">Frau</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Titel">
|
||||
<shared-select formControlName="title" placeholder="Titel" tabindex="5">
|
||||
<shared-select-option value="Dipl.-Ing.">Dipl.-Ing.</shared-select-option>
|
||||
<shared-select-option value="Dr.">Dr.</shared-select-option>
|
||||
<shared-select-option value="Dr. med.">Dr. med.</shared-select-option>
|
||||
<shared-select-option value="Prof.">Prof.</shared-select-option>
|
||||
<shared-select-option value="Prof. Dr.">Prof. Dr.</shared-select-option>
|
||||
<shared-select-option value="RA">RA</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Nachname">
|
||||
<input class="input-control" placeholder="Nachname" type="text" formControlName="lastName" tabindex="6" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Vorname">
|
||||
<input class="input-control" placeholder="Vorname" type="text" formControlName="firstName" tabindex="7" />
|
||||
</shared-form-control>
|
||||
|
||||
<ng-container *ngIf="!(isBusinessKonto$ | async)">
|
||||
<shared-form-control label="Firma" class="col-span-2">
|
||||
<input class="input-control" placeholder="Firma" type="text" formControlName="organisation" tabindex="8" />
|
||||
</shared-form-control>
|
||||
</ng-container>
|
||||
|
||||
<shared-form-control label="Straße">
|
||||
<input class="input-control" placeholder="Straße" type="text" formControlName="street" tabindex="9" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Hausnummer">
|
||||
<input class="input-control" placeholder="Hausnummer" type="text" formControlName="streetNumber" tabindex="10" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="PLZ">
|
||||
<input class="input-control" placeholder="PLZ" type="text" formControlName="zipCode" tabindex="11" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Ort">
|
||||
<input class="input-control" placeholder="Ort" type="text" formControlName="city" tabindex="12" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Adresszusatz" class="col-span-2">
|
||||
<input class="input-control" placeholder="Adresszusatz" type="text" formControlName="info" tabindex="13" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control class="col-span-2" label="Land">
|
||||
<shared-select placeholder="Land" formControlName="country" tabindex="14">
|
||||
<shared-select-option *ngFor="let country of countries$ | async" [value]="country.isO3166_A_3">
|
||||
{{ country.name }}
|
||||
</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
<div class="text-center col-span-2">
|
||||
<shared-checkbox>Diese Lieferadresse als Standard Adresse festlegen</shared-checkbox>
|
||||
</div>
|
||||
<div class="mt-6 text-center col-span-2">
|
||||
<button
|
||||
[disabled]="formGroup.invalid || formGroup.disabled"
|
||||
type="submit"
|
||||
class="px-5 py-3 font-bold text-lg rounded-full bg-brand text-white"
|
||||
tabindex="15"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,137 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CheckboxComponent } from '@shared/components/checkbox';
|
||||
import { FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { SelectModule } from '@shared/components/select';
|
||||
import { FormControlComponent } from '@shared/components/form-control';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { AddressDTO, Gender, ShippingAddressDTO } from '@swagger/crm';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
||||
import { AddressSelectionModalService } from '@shared/modals/address-selection-modal';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { CustomerSearchNavigation } from '../../navigations';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
|
||||
@Component({
|
||||
selector: 'page-add-shipping-address-main-view',
|
||||
templateUrl: 'add-shipping-address-main-view.component.html',
|
||||
styleUrls: ['add-shipping-address-main-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'page-add-shipping-address-main-view' },
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterLink,
|
||||
IconComponent,
|
||||
AsyncPipe,
|
||||
NgIf,
|
||||
NgForOf,
|
||||
ReactiveFormsModule,
|
||||
SelectModule,
|
||||
FormControlComponent,
|
||||
CheckboxComponent,
|
||||
],
|
||||
})
|
||||
export class AddShippingAddressMainViewComponent implements OnInit, OnDestroy {
|
||||
private _onDestroy = new Subject<void>();
|
||||
|
||||
detailsRoute$ = combineLatest([this._store.processId$, this._store.customerId$]).pipe(
|
||||
map(([processId, customerId]) => this._navigation.detailsRoute({ processId, customerId }))
|
||||
);
|
||||
|
||||
formGroup = new FormGroup({
|
||||
gender: new FormControl<Gender>(0, [Validators.required, Validators.min(1)]),
|
||||
title: new FormControl<string>(undefined),
|
||||
firstName: new FormControl<string>(undefined, [Validators.required]),
|
||||
lastName: new FormControl<string>(undefined, [Validators.required]),
|
||||
organisation: new FormControl<string>(undefined),
|
||||
department: new FormControl<string>(undefined),
|
||||
vatId: new FormControl<string>(undefined),
|
||||
street: new FormControl<string>(undefined, [Validators.required]),
|
||||
streetNumber: new FormControl<string>(undefined, [Validators.required]),
|
||||
zipCode: new FormControl<string>(undefined, [Validators.required]),
|
||||
city: new FormControl<string>(undefined, [Validators.required]),
|
||||
country: new FormControl<string>('DEU', [Validators.required]),
|
||||
info: new FormControl<string>(undefined),
|
||||
isDefault: new FormControl<boolean>(false),
|
||||
});
|
||||
|
||||
countries$ = this._customerService.getCountries().pipe(map((res) => res.result));
|
||||
|
||||
isBusinessKonto$ = this._store.isBusinessKonto$;
|
||||
|
||||
constructor(
|
||||
private _customerService: CrmCustomerService,
|
||||
private _addressSelection: AddressSelectionModalService,
|
||||
private _store: CustomerSearchStore,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this._store.customer$.pipe(takeUntil(this._onDestroy)).subscribe(() => {
|
||||
if (this._store.isBusinessKonto) {
|
||||
this.formGroup.controls.organisation.setValidators([Validators.required]);
|
||||
} else {
|
||||
this.formGroup.controls.organisation.clearValidators();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._onDestroy.next();
|
||||
this._onDestroy.complete();
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (this.formGroup.invalid) {
|
||||
this.formGroup.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.formGroup.disable();
|
||||
|
||||
const formData = this.formGroup.value;
|
||||
|
||||
const address: AddressDTO = {
|
||||
street: formData.street,
|
||||
streetNumber: formData.streetNumber,
|
||||
zipCode: formData.zipCode,
|
||||
city: formData.city,
|
||||
country: formData.country,
|
||||
info: formData.info,
|
||||
};
|
||||
|
||||
const addressValidationResult = await this._addressSelection.validateAddress(address);
|
||||
|
||||
if (addressValidationResult === undefined) {
|
||||
this.formGroup.enable();
|
||||
return;
|
||||
}
|
||||
|
||||
const addOrganization = this._store.isBusinessKonto || formData.organisation !== undefined;
|
||||
|
||||
const shippingAddress: ShippingAddressDTO = {
|
||||
gender: formData.gender,
|
||||
title: formData.title,
|
||||
firstName: formData.firstName,
|
||||
lastName: formData.lastName,
|
||||
organisation: addOrganization
|
||||
? {
|
||||
name: formData.organisation,
|
||||
department: formData.department,
|
||||
vatId: formData.vatId,
|
||||
}
|
||||
: undefined,
|
||||
address: addressValidationResult,
|
||||
};
|
||||
|
||||
const result = await this._customerService.createShippingAddress(this._store.customerId, shippingAddress, formData.isDefault);
|
||||
|
||||
this._navigation.navigateToDetails({ processId: this._store.processId, customerId: this._store.customerId });
|
||||
} catch (error) {
|
||||
this.formGroup.enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
<page-split-screen class="max-h-[calc(100vh-13.875rem)] h-[calc(100vh-13.875rem)]" [side]="side$ | async">
|
||||
<page-customer-results-side-view *sideTemplate="'results'"></page-customer-results-side-view>
|
||||
<page-customer-main-side-view [processId]="processId$ | async" *sideTemplate="'main'"></page-customer-main-side-view>
|
||||
<page-customer-order-details-side-view *sideTemplate="'order-details'"></page-customer-order-details-side-view>
|
||||
<router-outlet></router-outlet>
|
||||
</page-split-screen>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
import { BehaviorSubject, Subject, Subscription } from 'rxjs';
|
||||
import { CustomerSearchStore } from './store/customer-search.store';
|
||||
import { provideComponentStore } from '@ngrx/component-store';
|
||||
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
|
||||
import { filter, first, switchMap } from 'rxjs/operators';
|
||||
import { delay, filter, first, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { CustomerSearchNavigation } from '../navigations';
|
||||
import { CustomerCreateNavigation } from '../navigations/customer-create.navigation';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-search',
|
||||
@@ -45,12 +46,15 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
|
||||
private _customerChangedSubscription: Subscription;
|
||||
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private _store: CustomerSearchStore,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _router: Router,
|
||||
private _breadcrumbService: BreadcrumbService,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
private _navigation: CustomerSearchNavigation,
|
||||
private _createNavigation: CustomerCreateNavigation
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -59,11 +63,12 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
this.checkAndUpdateCustomerId();
|
||||
this.checkBreadcrumbs();
|
||||
|
||||
this._customerChangedSubscription = this._store.customer$.subscribe((c) => {
|
||||
// Wenn der delay nicht angewendet wird, dann wird die Breacrumb zwei mal angelegt
|
||||
this._customerChangedSubscription = this._store.customer$.pipe(takeUntil(this._onDestroy$), delay(1)).subscribe((c) => {
|
||||
this.checkDetailsBreadcrumb();
|
||||
});
|
||||
|
||||
this._eventsSubscription = this._router.events.subscribe((event) => {
|
||||
this._eventsSubscription = this._router.events.pipe(takeUntil(this._onDestroy$)).subscribe((event) => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
this.checkAndUpdateProcessId();
|
||||
this.checkAndUpdateSide();
|
||||
@@ -71,12 +76,56 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
this.checkBreadcrumbs();
|
||||
}
|
||||
});
|
||||
|
||||
this._store.customerListResponse$.pipe(takeUntil(this._onDestroy$)).subscribe(async ([response, filter, processId]) => {
|
||||
if (this._store.processId === processId) {
|
||||
if (response.hits === 1) {
|
||||
// Navigate to details page
|
||||
const customer = response.result[0];
|
||||
|
||||
if (customer.id < 0) {
|
||||
// navigate to create customer
|
||||
const route = this._createNavigation.createCustomerRoute({ processId, customerInfo: customer });
|
||||
await this._router.navigate(route.path, { queryParams: route.queryParams });
|
||||
} else {
|
||||
const route = this._navigation.detailsRoute({ processId, customerId: customer.id });
|
||||
await this._router.navigate(route.path, { queryParams: route.queryParams });
|
||||
}
|
||||
} else if (response.hits > 1) {
|
||||
const route = this._navigation.listRoute({ processId, filter });
|
||||
|
||||
if (
|
||||
[
|
||||
'details',
|
||||
'history',
|
||||
'edit',
|
||||
'add-billing-address',
|
||||
'edit-billing-address',
|
||||
'add-shipping-address',
|
||||
'edit-shipping-address',
|
||||
'orders',
|
||||
'order-details',
|
||||
'order-details-history',
|
||||
].includes(this._breadcrumb)
|
||||
) {
|
||||
await this._router.navigate([], { queryParams: route.queryParams });
|
||||
} else {
|
||||
await this._router.navigate(route.path, { queryParams: route.queryParams });
|
||||
}
|
||||
}
|
||||
|
||||
this.checkBreadcrumbs();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._eventsSubscription.unsubscribe();
|
||||
this._customerChangedSubscription.unsubscribe();
|
||||
this.side$.complete();
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
this._store.ngOnDestroy();
|
||||
}
|
||||
|
||||
checkAndUpdateProcessId() {
|
||||
@@ -88,8 +137,11 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
processId = this.parentSnapshot?.data?.processId;
|
||||
}
|
||||
|
||||
processId = +processId;
|
||||
|
||||
if (processId !== this._store.processId) {
|
||||
this._store.setProcessId(processId);
|
||||
this._store.reset(this._activatedRoute.snapshot.queryParams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,21 +173,22 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
async checkBreadcrumbs() {
|
||||
let breadcumb: string;
|
||||
async checkBreadcrumbs(force = false) {
|
||||
let breadcrumb: string;
|
||||
|
||||
breadcumb = this.snapshot.data?.breadcumb;
|
||||
breadcrumb = this.snapshot.data?.breadcrumb;
|
||||
|
||||
if (!breadcumb) {
|
||||
breadcumb = this.firstChildSnapshot.data?.breadcumb;
|
||||
if (!breadcrumb) {
|
||||
breadcrumb = this.firstChildSnapshot.data?.breadcrumb;
|
||||
}
|
||||
|
||||
if (breadcumb !== this._breadcrumb) {
|
||||
this._breadcrumb = breadcumb;
|
||||
await this.checkMainBreadcrumb();
|
||||
await this.checkDetailsBreadcrumb();
|
||||
await this.checkHistoryBreadcrumb();
|
||||
}
|
||||
this._breadcrumb = breadcrumb;
|
||||
await this.checkCreateCustomerBreadcrumb();
|
||||
await this.checkMainBreadcrumb();
|
||||
await this.checkSearchBreadcrumb();
|
||||
await this.checkDetailsBreadcrumb();
|
||||
await this.checkHistoryBreadcrumb();
|
||||
await this.checkEditBreadcrumb();
|
||||
}
|
||||
|
||||
getBreadcrumbs(): Promise<Breadcrumb[]> {
|
||||
@@ -154,26 +207,103 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
const navigation = this._navigation.defaultRoute({ processId: this._store.processId });
|
||||
const breadcrumb: Breadcrumb = {
|
||||
key: this._store.processId,
|
||||
tags: ['customer', 'main'],
|
||||
tags: ['customer', 'search', 'main'],
|
||||
name: 'Kundensuche',
|
||||
path: navigation.urlTree.toString(),
|
||||
params: navigation.queryParams,
|
||||
params: { ...this._store.queryParams, ...navigation.queryParams },
|
||||
section: 'customer',
|
||||
};
|
||||
|
||||
this._breadcrumbService.addBreadcrumb(breadcrumb);
|
||||
} else {
|
||||
this._breadcrumbService.patchBreadcrumb(mainBreadcrumb.id, {
|
||||
params: { ...this.snapshot.queryParams, ...(mainBreadcrumb.params ?? {}) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getCreateCustomerBreadcrumb(): Promise<Breadcrumb | undefined> {
|
||||
const breadcrumbs = await this.getBreadcrumbs();
|
||||
return breadcrumbs.find((b) => b.tags.includes('create') && b.tags.includes('customer'));
|
||||
}
|
||||
|
||||
async checkCreateCustomerBreadcrumb() {
|
||||
const createCustomerBreadcrumb = await this.getCreateCustomerBreadcrumb();
|
||||
|
||||
if (createCustomerBreadcrumb) {
|
||||
this._breadcrumbService.removeBreadcrumb(createCustomerBreadcrumb.id);
|
||||
}
|
||||
}
|
||||
|
||||
async getSearchBreadcrumb(): Promise<Breadcrumb | undefined> {
|
||||
const breadcrumbs = await this.getBreadcrumbs();
|
||||
return breadcrumbs.find((b) => b.tags.includes('list') && b.tags.includes('search'));
|
||||
}
|
||||
|
||||
async checkSearchBreadcrumb() {
|
||||
const searchBreadcrumb = await this.getSearchBreadcrumb();
|
||||
|
||||
if (
|
||||
[
|
||||
'search',
|
||||
'details',
|
||||
'history',
|
||||
'edit',
|
||||
'history',
|
||||
'orders',
|
||||
'order-details',
|
||||
'add-billing-address',
|
||||
'edit-billing-address',
|
||||
'add-shipping-address',
|
||||
'edit-shipping-address',
|
||||
].includes(this._breadcrumb)
|
||||
) {
|
||||
const name = this._store.queryParams?.main_qs || 'Suche';
|
||||
|
||||
if (!searchBreadcrumb) {
|
||||
const navigation = this._navigation.listRoute({ processId: this._store.processId });
|
||||
const breadcrumb: Breadcrumb = {
|
||||
key: this._store.processId,
|
||||
tags: ['customer', 'search', 'list'],
|
||||
name,
|
||||
path: navigation.urlTree.toString(),
|
||||
params: { ...this._store.queryParams, ...navigation.queryParams },
|
||||
section: 'customer',
|
||||
};
|
||||
|
||||
this._breadcrumbService.addBreadcrumb(breadcrumb);
|
||||
} else {
|
||||
this._breadcrumbService.patchBreadcrumb(searchBreadcrumb.id, { params: this.snapshot.queryParams, name });
|
||||
}
|
||||
} else {
|
||||
if (searchBreadcrumb) {
|
||||
this._breadcrumbService.removeBreadcrumb(searchBreadcrumb.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getDetailsBreadcrumb(): Promise<Breadcrumb | undefined> {
|
||||
const breadcrumbs = await this.getBreadcrumbs();
|
||||
return breadcrumbs.find((b) => b.tags.includes('details'));
|
||||
return breadcrumbs.find((b) => b.tags.includes('details') && b.tags.includes('search'));
|
||||
}
|
||||
|
||||
async checkDetailsBreadcrumb() {
|
||||
const detailsBreadcrumb = await this.getDetailsBreadcrumb();
|
||||
|
||||
if (['details', 'history'].includes(this._breadcrumb)) {
|
||||
if (
|
||||
[
|
||||
'details',
|
||||
'history',
|
||||
'edit',
|
||||
'history',
|
||||
'orders',
|
||||
'order-details',
|
||||
'add-billing-address',
|
||||
'edit-billing-address',
|
||||
'add-shipping-address',
|
||||
'edit-shipping-address',
|
||||
].includes(this._breadcrumb)
|
||||
) {
|
||||
const customer = this._store.customer;
|
||||
const fullName = `${customer?.firstName ?? ''} ${customer?.lastName ?? ''}`.trim();
|
||||
|
||||
@@ -182,10 +312,10 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
|
||||
const breadcrumb: Breadcrumb = {
|
||||
key: this._store.processId,
|
||||
tags: ['customer', 'details'],
|
||||
tags: ['customer', 'details', 'search'],
|
||||
name: fullName || 'Kunde',
|
||||
path: navigation.urlTree.toString(),
|
||||
params: navigation.queryParams,
|
||||
params: { ...this._store.queryParams, ...navigation.queryParams },
|
||||
section: 'customer',
|
||||
};
|
||||
|
||||
@@ -196,7 +326,7 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
this._breadcrumbService.patchBreadcrumb(detailsBreadcrumb.id, {
|
||||
name: fullName,
|
||||
path: navigation.urlTree.toString(),
|
||||
params: navigation.queryParams,
|
||||
params: { ...this._store.queryParams, ...navigation.queryParams },
|
||||
});
|
||||
}
|
||||
} else if (detailsBreadcrumb) {
|
||||
@@ -218,17 +348,56 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
|
||||
const breadcrumb: Breadcrumb = {
|
||||
key: this._store.processId,
|
||||
tags: ['customer', 'history'],
|
||||
tags: ['customer', 'search', 'history'],
|
||||
name: 'Historie',
|
||||
path: navigation.urlTree.toString(),
|
||||
params: navigation.queryParams,
|
||||
params: { ...this._store.queryParams, ...navigation.queryParams },
|
||||
section: 'customer',
|
||||
};
|
||||
|
||||
this._breadcrumbService.addBreadcrumb(breadcrumb);
|
||||
} else {
|
||||
const navigation = this._navigation.historyRoute({ processId: this._store.processId, customerId: this._store.customerId });
|
||||
|
||||
this._breadcrumbService.patchBreadcrumb(historyBreadcrumb.id, {
|
||||
path: navigation.urlTree.toString(),
|
||||
params: { ...this._store.queryParams, ...navigation.queryParams },
|
||||
});
|
||||
}
|
||||
} else if (historyBreadcrumb) {
|
||||
this._breadcrumbService.removeBreadcrumb(historyBreadcrumb.id);
|
||||
}
|
||||
}
|
||||
|
||||
async getEditBreadcrumb(): Promise<Breadcrumb | undefined> {
|
||||
const breadcrumbs = await this.getBreadcrumbs();
|
||||
return breadcrumbs.find((b) => b.tags.includes('edit'));
|
||||
}
|
||||
|
||||
async checkEditBreadcrumb() {
|
||||
const editBreadcrumb = await this.getEditBreadcrumb();
|
||||
|
||||
if (this._breadcrumb === 'edit') {
|
||||
if (!editBreadcrumb) {
|
||||
const navigation = this._navigation.editRoute({
|
||||
processId: this._store.processId,
|
||||
customerId: this._store.customerId,
|
||||
isB2b: this._store.isBusinessKonto,
|
||||
});
|
||||
|
||||
const breadcrumb: Breadcrumb = {
|
||||
key: this._store.processId,
|
||||
tags: ['customer', 'search', 'edit'],
|
||||
name: 'Bearbeiten',
|
||||
path: navigation.urlTree.toString(),
|
||||
params: { ...this._store.queryParams, ...navigation.queryParams },
|
||||
section: 'customer',
|
||||
};
|
||||
|
||||
this._breadcrumbService.addBreadcrumb(breadcrumb);
|
||||
}
|
||||
} else if (editBreadcrumb) {
|
||||
this._breadcrumbService.removeBreadcrumb(editBreadcrumb.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { CustomerHistoryMainViewModule } from './history-main-view/history-main-
|
||||
import { CustomerFilterMainViewModule } from './filter-main-view/filter-main-view.module';
|
||||
import { SplitScreenModule } from '../components/split-screen/split-screen.module';
|
||||
import { MainSideViewModule } from './main-side-view/main-side-view.module';
|
||||
import { OrderDetailsSideViewComponent } from './order-details-side-view/order-details-side-view.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -22,6 +23,7 @@ import { MainSideViewModule } from './main-side-view/main-side-view.module';
|
||||
CustomerHistoryMainViewModule,
|
||||
CustomerFilterMainViewModule,
|
||||
MainSideViewModule,
|
||||
OrderDetailsSideViewComponent,
|
||||
],
|
||||
exports: [CustomerSearchComponent],
|
||||
declarations: [CustomerSearchComponent],
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<div class="grid grid-flow-col items-center justify-between px-4 py-2 border-t-2 border-solid border-surface-2">
|
||||
<h3 class="font-bold text-xl">Rechnungsadresse</h3>
|
||||
<a
|
||||
*ngIf="addBillingAddressRoute$ | async; let addRoute"
|
||||
type="button"
|
||||
class="text-brand font-bold"
|
||||
[routerLink]="addRoute.path"
|
||||
[queryParams]="addRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
Hinzufügen
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-flow-row">
|
||||
<label
|
||||
class="px-4 py-3 font-bold border-t-2 border-solid border-surface-2 flex flex-row justify-between items-center"
|
||||
*ngIf="showCustomerAddress$ | async"
|
||||
>
|
||||
<input
|
||||
*ngIf="isNotBusinessKonto$ | async"
|
||||
name="shipping-address"
|
||||
type="radio"
|
||||
[checked]="!(customer$ | async)"
|
||||
(change)="selectCustomerAddress()"
|
||||
/>
|
||||
<div class="ml-2 flex flex-row justify-between items-center grow">
|
||||
<span class="truncate">
|
||||
{{ customer$ | async | address }}
|
||||
</span>
|
||||
<a
|
||||
*ngIf="editRoute$ | async; let editRoute"
|
||||
[routerLink]="editRoute.path"
|
||||
[queryParams]="editRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
class="text-brand font-bold"
|
||||
type="button"
|
||||
>
|
||||
Bearbeiten
|
||||
</a>
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="px-4 py-3 font-bold border-t-2 border-solid border-surface-2 cursor-pointer flex flex-row items-center justify-start"
|
||||
*ngFor="let assignedPayer of assignedPayers$ | async"
|
||||
>
|
||||
<input
|
||||
name="assigned-payer"
|
||||
type="radio"
|
||||
[value]="assignedPayer"
|
||||
[checked]="(selectedPayer$ | async)?.payer.id === assignedPayer.payer.id"
|
||||
(change)="selectPayer(assignedPayer)"
|
||||
/>
|
||||
<div class="flex flex-row justify-between items-center grow">
|
||||
<span class="ml-2">
|
||||
{{ assignedPayer.payer.data | address }}
|
||||
</span>
|
||||
<ng-container *ngIf="canEditAddress$ | async">
|
||||
<a
|
||||
*ngIf="editRoute(assignedPayer.payer.id); let editRoute"
|
||||
class="text-brand font-bold"
|
||||
[routerLink]="editRoute.path"
|
||||
[queryParams]="editRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
Bearbeiten
|
||||
</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
@@ -0,0 +1,200 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, Host } from '@angular/core';
|
||||
import { CustomerSearchStore } from '../../store';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { map, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||
import { Observable, Subject, combineLatest } from 'rxjs';
|
||||
import { AssignedPayerDTO, CustomerDTO, ListResponseArgsOfAssignedPayerDTO } from '@swagger/crm';
|
||||
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
|
||||
import { CustomerPipesModule } from '@shared/pipes/customer';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { CustomerSearchNavigation } from '../../../navigations';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { CustomerDetailsViewMainComponent } from '../details-main-view.component';
|
||||
import { PayerDTO } from '@swagger/checkout';
|
||||
|
||||
interface DetailsMainViewBillingAddressesComponentState {
|
||||
assignedPayers: AssignedPayerDTO[];
|
||||
selectedPayer: AssignedPayerDTO;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page-details-main-view-billing-addresses',
|
||||
templateUrl: 'details-main-view-billing-addresses.component.html',
|
||||
styleUrls: ['details-main-view-billing-addresses.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'page-details-main-view-billing-addresses' },
|
||||
standalone: true,
|
||||
imports: [NgIf, NgFor, AsyncPipe, CustomerPipesModule, RouterLink],
|
||||
})
|
||||
export class DetailsMainViewBillingAddressesComponent extends ComponentStore<DetailsMainViewBillingAddressesComponentState>
|
||||
implements OnInit, OnDestroy {
|
||||
assignedPayers$ = this.select((state) => state.assignedPayers);
|
||||
|
||||
selectedPayer$ = this.select((state) => state.selectedPayer);
|
||||
|
||||
isNotBusinessKonto$ = this._store.isBusinessKonto$.pipe(map((isBusinessKonto) => !isBusinessKonto));
|
||||
|
||||
showCustomerAddress$ = combineLatest([this._store.isBusinessKonto$, this._store.isMitarbeiter$, this._store.isKundenkarte$]).pipe(
|
||||
map(([isBusinessKonto, isMitarbeiter, isKundenkarte]) => isBusinessKonto || isMitarbeiter || isKundenkarte)
|
||||
);
|
||||
|
||||
get showCustomerAddress() {
|
||||
return this._store.isBusinessKonto || this._store.isMitarbeiter;
|
||||
}
|
||||
|
||||
canAddNewAddress$ = combineLatest([
|
||||
this._store.isOnlinekonto$,
|
||||
this._store.isOnlineKontoMitKundenkarte$,
|
||||
this._store.isKundenkarte$,
|
||||
this._store.isMitarbeiter$,
|
||||
]).pipe(
|
||||
map(
|
||||
([isOnlinekonto, isOnlineKontoMitKundenkarte, isKundenkarte, isMitarbeiter]) =>
|
||||
isOnlinekonto || isOnlineKontoMitKundenkarte || isKundenkarte || isMitarbeiter
|
||||
)
|
||||
);
|
||||
|
||||
canEditAddress$ = combineLatest([this._store.isKundenkarte$]).pipe(map(([isKundenkarte]) => isKundenkarte));
|
||||
|
||||
customer$ = this._store.customer$;
|
||||
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
editRoute$ = combineLatest([this._store.processId$, this._store.customerId$, this._store.isBusinessKonto$]).pipe(
|
||||
map(([processId, customerId, isB2b]) => this._navigation.editRoute({ processId, customerId, isB2b }))
|
||||
);
|
||||
|
||||
addBillingAddressRoute$ = combineLatest([this.canAddNewAddress$, this._store.processId$, this._store.customerId$]).pipe(
|
||||
map(([canAddNewAddress, processId, customerId]) =>
|
||||
canAddNewAddress ? this._navigation.addBillingAddressRoute({ processId, customerId }) : undefined
|
||||
)
|
||||
);
|
||||
|
||||
constructor(
|
||||
@Host() private _host: CustomerDetailsViewMainComponent,
|
||||
private _store: CustomerSearchStore,
|
||||
private _customerService: CrmCustomerService,
|
||||
private _modal: UiModalService,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
) {
|
||||
super({
|
||||
assignedPayers: [],
|
||||
selectedPayer: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
editRoute(payerId: number) {
|
||||
return this._navigation.editBillingAddressRoute({ customerId: this._store.customerId, payerId, processId: this._store.processId });
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._store.customerId$.pipe(takeUntil(this._onDestroy$)).subscribe((customerId) => {
|
||||
this.resetStore();
|
||||
if (customerId) {
|
||||
this.loadAssignedPayers(customerId);
|
||||
}
|
||||
});
|
||||
|
||||
combineLatest([this.selectedPayer$, this._store.customer$])
|
||||
.pipe(takeUntil(this._onDestroy$))
|
||||
.subscribe(([selectedPayer, customer]) => {
|
||||
if (selectedPayer) {
|
||||
this._host.setPayer(this._createPayerFromCrmPayerDTO(selectedPayer));
|
||||
} else if (this.showCustomerAddress) {
|
||||
this._host.setPayer(this._createPayerFormCustomer(customer));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_createPayerFromCrmPayerDTO(assignedPayer: AssignedPayerDTO): PayerDTO {
|
||||
const payer = assignedPayer.payer.data;
|
||||
return {
|
||||
reference: { id: payer.id },
|
||||
payerType: payer.payerType as any,
|
||||
payerNumber: payer.payerNumber,
|
||||
payerStatus: payer.payerStatus,
|
||||
gender: payer.gender,
|
||||
title: payer.title,
|
||||
firstName: payer.firstName,
|
||||
lastName: payer.lastName,
|
||||
communicationDetails: payer.communicationDetails ? { ...payer.communicationDetails } : undefined,
|
||||
organisation: payer.organisation ? { ...payer.organisation } : undefined,
|
||||
address: payer.address ? { ...payer.address } : undefined,
|
||||
source: payer.id,
|
||||
};
|
||||
}
|
||||
|
||||
_createPayerFormCustomer(customer: CustomerDTO): PayerDTO {
|
||||
return {
|
||||
reference: { id: customer.id },
|
||||
payerType: customer.customerType as any,
|
||||
payerNumber: customer.customerNumber,
|
||||
payerStatus: 0,
|
||||
gender: customer.gender,
|
||||
title: customer.title,
|
||||
firstName: customer.firstName,
|
||||
lastName: customer.lastName,
|
||||
communicationDetails: customer.communicationDetails ? { ...customer.communicationDetails } : undefined,
|
||||
organisation: customer.organisation ? { ...customer.organisation } : undefined,
|
||||
address: customer.address ? { ...customer.address } : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
loadAssignedPayers = this.effect((customerId$: Observable<number>) =>
|
||||
customerId$.pipe(
|
||||
switchMap((customerId) =>
|
||||
this._customerService
|
||||
.getAssignedPayers({ customerId })
|
||||
.pipe(tapResponse(this.handleLoadAssignedPayersResponse, this.handleLoadAssignedPayersError))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
handleLoadAssignedPayersResponse = (response: ListResponseArgsOfAssignedPayerDTO) => {
|
||||
const selectedPayer = response.result.reduce<AssignedPayerDTO>((selected, payer) => {
|
||||
if (!selected) {
|
||||
return payer;
|
||||
}
|
||||
|
||||
if (new Date(selected.isDefault).getTime() < new Date(payer.isDefault).getTime()) {
|
||||
return payer;
|
||||
}
|
||||
|
||||
return selected;
|
||||
}, undefined);
|
||||
|
||||
this.patchState({
|
||||
assignedPayers: response.result,
|
||||
selectedPayer,
|
||||
});
|
||||
};
|
||||
|
||||
handleLoadAssignedPayersError = (err: any) => {
|
||||
this._modal.error('Laden der Rechnungsadressen fehlgeschlagen', err);
|
||||
};
|
||||
|
||||
resetStore() {
|
||||
this.patchState({
|
||||
assignedPayers: [],
|
||||
selectedPayer: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
selectPayer(payer: AssignedPayerDTO) {
|
||||
this.patchState({
|
||||
selectedPayer: payer,
|
||||
});
|
||||
}
|
||||
|
||||
selectCustomerAddress() {
|
||||
this.patchState({
|
||||
selectedPayer: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<div class="grid grid-flow-col items-center justify-between px-4 py-2 border-t-2 border-solid border-surface-2">
|
||||
<h3 class="font-bold text-xl">Lieferadresse</h3>
|
||||
<a
|
||||
*ngIf="addShippingAddressRoute$ | async; let addRoute"
|
||||
type="button"
|
||||
class="text-brand font-bold"
|
||||
[routerLink]="addRoute.path"
|
||||
[queryParams]="addRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
Hinzufügen
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-flow-row">
|
||||
<label
|
||||
class="px-4 py-3 font-bold border-t-2 border-solid border-surface-2 cursor-pointer flex flex-row justify-start items-center"
|
||||
*ngIf="showCustomerAddress$ | async"
|
||||
>
|
||||
<input name="shipping-address" type="radio" [checked]="!(selectedShippingAddress$ | async)" (change)="selectCustomerAddress()" />
|
||||
<div class="ml-2 flex flex-row justify-between items-center grow">
|
||||
<span class="truncate">
|
||||
{{ customer$ | async | address }}
|
||||
</span>
|
||||
<a
|
||||
*ngIf="editRoute$ | async; let editRoute"
|
||||
[routerLink]="editRoute.path"
|
||||
[queryParams]="editRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
class="text-brand font-bold"
|
||||
type="button"
|
||||
>
|
||||
Bearbeiten
|
||||
</a>
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="px-4 py-3 font-bold border-t-2 border-solid border-surface-2 cursor-pointer flex flex-row justify-start items-center"
|
||||
*ngFor="let shippingAddress of shippingAddresses$ | async"
|
||||
>
|
||||
<input
|
||||
name="shipping-address"
|
||||
type="radio"
|
||||
[value]="shippingAddress"
|
||||
[checked]="(selectedShippingAddress$ | async)?.id === shippingAddress.id"
|
||||
(change)="selectShippingAddress(shippingAddress)"
|
||||
/>
|
||||
<div class="ml-2 flex flex-row justify-between items-center grow">
|
||||
<span class="ml-2">
|
||||
{{ shippingAddress | address }}
|
||||
</span>
|
||||
<a *ngIf="canEditAddress$ | async" class="text-brand font-bold" type="button">
|
||||
Bearbeiten
|
||||
</a>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
@@ -0,0 +1,193 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, Host } from '@angular/core';
|
||||
import { CustomerSearchStore } from '../../store';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { Observable, Subject, combineLatest } from 'rxjs';
|
||||
import { CustomerDTO, ListResponseArgsOfAssignedPayerDTO, ShippingAddressDTO } from '@swagger/crm';
|
||||
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
|
||||
import { CustomerPipesModule } from '@shared/pipes/customer';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { CustomerSearchNavigation } from '../../../navigations';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { CustomerDetailsViewMainComponent } from '../details-main-view.component';
|
||||
|
||||
interface DetailsMainViewDeliveryAddressesComponentState {
|
||||
shippingAddresses: ShippingAddressDTO[];
|
||||
selectedShippingAddress: ShippingAddressDTO;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page-details-main-view-delivery-addresses',
|
||||
templateUrl: 'details-main-view-delivery-addresses.component.html',
|
||||
styleUrls: ['details-main-view-delivery-addresses.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'page-details-main-view-delivery-addresses' },
|
||||
standalone: true,
|
||||
imports: [NgIf, NgFor, AsyncPipe, CustomerPipesModule, RouterLink],
|
||||
})
|
||||
export class DetailsMainViewDeliveryAddressesComponent extends ComponentStore<DetailsMainViewDeliveryAddressesComponentState>
|
||||
implements OnInit, OnDestroy {
|
||||
shippingAddresses$ = this.select((state) => state.shippingAddresses);
|
||||
|
||||
selectedShippingAddress$ = this.select((state) => state.selectedShippingAddress);
|
||||
|
||||
get selectedShippingAddress() {
|
||||
return this.get((s) => s.selectedShippingAddress);
|
||||
}
|
||||
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
showCustomerAddress$ = combineLatest([this._store.isBusinessKonto$, this._store.isMitarbeiter$, this._store.isKundenkarte$]).pipe(
|
||||
map(([isBusinessKonto, isMitarbeiter, isKundenkarte]) => isBusinessKonto || isMitarbeiter || isKundenkarte)
|
||||
);
|
||||
|
||||
get showCustomerAddress() {
|
||||
return this._store.isBusinessKonto || this._store.isMitarbeiter;
|
||||
}
|
||||
|
||||
customer$ = this._store.customer$;
|
||||
|
||||
canAddNewAddress$ = combineLatest([
|
||||
this._store.isOnlinekonto$,
|
||||
this._store.isOnlineKontoMitKundenkarte$,
|
||||
this._store.isKundenkarte$,
|
||||
this._store.isBusinessKonto$,
|
||||
this._store.isMitarbeiter$,
|
||||
]).pipe(
|
||||
map(
|
||||
([isOnlinekonto, isOnlineKontoMitKundenkarte, isKundenkarte, isBusinessKonto, isMitarbeiter]) =>
|
||||
isOnlinekonto || isOnlineKontoMitKundenkarte || isKundenkarte || isBusinessKonto || isMitarbeiter
|
||||
)
|
||||
);
|
||||
|
||||
editRoute$ = combineLatest([this._store.processId$, this._store.customerId$, this._store.isBusinessKonto$]).pipe(
|
||||
map(([processId, customerId, isB2b]) => this._navigation.editRoute({ processId, customerId, isB2b }))
|
||||
);
|
||||
|
||||
addShippingAddressRoute$ = combineLatest([this.canAddNewAddress$, this._store.processId$, this._store.customerId$]).pipe(
|
||||
map(([canAddNewAddress, processId, customerId]) =>
|
||||
canAddNewAddress ? this._navigation.addShippingAddressRoute({ processId, customerId }) : undefined
|
||||
)
|
||||
);
|
||||
|
||||
canEditAddress$ = combineLatest([this._store.isKundenkarte$, this._store.isBusinessKonto$, this._store.isMitarbeiter$]).pipe(
|
||||
map(([isKundenkarte, isBusinessKonto, isMitarbeiter]) => isKundenkarte || isBusinessKonto || isMitarbeiter)
|
||||
);
|
||||
|
||||
constructor(
|
||||
@Host() private _host: CustomerDetailsViewMainComponent,
|
||||
private _store: CustomerSearchStore,
|
||||
private _customerService: CrmCustomerService,
|
||||
private _modal: UiModalService,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
) {
|
||||
super({
|
||||
shippingAddresses: [],
|
||||
selectedShippingAddress: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._store.customerId$.pipe(takeUntil(this._onDestroy$)).subscribe((customerId) => {
|
||||
this.resetStore();
|
||||
if (customerId) {
|
||||
this.loadShippingAddresses(customerId);
|
||||
}
|
||||
});
|
||||
|
||||
combineLatest([this.selectedShippingAddress$, this._store.customer$])
|
||||
.pipe(takeUntil(this._onDestroy$))
|
||||
.subscribe(([selectedShippingAddress, customer]) => {
|
||||
if (selectedShippingAddress) {
|
||||
this._host.setShippingAddress(this._createShippingAddressFromShippingAddress(selectedShippingAddress));
|
||||
} else if (this.showCustomerAddress) {
|
||||
this._host.setShippingAddress(this._createShippingAddressFromCustomer(customer));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
_createShippingAddressFromCustomer(customer: CustomerDTO) {
|
||||
return {
|
||||
reference: { id: customer.id },
|
||||
gender: customer?.gender,
|
||||
title: customer?.title,
|
||||
firstName: customer?.firstName,
|
||||
lastName: customer?.lastName,
|
||||
communicationDetails: customer?.communicationDetails ? { ...customer?.communicationDetails } : undefined,
|
||||
organisation: customer?.organisation ? { ...customer?.organisation } : undefined,
|
||||
address: customer?.address ? { ...customer?.address } : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
_createShippingAddressFromShippingAddress(address: ShippingAddressDTO) {
|
||||
return {
|
||||
reference: { id: address.id },
|
||||
gender: address.gender,
|
||||
title: address.title,
|
||||
firstName: address.firstName,
|
||||
lastName: address.lastName,
|
||||
communicationDetails: address.communicationDetails ? { ...address.communicationDetails } : undefined,
|
||||
organisation: address.organisation ? { ...address.organisation } : undefined,
|
||||
address: address.address ? { ...address.address } : undefined,
|
||||
source: address.id,
|
||||
};
|
||||
}
|
||||
|
||||
loadShippingAddresses = this.effect((customerId$: Observable<number>) =>
|
||||
customerId$.pipe(
|
||||
switchMap((customerId) =>
|
||||
this._customerService
|
||||
.getShippingAddresses({ customerId })
|
||||
.pipe(tapResponse(this.handleLoadShippingAddressesResponse, this.handleLoadAssignedPayersError))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
handleLoadShippingAddressesResponse = (response: ListResponseArgsOfAssignedPayerDTO) => {
|
||||
const selectedShippingAddress = response.result.reduce<ShippingAddressDTO>((selected, shipping) => {
|
||||
if (!this.showCustomerAddress && !selected) {
|
||||
return shipping;
|
||||
}
|
||||
|
||||
if (selected && shipping && new Date(selected.isDefault).getTime() < new Date(shipping.isDefault).getTime()) {
|
||||
return shipping;
|
||||
}
|
||||
|
||||
return selected;
|
||||
}, undefined);
|
||||
|
||||
this.patchState({
|
||||
shippingAddresses: response.result,
|
||||
selectedShippingAddress,
|
||||
});
|
||||
};
|
||||
|
||||
handleLoadAssignedPayersError = (err: any) => {
|
||||
this._modal.error('Laden der Lieferadressen fehlgeschlagen', err);
|
||||
};
|
||||
|
||||
resetStore() {
|
||||
this.patchState({
|
||||
shippingAddresses: [],
|
||||
selectedShippingAddress: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
selectShippingAddress(shippingAddress: ShippingAddressDTO) {
|
||||
this.patchState({
|
||||
selectedShippingAddress: shippingAddress,
|
||||
});
|
||||
}
|
||||
|
||||
selectCustomerAddress() {
|
||||
this.patchState({
|
||||
selectedShippingAddress: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
:host {
|
||||
@apply block bg-surface text-surface-content rounded-[0.313rem] mb-3;
|
||||
@apply block bg-surface text-surface-content rounded-[0.313rem] mb-3 relative;
|
||||
}
|
||||
|
||||
.data-label {
|
||||
@apply w-[10.75rem];
|
||||
@apply w-52;
|
||||
}
|
||||
|
||||
.data-value {
|
||||
|
||||
@@ -1,98 +1,175 @@
|
||||
<ng-container *ngIf="fetching$ | async; else customerTemplate"></ng-container>
|
||||
|
||||
<ng-template #customerTemplate>
|
||||
<div class="customer-details-header grid grid-flow-row pt-1 px-1 pb-6">
|
||||
<div class="customer-details-header-actions flex flex-row justify-end pt-1 px-1">
|
||||
<div class="max-h-[calc(100vh-15.375rem)] overflow-scroll">
|
||||
<div class="customer-details-header grid grid-flow-row pt-1 px-1 pb-6">
|
||||
<div class="customer-details-header-actions flex flex-row justify-end pt-1 px-1">
|
||||
<a
|
||||
*ngIf="ordersRoute$ | async; let ordersRoute"
|
||||
class="btn btn-label font-bold text-brand"
|
||||
[routerLink]="ordersRoute.path"
|
||||
[queryParams]="ordersRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>Bestellungen</a
|
||||
>
|
||||
<a
|
||||
*ngIf="kundenkarteRoute$ | async; let kundenkarteRoute"
|
||||
class="btn btn-label font-bold text-brand"
|
||||
[routerLink]="kundenkarteRoute.path"
|
||||
[queryParams]="kundenkarteRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>Kundenkarte</a
|
||||
>
|
||||
<a
|
||||
*ngIf="historyRoute$ | async; let historyRoute"
|
||||
class="btn btn-label font-bold text-brand"
|
||||
[routerLink]="historyRoute.path"
|
||||
[queryParams]="historyRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>Historie</a
|
||||
>
|
||||
</div>
|
||||
<div class="customer-details-header-body text-center -mt-3">
|
||||
<h1 class="text-[1.625rem] font-bold">
|
||||
{{ (isBusinessKonto$ | async) ? 'Firmendetails' : 'Kundendetails' }}
|
||||
</h1>
|
||||
<p>Sind Ihre Kundendaten korrekt?</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-type flex flex-row justify-between items-center bg-surface-2 text-surface-2-content">
|
||||
<div class="pl-4 font-bold">{{ customerType$ | async }}</div>
|
||||
<a
|
||||
*ngIf="historyRoute$ | async; let historyRoute"
|
||||
*ngIf="editRoute$ | async; let editRoute"
|
||||
[routerLink]="editRoute.path"
|
||||
[queryParams]="editRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
class="btn btn-label font-bold text-brand"
|
||||
[routerLink]="historyRoute.path"
|
||||
[queryParams]="historyRoute.queryParams"
|
||||
>Historie</a
|
||||
type="button"
|
||||
>Bearbeiten</a
|
||||
>
|
||||
</div>
|
||||
<div class="customer-details-header-body text-center -mt-3">
|
||||
<h1 class="text-[1.625rem] font-bold">Kundendetails</h1>
|
||||
<p>Sind Ihre Kundendaten korrekt?</p>
|
||||
|
||||
<div class="customer-details-customer-main-data px-5 py-3 grid grid-flow-row gap-3">
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Erstellungsdatum</div>
|
||||
<div class="data-value">{{ created$ | async | date: 'dd.MM.yyyy' }} | {{ created$ | async | date: 'hh:mm' }} Uhr</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Kundennummer</div>
|
||||
<div class="data-value">{{ customerNumber$ | async }}</div>
|
||||
</div>
|
||||
<div class="flex flex-row" *ngIf="customerNumberDig$ | async; let customerNumberDig">
|
||||
<div class="data-label">Kundennummer-DIG</div>
|
||||
<div class="data-value">{{ customerNumberDig }}</div>
|
||||
</div>
|
||||
<div class="flex flex-row" *ngIf="customerNumberBeeline$ | async; let customerNumberBeeline">
|
||||
<div class="data-label">Kundennummer-BEELINE</div>
|
||||
<div class="data-value">{{ customerNumberBeeline }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-type flex flex-row justify-between items-center bg-surface-2 text-surface-2-content">
|
||||
<div class="pl-4 font-bold">{{ customerType$ | async }}</div>
|
||||
<button class="btn btn-label font-bold text-brand" type="button">Bearbeiten</button>
|
||||
<ng-container *ngIf="isBusinessKonto$ | async">
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Firmenname</div>
|
||||
<div class="data-value">{{ organisationName$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Abteilung</div>
|
||||
<div class="data-value">{{ department$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">USt-ID</div>
|
||||
<div class="data-value">{{ vatId$ | async }}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Anrede</div>
|
||||
<div class="data-value">{{ gender$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Titel</div>
|
||||
<div class="data-value">{{ title$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Nachname</div>
|
||||
<div class="data-value">{{ lastName$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Vorname</div>
|
||||
<div class="data-value">{{ firstName$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">E-Mail</div>
|
||||
<div class="data-value">{{ email$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Straße</div>
|
||||
<div class="data-value">{{ street$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Hausnr.</div>
|
||||
<div class="data-value">{{ streetNumber$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">PLZ</div>
|
||||
<div class="data-value">{{ zipCode$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Ort</div>
|
||||
<div class="data-value">{{ city$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Adresszusatz</div>
|
||||
<div class="data-value">{{ info$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Land</div>
|
||||
<div class="data-value">{{ country$ | async | country }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Festnetznr.</div>
|
||||
<div class="data-value">{{ landline$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Mobilnr.</div>
|
||||
<div class="data-value">{{ mobile$ | async }}</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!(isBusinessKonto$ | async)">
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Abteilung</div>
|
||||
<div class="data-value">{{ department$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">USt-ID</div>
|
||||
<div class="data-value">{{ vatId$ | async }}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<page-details-main-view-billing-addresses></page-details-main-view-billing-addresses>
|
||||
<page-details-main-view-delivery-addresses></page-details-main-view-delivery-addresses>
|
||||
</div>
|
||||
|
||||
<div class="customer-details-customer-main-data px-5 py-3 grid grid-flow-row gap-3">
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Erstellungsdatum</div>
|
||||
<div class="data-value">{{ created$ | async | date: 'dd.MM.yyyy' }} | {{ created$ | async | date: 'hh:mm' }} Uhr</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Kundennummer</div>
|
||||
<div class="data-value">{{ customerNumber$ | async }}</div>
|
||||
</div>
|
||||
<div class="flex flex-row" *ngIf="customerNumberDig$ | async; let customerNumberDig">
|
||||
<div class="data-label">Kundennummer-DIG</div>
|
||||
<div class="data-value">{{ customerNumberDig }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Anrede</div>
|
||||
<div class="data-value">{{ gender$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Titel</div>
|
||||
<div class="data-value">{{ title$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Nachname</div>
|
||||
<div class="data-value">{{ lastName$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Vorname</div>
|
||||
<div class="data-value">{{ firstName$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">E-Mail</div>
|
||||
<div class="data-value">{{ email$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Straße</div>
|
||||
<div class="data-value">{{ street$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Hausnr.</div>
|
||||
<div class="data-value">{{ streetNumber$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">PLZ</div>
|
||||
<div class="data-value">{{ zipCode$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Ort</div>
|
||||
<div class="data-value">{{ city$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Adresszusatz</div>
|
||||
<div class="data-value">{{ info$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Land</div>
|
||||
<div class="data-value">{{ country$ | async | country }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Festnetznr.</div>
|
||||
<div class="data-value">{{ landline$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Mobilnr.</div>
|
||||
<div class="data-value">{{ mobile$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Abteilung</div>
|
||||
<div class="data-value">{{ department$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">USt-ID</div>
|
||||
<div class="data-value">{{ vatId$ | async }}</div>
|
||||
</div>
|
||||
<button
|
||||
*ngIf="shoppingCartHasNoItems$ | async"
|
||||
type="button"
|
||||
(click)="continue()"
|
||||
class="text-white text-lg bg-brand rounded-full px-5 py-3 absolute top-[calc(100vh-19.375rem)] left-1/2 -translate-x-1/2 font-bold disabled:bg-inactive-branch"
|
||||
[disabled]="showLoader$ | async"
|
||||
>
|
||||
<shared-loader [loading]="showLoader$ | async" spinnerSize="32">
|
||||
Weiter zur Artielsuche
|
||||
</shared-loader>
|
||||
</button>
|
||||
|
||||
<button
|
||||
*ngIf="shoppingCartHasItems$ | async"
|
||||
type="button"
|
||||
class="text-white text-lg bg-brand rounded-full px-5 py-3 absolute top-[calc(100vh-19.375rem)] left-1/2 -translate-x-1/2 font-bold disabled:bg-inactive-branch"
|
||||
[disabled]="showLoader$ | async"
|
||||
>
|
||||
<shared-loader [loading]="showLoader$ | async" spinnerSize="32">
|
||||
Weiter zum Warenkorb
|
||||
</shared-loader>
|
||||
</button>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,23 +1,123 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
import { first, map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { CustomerSearchNavigation } from '../../navigations';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { ShippingAddressDTO, NotificationChannel, ShoppingCartDTO, PayerDTO, BuyerDTO } from '@swagger/checkout';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { CantAddCustomerToCartData, CantAddCustomerToCartModalComponent, CantSelectGuestModalComponent } from '../../modals';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
const GENDER_MAP = {
|
||||
2: 'Herr',
|
||||
4: 'Frau',
|
||||
};
|
||||
|
||||
export interface CustomerDetailsViewMainState {
|
||||
isBusy: boolean;
|
||||
shoppingCart: ShoppingCartDTO;
|
||||
shippingAddress: ShippingAddressDTO;
|
||||
payer: PayerDTO;
|
||||
}
|
||||
|
||||
const log = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
||||
// log the function name the input and return value
|
||||
|
||||
// get the function that is being decorated
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
// replace the function being decorated with a new function
|
||||
descriptor.value = function (...args: any[]) {
|
||||
const now = Date.now();
|
||||
let result;
|
||||
console.group(`Method: ${propertyKey}`);
|
||||
|
||||
// log the input
|
||||
console.log(`args: ${JSON.stringify(args)}`);
|
||||
|
||||
try {
|
||||
// call the original function
|
||||
result = originalMethod.apply(this, args);
|
||||
|
||||
// log the output
|
||||
console.log(`returned: ${JSON.stringify(result)}`);
|
||||
|
||||
// return the result of calling the original method
|
||||
} catch (error) {
|
||||
console.error(`error: ${error}`);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log(`execution time: ${Date.now() - now}ms`);
|
||||
console.groupEnd();
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// return the descriptor
|
||||
return descriptor;
|
||||
};
|
||||
|
||||
const logAsync = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
||||
// log the function name the input and return value
|
||||
|
||||
// get the function that is being decorated
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
// replace the function being decorated with a new function
|
||||
descriptor.value = async function (...args: any[]) {
|
||||
const now = Date.now();
|
||||
let result;
|
||||
console.group(`Method: ${propertyKey}`);
|
||||
|
||||
// log the input
|
||||
console.log(`args: ${JSON.stringify(args)}`);
|
||||
|
||||
try {
|
||||
// call the original function
|
||||
result = await originalMethod.apply(this, args);
|
||||
|
||||
// log the output
|
||||
console.log(`returned: ${JSON.stringify(result)}`);
|
||||
|
||||
// return the result of calling the original method
|
||||
} catch (error) {
|
||||
console.error(`error: ${error}`);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log(`execution time: ${Date.now() - now}ms`);
|
||||
console.groupEnd();
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// return the descriptor
|
||||
return descriptor;
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-details-main-view',
|
||||
templateUrl: 'details-main-view.component.html',
|
||||
styleUrls: ['details-main-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerDetailsViewMainComponent {
|
||||
export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDetailsViewMainState> implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject();
|
||||
|
||||
fetching$ = this._store.fetchingCustomer$;
|
||||
|
||||
isBusy$ = this.select((s) => s.isBusy);
|
||||
|
||||
showLoader$ = combineLatest([this.fetching$, this.isBusy$]).pipe(map(([fetching, isBusy]) => fetching || isBusy));
|
||||
|
||||
processId$ = this._store.processId$;
|
||||
|
||||
customerId$ = this._store.customerId$;
|
||||
@@ -26,6 +126,26 @@ export class CustomerDetailsViewMainComponent {
|
||||
map(([processId, customerId]) => this._navigation.historyRoute({ processId, customerId }))
|
||||
);
|
||||
|
||||
ordersRoute$ = combineLatest([this.processId$, this.customerId$]).pipe(
|
||||
map(([processId, customerId]) => this._navigation.ordersRoute({ processId, customerId }))
|
||||
);
|
||||
|
||||
isB2b$ = this._store.select((s) => s.customer?.features?.['b2b']);
|
||||
|
||||
editRoute$ = combineLatest([this.processId$, this.customerId$, this.isB2b$]).pipe(
|
||||
map(([processId, customerId, isB2b]) => this._navigation.editRoute({ processId, customerId, isB2b }))
|
||||
);
|
||||
|
||||
hasKundenkarte$ = combineLatest([this._store.isKundenkarte$, this._store.isOnlineKontoMitKundenkarte$]).pipe(
|
||||
map(([isKundenkarte, isOnlineKontoMitKundenkarte]) => isKundenkarte || isOnlineKontoMitKundenkarte)
|
||||
);
|
||||
|
||||
kundenkarteRoute$ = combineLatest([this.hasKundenkarte$, this.processId$, this.customerId$]).pipe(
|
||||
map(([hasKundenkarte, processId, customerId]) =>
|
||||
hasKundenkarte ? this._navigation.kundenkarteRoute({ processId, customerId }) : undefined
|
||||
)
|
||||
);
|
||||
|
||||
customerType$ = this._store.select((s) => s.customer?.features?.find((f) => f.enabled)?.description);
|
||||
|
||||
created$ = this._store.select((s) => s.customer?.created);
|
||||
@@ -34,6 +154,8 @@ export class CustomerDetailsViewMainComponent {
|
||||
|
||||
customerNumberDig$ = this._store.select((s) => s.customer?.linkedRecords?.find((r) => r.repository === 'dig')?.number);
|
||||
|
||||
customerNumberBeeline$ = this._store.select((s) => s.customer?.linkedRecords?.find((r) => r.repository === 'beeline')?.number);
|
||||
|
||||
gender$ = this._store.select((s) => GENDER_MAP[s.customer?.gender]);
|
||||
|
||||
title$ = this._store.select((s) => s.customer?.title);
|
||||
@@ -60,9 +182,329 @@ export class CustomerDetailsViewMainComponent {
|
||||
|
||||
mobile$ = this._store.select((s) => s.customer?.communicationDetails?.mobile);
|
||||
|
||||
organisationName$ = this._store.select((s) => s.customer?.organisation?.name);
|
||||
|
||||
department$ = this._store.select((s) => s.customer?.organisation?.department);
|
||||
|
||||
vatId$ = this._store.select((s) => s.customer?.organisation?.vatId);
|
||||
|
||||
constructor(private _store: CustomerSearchStore, private _navigation: CustomerSearchNavigation) {}
|
||||
isBusinessKonto$ = this._store.isBusinessKonto$;
|
||||
|
||||
shoppingCartHasItems$ = this.select((s) => s.shoppingCart?.items?.length > 0);
|
||||
|
||||
shoppingCartHasNoItems$ = this.shoppingCartHasItems$.pipe(map((hasItems) => !hasItems));
|
||||
|
||||
get isBusy() {
|
||||
return this.get((s) => s.isBusy);
|
||||
}
|
||||
|
||||
get shoppingCart() {
|
||||
return this.get((s) => s.shoppingCart);
|
||||
}
|
||||
|
||||
get shoppingCartHasItems() {
|
||||
return this.shoppingCart?.items?.length > 0;
|
||||
}
|
||||
|
||||
get processId() {
|
||||
return this._store.processId;
|
||||
}
|
||||
|
||||
get customer() {
|
||||
return this._store.customer;
|
||||
}
|
||||
|
||||
get customerFeatures() {
|
||||
const customerFeatures = this.customer.features;
|
||||
const features: { [key: string]: string } = {};
|
||||
|
||||
for (const feature of customerFeatures) {
|
||||
features[feature.key] = feature.key;
|
||||
}
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
get shippingAddress() {
|
||||
return this.get((s) => s.shippingAddress);
|
||||
}
|
||||
|
||||
get buyer() {
|
||||
const customer = this.customer;
|
||||
return {
|
||||
source: customer.id,
|
||||
reference: { id: customer.id },
|
||||
buyerType: customer.customerType as any,
|
||||
buyerNumber: customer.customerNumber,
|
||||
gender: customer.gender,
|
||||
title: customer.title,
|
||||
firstName: customer.firstName,
|
||||
lastName: customer.lastName,
|
||||
dateOfBirth: customer.dateOfBirth,
|
||||
communicationDetails: customer.communicationDetails ? { ...customer.communicationDetails } : undefined,
|
||||
organisation: customer.organisation ? { ...customer.organisation } : undefined,
|
||||
address: customer.address ? { ...customer.address } : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
get payer() {
|
||||
return this.get((s) => s.payer);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _store: CustomerSearchStore,
|
||||
private _navigation: CustomerSearchNavigation,
|
||||
private _checkoutService: DomainCheckoutService,
|
||||
private _modalService: UiModalService,
|
||||
private _application: ApplicationService,
|
||||
private _catalogNavigation: ProductCatalogNavigationService,
|
||||
private _checkoutNavigation: CheckoutNavigationService,
|
||||
private _router: Router
|
||||
) {
|
||||
super({ isBusy: true, shoppingCart: undefined, shippingAddress: undefined, payer: undefined });
|
||||
}
|
||||
|
||||
setIsBusy(isBusy: boolean) {
|
||||
this.patchState({ isBusy });
|
||||
}
|
||||
|
||||
@log
|
||||
setShippingAddress(address: ShippingAddressDTO) {
|
||||
this.patchState({ shippingAddress: address });
|
||||
}
|
||||
|
||||
@log
|
||||
setPayer(payer: PayerDTO) {
|
||||
this.patchState({ payer });
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.processId$
|
||||
.pipe(
|
||||
takeUntil(this._onDestroy$),
|
||||
switchMap((pid) =>
|
||||
this._checkoutService.getShoppingCart({
|
||||
processId: pid,
|
||||
latest: true,
|
||||
})
|
||||
)
|
||||
)
|
||||
.subscribe((shoppingCart) => {
|
||||
this.patchState({ shoppingCart });
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
@logAsync
|
||||
async continue() {
|
||||
if (this.isBusy) return;
|
||||
this.setIsBusy(true);
|
||||
|
||||
if (this.shoppingCartHasItems && !(await this._canAddCustomerAsync())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.shippingAddress && !this._canAddShippingAddressAsync()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await this._updateDestinationAsync())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isGuestWithOrder()) {
|
||||
this._modalService.open({
|
||||
content: CantSelectGuestModalComponent,
|
||||
data: this.customer,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._patchProcessName();
|
||||
|
||||
const currentBuyer = await this._getCurrentBuyer();
|
||||
|
||||
this._setCustomer();
|
||||
|
||||
this._setBuyer();
|
||||
|
||||
await this._updateNotifcationChannelsAsync(currentBuyer);
|
||||
|
||||
this._setPayer();
|
||||
|
||||
this._setShippingAddress();
|
||||
|
||||
if (this.shoppingCartHasItems) {
|
||||
// Navigation zum Warenkorb
|
||||
const path = this._checkoutNavigation.getCheckoutReviewPath(this.processId);
|
||||
this._router.navigate(path);
|
||||
} else {
|
||||
// Navigation zur Artikelsuche
|
||||
const path = this._catalogNavigation.getArticleSearchBasePath(this.processId);
|
||||
this._router.navigate(path);
|
||||
}
|
||||
|
||||
try {
|
||||
} catch (error) {
|
||||
this._modalService.error('Warenkorb kann dem Kunden nicht zugewiesen werden', error);
|
||||
} finally {
|
||||
this.setIsBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
@logAsync
|
||||
_getCurrentBuyer() {
|
||||
return this._checkoutService.getBuyer({ processId: this.processId }).pipe(first()).toPromise();
|
||||
}
|
||||
|
||||
@logAsync
|
||||
async _canAddCustomerAsync() {
|
||||
const res = await this._checkoutService
|
||||
.canSetCustomer({
|
||||
processId: this.processId,
|
||||
customerFeatures: this.customerFeatures,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
if (res.ok) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const upgradeableTo = res.create;
|
||||
const data: CantAddCustomerToCartData = {
|
||||
message: res.message,
|
||||
upgradeableTo,
|
||||
customer: this.customer,
|
||||
attributes: this.customer.attributes.map((a) => a.data),
|
||||
required: res.create,
|
||||
};
|
||||
|
||||
await this._modalService
|
||||
.open({
|
||||
content: CantAddCustomerToCartModalComponent,
|
||||
data,
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@logAsync
|
||||
async _canAddShippingAddressAsync() {
|
||||
const res = await this._checkoutService
|
||||
.canAddDestination({
|
||||
processId: this.processId,
|
||||
destinationDTO: {
|
||||
target: 2,
|
||||
shippingAddress: this.shippingAddress,
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
if (typeof res === 'string') {
|
||||
throw new Error(res);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@logAsync
|
||||
async _updateDestinationAsync() {
|
||||
const res = await this._checkoutService
|
||||
.setDestinationForCustomer({
|
||||
processId: this.processId,
|
||||
customerFeatures: this.customerFeatures,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
if (!res.ok) {
|
||||
this._modalService.open({
|
||||
content: CantAddCustomerToCartModalComponent,
|
||||
data: {
|
||||
message: '',
|
||||
customer: this.customer,
|
||||
required: res.create,
|
||||
upgradeableTo: undefined,
|
||||
attributes: this.customer.attributes.map((s) => s.data),
|
||||
} as CantAddCustomerToCartData,
|
||||
});
|
||||
}
|
||||
|
||||
return res.ok;
|
||||
}
|
||||
|
||||
@log
|
||||
_isGuestWithOrder() {
|
||||
const isGuest = this.customer.features.some((f) => f.key === 'guest');
|
||||
if (!isGuest) return false;
|
||||
|
||||
const hasOrder = this.customer.orderCount > 0;
|
||||
return hasOrder;
|
||||
}
|
||||
|
||||
@log
|
||||
_patchProcessName() {
|
||||
let name = `${this.customer.firstName} ${this.customer.lastName}`;
|
||||
|
||||
if (this._store.isBusinessKonto) {
|
||||
name = `${this.customer.organisation?.name}`;
|
||||
}
|
||||
|
||||
this._application.patchProcess(this.processId, {
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
@log
|
||||
_setCustomer() {
|
||||
this._checkoutService.setCustomer({
|
||||
processId: this.processId,
|
||||
customerDto: this.customer,
|
||||
});
|
||||
}
|
||||
|
||||
@log
|
||||
_setBuyer() {
|
||||
this._checkoutService.setBuyer({
|
||||
processId: this.processId,
|
||||
buyer: this.buyer,
|
||||
});
|
||||
}
|
||||
|
||||
@logAsync
|
||||
async _updateNotifcationChannelsAsync(currentBuyer: BuyerDTO | undefined) {
|
||||
if (currentBuyer?.buyerNumber !== this.customer.customerNumber) {
|
||||
const notificationChannels =
|
||||
this.customer.notificationChannels === (3 as NotificationChannel) ? 1 : this.customer.notificationChannels;
|
||||
|
||||
this._checkoutService.setNotificationChannels({
|
||||
processId: this.processId,
|
||||
notificationChannels,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@log
|
||||
_setPayer() {
|
||||
if (this.payer) {
|
||||
this._checkoutService.setPayer({
|
||||
processId: this.processId,
|
||||
payer: this.payer,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@log
|
||||
_setShippingAddress() {
|
||||
if (this.shippingAddress) {
|
||||
this._checkoutService.setShippingAddress({
|
||||
processId: this.processId,
|
||||
shippingAddress: this.shippingAddress,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,19 @@ import { CommonModule } from '@angular/common';
|
||||
import { CustomerDetailsViewMainComponent } from './details-main-view.component';
|
||||
import { CountryPipe } from '@shared/pipes/country';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { DetailsMainViewBillingAddressesComponent } from './details-main-view-billing-addresses/details-main-view-billing-addresses.component';
|
||||
import { DetailsMainViewDeliveryAddressesComponent } from './details-main-view-delivery-addresses/details-main-view-delivery-addresses.component';
|
||||
import { LoaderComponent } from '@shared/components/loader';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, CountryPipe, RouterModule],
|
||||
imports: [
|
||||
LoaderComponent,
|
||||
CommonModule,
|
||||
CountryPipe,
|
||||
RouterModule,
|
||||
DetailsMainViewBillingAddressesComponent,
|
||||
DetailsMainViewDeliveryAddressesComponent,
|
||||
],
|
||||
exports: [CustomerDetailsViewMainComponent],
|
||||
declarations: [CustomerDetailsViewMainComponent],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
:host {
|
||||
@apply block bg-surface text-surface-content rounded px-6 py-8;
|
||||
}
|
||||
|
||||
form {
|
||||
@apply grid grid-cols-2 gap-x-8;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<div class="flex flex-row justify-end -mt-4 -mr-2">
|
||||
<a
|
||||
*ngIf="detailsRoute$ | async; let detailsRoute"
|
||||
[routerLink]="detailsRoute.path"
|
||||
[queryParams]="detailsRoute.urlTree.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
<shared-icon icon="close" [size]="32"></shared-icon>
|
||||
</a>
|
||||
</div>
|
||||
<h1 class="text-2xl text-center font-bold mb-6">Rechnungsadresse bearbeiten</h1>
|
||||
|
||||
<form [formGroup]="formGroup" (ngSubmit)="save()">
|
||||
<shared-form-control label="Anrede">
|
||||
<shared-select formControlName="gender" placeholder="Anrede" tabindex="1" [autofocus]="true">
|
||||
<shared-select-option [value]="2">Herr</shared-select-option>
|
||||
<shared-select-option [value]="4">Frau</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Titel">
|
||||
<shared-select formControlName="title" placeholder="Titel" tabindex="2">
|
||||
<shared-select-option value="Dipl.-Ing.">Dipl.-Ing.</shared-select-option>
|
||||
<shared-select-option value="Dr.">Dr.</shared-select-option>
|
||||
<shared-select-option value="Dr. med.">Dr. med.</shared-select-option>
|
||||
<shared-select-option value="Prof.">Prof.</shared-select-option>
|
||||
<shared-select-option value="Prof. Dr.">Prof. Dr.</shared-select-option>
|
||||
<shared-select-option value="RA">RA</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Nachname">
|
||||
<input class="input-control" placeholder="Nachname" type="text" formControlName="lastName" tabindex="3" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Vorname">
|
||||
<input class="input-control" placeholder="Vorname" type="text" formControlName="firstName" tabindex="4" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Firma" class="col-span-2">
|
||||
<input class="input-control" placeholder="Firma" type="text" formControlName="organisation" tabindex="5" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Straße">
|
||||
<input class="input-control" placeholder="Straße" type="text" formControlName="street" tabindex="6" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Hausnummer">
|
||||
<input class="input-control" placeholder="Hausnummer" type="text" formControlName="streetNumber" tabindex="7" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="PLZ">
|
||||
<input class="input-control" placeholder="PLZ" type="text" formControlName="zipCode" tabindex="8" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Ort">
|
||||
<input class="input-control" placeholder="Ort" type="text" formControlName="city" tabindex="9" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Adresszusatz" class="col-span-2">
|
||||
<input class="input-control" placeholder="Adresszusatz" type="text" formControlName="info" tabindex="10" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control class="col-span-2" label="Land">
|
||||
<shared-select placeholder="Land" formControlName="country" tabindex="11">
|
||||
<shared-select-option *ngFor="let country of countries$ | async" [value]="country.isO3166_A_3">
|
||||
{{ country.name }}
|
||||
</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
<div class="text-center col-span-2">
|
||||
<shared-checkbox>Diese Rechnungsadresse als Standard Adresse festlegen</shared-checkbox>
|
||||
</div>
|
||||
<div class="mt-6 text-center col-span-2">
|
||||
<button
|
||||
[disabled]="formGroup.invalid || formGroup.disabled"
|
||||
type="submit"
|
||||
class="px-5 py-3 font-bold text-lg rounded-full bg-brand text-white"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,153 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CheckboxComponent } from '@shared/components/checkbox';
|
||||
import { FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { SelectModule } from '@shared/components/select';
|
||||
import { FormControlComponent } from '@shared/components/form-control';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { AddressDTO, Gender, PayerDTO } from '@swagger/crm';
|
||||
import { map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { AsyncPipe, NgForOf } from '@angular/common';
|
||||
import { AddressSelectionModalService } from '@shared/modals/address-selection-modal';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { CustomerSearchNavigation } from '../../navigations';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
|
||||
export interface EditBillingAddressMainViewState {
|
||||
payer?: PayerDTO;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page-edit-billing-address-main-view',
|
||||
templateUrl: 'edit-billing-address-main-view.component.html',
|
||||
styleUrls: ['edit-billing-address-main-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'page-edit-billing-address-main-view' },
|
||||
standalone: true,
|
||||
imports: [AsyncPipe, RouterLink, IconComponent, NgForOf, ReactiveFormsModule, SelectModule, FormControlComponent, CheckboxComponent],
|
||||
})
|
||||
export class EditBillingAddressMainViewComponent extends ComponentStore<EditBillingAddressMainViewState> implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
detailsRoute$ = combineLatest([this._store.processId$, this._store.customerId$]).pipe(
|
||||
map(([processId, customerId]) => this._navigation.detailsRoute({ processId, customerId }))
|
||||
);
|
||||
|
||||
formGroup = new FormGroup({
|
||||
gender: new FormControl<Gender>(0, [Validators.required, Validators.min(1)]),
|
||||
title: new FormControl<string>(undefined),
|
||||
firstName: new FormControl<string>(undefined, [Validators.required]),
|
||||
lastName: new FormControl<string>(undefined, [Validators.required]),
|
||||
organisation: new FormControl<string>(undefined),
|
||||
street: new FormControl<string>(undefined, [Validators.required]),
|
||||
streetNumber: new FormControl<string>(undefined, [Validators.required]),
|
||||
zipCode: new FormControl<string>(undefined, [Validators.required]),
|
||||
city: new FormControl<string>(undefined, [Validators.required]),
|
||||
country: new FormControl<string>('DEU', [Validators.required]),
|
||||
info: new FormControl<string>(undefined),
|
||||
isDefault: new FormControl<boolean>(false),
|
||||
});
|
||||
|
||||
countries$ = this._customerService.getCountries().pipe(map((res) => res.result));
|
||||
|
||||
payerId$ = this._activatedRoute.params.pipe(
|
||||
map((params) => params.payerId),
|
||||
switchMap((payerId) => this._customerService.getPayer(payerId).pipe(map((res) => res.result)))
|
||||
);
|
||||
|
||||
get payerId() {
|
||||
return this.get((s) => s.payer?.id);
|
||||
}
|
||||
|
||||
get payer() {
|
||||
return this.get((s) => s.payer);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _customerService: CrmCustomerService,
|
||||
private _addressSelection: AddressSelectionModalService,
|
||||
private _store: CustomerSearchStore,
|
||||
private _navigation: CustomerSearchNavigation,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _modal: UiModalService
|
||||
) {
|
||||
super({ payer: undefined });
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.payerId$.pipe(takeUntil(this._onDestroy$)).subscribe((payer) => {
|
||||
this.patchState({ payer });
|
||||
this.patchFormGroup(payer);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
patchFormGroup(payer: PayerDTO) {
|
||||
this.formGroup.patchValue({
|
||||
gender: payer.gender ?? 0,
|
||||
title: payer.title,
|
||||
lastName: payer.lastName,
|
||||
firstName: payer.firstName,
|
||||
organisation: payer.organisation?.name,
|
||||
street: payer.address.street,
|
||||
streetNumber: payer.address.streetNumber,
|
||||
zipCode: payer.address.zipCode,
|
||||
city: payer.address.city,
|
||||
country: payer.address.country,
|
||||
info: payer.address.info,
|
||||
});
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (this.formGroup.invalid) {
|
||||
this.formGroup.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.formGroup.disable();
|
||||
|
||||
const formData = this.formGroup.value;
|
||||
|
||||
const address: AddressDTO = {
|
||||
street: formData.street,
|
||||
streetNumber: formData.streetNumber,
|
||||
zipCode: formData.zipCode,
|
||||
city: formData.city,
|
||||
country: formData.country,
|
||||
info: formData.info,
|
||||
};
|
||||
|
||||
const addressValidationResult = await this._addressSelection.validateAddress(address);
|
||||
|
||||
if (addressValidationResult === undefined) {
|
||||
this.formGroup.enable();
|
||||
return;
|
||||
}
|
||||
|
||||
const payer: PayerDTO = {
|
||||
...this.payer,
|
||||
gender: formData.gender,
|
||||
title: formData.title,
|
||||
firstName: formData.firstName,
|
||||
lastName: formData.lastName,
|
||||
organisation: formData.organisation ? { name: formData.organisation } : undefined,
|
||||
address: addressValidationResult,
|
||||
};
|
||||
|
||||
const result = await this._customerService.updatePayer(this._store.customerId, this.payerId, payer, formData.isDefault).toPromise();
|
||||
|
||||
this._navigation.navigateToDetails({ processId: this._store.processId, customerId: this._store.customerId });
|
||||
} catch (error) {
|
||||
this.formGroup.enable();
|
||||
this._modal.error('Fehler beim speichern der Rechnungsadresse', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<div class="customer-edit-header-actions flex flex-row justify-end pt-1 px-1">
|
||||
<a
|
||||
*ngIf="detailsRoute$ | async; let route"
|
||||
[routerLink]="route.path"
|
||||
[queryParams]="route.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
class="btn btn-label"
|
||||
>
|
||||
<ui-icon [icon]="'close'"></ui-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="header">
|
||||
<h1>Firmendetails</h1>
|
||||
</div>
|
||||
|
||||
<form *ngIf="control" [formGroup]="control" (ngSubmit)="submit()">
|
||||
<ng-container formGroupName="organisation">
|
||||
<ui-form-control label="Firmenname" variant="inline">
|
||||
<input uiInput type="text" formControlName="name" tabindex="1" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Abteilung" variant="inline">
|
||||
<input uiInput type="text" formControlName="department" tabindex="2" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="USt ID" variant="inline">
|
||||
<input uiInput type="text" formControlName="vatId" tabindex="3" />
|
||||
</ui-form-control>
|
||||
</ng-container>
|
||||
|
||||
<ui-form-control [clearable]="true" label="Anrede" variant="inline">
|
||||
<ui-select formControlName="gender" tabindex="4">
|
||||
<ui-select-option [value]="2" label="Herr"></ui-select-option>
|
||||
<ui-select-option [value]="4" label="Frau"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
<ui-form-control [clearable]="true" label="Titel" variant="inline">
|
||||
<ui-select formControlName="title" tabindex="5">
|
||||
<ui-select-option value="Dipl.-Ing." label="Dipl.-Ing."></ui-select-option>
|
||||
<ui-select-option value="Dr." label="Dr."></ui-select-option>
|
||||
<ui-select-option value="Dr. med." label="Dr. med."></ui-select-option>
|
||||
<ui-select-option value="Prof." label="Prof."></ui-select-option>
|
||||
<ui-select-option value="Prof. Dr." label="Prof. Dr."></ui-select-option>
|
||||
<ui-select-option value="RA" label="RA"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Nachname" variant="inline">
|
||||
<input uiInput type="text" formControlName="lastName" tabindex="6" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Vorname" variant="inline">
|
||||
<input uiInput type="text" formControlName="firstName" tabindex="7" />
|
||||
</ui-form-control>
|
||||
|
||||
<ng-container formGroupName="communicationDetails">
|
||||
<ui-form-control label="E-Mail" variant="inline">
|
||||
<input uiInput type="mail" formControlName="email" tabindex="8" />
|
||||
</ui-form-control>
|
||||
|
||||
<ui-form-control label="Festnetznummer" variant="inline">
|
||||
<input uiInput type="tel" formControlName="phone" tabindex="9" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Mobilnummer" variant="inline">
|
||||
<input uiInput type="tel" formControlName="mobile" tabindex="10" />
|
||||
</ui-form-control>
|
||||
</ng-container>
|
||||
|
||||
<ng-container formGroupName="address">
|
||||
<ui-form-control label="Straße" variant="inline">
|
||||
<input uiInput type="text" formControlName="street" tabindex="11" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Hausnummer" variant="inline">
|
||||
<input uiInput type="text" formControlName="streetNumber" tabindex="12" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="PLZ" variant="inline">
|
||||
<input uiInput type="text" formControlName="zipCode" tabindex="13" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Ort" variant="inline">
|
||||
<input uiInput type="text" formControlName="city" tabindex="14" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Adresszusatz" variant="inline">
|
||||
<input uiInput type="text" formControlName="info" tabindex="15" />
|
||||
</ui-form-control>
|
||||
<ui-form-control [clearable]="true" label="Land" variant="inline">
|
||||
<ui-select formControlName="country" tabindex="16">
|
||||
<ui-select-option
|
||||
*ngFor="let country of countries$ | async"
|
||||
[label]="country.name"
|
||||
[value]="country.isO3166_A_3"
|
||||
></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
</ng-container>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn-cancel" type="button" (click)="cancel()">Abbrechen</button>
|
||||
<button class="btn-save" type="submit" [disabled]="control.invalid || control.disabled" [ngSwitch]="control.enabled">
|
||||
<ng-container *ngSwitchCase="true">
|
||||
Speichern
|
||||
</ng-container>
|
||||
<ui-icon class="spin" icon="loading" size="18px" *ngSwitchCase="false"></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,24 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { CustomerDataEditComponent } from './customer-data-edit.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { UiFormControlModule } from '@ui/form-control';
|
||||
import { UiSelectModule } from '@ui/select';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-data-edit-b2b',
|
||||
templateUrl: 'customer-data-edit-b2b.component.html',
|
||||
styleUrls: ['customer-data-edit.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [CommonModule, UiCommonModule, UiFormControlModule, UiSelectModule, UiIconModule, ReactiveFormsModule, RouterLink],
|
||||
})
|
||||
export class CustomerDataEditB2BComponent extends CustomerDataEditComponent {
|
||||
afterInitForm = (control) => {
|
||||
control.get('lastName').setValidators([]);
|
||||
control.get('firstName').setValidators([]);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<div class="customer-edit-header-actions flex flex-row justify-end pt-1 px-1">
|
||||
<a
|
||||
*ngIf="detailsRoute$ | async; let route"
|
||||
[routerLink]="route.path"
|
||||
[queryParams]="route.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
class="btn btn-label"
|
||||
>
|
||||
<ui-icon [icon]="'close'"></ui-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="header">
|
||||
<h1>Kundendetails</h1>
|
||||
</div>
|
||||
|
||||
<form *ngIf="control" [formGroup]="control" (ngSubmit)="submit()">
|
||||
<ui-form-control [clearable]="true" label="Anrede" variant="inline">
|
||||
<ui-select formControlName="gender" tabindex="1">
|
||||
<ui-select-option [value]="2" label="Herr"></ui-select-option>
|
||||
<ui-select-option [value]="4" label="Frau"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
<ui-form-control [clearable]="true" label="Titel" variant="inline">
|
||||
<ui-select formControlName="title" tabindex="2">
|
||||
<ui-select-option value="Dipl.-Ing." label="Dipl.-Ing."></ui-select-option>
|
||||
<ui-select-option value="Dr." label="Dr."></ui-select-option>
|
||||
<ui-select-option value="Dr. med." label="Dr. med."></ui-select-option>
|
||||
<ui-select-option value="Prof." label="Prof."></ui-select-option>
|
||||
<ui-select-option value="Prof. Dr." label="Prof. Dr."></ui-select-option>
|
||||
<ui-select-option value="RA" label="RA"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Nachname" variant="inline">
|
||||
<input uiInput type="text" formControlName="lastName" tabindex="3" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Vorname" variant="inline">
|
||||
<input uiInput type="text" formControlName="firstName" tabindex="4" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="E-Mail" formGroupName="communicationDetails" variant="inline">
|
||||
<input uiInput type="mail" formControlName="email" tabindex="5" />
|
||||
</ui-form-control>
|
||||
|
||||
<ng-container formGroupName="address">
|
||||
<ui-form-control label="Straße" variant="inline">
|
||||
<input uiInput type="text" formControlName="street" tabindex="6" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Hausnummer" variant="inline">
|
||||
<input uiInput type="text" formControlName="streetNumber" tabindex="7" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="PLZ" variant="inline">
|
||||
<input uiInput type="text" formControlName="zipCode" tabindex="8" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Ort" variant="inline">
|
||||
<input uiInput type="text" formControlName="city" tabindex="9" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Adresszusatz" variant="inline">
|
||||
<input uiInput type="text" formControlName="info" tabindex="10" />
|
||||
</ui-form-control>
|
||||
<ui-form-control [clearable]="true" label="Land" variant="inline">
|
||||
<ui-select formControlName="country" tabindex="11">
|
||||
<ui-select-option
|
||||
*ngFor="let country of countries$ | async"
|
||||
[label]="country.name"
|
||||
[value]="country.isO3166_A_3"
|
||||
></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
</ng-container>
|
||||
|
||||
<ng-container formGroupName="communicationDetails">
|
||||
<ui-form-control label="Festnetznummer" variant="inline">
|
||||
<input uiInput type="tel" formControlName="phone" tabindex="12" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Mobilnummer" variant="inline">
|
||||
<input uiInput type="tel" formControlName="mobile" tabindex="13" />
|
||||
</ui-form-control>
|
||||
</ng-container>
|
||||
<ui-form-control label="Geburtsdatum" variant="inline">
|
||||
<input uiDateInput type="text" formControlName="dateOfBirth" tabindex="14" />
|
||||
</ui-form-control>
|
||||
<ng-container formGroupName="organisation">
|
||||
<ui-form-control label="Firmenname" variant="inline">
|
||||
<input uiInput type="text" formControlName="name" tabindex="15" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Abteilung" variant="inline">
|
||||
<input uiInput type="text" formControlName="department" tabindex="16" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="USt ID" variant="inline">
|
||||
<input uiInput type="text" formControlName="vatId" tabindex="17" />
|
||||
</ui-form-control>
|
||||
</ng-container>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn-cancel" type="button" (click)="cancel()">Abbrechen</button>
|
||||
<button class="btn-save" type="submit" [disabled]="control.invalid || control.disabled" [ngSwitch]="control.enabled">
|
||||
<ng-container *ngSwitchCase="true">
|
||||
Speichern
|
||||
</ng-container>
|
||||
<ui-icon class="spin" icon="loading" size="18px" *ngSwitchCase="false"></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { CustomerDataEditComponent } from './customer-data-edit.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { UiFormControlModule } from '@ui/form-control';
|
||||
import { UiSelectModule } from '@ui/select';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-data-edit-b2c',
|
||||
templateUrl: 'customer-data-edit-b2c.component.html',
|
||||
styleUrls: ['customer-data-edit.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [CommonModule, UiCommonModule, UiFormControlModule, UiSelectModule, UiIconModule, ReactiveFormsModule, RouterLink],
|
||||
})
|
||||
export class CustomerDataEditB2CComponent extends CustomerDataEditComponent {}
|
||||
@@ -0,0 +1,54 @@
|
||||
:host {
|
||||
@apply flex flex-col box-border shadow-card rounded;
|
||||
}
|
||||
|
||||
.customer-edit-header-actions {
|
||||
@apply bg-white;
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply bg-white;
|
||||
h1 {
|
||||
@apply text-center text-h3 my-8 font-bold;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
@apply grid flex-col gap-px-2 bg-transparent;
|
||||
|
||||
ui-form-control {
|
||||
@apply p-4 bg-white;
|
||||
|
||||
ui-select {
|
||||
@apply ml-8;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply ml-8;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply text-center my-8;
|
||||
|
||||
button {
|
||||
@apply rounded-full outline-none p-4 text-p1 border-brand border-solid border-2 px-px-25 py-px-15 mx-4 font-bold;
|
||||
|
||||
&.btn-cancel {
|
||||
@apply text-brand bg-white;
|
||||
}
|
||||
|
||||
&.btn-save {
|
||||
@apply text-white bg-brand;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@apply bg-inactive-branch border-none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spin {
|
||||
@apply animate-spin;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { Location } from '@angular/common';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core';
|
||||
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { CountryDTO, CustomerDTO, KeyValueDTOOfStringAndString } from '@swagger/crm';
|
||||
import { UiValidators } from '@ui/validators';
|
||||
import { Observable, combineLatest } from 'rxjs';
|
||||
import { first, map, switchMap } from 'rxjs/operators';
|
||||
import { validateEmail } from '../../validators/email-validator';
|
||||
import { genderLastNameValidator } from '../../validators/gender-b2b-validator';
|
||||
import { camelCase } from 'lodash';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { CustomerSearchNavigation, NavigationRoute } from '../../navigations';
|
||||
|
||||
@Component({ template: '' })
|
||||
export abstract class CustomerDataEditComponent implements OnInit {
|
||||
customer$: Observable<CustomerDTO>;
|
||||
customerId$: Observable<number>;
|
||||
countries$: Observable<CountryDTO[]>;
|
||||
customerFeatures$: Observable<KeyValueDTOOfStringAndString[]>;
|
||||
customerType$: Observable<string>;
|
||||
isWebshopOrGuest$: Observable<boolean>;
|
||||
customerFeatureB2bOrB2c$: Observable<string>;
|
||||
|
||||
control: UntypedFormGroup;
|
||||
|
||||
detailsRoute$: Observable<NavigationRoute>;
|
||||
|
||||
get customerId() {
|
||||
return Number(this.activatedRoute.snapshot.params['customerId']);
|
||||
}
|
||||
|
||||
afterInitForm?: (control: AbstractControl) => void;
|
||||
|
||||
constructor(
|
||||
private customerService: CrmCustomerService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private fb: UntypedFormBuilder,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private location: Location,
|
||||
private _store: CustomerSearchStore,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.customerId$ = this.activatedRoute.params.pipe(map((p) => Number(p['customerId'])));
|
||||
this.customer$ = this.customerId$.pipe(
|
||||
switchMap((id) => this.customerService.getCustomer(id, 2)),
|
||||
map((cr) => cr.result)
|
||||
);
|
||||
this.customerFeatures$ = this.customer$.pipe(map((customer: CustomerDTO) => Object.values(customer.features).filter((f) => f.enabled)));
|
||||
this.customerType$ = this.customerFeatures$.pipe(
|
||||
map(
|
||||
(features: KeyValueDTOOfStringAndString[]) =>
|
||||
features.find((f) => f.key === 'store' || f.key === 'webshop' || f.key === 'b2b' || f.key === 'guest')?.key
|
||||
)
|
||||
);
|
||||
this.isWebshopOrGuest$ = this.customerType$.pipe(map((type) => type === 'webshop' || type === 'guest'));
|
||||
this.customerFeatureB2bOrB2c$ = this.customerType$.pipe(map((type) => (type === 'b2b' ? 'b2b' : 'b2c')));
|
||||
this.countries$ = this.isWebshopOrGuest$.pipe(
|
||||
switchMap((webshopOrGuest) => {
|
||||
if (!webshopOrGuest) {
|
||||
return this.customerService.getCountries().pipe(map((p) => p.result));
|
||||
} else {
|
||||
return this.customerService.getCountries().pipe(
|
||||
map((p) => p.result),
|
||||
map((countries) => countries.filter((country) => country.name === 'Deutschland'))
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
this.initForm();
|
||||
|
||||
this.detailsRoute$ = combineLatest([this._store.processId$, this._store.customerId$]).pipe(
|
||||
map(([processId, customerId]) => this._navigation.detailsRoute({ processId, customerId }))
|
||||
);
|
||||
}
|
||||
|
||||
async initForm() {
|
||||
const { fb } = this;
|
||||
const customerDTO = await this.customer$.pipe(first()).toPromise();
|
||||
const customerType = await this.customerType$.pipe(first()).toPromise();
|
||||
const customerFeatures = await this.customerFeatures$.pipe(first()).toPromise();
|
||||
const isB2b = customerType === 'b2b';
|
||||
const isBranch = customerType === 'store';
|
||||
const isWebshop = customerType === 'webshop';
|
||||
const isCard = customerFeatures.find((feature) => feature.key === 'p4muser');
|
||||
|
||||
this.control = fb.group(
|
||||
{
|
||||
gender: fb.control(customerDTO?.gender, [isBranch ? Validators.min(1) : () => null, isBranch ? Validators.required : () => null]),
|
||||
title: fb.control(customerDTO?.title),
|
||||
lastName: fb.control(customerDTO?.lastName, [Validators.required]),
|
||||
firstName: fb.control(customerDTO?.firstName, [Validators.required]),
|
||||
dateOfBirth: fb.control(customerDTO?.dateOfBirth, [isWebshop && isCard ? Validators.required : () => null, UiValidators.date]),
|
||||
communicationDetails: fb.group({
|
||||
email: fb.control(customerDTO?.communicationDetails?.email, [validateEmail]),
|
||||
phone: fb.control(customerDTO?.communicationDetails?.phone),
|
||||
mobile: fb.control(customerDTO?.communicationDetails?.mobile),
|
||||
}),
|
||||
organisation: fb.group({
|
||||
name: fb.control(customerDTO?.organisation?.name),
|
||||
vatId: fb.control(customerDTO?.organisation?.vatId),
|
||||
department: fb.control(customerDTO?.organisation?.department),
|
||||
}),
|
||||
address: fb.group({
|
||||
street: fb.control(customerDTO?.address?.street),
|
||||
streetNumber: fb.control(customerDTO?.address?.streetNumber),
|
||||
zipCode: fb.control(customerDTO?.address?.zipCode),
|
||||
city: fb.control(customerDTO?.address?.city),
|
||||
country: fb.control(customerDTO?.address?.country),
|
||||
info: fb.control(customerDTO?.address?.info),
|
||||
}),
|
||||
},
|
||||
{ validators: genderLastNameValidator(isB2b) }
|
||||
);
|
||||
|
||||
if (typeof this.afterInitForm === 'function') {
|
||||
this.afterInitForm.call(this, this.control);
|
||||
}
|
||||
|
||||
this.control.markAllAsTouched();
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.location.back();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (!this.control.valid || !this.control.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.control.disable();
|
||||
|
||||
try {
|
||||
await this.customerService.patchCustomer(this.customerId, this.control.value).toPromise();
|
||||
this.location.back();
|
||||
} catch (error) {
|
||||
this.control.enable();
|
||||
if (error instanceof HttpErrorResponse) {
|
||||
if (error.error.invalidProperties) {
|
||||
const invProps = error.error.invalidProperties;
|
||||
const keys = Object.keys(invProps);
|
||||
for (const key of keys) {
|
||||
this.control.get('address')?.get(camelCase(key))?.setErrors({ validateAddress: invProps[key] });
|
||||
}
|
||||
}
|
||||
}
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
:host {
|
||||
@apply block bg-surface text-surface-content rounded px-6 py-8;
|
||||
}
|
||||
|
||||
form {
|
||||
@apply grid grid-cols-2 gap-x-8;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<div class="flex flex-row justify-end -mt-4 -mr-2">
|
||||
<a
|
||||
*ngIf="detailsRoute$ | async; let detailsRoute"
|
||||
[routerLink]="detailsRoute.path"
|
||||
[queryParams]="detailsRoute.urlTree.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
<shared-icon icon="close" [size]="32"></shared-icon>
|
||||
</a>
|
||||
</div>
|
||||
<h1 class="text-2xl text-center font-bold mb-6">Lieferadresse bearbeiten</h1>
|
||||
|
||||
<form [formGroup]="formGroup" (ngSubmit)="save()">
|
||||
<ng-container *ngIf="isBusinessKonto$ | async">
|
||||
<shared-form-control label="Firma" class="col-span-2">
|
||||
<input class="input-control" placeholder="Firma" type="text" formControlName="organisation" tabindex="1" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Abteilung">
|
||||
<input class="input-control" placeholder="Abteilung" type="text" formControlName="department" tabindex="2" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="USt-ID">
|
||||
<input class="input-control" placeholder="Abteilung" type="text" formControlName="vatId" tabindex="3" />
|
||||
</shared-form-control>
|
||||
</ng-container>
|
||||
|
||||
<shared-form-control label="Anrede">
|
||||
<shared-select formControlName="gender" placeholder="Anrede" tabindex="4" [autofocus]="true">
|
||||
<shared-select-option [value]="2">Herr</shared-select-option>
|
||||
<shared-select-option [value]="4">Frau</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Titel">
|
||||
<shared-select formControlName="title" placeholder="Titel" tabindex="5">
|
||||
<shared-select-option value="Dipl.-Ing.">Dipl.-Ing.</shared-select-option>
|
||||
<shared-select-option value="Dr.">Dr.</shared-select-option>
|
||||
<shared-select-option value="Dr. med.">Dr. med.</shared-select-option>
|
||||
<shared-select-option value="Prof.">Prof.</shared-select-option>
|
||||
<shared-select-option value="Prof. Dr.">Prof. Dr.</shared-select-option>
|
||||
<shared-select-option value="RA">RA</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Nachname">
|
||||
<input class="input-control" placeholder="Nachname" type="text" formControlName="lastName" tabindex="6" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Vorname">
|
||||
<input class="input-control" placeholder="Vorname" type="text" formControlName="firstName" tabindex="7" />
|
||||
</shared-form-control>
|
||||
|
||||
<ng-container *ngIf="!(isBusinessKonto$ | async)">
|
||||
<shared-form-control label="Firma" class="col-span-2">
|
||||
<input class="input-control" placeholder="Firma" type="text" formControlName="organisation" tabindex="8" />
|
||||
</shared-form-control>
|
||||
</ng-container>
|
||||
|
||||
<shared-form-control label="Straße">
|
||||
<input class="input-control" placeholder="Straße" type="text" formControlName="street" tabindex="9" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Hausnummer">
|
||||
<input class="input-control" placeholder="Hausnummer" type="text" formControlName="streetNumber" tabindex="10" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="PLZ">
|
||||
<input class="input-control" placeholder="PLZ" type="text" formControlName="zipCode" tabindex="11" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Ort">
|
||||
<input class="input-control" placeholder="Ort" type="text" formControlName="city" tabindex="12" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Adresszusatz" class="col-span-2">
|
||||
<input class="input-control" placeholder="Adresszusatz" type="text" formControlName="info" tabindex="13" />
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control class="col-span-2" label="Land">
|
||||
<shared-select placeholder="Land" formControlName="country" tabindex="14">
|
||||
<shared-select-option *ngFor="let country of countries$ | async" [value]="country.isO3166_A_3">
|
||||
{{ country.name }}
|
||||
</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
<div class="text-center col-span-2">
|
||||
<shared-checkbox>Diese Lieferadresse als Standard Adresse festlegen</shared-checkbox>
|
||||
</div>
|
||||
<div class="mt-6 text-center col-span-2">
|
||||
<button
|
||||
[disabled]="formGroup.invalid || formGroup.disabled"
|
||||
type="submit"
|
||||
class="px-5 py-3 font-bold text-lg rounded-full bg-brand text-white"
|
||||
tabindex="15"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,137 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CheckboxComponent } from '@shared/components/checkbox';
|
||||
import { FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { SelectModule } from '@shared/components/select';
|
||||
import { FormControlComponent } from '@shared/components/form-control';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { AddressDTO, Gender, ShippingAddressDTO } from '@swagger/crm';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
||||
import { AddressSelectionModalService } from '@shared/modals/address-selection-modal';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { CustomerSearchNavigation } from '../../navigations';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'page-edit-shipping-address-main-view',
|
||||
templateUrl: 'edit-shipping-address-main-view.component.html',
|
||||
styleUrls: ['edit-shipping-address-main-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'page-edit-shipping-address-main-view' },
|
||||
standalone: true,
|
||||
imports: [
|
||||
IconComponent,
|
||||
RouterLink,
|
||||
AsyncPipe,
|
||||
NgIf,
|
||||
NgForOf,
|
||||
ReactiveFormsModule,
|
||||
SelectModule,
|
||||
FormControlComponent,
|
||||
CheckboxComponent,
|
||||
],
|
||||
})
|
||||
export class EditShippingAddressMainViewComponent implements OnInit, OnDestroy {
|
||||
private _onDestroy = new Subject<void>();
|
||||
|
||||
detailsRoute$ = combineLatest([this._store.processId$, this._store.customerId$]).pipe(
|
||||
map(([processId, customerId]) => this._navigation.detailsRoute({ processId, customerId }))
|
||||
);
|
||||
|
||||
formGroup = new FormGroup({
|
||||
gender: new FormControl<Gender>(0, [Validators.required, Validators.min(1)]),
|
||||
title: new FormControl<string>(undefined),
|
||||
firstName: new FormControl<string>(undefined, [Validators.required]),
|
||||
lastName: new FormControl<string>(undefined, [Validators.required]),
|
||||
organisation: new FormControl<string>(undefined),
|
||||
department: new FormControl<string>(undefined),
|
||||
vatId: new FormControl<string>(undefined),
|
||||
street: new FormControl<string>(undefined, [Validators.required]),
|
||||
streetNumber: new FormControl<string>(undefined, [Validators.required]),
|
||||
zipCode: new FormControl<string>(undefined, [Validators.required]),
|
||||
city: new FormControl<string>(undefined, [Validators.required]),
|
||||
country: new FormControl<string>('DEU', [Validators.required]),
|
||||
info: new FormControl<string>(undefined),
|
||||
isDefault: new FormControl<boolean>(false),
|
||||
});
|
||||
|
||||
countries$ = this._customerService.getCountries().pipe(map((res) => res.result));
|
||||
|
||||
isBusinessKonto$ = this._store.isBusinessKonto$;
|
||||
|
||||
constructor(
|
||||
private _customerService: CrmCustomerService,
|
||||
private _addressSelection: AddressSelectionModalService,
|
||||
private _store: CustomerSearchStore,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this._store.customer$.pipe(takeUntil(this._onDestroy)).subscribe(() => {
|
||||
if (this._store.isBusinessKonto) {
|
||||
this.formGroup.controls.organisation.setValidators([Validators.required]);
|
||||
} else {
|
||||
this.formGroup.controls.organisation.clearValidators();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._onDestroy.next();
|
||||
this._onDestroy.complete();
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (this.formGroup.invalid) {
|
||||
this.formGroup.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.formGroup.disable();
|
||||
|
||||
const formData = this.formGroup.value;
|
||||
|
||||
const address: AddressDTO = {
|
||||
street: formData.street,
|
||||
streetNumber: formData.streetNumber,
|
||||
zipCode: formData.zipCode,
|
||||
city: formData.city,
|
||||
country: formData.country,
|
||||
info: formData.info,
|
||||
};
|
||||
|
||||
const addressValidationResult = await this._addressSelection.validateAddress(address);
|
||||
|
||||
if (addressValidationResult === undefined) {
|
||||
this.formGroup.enable();
|
||||
return;
|
||||
}
|
||||
|
||||
const addOrganization = this._store.isBusinessKonto || formData.organisation !== undefined;
|
||||
|
||||
const shippingAddress: ShippingAddressDTO = {
|
||||
gender: formData.gender,
|
||||
title: formData.title,
|
||||
firstName: formData.firstName,
|
||||
lastName: formData.lastName,
|
||||
organisation: addOrganization
|
||||
? {
|
||||
name: formData.organisation,
|
||||
department: formData.department,
|
||||
vatId: formData.vatId,
|
||||
}
|
||||
: undefined,
|
||||
address: addressValidationResult,
|
||||
};
|
||||
|
||||
const result = await this._customerService.createShippingAddress(this._store.customerId, shippingAddress, formData.isDefault);
|
||||
|
||||
this._navigation.navigateToDetails({ processId: this._store.processId, customerId: this._store.customerId });
|
||||
} catch (error) {
|
||||
this.formGroup.enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<ng-container *ngIf="fetching$ | async; else filterTemplate"></ng-container>
|
||||
<ng-container *ngIf="fetchingFilterSettings$ | async; else filterTemplate"></ng-container>
|
||||
|
||||
<ng-template #filterTemplate>
|
||||
<div>
|
||||
@@ -13,7 +13,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-3 bg-surface-2 text-surface-2-content">
|
||||
<shared-filter [filter]="filter$ | async"></shared-filter>
|
||||
<shared-filter
|
||||
*ngIf="filter$ | async; let filter"
|
||||
[filter]="filter"
|
||||
[loading]="fetching$ | async"
|
||||
(search)="search(filter)"
|
||||
></shared-filter>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { CustomerSearchNavigation, NavigationRoute } from '../../navigations';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { Filter } from '@shared/components/filter';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-filter-main-view',
|
||||
@@ -12,7 +13,9 @@ import { combineLatest } from 'rxjs';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerFilterMainViewComponent {
|
||||
fetching$ = this._store.fetchingFilter$;
|
||||
fetchingFilterSettings$ = this._store.fetchingFilter$;
|
||||
|
||||
fetching$ = this._store.fetchingCustomerList$;
|
||||
|
||||
filter$ = this._store.filter$;
|
||||
|
||||
@@ -33,4 +36,9 @@ export class CustomerFilterMainViewComponent {
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
) {}
|
||||
|
||||
search(filter: Filter) {
|
||||
this._store.setFilter(filter);
|
||||
this._store.search();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,13 @@
|
||||
<div>
|
||||
<div class="customer-history-header">
|
||||
<div class="customer-history-header-actions flex flex-row justify-end pt-1 px-1">
|
||||
<a *ngIf="detailsRoute$ | async; let route" [routerLink]="route.path" [queryParams]="route.queryParams" class="btn btn-label">
|
||||
<a
|
||||
*ngIf="detailsRoute$ | async; let route"
|
||||
[routerLink]="route.path"
|
||||
[queryParams]="route.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
class="btn btn-label"
|
||||
>
|
||||
<ui-icon [icon]="'close'"></ui-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row justify-center items-center gap-4 bg-surface text-surface-content rounded px-4 py-6;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<h1 class="text-center text-2xl font-bold">Kundenkarte</h1>
|
||||
<p class="text-center text-xl">
|
||||
Alle Infos zu Ihrer Kundenkarte <br />
|
||||
und allen Partnerkarten.
|
||||
</p>
|
||||
<page-customer-kundenkarte
|
||||
*ngFor="let karte of primaryKundenkarte$ | async"
|
||||
[cardDetails]="karte"
|
||||
[isCustomerCard]="true"
|
||||
></page-customer-kundenkarte>
|
||||
|
||||
<p class="text-center text-xl font-bold" *ngIf="(partnerKundenkarte$ | async)?.length">Partnerkarten</p>
|
||||
|
||||
<page-customer-kundenkarte
|
||||
*ngFor="let karte of partnerKundenkarte$ | async"
|
||||
[cardDetails]="karte"
|
||||
[isCustomerCard]="false"
|
||||
></page-customer-kundenkarte>
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { map, share, switchMap } from 'rxjs/operators';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { KundenkarteComponent } from '../../components/kundenkarte';
|
||||
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-kundenkarte-main-view',
|
||||
templateUrl: 'kundenkarte-main-view.component.html',
|
||||
styleUrls: ['kundenkarte-main-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'page-customer-kundenkarte-main-view' },
|
||||
standalone: true,
|
||||
imports: [KundenkarteComponent, NgFor, AsyncPipe, NgIf],
|
||||
})
|
||||
export class KundenkarteMainViewComponent implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
customerId$ = this._activatedRoute.params.pipe(map((params) => params.customerId));
|
||||
|
||||
kundenkarte$ = this.customerId$.pipe(
|
||||
switchMap((customerId) =>
|
||||
this._customerService.getCustomerCard(customerId).pipe(map((response) => response.result?.filter((f) => f.isActive)))
|
||||
),
|
||||
share()
|
||||
);
|
||||
|
||||
primaryKundenkarte$ = this.kundenkarte$.pipe(map((kundenkarte) => kundenkarte?.filter((k) => k.isPrimary)));
|
||||
|
||||
partnerKundenkarte$ = this.kundenkarte$.pipe(map((kundenkarte) => kundenkarte?.filter((k) => !k.isPrimary)));
|
||||
|
||||
constructor(private _store: CustomerSearchStore, private _activatedRoute: ActivatedRoute, private _customerService: CrmCustomerService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.customerId$.subscribe((customerId) => {
|
||||
this._store.selectCustomer(customerId);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,13 @@
|
||||
>
|
||||
<div class="text-center pt-10 px-8 rounded-card side-view-shadow grow">
|
||||
<h1 class="text-[1.625rem] font-bold">Kundensuche</h1>
|
||||
<p class="text-lg mt-2">
|
||||
Wir legen Ihnen gerne ein Onlinekonto an, dort können Sie Ihre Bestellungen einsehen.
|
||||
<p class="text-lg mt-2 mb-6">
|
||||
Haben Sie ein Konto bei uns?
|
||||
</p>
|
||||
<shared-filter-input-group-main
|
||||
*ngIf="filter$ | async; let filter"
|
||||
[inputGroup]="filter?.input | group: 'main'"
|
||||
(search)="search(filter)"
|
||||
[loading]="fetching$ | async"
|
||||
></shared-filter-input-group-main>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,10 @@ import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { CustomerCreateNavigation } from '../../navigations/customer-create.navigation';
|
||||
import { NumberInput } from '@angular/cdk/coercion';
|
||||
import { Filter, FilterModule } from '@shared/components/filter';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-main-side-view',
|
||||
@@ -10,7 +14,7 @@ import { NumberInput } from '@angular/cdk/coercion';
|
||||
styleUrls: ['main-side-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterModule],
|
||||
imports: [CommonModule, RouterModule, FilterModule],
|
||||
})
|
||||
export class MainSideViewComponent {
|
||||
@Input()
|
||||
@@ -20,5 +24,14 @@ export class MainSideViewComponent {
|
||||
return this._navigation.defaultRoute({ processId: this.processId });
|
||||
}
|
||||
|
||||
constructor(private _navigation: CustomerCreateNavigation) {}
|
||||
filter$ = this._store.filter$;
|
||||
|
||||
fetching$ = this._store.fetchingCustomerList$;
|
||||
|
||||
constructor(private _navigation: CustomerCreateNavigation, private _store: CustomerSearchStore) {}
|
||||
|
||||
search(filter: Filter) {
|
||||
this._store.setFilter(filter);
|
||||
this._store.search();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
:host {
|
||||
@apply block bg-surface text-surface-content rounded-[0.313rem] mb-3;
|
||||
}
|
||||
|
||||
::ng-deep page-customer-history-main-view shared-history-list .scroll-container {
|
||||
@apply h-[calc(100vh-24rem)];
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<ng-container *ngIf="fetching$ | async; else historyTemplate"></ng-container>
|
||||
|
||||
<ng-template #historyTemplate>
|
||||
<div>
|
||||
<div class="customer-history-header">
|
||||
<div class="customer-history-header-actions flex flex-row justify-end pt-1 px-1">
|
||||
<a *ngIf="detailsRoute$ | async; let route" [routerLink]="route.path" [queryParams]="route.queryParams" class="btn btn-label">
|
||||
<shared-icon [icon]="'close'" [size]="32"></shared-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="customer-history-header-body text-center -mt-3">
|
||||
<h1 class="text-[1.625rem] font-bold">Historie</h1>
|
||||
</div>
|
||||
<div class="customer-history-header-info flex flex-row justify-evenly items-center my-5">
|
||||
<div class="flex flex-row">
|
||||
<div class="w-36">Kundenname</div>
|
||||
<div class="grow font-bold">{{ customerName$ | async }}</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="w-36">Kundennummer</div>
|
||||
<div class="grow font-bold">{{ customerNumber$ | async }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-3 bg-surface-2 text-surface-2-content">
|
||||
<shared-history-list [history]="history$ | async"> </shared-history-list>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,111 @@
|
||||
import { Component, ChangeDetectionStrategy, AfterViewInit, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { HistoryDTO } from '@swagger/crm';
|
||||
import { Observable, Subject, combineLatest } from 'rxjs';
|
||||
import { map, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { CustomerSearchNavigation } from '../../navigations';
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { IconModule } from '@shared/components/icon';
|
||||
import { SharedHistoryListModule } from '@shared/components/history';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { DomainOmsService } from '@domain/oms';
|
||||
|
||||
export interface CustomerHistoryViewMainState {
|
||||
history?: HistoryDTO[];
|
||||
fetching?: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-details-history-main-view',
|
||||
templateUrl: 'order-details-history-main-view.component.html',
|
||||
styleUrls: ['order-details-history-main-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [AsyncPipe, RouterLink, NgIf, IconModule, SharedHistoryListModule],
|
||||
})
|
||||
export class CustomerOrderDetailsHistoryMainViewComponent extends ComponentStore<CustomerHistoryViewMainState>
|
||||
implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
fetching$ = this.select((s) => s.fetching);
|
||||
|
||||
history$ = this.select((s) => s.history);
|
||||
|
||||
processId$ = this._store.processId$;
|
||||
|
||||
customerId$ = this._store.customerId$;
|
||||
|
||||
customer$ = this._store.customer$;
|
||||
|
||||
customerName$ = this.customer$.pipe(map((customer) => `${customer?.lastName}, ${customer?.firstName}`));
|
||||
|
||||
customerNumber$ = this.customer$.pipe(map((customer) => customer?.customerNumber));
|
||||
|
||||
orderId$ = this._activvatedRoute.params.pipe(map((params) => Number(params.orderId)));
|
||||
|
||||
order$ = this._store.order$;
|
||||
|
||||
orderItemId$ = this._activvatedRoute.params.pipe(map((params) => Number(params.orderItemId)));
|
||||
|
||||
orderItemSubsetId$ = combineLatest([this.order$, this.orderItemId$]).pipe(
|
||||
map(([order, orderItemId]) => order?.items?.find((item) => item.id === orderItemId)?.data?.subsetItems?.[0]?.id)
|
||||
);
|
||||
|
||||
detailsRoute$ = combineLatest([this.processId$, this.customerId$, this.orderId$, this.orderItemId$]).pipe(
|
||||
map(([processId, customerId, orderId, orderItemId]) =>
|
||||
this._navigation.orderDetialsRoute({ processId, customerId, orderId, orderItemId })
|
||||
)
|
||||
);
|
||||
|
||||
constructor(
|
||||
private _activvatedRoute: ActivatedRoute,
|
||||
private _store: CustomerSearchStore,
|
||||
private _customerService: CrmCustomerService,
|
||||
private _omsService: DomainOmsService,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
) {
|
||||
super({});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.orderId$.pipe(takeUntil(this._onDestroy$)).subscribe((orderId) => {
|
||||
this._store.selectOrder(orderId);
|
||||
});
|
||||
|
||||
this.orderItemSubsetId$.pipe(takeUntil(this._onDestroy$)).subscribe((orderItemSubsetId) => {
|
||||
if (orderItemSubsetId) {
|
||||
this.fetchHistory(orderItemSubsetId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
fetchHistory = this.effect((orderItemSubsetId$: Observable<number>) =>
|
||||
orderItemSubsetId$.pipe(
|
||||
tap(() => this.patchState({ fetching: true })),
|
||||
switchMap((orderItemSubsetId) =>
|
||||
this._omsService
|
||||
.getHistory(orderItemSubsetId)
|
||||
.pipe(tapResponse(this.handleFetchHistoryResponse, this.handleFetchHistoryError, this.handleFetchHistoryComplete))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
handleFetchHistoryResponse = (history: HistoryDTO[]) => {
|
||||
this.patchState({ history });
|
||||
};
|
||||
|
||||
handleFetchHistoryError = (err: any) => {
|
||||
console.error(err);
|
||||
};
|
||||
|
||||
handleFetchHistoryComplete = () => {
|
||||
this.patchState({ fetching: false });
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row gap-2;
|
||||
}
|
||||
|
||||
.page-customer-order-details-main-view__card {
|
||||
@apply grid grid-flow-row;
|
||||
}
|
||||
|
||||
.page-customer-order-details-main-view__card-header {
|
||||
@apply grid grid-flow-col items-center justify-between bg-surface-2 text-surface-2-content p-4 font-bold rounded-t;
|
||||
}
|
||||
|
||||
.page-customer-order-details-main-view__card-content {
|
||||
@apply bg-surface text-surface-content p-4 rounded-b;
|
||||
}
|
||||
|
||||
.col-data {
|
||||
@apply grid grid-cols-[10rem_1fr] gap-2;
|
||||
}
|
||||
|
||||
.col-label {
|
||||
}
|
||||
|
||||
.col-value {
|
||||
@apply font-bold text-left;
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<div class="page-customer-order-details-main-view__card">
|
||||
<div class="page-customer-order-details-main-view__card-header">
|
||||
<div class="flex flex-row">
|
||||
<shared-icon class="mr-3" [icon]="accountTypeKey$ | async"></shared-icon>
|
||||
<span>{{ accountTypeDescription$ | async }}</span>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<a
|
||||
*ngIf="ordersRoute$ | async; let ordersRoute"
|
||||
[routerLink]="ordersRoute.path"
|
||||
[queryParams]="ordersRoute.urlTree.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
<shared-icon icon="close" class="text-2xl"></shared-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-customer-order-details-main-view__card-content" *ngIf="order$ | async; let order">
|
||||
<div class="grid grid-cols-2 items-end gap-2">
|
||||
<div class="col-data order-date">
|
||||
<div class="col-label">
|
||||
Bestelldatum
|
||||
</div>
|
||||
<div class="col-value">{{ order?.orderDate | date: 'dd.MM.yyyy | HH:mm' }} Uhr</div>
|
||||
</div>
|
||||
<div class="font-bold text-right text-2xl">
|
||||
{{ order?.orderNumber }}
|
||||
</div>
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
Bestellkanal
|
||||
</div>
|
||||
<div class="col-value">
|
||||
{{ order?.features?.orderSource }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="font-bold text-right">
|
||||
{{ order?.processingStatus | orderProcessingStatus }}
|
||||
</div>
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
Zielfiliale
|
||||
</div>
|
||||
<div class="col-value">
|
||||
{{ orderTargetBranch$ | async | resolveBranch | branchName: 'name' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<span class="font-bold">{{ order?.orderValue | currency: order?.orderValueCurrency:'code' }}</span> |
|
||||
<span>{{ order?.items?.length }} Artikel</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-customer-order-details-main-view__card" *ngIf="orderItem$ | async; let orderItem">
|
||||
<div class="page-customer-order-details-main-view__card-header">
|
||||
<div class="grid grid-flow-col gap-3 justify-start">
|
||||
<shared-icon [icon]="orderItemOrderType$ | async"></shared-icon>
|
||||
<span>{{ orderItemOrderType$ | async }}</span>
|
||||
<span class="font-normal">{{ orderTargetBranch$ | async | resolveBranch | branchName: 'name' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-customer-order-details-main-view__card-content grid grid-cols-[6rem_1fr] gap-4">
|
||||
<div>
|
||||
<img class="rounded shadow mx-auto w-[5.9rem]" [src]="orderItem?.product?.ean | productImage" [alt]="orderItem?.product?.name" />
|
||||
</div>
|
||||
<div class="grid grid-flow-row gap-2">
|
||||
<div class="grid grid-flow-col justify-between items-end">
|
||||
<span>{{ orderItem.product?.contributors }}</span>
|
||||
<a
|
||||
*ngIf="orderDetailsHistoryRoute$ | async; let orderDetailsHistoryRoute"
|
||||
[routerLink]="orderDetailsHistoryRoute.path"
|
||||
[queryParams]="orderDetailsHistoryRoute.urlTree.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
class="text-brand font-bold text-xl"
|
||||
>Historie</a
|
||||
>
|
||||
</div>
|
||||
<div class="font-bold text-lg">
|
||||
{{ orderItem?.product?.name }}
|
||||
</div>
|
||||
<div>
|
||||
<span class="isa-label">
|
||||
ans Lager
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid grid-flow-row gap-2" *ngIf="orderItemSubsetItem$ | async; let orderItemSubsetItem">
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
Menge
|
||||
</div>
|
||||
<div class="col-value">{{ orderItem?.quantity?.quantity }}x</div>
|
||||
</div>
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
Format
|
||||
</div>
|
||||
<div class="col-value grid-flow-col grid gap-3 items-center justify-start">
|
||||
<shared-icon [icon]="orderItem?.product?.format"></shared-icon>
|
||||
<span>{{ orderItem?.product?.formatDetail }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
ISBN/EAN
|
||||
</div>
|
||||
<div class="col-value">{{ orderItem?.product?.ean }}</div>
|
||||
</div>
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
Preis
|
||||
</div>
|
||||
<div class="col-value">{{ orderItem?.unitPrice?.value?.value | currency: orderItem?.unitPrice?.value?.currency:'code' }}</div>
|
||||
</div>
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
MwSt
|
||||
</div>
|
||||
<div class="col-value">{{ orderItem?.unitPrice?.vat?.inPercent }}%</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
Lieferant
|
||||
</div>
|
||||
<div class="col-value">
|
||||
{{ orderItemSubsetItem?.supplier?.data?.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
Meldenummer
|
||||
</div>
|
||||
<div class="col-value">{{ orderItemSubsetItem?.ssc }} - {{ orderItemSubsetItem?.sscText }}</div>
|
||||
</div>
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
Vsl. Lieferdatum
|
||||
</div>
|
||||
<div class="col-value">
|
||||
{{ orderItemSubsetItem?.estimatedShippingDate | date: 'dd.MM.yyyy' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
Zurücklegen bis
|
||||
</div>
|
||||
<div class="col-value">
|
||||
{{ orderItemSubsetItem?.preferredPickUpDate | date: 'dd.MM.yyyy' }}
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="col-data" *ngIf="orderItemSubsetItem?.compartmentCode">
|
||||
<div class="col-label">
|
||||
Abholfachnummer
|
||||
</div>
|
||||
<div class="col-value">
|
||||
<span>{{ orderItemSubsetItem?.compartmentCode }}</span>
|
||||
<span *ngIf="orderItemSubsetItem?.compartmentInfo">_{{ orderItemSubsetItem?.compartmentInfo }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
Vormerker
|
||||
</div>
|
||||
<div class="col-value">-</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
Zahlungsweg
|
||||
</div>
|
||||
<div class="col-value">-</div>
|
||||
</div>
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
Zahlungsart
|
||||
</div>
|
||||
<div class="col-value">-</div>
|
||||
</div>
|
||||
<div class="col-data">
|
||||
<div class="col-label">
|
||||
Anmerkung
|
||||
</div>
|
||||
<div class="col-value">
|
||||
{{ orderItemSubsetItem?.specialComment }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { debounceTime, map, takeUntil, tap } from 'rxjs/operators';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
import { AsyncPipe, CurrencyPipe, DatePipe, NgIf } from '@angular/common';
|
||||
import { OrderProcessingStatusPipe } from '@shared/pipes/order';
|
||||
import { BranchNamePipe, ResolveBranchPipe } from '@shared/pipes/branch';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
import { ProductImagePipe } from '@cdn/product-image';
|
||||
import { CustomerSearchNavigation } from '../../navigations';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-details-main-view',
|
||||
templateUrl: 'order-details-main-view.component.html',
|
||||
styleUrls: ['order-details-main-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'page-customer-order-details-main-view' },
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgIf,
|
||||
AsyncPipe,
|
||||
DatePipe,
|
||||
OrderProcessingStatusPipe,
|
||||
ProductImagePipe,
|
||||
ResolveBranchPipe,
|
||||
BranchNamePipe,
|
||||
CurrencyPipe,
|
||||
IconComponent,
|
||||
RouterLink,
|
||||
],
|
||||
})
|
||||
export class OrderDetailsMainViewComponent implements OnInit, OnDestroy {
|
||||
orderId$ = this._activateRoute.params.pipe(map((params) => Number(params.orderId)));
|
||||
|
||||
order$ = this._store.order$;
|
||||
|
||||
orderTargetBranch$ = this.order$.pipe(map((order) => order?.targetBranch?.id));
|
||||
|
||||
customerId$ = this._activateRoute.params.pipe(map((params) => Number(params.customerId)));
|
||||
|
||||
customer$ = this._store.customer$;
|
||||
|
||||
accountType$ = this.customer$.pipe(map((customer) => customer?.features?.find((feature) => feature.group === 'd-customertype')));
|
||||
|
||||
accountTypeKey$ = this.accountType$.pipe(map((accountType) => accountType?.key));
|
||||
|
||||
accountTypeDescription$ = this.accountType$.pipe(map((accountType) => accountType?.description));
|
||||
|
||||
orderItemId$ = this._activateRoute.params.pipe(map((params) => Number(params.orderItemId)));
|
||||
|
||||
orderItem$ = combineLatest([this.order$, this.orderItemId$, this.customerId$]).pipe(
|
||||
tap(([order, orderItemId, customerId]) => {
|
||||
if (order?.items && customerId && !orderItemId) {
|
||||
this._navigation.navigateToOrderDetails({
|
||||
processId: this._store.processId,
|
||||
customerId: customerId,
|
||||
orderId: order?.id,
|
||||
orderItemId: order?.items?.[0]?.id,
|
||||
});
|
||||
}
|
||||
}),
|
||||
map(([order, orderItemId]) => order?.items?.find((orderItem) => orderItem?.id === orderItemId)),
|
||||
map((orderItem) => orderItem?.data)
|
||||
);
|
||||
|
||||
orderItemOrderType$ = this.orderItem$.pipe(map((orderItem) => orderItem?.features?.orderType));
|
||||
|
||||
orderItemSubsetItem$ = this.orderItem$.pipe(map((orderItem) => orderItem?.subsetItems?.[0]?.data));
|
||||
|
||||
private _onDestroy = new Subject<void>();
|
||||
|
||||
ordersRoute$ = combineLatest([this.customerId$, this._store.processId$]).pipe(
|
||||
map(([customerId, processId]) => this._navigation.ordersRoute({ processId, customerId }))
|
||||
);
|
||||
|
||||
orderDetailsHistoryRoute$ = combineLatest([this.customerId$, this._store.processId$, this.orderId$, this.orderItemId$]).pipe(
|
||||
map(([customerId, processId, orderId, orderItemId]) =>
|
||||
this._navigation.orderDetailsHistoryRoute({ processId, customerId, orderId, orderItemId })
|
||||
)
|
||||
);
|
||||
|
||||
constructor(private _activateRoute: ActivatedRoute, private _store: CustomerSearchStore, private _navigation: CustomerSearchNavigation) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.orderId$.pipe(takeUntil(this._onDestroy)).subscribe((orderId) => {
|
||||
this._store.selectOrder(orderId);
|
||||
});
|
||||
|
||||
this.customerId$.pipe(takeUntil(this._onDestroy)).subscribe((customerId) => {
|
||||
this._store.selectCustomer(customerId);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._onDestroy.next();
|
||||
this._onDestroy.complete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
a.order-item-active {
|
||||
@apply border-[#0556B4] bg-[#D8DFE5];
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<div class="bg-surface text-surface-content rounded-t p-4 font-bold text-xl">
|
||||
{{ customer$ | async | customerName }}
|
||||
</div>
|
||||
<div class="bg-surface-2 text-surface-2-content p-4 text-right">{{ (orderItems$ | async)?.length }} Artikel</div>
|
||||
<div class="grid grid-flow-row gap-1">
|
||||
<ng-container *ngFor="let orderItem of orderItems$ | async">
|
||||
<a
|
||||
*ngIf="getOrderDetailsRoute(orderItem.id); let route"
|
||||
[routerLink]="route.path"
|
||||
[queryParams]="route.urlTree.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
routerLinkActive="order-item-active"
|
||||
class="grid grid-cols-[auto_1fr] gap-4 p-4 border border-solid bg-surface text-surface-content rounded first:rounded-t-none"
|
||||
>
|
||||
<div>
|
||||
<img [src]="orderItem?.product?.ean | productImage" [alt]="orderItem?.product?.name" class="rounded shadow mx-auto w-[3.75rem]" />
|
||||
</div>
|
||||
<div class="grid grid-flow-row gap-1">
|
||||
<span>{{ orderItem?.product?.contributors }}</span>
|
||||
<span class="font-bold truncate whitespace-nowrap">{{ orderItem?.product?.name }}</span>
|
||||
<span>
|
||||
<span class="isa-label">ans Lager</span>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { CustomerNamePipe } from '@shared/pipes/customer';
|
||||
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
|
||||
import { ProductImagePipe } from '@cdn/product-image';
|
||||
import { CustomerSearchNavigation } from '../../navigations';
|
||||
import { RouterLink, RouterLinkActive } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-details-side-view',
|
||||
templateUrl: 'order-details-side-view.component.html',
|
||||
styleUrls: ['order-details-side-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'page-customer-order-details-side-view' },
|
||||
standalone: true,
|
||||
imports: [CustomerNamePipe, AsyncPipe, NgFor, ProductImagePipe, RouterLink, NgIf, RouterLinkActive],
|
||||
})
|
||||
export class OrderDetailsSideViewComponent {
|
||||
customer$ = this._store.customer$;
|
||||
|
||||
orderItems$ = this._store.order$.pipe(map((order) => order?.items?.map((item) => item.data)));
|
||||
|
||||
constructor(private _store: CustomerSearchStore, private _navigation: CustomerSearchNavigation) {}
|
||||
|
||||
getOrderDetailsRoute(orderItemId: number) {
|
||||
return this._navigation.orderDetialsRoute({
|
||||
processId: this._store.processId,
|
||||
customerId: this._store.customer.id,
|
||||
orderId: this._store.order.id,
|
||||
orderItemId,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
:host {
|
||||
@apply block bg-surface text-surface-content rounded px-6 py-8;
|
||||
}
|
||||
|
||||
.order-item {
|
||||
@apply grid;
|
||||
grid-template-columns: 4rem 7.5rem 1fr auto auto;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<div class="flex flex-row justify-end -mt-4 -mr-2">
|
||||
<a
|
||||
*ngIf="detailsRoute$ | async; let detailsRoute"
|
||||
[routerLink]="detailsRoute.path"
|
||||
[queryParams]="detailsRoute.urlTree.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
<shared-icon icon="close" [size]="32"></shared-icon>
|
||||
</a>
|
||||
</div>
|
||||
<h1 class="text-2xl text-center font-bold mb-4">Bestellungen</h1>
|
||||
<p class="text-xl text-center">
|
||||
Übersicht aller offenen und <br />
|
||||
früheren Bestellungen
|
||||
</p>
|
||||
<div class="grid grid-flow-row -mx-6 -mb-8 mt-8 overflow-scroll max-h-[calc(100vh-390px)] scroll-bar">
|
||||
<div *ngFor="let order of orders$ | async" class="order-item gap-6 items-center border-t-2 border-solid border-background px-6 py-3">
|
||||
<div class="font-bold">
|
||||
{{ order.orderDate | date: 'dd.MM.yy' }}
|
||||
</div>
|
||||
<div class="font-bold text-center">
|
||||
{{ order.processingStatus | orderProcessingStatus }}
|
||||
</div>
|
||||
<div>
|
||||
{{ order.features?.orderSource }}
|
||||
<span *ngIf="order.features?.orderSource && (order | orderDestination)">|</span>
|
||||
{{ order | orderDestination }}
|
||||
</div>
|
||||
<div>{{ order.orderValue | currency: order.currency:'code' }} | {{ order.itemsCount }} Artikel</div>
|
||||
<div class="justify-self-end">
|
||||
<a
|
||||
class="grid justify-center items-center font-bold text-brand h-12"
|
||||
*ngIf="getOrderDetailsRoute(order.id); let orderDetailsRoute"
|
||||
[routerLink]="orderDetailsRoute.path"
|
||||
[queryParams]="orderDetailsRoute.urlTree.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
Details
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
|
||||
import { isEqual } from 'lodash';
|
||||
import { AsyncPipe, CurrencyPipe, DatePipe, JsonPipe, NgFor, NgIf } from '@angular/common';
|
||||
import { OrderDestinationPipe, OrderProcessingStatusPipe } from '@shared/pipes/order';
|
||||
import { CustomerSearchNavigation } from '../../navigations';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-orders-main-view',
|
||||
templateUrl: 'orders-main-view.component.html',
|
||||
styleUrls: ['orders-main-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'page-customer-orders-main-view' },
|
||||
standalone: true,
|
||||
imports: [AsyncPipe, NgFor, NgIf, DatePipe, OrderProcessingStatusPipe, OrderDestinationPipe, CurrencyPipe, RouterLink, IconComponent],
|
||||
})
|
||||
export class CustomerOrdersMainViewComponent implements OnInit, OnDestroy {
|
||||
private _onDestroy = new Subject<void>();
|
||||
|
||||
orders$ = this._store.customerOrders$;
|
||||
|
||||
fetching$ = this._store.fetchingCustomerOrders$;
|
||||
|
||||
detailsRoute$ = combineLatest([this._store.processId$, this._store.customerId$]).pipe(
|
||||
map(([processId, customerId]) => this._navigation.detailsRoute({ processId, customerId }))
|
||||
);
|
||||
|
||||
constructor(private _store: CustomerSearchStore, private _navigation: CustomerSearchNavigation) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._store.customer$
|
||||
.pipe(
|
||||
takeUntil(this._onDestroy),
|
||||
map((c) => c?.customerNumber),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
.subscribe((customerNumber) => {
|
||||
if (customerNumber) {
|
||||
this._store.fetchOrders();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._onDestroy.next();
|
||||
this._onDestroy.complete();
|
||||
}
|
||||
|
||||
getOrderDetailsRoute(orderId: number) {
|
||||
return this._navigation.orderDetialsRoute({ customerId: this._store.customerId, processId: this._store.processId, orderId });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { CustomerOrdersMainViewComponent } from './orders-main-view.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CustomerOrdersMainViewComponent],
|
||||
exports: [CustomerOrdersMainViewComponent],
|
||||
})
|
||||
export class CustomerOrdersMainViewModule {}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<div class="customer-result-list-header bg-surface-2 text-surface-2-content flex flex-row justify-between pb-4">
|
||||
<div class="grid grid-flow-col gap-3 items-center justify-start grow-0">
|
||||
<shared-searchbox class="w-[20.892rem]"> </shared-searchbox>
|
||||
<shared-filter-input-group-main
|
||||
class="w-[20.892rem]"
|
||||
*ngIf="filter$ | async; let filter"
|
||||
[inputGroup]="filter?.input | group: 'main'"
|
||||
(search)="search(filter)"
|
||||
[loading]="fetching$ | async"
|
||||
></shared-filter-input-group-main>
|
||||
<a
|
||||
class="btn btn-light w-[5.813rem] h-14"
|
||||
*ngIf="filterRoute$ | async; let route"
|
||||
@@ -11,8 +17,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<span class="mr-5 self-end text-sm">
|
||||
xxx Treffer
|
||||
</span>
|
||||
<span class="mr-5 self-end text-sm"> {{ hits$ | async }} Treffer </span>
|
||||
</div>
|
||||
<page-customer-result-list [processId]="processId$ | async"> </page-customer-result-list>
|
||||
<page-customer-result-list [processId]="processId$ | async" [customers]="customers$ | async" (endReached)="paginate()">
|
||||
</page-customer-result-list>
|
||||
|
||||
@@ -4,6 +4,8 @@ import { CustomerSearchStore } from '../store/customer-search.store';
|
||||
import { BehaviorSubject, Subscription, combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { CustomerSearchNavigation, NavigationRoute } from '../../navigations';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { Filter } from '@shared/components/filter';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-results-main-view',
|
||||
@@ -39,6 +41,14 @@ export class CustomerResultsMainViewComponent implements OnInit, OnDestroy {
|
||||
|
||||
routerEventsSubscription: Subscription;
|
||||
|
||||
filter$ = this._store.filter$.pipe(map((filter) => cloneDeep(filter)));
|
||||
|
||||
fetching$ = this._store.fetchingCustomerList$;
|
||||
|
||||
hits$ = this._store.customerListCount$;
|
||||
|
||||
customers$ = this._store.customerList$;
|
||||
|
||||
constructor(private _store: CustomerSearchStore, private _router: Router, private _navigation: CustomerSearchNavigation) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -52,4 +62,14 @@ export class CustomerResultsMainViewComponent implements OnInit, OnDestroy {
|
||||
ngOnDestroy(): void {
|
||||
this.routerEventsSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
search(filter: Filter) {
|
||||
this._store.setFilter(filter);
|
||||
this._store.search();
|
||||
}
|
||||
|
||||
paginate() {
|
||||
console.log('paginate');
|
||||
this._store.paginate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ import { CommonModule } from '@angular/common';
|
||||
|
||||
import { CustomerResultsMainViewComponent } from './results-main-view.component';
|
||||
import { CustomerResultListModule } from '../../components/customer-result-list/customer-result-list.module';
|
||||
import { SearchboxModule } from '@shared/components/searchbox';
|
||||
import { FilterModule } from '@shared/components/filter';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, CustomerResultListModule, SearchboxModule, RouterModule],
|
||||
imports: [CommonModule, CustomerResultListModule, FilterModule, RouterModule],
|
||||
exports: [CustomerResultsMainViewComponent],
|
||||
declarations: [CustomerResultsMainViewComponent],
|
||||
})
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<div class="customer-result-list-header bg-surface-2 text-surface-2-content flex flex-col justify-between pb-4">
|
||||
<div class="grid grid-flow-col gap-3 items-center justify-start grow-0">
|
||||
<shared-searchbox class="w-[20.892rem]"> </shared-searchbox>
|
||||
<shared-filter-input-group-main
|
||||
class="w-[20.892rem]"
|
||||
*ngIf="filter$ | async; let filter"
|
||||
[inputGroup]="filter?.input | group: 'main'"
|
||||
(search)="search(filter)"
|
||||
[loading]="fetching$ | async"
|
||||
></shared-filter-input-group-main>
|
||||
<a
|
||||
class="btn btn-light w-[5.813rem] h-14"
|
||||
*ngIf="filterRoute$ | async; let route"
|
||||
@@ -11,8 +17,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<span class="mr-5 self-end text-sm mt-4">
|
||||
xxx Treffer
|
||||
</span>
|
||||
<span class="mr-5 self-end text-sm mt-4"> {{ hits$ | async }} Treffer </span>
|
||||
</div>
|
||||
<page-customer-result-list [processId]="processId$ | async" compact="true"> </page-customer-result-list>
|
||||
<page-customer-result-list [processId]="processId$ | async" compact="true" [customers]="customers$ | async" (endReached)="paginate()">
|
||||
</page-customer-result-list>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CustomerSearchStore } from '../store/customer-search.store';
|
||||
import { Subscription, BehaviorSubject, combineLatest } from 'rxjs';
|
||||
import { CustomerSearchNavigation, NavigationRoute } from '../../navigations';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Filter } from '@shared/components/filter';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-results-side-view',
|
||||
@@ -39,6 +40,14 @@ export class CustomerResultsSideViewComponent implements OnInit, OnDestroy {
|
||||
|
||||
routerEventsSubscription: Subscription;
|
||||
|
||||
filter$ = this._store.filter$;
|
||||
|
||||
fetching$ = this._store.fetchingCustomerList$;
|
||||
|
||||
hits$ = this._store.customerListCount$;
|
||||
|
||||
customers$ = this._store.customerList$;
|
||||
|
||||
constructor(private _store: CustomerSearchStore, private _router: Router, private _navigation: CustomerSearchNavigation) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -52,4 +61,13 @@ export class CustomerResultsSideViewComponent implements OnInit, OnDestroy {
|
||||
ngOnDestroy(): void {
|
||||
this.routerEventsSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
search(filter: Filter) {
|
||||
this._store.setFilter(filter);
|
||||
this._store.search();
|
||||
}
|
||||
|
||||
paginate() {
|
||||
this._store.paginate();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user