#1145 Anlage von Kunden (Gast, Filiale und Online)

This commit is contained in:
Lorenz Hilpert
2020-12-22 13:53:55 +01:00
parent 5026db9aaf
commit 416a19ca9d
83 changed files with 1690 additions and 226 deletions

View File

@@ -1456,6 +1456,46 @@
}
}
}
},
"@ui/modal": {
"projectType": "library",
"root": "apps/ui/modal",
"sourceRoot": "apps/ui/modal/src",
"prefix": "ui",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "apps/ui/modal/tsconfig.lib.json",
"project": "apps/ui/modal/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/ui/modal/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/ui/modal/src/test.ts",
"tsConfig": "apps/ui/modal/tsconfig.spec.json",
"karmaConfig": "apps/ui/modal/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/ui/modal/tsconfig.lib.json",
"apps/ui/modal/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "sales"

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { StringDictionary } from '@cmf/core';
import { AutocompleteDTO, CustomerDTO, CustomerInfoDTO, CustomerService, InputDTO } from '@swagger/crm';
import { AddressDTO, AutocompleteDTO, CustomerDTO, CustomerInfoDTO, CustomerService, InputDTO } from '@swagger/crm';
import { PagedResult, Result } from 'apps/domain/defs/src/public-api';
import { Observable } from 'rxjs';
@@ -39,4 +39,32 @@ export class CrmCustomerService {
patchCustomer(customerId: number, customer: CustomerDTO): Observable<Result<CustomerDTO>> {
return this.customerService.CustomerPatchCustomer({ customerId, customer });
}
createB2BCustomer(customer: CustomerDTO) {
// this.customerService.CustomerCreateCustomer({ ...customer, })
}
createOnlineCustomer(customer: CustomerDTO) {
return this.customerService.CustomerCreateOnlineCustomer({ ...customer, customerType: 8, hasOnlineAccount: true });
}
createGuestCustomer(customer: CustomerDTO) {
return this.customerService.CustomerCreateOnlineCustomer({ ...customer, customerType: 8, isGuestAccount: true });
}
createBranchCustomer(customer: CustomerDTO) {
return this.customerService.CustomerCreateCustomer({ ...customer, customerType: 8 });
}
validateAddress(address: AddressDTO) {
return this.customerService.CustomerValidateAddress(address);
}
getCountries() {
return this.customerService.CustomerGetCountries({});
}
emailExists(email: string) {
return this.customerService.CustomerEmailExists(email);
}
}

View File

@@ -1,86 +0,0 @@
<form *ngIf="control" [formGroup]="control">
<ng-container formGroupName="organisation">
<ui-form-control label="Firmenname" requiredMark="*">
<input uiInput type="text" formControlName="name" tabindex="1" />
</ui-form-control>
<div class="control-row">
<ui-form-control label="Abteilung" [clearable]="false">
<input uiInput type="text" formControlName="department" tabindex="2" />
</ui-form-control>
<ui-form-control label="USt-ID" [clearable]="false">
<input uiInput type="text" formControlName="vatId" tabindex="3" />
</ui-form-control>
</div>
</ng-container>
<div class="control-row">
<ui-form-control label="Anrede" [clearable]="false">
<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 label="Titel">
<ui-select formControlName="title" tabindex="5">
<ui-select-option value="Dr." label="Dr."></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>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Nachname" [clearable]="false">
<input uiInput type="text" formControlName="lastName" tabindex="6" />
</ui-form-control>
<ui-form-control label="Vorname" [clearable]="false">
<input uiInput type="text" formControlName="firstName" tabindex="7" />
</ui-form-control>
</div>
<ng-container formGroupName="address">
<div class="control-row">
<ui-form-control label="Straße" requiredMark="*">
<input uiInput type="text" formControlName="street" tabindex="8" />
</ui-form-control>
<ui-form-control label="Hausnummer" requiredMark="*">
<input uiInput type="text" formControlName="streetNumber" tabindex="9" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ" requiredMark="*">
<input uiInput type="text" formControlName="zipCode" tabindex="10" />
</ui-form-control>
<ui-form-control label="Ort" requiredMark="*">
<input uiInput type="text" formControlName="city" tabindex="11" />
</ui-form-control>
</div>
<ui-form-control label="Adresszusatz" [clearable]="false">
<input uiInput type="text" formControlName="info" tabindex="12" />
</ui-form-control>
<ui-form-control label="Land" [clearable]="false">
<ui-select formControlName="country" tabindex="13">
<ui-select-option value="DEU" label="Deutschland"></ui-select-option>
</ui-select>
</ui-form-control>
</ng-container>
<ng-container formGroupName="communicationDetails">
<ui-form-control label="E-Mail" [clearable]="false">
<input uiInput type="mail" formControlName="email" tabindex="14" />
</ui-form-control>
<div class="control-row">
<ui-form-control label="Festnetznummer">
<input uiInput type="tel" formControlName="phone" tabindex="15" />
</ui-form-control>
<ui-form-control label="Mobilnummer" [clearable]="false">
<input uiInput type="tel" formControlName="mobile" tabindex="16" />
</ui-form-control>
</div>
</ng-container>
</form>

View File

@@ -1,7 +0,0 @@
.control-row {
@apply flex flex-row gap-8;
ui-form-control {
width: 50%;
}
}

View File

@@ -1,49 +0,0 @@
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { CustomerDTO } from '@swagger/crm';
@Component({
selector: 'page-create-b2b',
templateUrl: 'create-b2b-form.component.html',
styleUrls: ['create-b2b-form.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CreateB2BComponent {
@Input()
customer: CustomerDTO;
control: FormGroup;
constructor(private fb: FormBuilder) {
this.initForm();
}
initForm() {
const { fb } = this;
this.control = fb.group({
organisation: fb.group({
name: fb.control('', [Validators.required]),
department: fb.control(''),
vatId: fb.control(''),
}),
gender: fb.control(0),
title: fb.control(''),
firstName: fb.control(''),
lastName: fb.control(''),
address: fb.group({
street: fb.control('', [Validators.required]),
streetNumber: fb.control('', [Validators.required]),
zipCode: fb.control('', [Validators.required]),
city: fb.control('', [Validators.required]),
info: fb.control(''),
country: fb.control('DEU', [Validators.required]),
}),
communicationDetails: fb.group({
email: fb.control(''),
phone: fb.control(''),
mobile: fb.control(''),
}),
});
}
}

View File

@@ -0,0 +1,105 @@
<div class="card">
<h1>Kundendaten erfassen</h1>
<p>
Für eine B2B Bestellung benötigen Sie<br />
einen Firmenaccount. Wir legen diesen<br />
gerne direkt für Sie an.
</p>
<page-customer-type-selector [(ngModel)]="type" (ngModelChange)="setType($event)"></page-customer-type-selector>
<div class="router-outlet-wrapper">
<form *ngIf="control" [formGroup]="control" (ngSubmit)="submit()">
<ng-container formGroupName="organisation">
<ui-form-control label="Firmenname" requiredMark="*">
<input uiInput type="text" formControlName="name" tabindex="1" />
</ui-form-control>
<div class="control-row">
<ui-form-control label="Abteilung" [clearable]="false">
<input uiInput type="text" formControlName="department" tabindex="2" />
</ui-form-control>
<ui-form-control label="USt-ID" [clearable]="false">
<input uiInput type="text" formControlName="vatId" tabindex="3" />
</ui-form-control>
</div>
</ng-container>
<div class="control-row">
<ui-form-control label="Anrede" [clearable]="false">
<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 label="Titel">
<ui-select formControlName="title" tabindex="5">
<ui-select-option value="Dr." label="Dr."></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>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Vorname" [clearable]="false">
<input uiInput type="text" formControlName="firstName" tabindex="6" />
</ui-form-control>
<ui-form-control label="Nachname" [clearable]="false">
<input uiInput type="text" formControlName="lastName" tabindex="7" />
</ui-form-control>
</div>
<ng-container formGroupName="address">
<div class="control-row">
<ui-form-control label="Straße" requiredMark="*">
<input uiInput type="text" formControlName="street" tabindex="8" />
</ui-form-control>
<ui-form-control label="Hausnummer" requiredMark="*">
<input uiInput type="text" formControlName="streetNumber" tabindex="9" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ" requiredMark="*">
<input uiInput type="text" formControlName="zipCode" tabindex="10" />
</ui-form-control>
<ui-form-control label="Ort" requiredMark="*">
<input uiInput type="text" formControlName="city" tabindex="11" />
</ui-form-control>
</div>
<ui-form-control label="Adresszusatz" [clearable]="false">
<input uiInput type="text" formControlName="info" tabindex="12" />
</ui-form-control>
<ui-form-control label="Land" [clearable]="false" requiredMark="*">
<ui-select formControlName="country" tabindex="13">
<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="E-Mail" [clearable]="false">
<input uiInput type="mail" formControlName="email" tabindex="14" />
</ui-form-control>
<div class="control-row">
<ui-form-control label="Festnetznummer">
<input uiInput type="tel" formControlName="phone" tabindex="15" />
</ui-form-control>
<ui-form-control label="Mobilnummer" [clearable]="false">
<input uiInput type="tel" formControlName="mobile" tabindex="16" />
</ui-form-control>
</div>
</ng-container>
<div class="center">
<button class="create-customer-submit" type="submit" [disabled]="control.invalid">Speichern</button>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,68 @@
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { CrmCustomerService } from '@domain/crm';
import { UiModalService } from '@ui/modal';
import { CustomerCreateComponentBase } from './customer-create.component';
@Component({
selector: 'page-customer-create-b2b',
templateUrl: 'customer-create-b2b.component.html',
styleUrls: ['customer-create.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCreateB2BComponent extends CustomerCreateComponentBase implements OnInit {
constructor(
public activatedRoute: ActivatedRoute,
public router: Router,
private fb: FormBuilder,
public modal: UiModalService,
public customerService: CrmCustomerService
) {
super();
}
ngOnInit(): void {
this.init();
}
createControl(): FormGroup {
const fb = this.fb;
return fb.group({
organisation: fb.group({
name: fb.control('', [Validators.required]),
department: fb.control(''),
vatId: fb.control(''),
}),
gender: fb.control(0),
title: fb.control(''),
firstName: fb.control(''),
lastName: fb.control(''),
address: fb.group({
street: fb.control('', [Validators.required]),
streetNumber: fb.control('', [Validators.required]),
zipCode: fb.control('', [Validators.required]),
city: fb.control('', [Validators.required]),
info: fb.control(''),
country: fb.control('DEU', [Validators.required]),
}),
communicationDetails: fb.group({
email: fb.control(''),
phone: fb.control(''),
mobile: fb.control(''),
}),
});
}
async submit(): Promise<void> {
if (this.control.invalid) {
return;
}
let address = await this.validateAddress(this.control.value.address);
if (address) {
this.control.patchValue({ address });
}
}
}

View File

@@ -0,0 +1,97 @@
<div class="card">
<h1>Kundendaten erfassen</h1>
<p>
TEXT FÜR FILIALKUNDE(MINIMALKUNDE)
</p>
<page-customer-type-selector [(ngModel)]="type" (ngModelChange)="setType($event)"></page-customer-type-selector>
<div class="router-outlet-wrapper">
<form *ngIf="control" [formGroup]="control" (ngSubmit)="submit()">
<div class="control-row">
<ui-form-control label="Anrede">
<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 label="Titel">
<ui-select formControlName="title" tabindex="2">
<ui-select-option value="Dr." label="Dr."></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>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Vorname" requiredMark="*">
<input uiInput type="text" formControlName="firstName" tabindex="3" />
</ui-form-control>
<ui-form-control label="Nachname" requiredMark="*">
<input uiInput type="text" formControlName="lastName" tabindex="4" />
</ui-form-control>
</div>
<ng-container formGroupName="communicationDetails">
<ui-form-control label="E-Mail" [clearable]="false">
<input uiInput type="mail" formControlName="email" tabindex="5" />
</ui-form-control>
</ng-container>
<ng-container formGroupName="organisation">
<ui-form-control label="Firmenname">
<input uiInput type="text" formControlName="name" tabindex="6" />
</ui-form-control>
</ng-container>
<ng-container formGroupName="address">
<div class="control-row">
<ui-form-control label="Straße">
<input uiInput type="text" formControlName="street" tabindex="7" />
</ui-form-control>
<ui-form-control label="Hausnummer">
<input uiInput type="text" formControlName="streetNumber" tabindex="8" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ">
<input uiInput type="text" formControlName="zipCode" tabindex="9" />
</ui-form-control>
<ui-form-control label="Ort">
<input uiInput type="text" formControlName="city" tabindex="10" />
</ui-form-control>
</div>
<ui-form-control label="Land" [clearable]="false">
<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">
<div class="control-row">
<ui-form-control label="Festnetznummer">
<input uiInput type="tel" formControlName="phone" tabindex="12" />
</ui-form-control>
<ui-form-control label="Mobilnummer" [clearable]="false">
<input uiInput type="tel" formControlName="mobile" tabindex="13" />
</ui-form-control>
</div>
</ng-container>
<ui-form-control label="Geburtsdatum (TT.MM.JJJJ)">
<input uiInput type="text" formControlName="dateOfBirth" tabindex="14" />
</ui-form-control>
<div class="center">
<button class="create-customer-submit" type="submit" [disabled]="control.invalid">Speichern</button>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,80 @@
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { CrmCustomerService } from '@domain/crm';
import { CustomerDTO } from '@swagger/crm';
import { UiModalService } from '@ui/modal';
import { CustomerCreateGuestComponent } from './customer-create-guest.component';
import { CustomerCreateComponentBase } from './customer-create.component';
@Component({
selector: 'page-customer-create-branch',
templateUrl: 'customer-create-branch.component.html',
styleUrls: ['customer-create.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCreateBranchComponent extends CustomerCreateComponentBase implements OnInit {
constructor(
public activatedRoute: ActivatedRoute,
public router: Router,
private fb: FormBuilder,
public modal: UiModalService,
public customerService: CrmCustomerService
) {
super();
}
ngOnInit() {
this.init();
}
createControl(): FormGroup {
const fb = this.fb;
return fb.group({
organisation: fb.group({
name: fb.control(''),
}),
gender: fb.control(0),
title: fb.control(''),
firstName: fb.control('', [Validators.required]),
lastName: fb.control('', [Validators.required]),
dateOfBirth: fb.control('', [CustomerCreateGuestComponent.dateOfBirthValidator]),
address: fb.group({
street: fb.control(''),
streetNumber: fb.control(''),
zipCode: fb.control(''),
city: fb.control(''),
country: fb.control('DEU'),
}),
communicationDetails: fb.group({
email: fb.control(''),
phone: fb.control(''),
mobile: fb.control(''),
}),
});
}
async submit(): Promise<void> {
if (this.control.invalid) {
return;
}
let address = await this.validateAddress(this.control.value.address);
if (address) {
this.control.patchValue({ address });
}
try {
const response = await this.customerService.createBranchCustomer(this.control.value).toPromise();
if (response.error) {
throw new Error(response.message);
} else {
this.router.navigate(['/customer', response.result.id]);
}
} catch (error) {
console.error(error);
}
}
}

View File

@@ -0,0 +1,104 @@
<div class="card">
<h1>Kundendaten erfassen</h1>
<p>
Wenn Sie möchten legen wir Ihnen <br />
gerne ein Onlinekonto an. Dort können <br />
Sie Ihre Bestellungen einsehen.
</p>
<page-customer-type-selector [(ngModel)]="type" (ngModelChange)="setType($event)"></page-customer-type-selector>
<div class="router-outlet-wrapper">
<form *ngIf="control" [formGroup]="control" (ngSubmit)="submit()">
<div class="control-row">
<ui-form-control label="Anrede" requiredMark="*">
<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 label="Titel">
<ui-select formControlName="title" tabindex="2">
<ui-select-option value="Dr." label="Dr."></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>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Vorname" requiredMark="*">
<input uiInput type="text" formControlName="firstName" tabindex="3" />
</ui-form-control>
<ui-form-control label="Nachname" requiredMark="*">
<input uiInput type="text" formControlName="lastName" tabindex="4" />
</ui-form-control>
</div>
<p class="bold">
Wir werden Ihnen Werbung zu ähnlichen Produkten oder Dienstleistungen aus unserem Sortiment per E-Mail zusenden. Sie können der
Verwendung Ihrer Daten jederzeit z.B. mittels der in den E-Mails enthaltenen Abmeldelinks widersprechen, ohne dass hierfür andere
als die Übermittlungskosten nach den Basistarifen entstehen.
</p>
<ng-container formGroupName="communicationDetails">
<ui-form-control label="E-Mail" requiredMark="*">
<input uiInput type="mail" formControlName="email" tabindex="5" />
</ui-form-control>
</ng-container>
<ng-container formGroupName="organisation">
<ui-form-control label="Firmenname">
<input uiInput type="text" formControlName="name" tabindex="6" />
</ui-form-control>
</ng-container>
<ng-container formGroupName="address">
<div class="control-row">
<ui-form-control label="Straße" requiredMark="*">
<input uiInput type="text" formControlName="street" tabindex="7" />
</ui-form-control>
<ui-form-control label="Hausnummer" requiredMark="*">
<input uiInput type="text" formControlName="streetNumber" tabindex="8" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ" requiredMark="*">
<input uiInput type="text" formControlName="zipCode" tabindex="9" />
</ui-form-control>
<ui-form-control label="Ort" requiredMark="*">
<input uiInput type="text" formControlName="city" tabindex="10" />
</ui-form-control>
</div>
<ui-form-control label="Land" requiredMark="*">
<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">
<div class="control-row">
<ui-form-control label="Festnetznummer">
<input uiInput type="tel" formControlName="phone" tabindex="12" />
</ui-form-control>
<ui-form-control label="Mobilnummer" [clearable]="false">
<input uiInput type="tel" formControlName="mobile" tabindex="13" />
</ui-form-control>
</div>
</ng-container>
<ui-form-control label="Geburtsdatum (TT.MM.JJJJ)">
<input type="text" formControlName="dateOfBirth" pagesDateOfBirth tabindex="14" />
</ui-form-control>
<div class="center">
<button class="create-customer-submit" type="submit" [disabled]="control.invalid">Speichern</button>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,78 @@
import { Component, ChangeDetectionStrategy, OnInit, ChangeDetectorRef } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { CrmCustomerService } from '@domain/crm';
import { UiModalService } from '@ui/modal';
import { CustomerCreateComponentBase } from './customer-create.component';
@Component({
selector: 'page-customer-create-guest',
templateUrl: 'customer-create-guest.component.html',
styleUrls: ['customer-create.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCreateGuestComponent extends CustomerCreateComponentBase implements OnInit {
constructor(
public activatedRoute: ActivatedRoute,
public router: Router,
private fb: FormBuilder,
public modal: UiModalService,
public customerService: CrmCustomerService
) {
super();
}
ngOnInit(): void {
this.init();
}
createControl(): FormGroup {
const { fb } = this;
return fb.group({
organisation: fb.group({
name: fb.control(''),
}),
gender: fb.control(0, [Validators.required]),
title: fb.control(''),
firstName: fb.control('', [Validators.required]),
lastName: fb.control('', [Validators.required]),
dateOfBirth: fb.control('', [CustomerCreateGuestComponent.dateOfBirthValidator]),
address: fb.group({
street: fb.control('', [Validators.required]),
streetNumber: fb.control('', [Validators.required]),
zipCode: fb.control('', [Validators.required]),
city: fb.control('', [Validators.required]),
country: fb.control('DEU', [Validators.required]),
}),
communicationDetails: fb.group({
email: fb.control('', [Validators.required, Validators.email]),
phone: fb.control(''),
mobile: fb.control(''),
}),
});
}
async submit(): Promise<void> {
if (this.control.invalid) {
return;
}
let address = await this.validateAddress(this.control.value.address);
if (address) {
this.control.patchValue({ address });
}
try {
const response = await this.customerService.createGuestCustomer(this.control.value).toPromise();
if (response.error) {
throw new Error(response.message);
} else {
this.router.navigate(['/customer', response.result.id]);
}
} catch (error) {
console.error(error);
}
}
}

View File

@@ -0,0 +1,101 @@
<div class="card">
<h1>Kundendaten erfassen</h1>
<p>
Wenn Sie möchten legen wir Ihnen <br />
gerne ein Onlinekonto an. Dort können <br />
Sie Ihre Bestellungen einsehen.
</p>
<page-customer-type-selector [(ngModel)]="type" (ngModelChange)="setType($event)"></page-customer-type-selector>
<div class="router-outlet-wrapper">
<form *ngIf="control" [formGroup]="control">
<div class="control-row">
<ui-form-control label="Anrede" requiredMark="*">
<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 label="Titel">
<ui-select formControlName="title" tabindex="2">
<ui-select-option value="Dr." label="Dr."></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>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Vorname" requiredMark="*">
<input uiInput type="text" formControlName="firstName" tabindex="3" />
</ui-form-control>
<ui-form-control label="Nachname" requiredMark="*">
<input uiInput type="text" formControlName="lastName" tabindex="4" />
</ui-form-control>
</div>
<ng-container formGroupName="communicationDetails">
<ui-form-control label="E-Mail" requiredMark="*">
<input uiInput type="mail" formControlName="email" tabindex="5" />
</ui-form-control>
</ng-container>
<ng-container formGroupName="organisation">
<ui-form-control label="Firmenname">
<input uiInput type="text" formControlName="name" tabindex="6" />
</ui-form-control>
</ng-container>
<ng-container formGroupName="address">
<div class="control-row">
<ui-form-control label="Straße" requiredMark="*">
<input uiInput type="text" formControlName="street" tabindex="7" />
</ui-form-control>
<ui-form-control label="Hausnummer" requiredMark="*">
<input uiInput type="text" formControlName="streetNumber" tabindex="8" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ" requiredMark="*">
<input uiInput type="text" formControlName="zipCode" tabindex="9" />
</ui-form-control>
<ui-form-control label="Ort" requiredMark="*">
<input uiInput type="text" formControlName="city" tabindex="10" />
</ui-form-control>
</div>
<ui-form-control label="Land" [clearable]="false" requiredMark="*">
<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>
<p class="bold">Das Anlegen geht für Sie noch schneller, wenn wir Ihnen das initiale Passwort per SMS auf Ihr Mobilgerät schicken.</p>
<ng-container formGroupName="communicationDetails">
<div class="control-row">
<ui-form-control label="Festnetznummer">
<input uiInput type="tel" formControlName="phone" tabindex="12" />
</ui-form-control>
<ui-form-control label="Mobilnummer" [clearable]="false">
<input uiInput type="tel" formControlName="mobile" tabindex="13" />
</ui-form-control>
</div>
</ng-container>
<ui-form-control label="Geburtsdatum (TT.MM.JJJJ)">
<input pagesDateOfBirth type="text" formControlName="dateOfBirth" tabindex="14" />
</ui-form-control>
<div class="center">
<button class="create-customer-submit" type="submit" [disabled]="control.invalid">Speichern</button>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,77 @@
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { CrmCustomerService } from '@domain/crm';
import { UiModalService } from '@ui/modal';
import { CustomerCreateComponentBase } from './customer-create.component';
@Component({
selector: 'customer-create-online',
templateUrl: 'customer-create-online.component.html',
styleUrls: ['customer-create.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCreateOnlineComponent extends CustomerCreateComponentBase implements OnInit {
constructor(
public activatedRoute: ActivatedRoute,
public router: Router,
private fb: FormBuilder,
public modal: UiModalService,
public customerService: CrmCustomerService
) {
super();
}
ngOnInit(): void {
this.init();
}
createControl(): FormGroup {
const { fb } = this;
return fb.group({
organisation: fb.group({
name: fb.control(''),
}),
gender: fb.control(0, [Validators.required]),
title: fb.control(''),
firstName: fb.control('', [Validators.required]),
lastName: fb.control('', [Validators.required]),
dateOfBirth: fb.control('', [CustomerCreateComponentBase.dateOfBirthValidator]),
address: fb.group({
street: fb.control('', [Validators.required]),
streetNumber: fb.control('', [Validators.required]),
zipCode: fb.control('', [Validators.required]),
city: fb.control('', [Validators.required]),
country: fb.control('DEU', [Validators.required]),
}),
communicationDetails: fb.group({
email: fb.control('', [Validators.required, Validators.email], [this.emailExistsValidator]),
phone: fb.control(''),
mobile: fb.control(''),
}),
});
}
async submit(): Promise<void> {
if (this.control.invalid) {
return;
}
let address = await this.validateAddress(this.control.value.address);
if (address) {
this.control.patchValue({ address });
}
try {
const response = await this.customerService.createOnlineCustomer(this.control.value).toPromise();
if (response.error) {
throw new Error(response.message);
} else {
this.router.navigate(['/customer', response.result.id]);
}
} catch (error) {
console.error(error);
}
}
}

View File

@@ -1,14 +0,0 @@
<div class="card">
<h1>Kundendaten erfassen</h1>
<p>
Für eine B2B Bestellung benötigen Sie <br />
einen Firmenaccount. Wir legen diesen <br />
gerne direkt für Sie an.
</p>
<page-customer-type-selector [(ngModel)]="type" (ngModelChange)="setType($event)"></page-customer-type-selector>
<div class="router-outlet-wrapper">
<router-outlet></router-outlet>
</div>
</div>

View File

@@ -30,4 +30,32 @@ page-customer-type-selector {
.router-outlet-wrapper {
max-width: 650px;
@apply mx-auto;
p {
@apply text-regular mt-8;
}
}
.control-row {
@apply flex flex-row gap-8;
ui-form-control {
width: 50%;
}
}
.center {
@apply text-center;
}
.create-customer-submit {
@apply border-none outline-none bg-brand text-white font-bold text-cta-l px-px-25 py-px-15 rounded-full my-8;
&:disabled {
@apply bg-inactive-branch;
}
}
.bold {
@apply font-semibold;
}

View File

@@ -1,26 +1,83 @@
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { AsyncValidatorFn, FormGroup, ValidationErrors, ValidatorFn } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { CrmCustomerService } from '@domain/crm';
import { AddressDTO, CountryDTO } from '@swagger/crm';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { UiModalService } from '@ui/modal';
import { AddressSelectionModalComponent } from '../modals/address-selection-modal.component';
export abstract class CustomerCreateComponentBase {
static dateOfBirthValidator: ValidatorFn = (control): ValidationErrors | null => {
if (control.value) {
const match = /^(\d{4})(-(\d{2})){2}T((\d{2})(:|.)){3}(\d{3})Z$/g.test(control.value);
if (!match) {
return { dateOfBirth: 'Geburtsdatum ist ungültig' };
}
}
return null;
};
emailExistsValidator: AsyncValidatorFn = async (control): Promise<ValidationErrors | null> => {
if (control.value) {
if ((await this.customerService.emailExists(control.value).toPromise())?.result) {
return { exists: 'E-Mail existiert bereits' };
}
}
return null;
};
@Component({
selector: 'page-customer-create',
templateUrl: 'customer-create.component.html',
styleUrls: ['customer-create.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCreateComponent implements OnInit {
type: string;
control: FormGroup;
constructor(private activatedRoute: ActivatedRoute, private router: Router) {}
countries$: Observable<CountryDTO[]>;
ngOnInit() {
this.type = this.activatedRoute.snapshot.queryParams.type || 'guest';
abstract activatedRoute: ActivatedRoute;
abstract router: Router;
abstract modal: UiModalService;
abstract customerService: CrmCustomerService;
init() {
this.type = this.activatedRoute.snapshot?.routeConfig?.path;
this.control = this.createControl();
this.countries$ = this.customerService.getCountries().pipe(map((response) => response.result));
}
setType(type: string) {
this.router.navigate(['./', type], {
this.router.navigate(['../', type], {
relativeTo: this.activatedRoute,
});
}
abstract createControl(): FormGroup;
abstract submit(): Promise<void>;
async validateAddress(address: AddressDTO): Promise<AddressDTO> {
if (address.street && address.streetNumber && address.zipCode && address.city && address.country) {
try {
let addresses = await this.customerService
.validateAddress(address)
.pipe(
map((response) =>
response.result.map((ad) => ({
...address,
...ad,
}))
)
)
.toPromise();
if (addresses?.length > 0) {
const modalResult = await this.modal.open({ content: AddressSelectionModalComponent, data: addresses }).afterClosed$.toPromise();
if (modalResult?.data) {
return modalResult.data;
}
}
} catch (error) {}
}
return undefined;
}
}

View File

@@ -1,18 +1,38 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CustomerCreateComponent } from './customer-create.component';
import { CustomerTypeSelectorComponent } from './customer-type-selector/customer-type-selector.component';
import { UiFormControlModule } from '@ui/form-control';
import { UiInputModule } from '@ui/input';
import { CreateB2BComponent } from './create-b2b-form/create-b2b-form.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { UiSelectModule } from '@ui/select';
import { CustomerCreateBranchComponent } from './customer-create-branch.component';
import { CustomerCreateGuestComponent } from './customer-create-guest.component';
import { CustomerCreateOnlineComponent } from './customer-create-online.component';
import { CustomerCreateB2BComponent } from './customer-create-b2b.component';
import { DateOfBirthValueAccessorDirective } from './value-accessors/json-date.value-accessor';
import { AddressSelectionModalModule } from '../modals/address-selection-modal.module';
@NgModule({
imports: [CommonModule, UiFormControlModule, UiInputModule, FormsModule, ReactiveFormsModule, RouterModule, UiSelectModule],
exports: [CustomerCreateComponent, CreateB2BComponent],
declarations: [CustomerCreateComponent, CustomerTypeSelectorComponent, CreateB2BComponent],
imports: [
CommonModule,
UiFormControlModule,
UiInputModule,
FormsModule,
ReactiveFormsModule,
RouterModule,
UiSelectModule,
AddressSelectionModalModule,
],
exports: [CustomerCreateBranchComponent, CustomerCreateGuestComponent, CustomerCreateOnlineComponent, CustomerCreateB2BComponent],
declarations: [
CustomerCreateBranchComponent,
CustomerTypeSelectorComponent,
CustomerCreateGuestComponent,
CustomerCreateOnlineComponent,
CustomerCreateB2BComponent,
DateOfBirthValueAccessorDirective,
],
})
export class CustomerCreateModule {}

View File

@@ -0,0 +1,125 @@
import { Directive, ElementRef, forwardRef, HostBinding, HostListener, Input, Renderer2 } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { UiFormControlDirective } from '@ui/form-control';
@Directive({
selector: 'input[pagesDateOfBirth]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DateOfBirthValueAccessorDirective),
multi: true,
},
{
provide: UiFormControlDirective,
useExisting: DateOfBirthValueAccessorDirective,
},
],
})
export class DateOfBirthValueAccessorDirective extends UiFormControlDirective<any> implements ControlValueAccessor {
@Input()
@HostBinding('attr.type')
type: string;
get valueEmpty(): boolean {
return !!this.value;
}
readonly today = new Date();
value: string;
private onChange = (_: any) => {};
private onTouched = () => {};
constructor(private elementRef: ElementRef, private renderer: Renderer2) {
super();
}
writeValue(obj: string | Date): void {
this.setValue(obj, false);
this.renderInputValue();
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
@HostListener('keyup', ['$event.target.value'])
setValue(value: string | Date, emitEvent = true) {
if (value instanceof Date) {
this.value = this.format(value);
} else {
this.value = value;
}
if (emitEvent) {
const dateStruct = this.getDateStruct(this.value);
if (dateStruct) {
this.onChange(this.parseDateStruct(dateStruct).toJSON());
} else {
this.onChange(this.value);
}
}
this.onTouched();
}
getDateStruct(stringDate: string): { date: number; month: number; year: number } | undefined {
if (!!stringDate) {
const parts = stringDate.trim().split('.');
if (parts.length === 3) {
const date = +parts[0];
const month = +parts[1];
const year = +parts[2];
if (date >= 1 && date <= 31 && month >= 1 && month <= 12 && year >= 1900 && year <= this.today.getFullYear()) {
return { date, month, year };
}
}
}
return undefined;
}
parseDateStruct({ year, month, date }: { date: number; month: number; year: number }) {
return new Date(year, month - 1, date);
}
format(date: Date): string {
if (date) {
return `${date.getDate()}.${date.getMonth() + 1},${date.getFullYear()}`;
}
return '';
}
renderInputValue() {
this.renderer.setAttribute(this.elementRef.nativeElement, 'value', this.value);
}
clear(): void {
this.setValue(undefined);
this.renderInputValue();
}
focus(): void {
setTimeout(() => {
this.elementRef?.nativeElement?.click()?.focus();
}, 0);
}
@HostListener('focus')
onFocus() {
this.focused.emit(true);
}
@HostListener('blur')
onBlur() {
this.onTouched();
this.focused.emit(false);
}
}

View File

@@ -0,0 +1,21 @@
<div class="wrapper">
<div class="actions">
<button class="close-btn" (click)="ref.close()">
<ui-icon icon="close" size="21px"></ui-icon>
</button>
</div>
<h2>Bitte überprüfen Sie die eingegebenen Adressdaten</h2>
<p>Vorschläge:</p>
<ul class="content">
<li *ngFor="let item of ref?.data">
<span>{{ item.street }} {{ item.streetNumber }}, {{ item.zipCode }} {{ item.city }}</span>
<button (click)="ref.close(item)">Übernehmen</button>
</li>
</ul>
<div class="center">
<button class="select-btn" (click)="ref.close()">Eingegebene Adresse übernehmen</button>
</div>
</div>

View File

@@ -0,0 +1,44 @@
:host {
@apply bg-white;
}
.actions {
@apply flex flex-row justify-end;
}
.close-btn {
@apply bg-transparent border-none text-ucla-blue;
}
h2,
p {
@apply text-center text-regular;
}
h2 {
@apply font-bold text-card-sub;
}
ul {
@apply list-none text-regular -mx-4 p-0 mt-px-35;
li {
@apply flex flex-row items-center justify-between border-glitter border-t-4 border-b-0 border-solid border-r-0 border-l-0 py-px-15 px-px-25;
&:last-child {
@apply border-b-4;
}
button {
@apply border-none outline-none bg-transparent text-brand text-cta-l text-right font-bold;
}
}
}
.center {
@apply text-center;
}
.select-btn {
@apply border-none outline-none bg-brand text-white font-bold text-cta-l px-px-25 py-px-15 rounded-full my-8;
}

View File

@@ -0,0 +1,13 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { AddressDTO } from '@swagger/crm';
import { UiModalRef } from 'apps/ui/modal/src/lib/defs';
@Component({
selector: 'page-address-selection-modal',
templateUrl: 'address-selection-modal.component.html',
styleUrls: ['address-selection-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AddressSelectionModalComponent {
constructor(public ref: UiModalRef<AddressDTO[]>) {}
}

View File

@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AddressSelectionModalComponent } from './address-selection-modal.component';
import { UiIconModule } from '@ui/icon';
@NgModule({
imports: [CommonModule, UiIconModule],
exports: [AddressSelectionModalComponent],
declarations: [AddressSelectionModalComponent],
})
export class AddressSelectionModalModule {}

View File

@@ -1,7 +1,9 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CreateB2BComponent } from './customer-create/create-b2b-form/create-b2b-form.component';
import { CustomerCreateComponent } from './customer-create/customer-create.component';
import { CustomerCreateB2BComponent } from './customer-create/customer-create-b2b.component';
import { CustomerCreateBranchComponent } from './customer-create/customer-create-branch.component';
import { CustomerCreateGuestComponent } from './customer-create/customer-create-guest.component';
import { CustomerCreateOnlineComponent } from './customer-create/customer-create-online.component';
import { CustomerDetailsComponent } from './customer-details/customer-details.component';
import { CustomerSearchComponent } from './customer-search/customer-search.component';
import { CustomerSearchMainComponent } from './customer-search/search-main/search-main.component';
@@ -24,11 +26,12 @@ const routes: Routes = [
},
{
path: 'create',
component: CustomerCreateComponent,
children: [
{ path: 'guest', component: CreateB2BComponent },
{ path: 'online', component: CreateB2BComponent },
{ path: 'b2b', component: CreateB2BComponent },
{ path: 'branch', component: CustomerCreateBranchComponent },
{ path: 'online', component: CustomerCreateOnlineComponent },
{ path: 'b2b', component: CustomerCreateB2BComponent },
{ path: 'guest', component: CustomerCreateGuestComponent },
{ path: '', pathMatch: 'full', redirectTo: 'branch' },
],
},
{

View File

@@ -1,3 +1,5 @@
<shell-breadcrumb [key]="application.activatedProcessId$ | async"></shell-breadcrumb>
<router-outlet></router-outlet>
<div class="content-container">
<router-outlet></router-outlet>
</div>

View File

@@ -2,3 +2,8 @@ shell-breadcrumb {
margin-top: -5px;
@apply mb-px-10 pb-px-10;
}
.content-container {
max-height: calc(100vh - 267px);
overflow: scroll;
}

View File

@@ -57,6 +57,7 @@ import { OverlaysModule } from './core/overlay/overlays.module';
import { DateAdapter, NativeDateAdapter } from '@isa-ui/core/date';
import { CoreBreadcrumbModule } from '@core/breadcrumb';
import { CoreApplicationModule } from '@core/application';
import { UiModalModule } from '@ui/modal';
registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);
@@ -123,6 +124,11 @@ export function remissionModuleOptionsFactory(config: AppConfiguration): Remissi
*/
CoreBreadcrumbModule.forRoot(),
CoreApplicationModule.forRoot(),
/**
* @ui Modules
*/
UiModalModule.forRoot(),
],
providers: [
{

View File

@@ -6,10 +6,21 @@ export { LesepunkteRequest } from './models/lesepunkte-request';
export { ListResponseArgsOfItemDTO } from './models/list-response-args-of-item-dto';
export { ResponseArgsOfIEnumerableOfItemDTO } from './models/response-args-of-ienumerable-of-item-dto';
export { ItemDTO } from './models/item-dto';
export { ItemType } from './models/item-type';
export { ProductDTO } from './models/product-dto';
export { SizeOfString } from './models/size-of-string';
export { WeightOfAvoirdupois } from './models/weight-of-avoirdupois';
export { Avoirdupois } from './models/avoirdupois';
export { SpecDTO } from './models/spec-dto';
export { TextDTO } from './models/text-dto';
export { ImageDTO } from './models/image-dto';
export { AvailabilityDTO } from './models/availability-dto';
export { ShopDTO } from './models/shop-dto';
export { PriceDTO } from './models/price-dto';
export { PriceValueDTO } from './models/price-value-dto';
export { VATValueDTO } from './models/vatvalue-dto';
export { VATType } from './models/vattype';
export { AvailabilityType } from './models/availability-type';
export { StockInfoDTO } from './models/stock-info-dto';
export { ShelfInfoDTO } from './models/shelf-info-dto';
export { KeyValueDTOOfStringAndString } from './models/key-value-dtoof-string-and-string';
@@ -17,6 +28,7 @@ export { ReviewDTO } from './models/review-dto';
export { EntityDTO } from './models/entity-dto';
export { EntityStatus } from './models/entity-status';
export { QueryTokenDTO } from './models/query-token-dto';
export { CatalogType } from './models/catalog-type';
export { OrderByDTO } from './models/order-by-dto';
export { ListResponseArgsOfAutocompleteDTO } from './models/list-response-args-of-autocomplete-dto';
export { ResponseArgsOfIEnumerableOfAutocompleteDTO } from './models/response-args-of-ienumerable-of-autocomplete-dto';
@@ -27,6 +39,9 @@ export { ResponseArgsOfUISettingsDTO } from './models/response-args-of-uisetting
export { UISettingsDTO } from './models/uisettings-dto';
export { InputGroupDTO } from './models/input-group-dto';
export { InputDTO } from './models/input-dto';
export { InputType } from './models/input-type';
export { InputOptionsDTO } from './models/input-options-dto';
export { OptionDTO } from './models/option-dto';
export { TranslationDTO } from './models/translation-dto';
export { ResponseArgsOfIEnumerableOfQueryTokenDTO } from './models/response-args-of-ienumerable-of-query-token-dto';
export { ResponseArgsOfIEnumerableOfStockInfoDTO } from './models/response-args-of-ienumerable-of-stock-info-dto';

View File

@@ -5,16 +5,16 @@
*/
export interface AvailabilityDTO {
/**
* Stock Status Code / Beschreibung
*/
sscText?: string;
/**
* Produkt / Artikel PK
*/
itemId?: number;
/**
* Shop
*/
shop?: any;
/**
* Preis (VK)
*/
@@ -31,9 +31,9 @@ export interface AvailabilityDTO {
ssc?: string;
/**
* Shop
* Stock Status Code / Beschreibung
*/
shop?: any;
sscText?: string;
/**
* Verfügbare Menge

View File

@@ -0,0 +1,2 @@
/* tslint:disable */
export type AvailabilityType = 0 | 1 | 2 | 32 | 256 | 1024 | 2048 | 4096 | 8192 | 16384;

View File

@@ -0,0 +1,2 @@
/* tslint:disable */
export type Avoirdupois = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096;

View File

@@ -0,0 +1,6 @@
/* tslint:disable */
/**
* Katalogbereich
*/
export type CatalogType = 0 | 1 | 2 | 4;

View File

@@ -0,0 +1,18 @@
/* tslint:disable */
import { OptionDTO } from './option-dto';
/**
* Auswahl
*/
export interface InputOptionsDTO {
/**
* Maximale Anzahl auswählbarer Elemente (null =&gt; alle, 1 = single select)
*/
max?: number;
/**
* Werte
*/
values?: Array<OptionDTO>;
}

View File

@@ -0,0 +1,6 @@
/* tslint:disable */
/**
* Art des Eingabewerts
*/
export type InputType = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 96;

View File

@@ -10,16 +10,16 @@ import { KeyValueDTOOfStringAndString } from './key-value-dtoof-string-and-strin
import { ReviewDTO } from './review-dto';
export interface ItemDTO extends EntityDTO {
/**
* Verfügbarkeit laut Katalog
*/
catalogAvailability?: any;
/**
* Rang
*/
scoring?: number;
/**
* Weitere Artikel-IDs
*/
ids?: {[key: string]: number};
/**
* Artikel / Produkttyp
*/
@@ -56,9 +56,9 @@ export interface ItemDTO extends EntityDTO {
images?: Array<ImageDTO>;
/**
* Weitere Artikel-IDs
* Verfügbarkeit laut Katalog
*/
ids?: {[key: string]: number};
catalogAvailability?: any;
/**
* Verfügbarkeit zur Bestellung in die Filiale

View File

@@ -0,0 +1,2 @@
/* tslint:disable */
export type ItemType = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384 | 32768;

View File

@@ -1,8 +1,11 @@
/* tslint:disable */
export interface KeyValueDTOOfStringAndString {
enabled?: boolean;
label?: string;
key?: string;
value?: string;
selected?: boolean;
description?: string;
sort?: number;
command?: string;
}

View File

@@ -0,0 +1,27 @@
/* tslint:disable */
/**
* Auswahlelement
*/
export interface OptionDTO {
/**
* Key / ID
*/
key?: string;
/**
* Label
*/
label?: string;
/**
* Wert
*/
value?: string;
/**
* Unter-Optionen
*/
values?: Array<OptionDTO>;
}

View File

@@ -0,0 +1,7 @@
/* tslint:disable */
import { PriceValueDTO } from './price-value-dto';
import { VATValueDTO } from './vatvalue-dto';
export interface PriceDTO {
value?: PriceValueDTO;
vat?: VATValueDTO;
}

View File

@@ -0,0 +1,6 @@
/* tslint:disable */
export interface PriceValueDTO {
value?: number;
currency?: string;
currencySymbol?: string;
}

View File

@@ -0,0 +1,22 @@
/* tslint:disable */
import { SizeOfString } from './size-of-string';
import { WeightOfAvoirdupois } from './weight-of-avoirdupois';
export interface ProductDTO {
name?: string;
additionalName?: string;
ean?: string;
supplierProductNumber?: string;
catalogProductNumber?: string;
contributors?: string;
manufacturer?: string;
publicationDate?: string;
productGroup?: string;
edition?: string;
volume?: string;
serial?: string;
format?: string;
formatDetail?: string;
locale?: string;
size?: SizeOfString;
weight?: WeightOfAvoirdupois;
}

View File

@@ -6,16 +6,16 @@ import { OrderByDTO } from './order-by-dto';
*/
export interface QueryTokenDTO {
/**
* Katalogbereich
*/
catalogType?: any;
/**
* Lager PK
*/
stockId?: number;
/**
* Bezeichner
*/
friendlyName?: string;
/**
* Eingabewerte z.B. ("qs", "heller süden")
*/
@@ -32,9 +32,9 @@ export interface QueryTokenDTO {
fuzzy?: number;
/**
* Bezeichner
* Katalogbereich
*/
friendlyName?: string;
catalogType?: any;
/**
* Filter

View File

@@ -0,0 +1,7 @@
/* tslint:disable */
/**
* Shop
*/
export interface ShopDTO {
}

View File

@@ -0,0 +1,7 @@
/* tslint:disable */
export interface SizeOfString {
height: number;
width: number;
length: number;
unit?: string;
}

View File

@@ -0,0 +1,2 @@
/* tslint:disable */
export type VATType = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128;

View File

@@ -0,0 +1,8 @@
/* tslint:disable */
import { VATType } from './vattype';
export interface VATValueDTO {
label?: string;
inPercent?: number;
vatType?: VATType;
value?: number;
}

View File

@@ -0,0 +1,6 @@
/* tslint:disable */
import { Avoirdupois } from './avoirdupois';
export interface WeightOfAvoirdupois {
value: number;
unit: Avoirdupois;
}

View File

@@ -1,6 +1,6 @@
<div class="input-wrapper" [class.empty]="!ngControl.value" [class.focused]="uiControl.focused | async">
<ng-content select="input[uiInput], ui-select"></ng-content>
<label *ngIf="label" (click)="uiControl.focus()">{{ label }}{{ requiredMark }}</label>
<ng-content select="input, ui-select"></ng-content>
<label *ngIf="label" [for]="uiControl.id">{{ label }}{{ requiredMark }}</label>
</div>
<span class="hint" *ngIf="ngControl.touched && ngControl.errors">
{{ ngControl.errors | uiFormControlFirstError: label }}

View File

@@ -16,10 +16,7 @@ button.clear {
@apply font-bold flex-grow;
}
input[type='text'],
input[type='password'],
input[type='tel'],
input[type='mail'] {
input:not([type='radio']):not([type='checkbox']) {
@apply outline-none border-none font-bold text-regular;
&:disabled {
@@ -68,10 +65,7 @@ button.clear {
:host[variant='default'] {
::ng-deep {
input[type='text'],
input[type='password'],
input[type='tel'],
input[type='mail'],
input:not([type='radio']):not([type='checkbox']),
ui-select {
@apply border-t-0 border-l-0 border-r-0 border-solid pt-px-20 pb-px-10;
border-bottom-width: 2px;
@@ -114,3 +108,13 @@ button.clear {
}
}
}
:host[variant='default'] {
&[type='radio'],
&[type='checkbox'] {
label,
input {
@apply cursor-pointer;
}
}
}

View File

@@ -1,7 +1,15 @@
import { Directive, EventEmitter } from '@angular/core';
import { Directive, EventEmitter, HostBinding, Input } from '@angular/core';
@Directive()
export abstract class UiFormControlDirective<T> {
@Input()
@HostBinding('attr.name')
name: string = (Date.now() + Math.random()).toString(32);
@Input()
@HostBinding('attr.id')
id: string = (Date.now() + Math.random()).toString(32);
focused = new EventEmitter<boolean>();
abstract type: string;

View File

@@ -5,13 +5,16 @@ import { ValidationErrors } from '@angular/forms';
})
export class UiFormControlFirstErrorPipe implements PipeTransform {
transform(errors: ValidationErrors, label: string): string {
console.log(errors);
if (errors) {
const error = Object.keys(errors)[0];
switch (error) {
case 'required':
return `${label} wird benötigt`;
case 'email':
return `${label} ist ungültig`;
default:
return errors[error];
}
}
return undefined;

View File

@@ -1,5 +1,5 @@
import { Directive, ElementRef, forwardRef, HostBinding, HostListener, Input, Renderer2, Self } from '@angular/core';
import { ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { ChangeDetectorRef, Directive, ElementRef, forwardRef, HostBinding, HostListener, Input, Renderer2, Self } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { UiFormControlDirective } from '@ui/form-control';
@Directive({
@@ -66,7 +66,7 @@ export class UiInputDirective extends UiFormControlDirective<any> implements Con
focus(): void {
setTimeout(() => {
this.elementRef?.nativeElement?.click().focus();
this.elementRef?.nativeElement?.click()?.focus();
}, 0);
}
@@ -74,8 +74,7 @@ export class UiInputDirective extends UiFormControlDirective<any> implements Con
setValue(value: any, emitEvent = true) {
if (this.value !== value) {
this.currentValue = value;
this.renderer.setAttribute(this.elementRef.nativeElement, 'value', this.value);
this.renderer.setProperty(this.elementRef.nativeElement, 'value', value);
if (emitEvent) {
this.onChange(value);
this.onTouched();

25
apps/ui/modal/README.md Normal file
View File

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

View File

@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../../coverage/ui/modal'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true,
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true,
});
};

View File

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

View File

@@ -0,0 +1,11 @@
{
"name": "@ui/modal",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^10.1.2",
"@angular/core": "^10.1.2"
},
"dependencies": {
"tslib": "^2.0.0"
}
}

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './modal-config';
export * from './modal-ref';
export * from './modal-result';
// end:ng42.barrel

View File

@@ -0,0 +1,5 @@
import { OverlayConfig } from '@angular/cdk/overlay';
export class UiModalConfig extends OverlayConfig {
backdropClose?: boolean;
}

View File

@@ -0,0 +1,24 @@
import { OverlayRef } from '@angular/cdk/overlay';
import { TemplateRef, Type } from '@angular/core';
import { Subject } from 'rxjs';
import { UiModalResult } from './modal-result';
export class UiModalRef<TR = any, TD = any> {
afterClosed$ = new Subject<UiModalResult<TR>>();
constructor(public overlay: OverlayRef, public content: TemplateRef<any> | Type<any>, public data: TD) {}
_close({ type, data }: { type: 'backdropClick' | 'close'; data: TR }): void {
this.overlay.dispose();
this.afterClosed$.next({
type,
data,
});
this.afterClosed$.complete();
}
close(data?: TR): void {
this._close({ type: 'close', data });
}
}

View File

@@ -0,0 +1,4 @@
export interface UiModalResult<T> {
type: 'backdropClick' | 'close';
data: T;
}

View File

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

View File

@@ -0,0 +1,8 @@
<ng-container [ngSwitch]="contentType">
<ng-container *ngSwitchCase="'template'">
<ng-container *ngTemplateOutlet="content; context: context"></ng-container>
</ng-container>
<ng-container *ngSwitchCase="'component'">
<ng-container *ngComponentOutlet="content"></ng-container>
</ng-container>
</ng-container>

View File

@@ -0,0 +1,3 @@
:host {
@apply flex flex-col box-border bg-white p-4 w-full rounded-card;
}

View File

@@ -0,0 +1,35 @@
import { Component, ChangeDetectionStrategy, OnInit, TemplateRef, Type } from '@angular/core';
import { UiModalRef } from './defs/modal-ref';
@Component({
selector: 'ui-modal',
templateUrl: 'modal.component.html',
styleUrls: ['modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UiModalComponent implements OnInit {
contentType: 'template' | 'string' | 'component';
content: string | TemplateRef<any> | Type<any>;
context;
constructor(public ref: UiModalRef) {}
close(): void {
this.ref.close(null);
}
ngOnInit(): void {
this.content = this.ref.content;
if (typeof this.content === 'string') {
this.contentType = 'string';
} else if (this.content instanceof TemplateRef) {
this.contentType = 'template';
this.context = {
close: this.ref.close.bind(this.ref),
};
} else {
this.contentType = 'component';
}
}
}

View File

@@ -0,0 +1,19 @@
import { OverlayModule } from '@angular/cdk/overlay';
import { CommonModule } from '@angular/common';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { UiModalComponent } from './modal.component';
import { UiModalService } from './modal.service';
@NgModule({
imports: [CommonModule, OverlayModule],
declarations: [UiModalComponent],
exports: [UiModalComponent],
})
export class UiModalModule {
static forRoot(): ModuleWithProviders<UiModalModule> {
return {
ngModule: UiModalModule,
providers: [UiModalService],
};
}
}

View File

@@ -0,0 +1,49 @@
import { GlobalPositionStrategy, Overlay } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { Injectable, Injector, TemplateRef, Type } from '@angular/core';
import { UiModalConfig } from './defs/modal-config';
import { UiModalRef } from './defs/modal-ref';
import { UiModalComponent } from './modal.component';
@Injectable()
export class UiModalService {
constructor(private overlay: Overlay, private injector: Injector) {}
open<R = any, T = any>({
content,
data,
config,
}: {
content: TemplateRef<any> | Type<any>;
data?: T;
config?: UiModalConfig;
}): UiModalRef<R> {
const configs = new UiModalConfig({
positionStrategy: new GlobalPositionStrategy().centerHorizontally().centerVertically(),
hasBackdrop: true,
backdropClose: true,
width: '917px',
...config,
});
const overlay = this.overlay.create(configs);
const modalRef = new UiModalRef<R, T>(overlay, content, data);
const injector = this.createInjector(modalRef);
overlay.attach(new ComponentPortal(UiModalComponent, null, injector));
if (configs.backdropClose) {
overlay.backdropClick().subscribe(() => modalRef._close({ type: 'backdropClick', data: null }));
}
return modalRef;
}
createInjector(ref: UiModalRef): Injector {
return Injector.create({
providers: [{ provide: UiModalRef, useValue: ref }],
parent: this.injector,
});
}
}

View File

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

24
apps/ui/modal/src/test.ts Normal file
View File

@@ -0,0 +1,24 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(
path: string,
deep?: boolean,
filter?: RegExp
): {
keys(): string[];
<T>(id: string): T;
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@@ -0,0 +1,25 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

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

View File

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

17
apps/ui/modal/tslint.json Normal file
View File

@@ -0,0 +1,17 @@
{
"extends": "../../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"ui",
"camelCase"
],
"component-selector": [
true,
"element",
"ui",
"kebab-case"
]
}
}

View File

@@ -1,3 +1,3 @@
<button #selectButton (click)="select()" tabIndex="-1">
<button type="button" #selectButton (click)="select()" tabIndex="-1">
{{ label }}
</button>

View File

@@ -1,6 +1,6 @@
<div class="ui-input-wrapper">
<div class="ui-select-value">{{ label }}</div>
<button class="ui-select-toggle" (click)="toggle()" [disabled]="disabled">
<button type="button" class="ui-select-toggle" (click)="toggle()" [disabled]="disabled">
<ui-icon icon="arrow_head"></ui-icon>
</button>
</div>

View File

@@ -41,7 +41,7 @@
}
:host.toggled .ui-select-dropdown-wrapper .ui-select-options {
max-height: 20rem;
max-height: 15rem;
}
.ui-select-dropdown-wrapper .ui-select-options {

View File

@@ -71,6 +71,7 @@ export class UiSelectComponent extends UiFormControlDirective<any> implements Co
this.registerOptionsSelect();
this.options.changes.subscribe((_) => {
this.registerOptionsSelect();
this.cdr.markForCheck();
});
}
@@ -97,6 +98,9 @@ export class UiSelectComponent extends UiFormControlDirective<any> implements Co
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.close();
}
}
setValue(value: any, emitEvent: boolean = true) {
@@ -109,6 +113,7 @@ export class UiSelectComponent extends UiFormControlDirective<any> implements Co
}
this.onTouched();
this.cdr.markForCheck();
}
toggle() {
@@ -128,21 +133,27 @@ export class UiSelectComponent extends UiFormControlDirective<any> implements Co
}
close() {
this.toggled = false;
this.onTouched();
this.cdr.markForCheck();
if (this.toggled) {
this.toggled = false;
this.onTouched();
this.cdr.markForCheck();
}
}
@HostListener('keyup', ['$event'])
@HostListener('keydown', ['$event'])
keyup(event: KeyboardEvent) {
let optionIndex = this.options.toArray().findIndex((o) => o.focused);
if (event.key === 'ArrowUp') {
event.preventDefault();
optionIndex--;
if (optionIndex < 0) {
return;
}
} else if (event.key === 'ArrowDown') {
event.preventDefault();
optionIndex++;
if (this.options.length - 1 < optionIndex) {
return;

View File

@@ -42,6 +42,8 @@ module.exports = {
'px-15': '15px',
'px-20': '20px',
'px-25': '25px',
'px-30': '30px',
'px-35': '35px',
'px-40': '40px',
'px-50': '50px',
'px-100': '100px',

View File

@@ -145,6 +145,9 @@
],
"@ui/form-control": [
"apps/ui/form-control/src/public-api.ts"
],
"@ui/modal": [
"apps/ui/modal/src/public-api.ts"
]
}
}