Merge with develop

This commit is contained in:
Peter Skrlj
2019-02-07 23:09:23 +01:00
37 changed files with 2461 additions and 207 deletions

View File

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@
"@angular/router": "~7.2.0",
"@ngxs/store": "^3.3.4",
"angular2-signaturepad": "^2.8.0",
"@zxing/ngx-scanner": "^1.3.0",
"core-js": "^2.5.4",
"ngx-infinite-scroll": "^7.0.1",
"rxjs": "~6.3.3",

View File

@@ -22,9 +22,8 @@ import { BreadcrumbsState } from './core/store/state/breadcrumbs.state';
import { FilterState } from './core/store/state/filter.state';
import { CheckoutComponent } from './components/checkout/checkout.component';
import { ModalComponent } from './shared/components/modal/modal.component';
import { CustomerSearchComponent } from './components/customer-search/customer-search.component';
import { FormsModule } from '@angular/forms';
import { CustomerSearchResultComponent } from './components/customer-search-result/customer-search-result.component';
import { CustomerSearchModule } from './components/customer-search/customer-search.module';
const states = [
FeedState,
@@ -62,8 +61,6 @@ export function _feedServiceEndpointProviderFactory(conf: ConfigService) {
ProductDetailsComponent,
CheckoutComponent,
ModalComponent,
CustomerSearchComponent,
CustomerSearchResultComponent,
],
imports: [
BrowserModule,
@@ -76,6 +73,7 @@ export function _feedServiceEndpointProviderFactory(conf: ConfigService) {
CatServiceModule,
FeedServiceModule,
FormsModule,
CustomerSearchModule
],
providers: [
{ provide: APP_INITIALIZER, useFactory: _configInitializer, multi: true, deps: [ ConfigService ] },

View File

@@ -19,9 +19,12 @@ export class CheckoutComponent implements OnInit {
// Step one mock data
locations = [
{ name: 'Filliale Stachus', date: '18.02.2019'},
{ name: 'Filliale Berlin', date: '20.02.2019'},
{ name: 'Filliale Frankfurt', date: '22.02.2019'}
{ name: 'München Karlsplatz', date: '01.05.2019'},
{ name: 'München Marienplatz', date: '01.05.2019'},
{ name: 'München Ollenhauerstraße', date: '01.05.2019'},
{ name: 'München Pasing Bahnhofsplatz', date: '01.05.2019'},
{ name: 'München Theatinerstraße', date: '01.05.2019'}
];
currentLocation = this.locations[0];
currentPickUpDate = this.locations[0].date;
@@ -127,7 +130,6 @@ export class CheckoutComponent implements OnInit {
switchSteps(reset: boolean = false) {
if (reset) {
console.log('Should swtich');
this.defaultValues();
}

View File

@@ -0,0 +1,66 @@
<div class="customer-section-create">
<div class="customer-section-create-title">
<span>Kundendaten erfassen</span>
</div>
<div class="align-center customer-section-create-description">
<span>Damit wir Ihnen die Bestelldetails zukommen lassen können.</span>
</div>
<div class="create-wrapper">
<form class="form" [formGroup]="userForm">
<div class="form-group">
<input type="text" id="name" class="input form-control" formControlName="firstName" [ngClass]="{ 'error-visible': submitted && f.firstName.errors }" required autofocus>
<label class="placeholder form-control-placeholder" for="name">Vorname</label>
<div *ngIf="submitted && f.firstName.errors" class="invalid-feedback">
<div>Vorname wird benötigt</div>
</div>
<div class="line"></div>
</div>
<div class="form-group">
<input type="text" id="lastName" class="input form-control" formControlName="lastName" [ngClass]="{ 'error-visible': submitted && f.lastName.errors }" required>
<label class="placeholder form-control-placeholder" for="lastName">Nachname</label>
<div *ngIf="submitted && f.lastName.errors" class="invalid-feedback">
<div>Nachname wird benötigt</div>
</div>
<div class="line"></div>
</div>
<div class="form-group">
<input type="text" id="date" class="input form-control" formControlName="dateOfBirth" required>
<label class="placeholder form-control-placeholder" for="date">Geburtsdatum (TT.MM.JJJJ)</label>
<div *ngIf="submitted && f.dateOfBirth.errors" class="invalid-feedback">
<div *ngIf="submitted && f.dateOfBirth.errors.required">Geburtsdatum wird benötigt</div>
<div *ngIf="submitted && f.dateOfBirth.errors.invalidDate">Geburtsdatum hat ein falsches Format</div>
</div>
<div class="line"></div>
</div>
<div class="form-group">
<input type="text" id="address" class="input form-control" formControlName="address" [ngClass]="{ 'error-visible': submitted && f.address.errors }" required>
<label class="placeholder form-control-placeholder" for="address">Straße und Hausnummer</label>
<div *ngIf="submitted && f.address.errors" class="invalid-feedback">
<div>Straße und Hausnummer wird benötigt</div>
</div>
<div class="line"></div>
</div>
<div class="form-group form-group-location">
<div class="form-group-split">
<input type="text" id="zipCode" class="input form-control" formControlName="zipCode" [ngClass]="{ 'error-visible': submitted && f.zipCode.errors }" required>
<label class="placeholder form-control-placeholder" for="zipCode">PLZ</label>
<div *ngIf="submitted && f.zipCode.errors" class="invalid-feedback invalid-feedback-under invalid-feedback-under-start">
<div>PLZ wird benötigt</div>
</div>
<div class="line"></div>
</div>
<div class="form-group-split">
<input type="text" id="city" class="input form-control" formControlName="city" [ngClass]="{ 'error-visible': submitted && f.city.errors }" required>
<label class="placeholder form-control-placeholder city-label" for="city">Ort</label>
<div *ngIf="submitted && f.city.errors" class="invalid-feedback invalid-feedback-under">
<div>Ort wird benötigt</div>
</div>
<div class="line"></div>
</div>
</div>
<div class="form-group btn-container">
<a class="btn" (click)="createUser()">Speichern</a>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,284 @@
@import "../../../../../assets/scss/variables";
.customer-section-create {
border-radius: 4px;
padding-top: 45px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
height: 100%;
padding-bottom: 30px;
background-image: -webkit-linear-gradient(-180deg, #ffffff 0%, #ffffff 100%);
background-image: -moz-linear-gradient(-180deg, #ffffff 0%, #ffffff 100%);
background-image: -o-linear-gradient(-180deg, #ffffff 0%, #ffffff 100%);
background-image: linear-gradient(-180deg, #ffffff 0%, #ffffff 100%);
box-shadow: 0px -2px 24px 0px #dce2e9;
}
.customer-section-create-title {
font-weight: bold;
font-size: 26px;
text-align: center;
}
.customer-section-create-description {
font-size: 22px;
width: 359px;
padding-top: 15px;
text-align: center;
}
// SEARCH
.create-wrapper {
margin-top: 35px;
position: relative;
width: 76%;
display: flex;
justify-content: center;
align-items: center;
.error-message {
animation: shake 0.3s cubic-bezier(.7,.07,.19,.97) both;
transform: translate3d(0, 0, 0);
position: absolute;
left: 45%;
font-size: 14px;
font-weight: bold;
color: $hima-error-msg-color;
}
.error-visible {
width: 65%;
}
.form-group-location {
display: flex;
justify-content: space-between;
align-items: center;
.form-group-split {
width: 47%;
}
}
}
// OLD IPADS
@media only screen
and (min-device-width : 768px)
and (max-device-width : 1024px)
and (-webkit-min-device-pixel-ratio: 1){
.create-wrapper {
.error-visible {
width: 65%;
}
.invalid-feedback {
left: 47%;
width: 52%;
}
.city-label {
left:294px;
}
.invalid-feedback-under-start {
left: -29%;
}
}
}
// OLD IPADS LANDSCAPE
@media only screen
and (min-device-width : 768px)
and (max-device-width : 1024px)
and (orientation : landscape)
and (-webkit-min-device-pixel-ratio: 1) {
.create-wrapper {
.error-message {
left: 58%;
}
.invalid-feedback {
width: 52%;
}
.city-label {
left: 395px;
}
.invalid-feedback-under-start {
left: 1%;
text-align: left;
}
}
}
// RETINA IPADS LANDSCAPE
@media only screen
and (min-device-width : 768px)
and (max-device-width : 1366px)
and (orientation : landscape)
and (-webkit-min-device-pixel-ratio: 2) {
.create-wrapper {
.invalid-feedback {
width: 49%;
}
.city-label {
left: 535px;
}
.invalid-feedback-under-start {
left: 1%;
text-align: left;
}
}
}
// RETINA IPADS
@media only screen
and (min-device-width : 768px)
and (max-device-width : 1024px)
and (-webkit-min-device-pixel-ratio: 2) {
.create-wrapper {
.error-message {
left: 56%;
}
.error-visible {
width: 65%;
}
}
}
@media screen
and (max-width: 768px) {
.error-message {
left: 45% !important;
}
}
@keyframes shake {
10% {
transform: translate3d(-1px, 0, 0);
}
50% {
transform: translate3d(2px, 0, 0);
}
70% {
transform: translate3d(-3px, 0, 0);
}
90% {
transform: translate3d(3px, 0, 0);
}
}
.invalid-feedback {
position: absolute;
top: 13px;
left: 51%;
// width: 52%;
text-align: right;
animation: shake 0.3s cubic-bezier(.7,.07,.19,.97) both;
transform: translate3d(0, 0, 0);
position: absolute;
font-size: 14px;
font-weight: bold;
color: $hima-error-msg-color;
}
.invalid-feedback-under {
top: 58px;
}
.btn-container {
text-align: center;
margin-top: 30px;
a {
background-color: $hima-button-color;
border: none;
border-radius: 25px;
color: #ffffff;
padding: 14px 30px;
width: 144px;
&:hover {
cursor: pointer;
}
}
}
.form-group {
position: relative;
margin-bottom: 1.5rem;
width: 100%;
}
.form-control-placeholder {
position: absolute;
top: 18px;
padding: 7px 10px 10px;
transition: all 200ms;
opacity: 0.5;
left: 0px;
}
.form-control:focus + .form-control-placeholder,
.form-control:valid + .form-control-placeholder {
font-size: 75%;
transform: translate3d(0, -100%, 0);
opacity: 1;
border: none;
outline: none;
}
.form {
display: flex;
flex-direction: column;
width: 100%;
justify-content: center;
align-items: center;
}
.line {
height: 3px;
width: 100%;
background-image: url('../../../../../assets/images/Line.svg');
background-repeat: repeat-x;
}
.input {
width: 100%;
padding: 15px 10px;
caret-color: $hima-button-color;
border: none;
font-size: 18px;
font-weight: bold;
color: #000000;
text-align: left;
line-height: 21px;
}
input:focus {
outline: none;
border:none;
}
label {
font-size: 18px;
font-weight: bold;
color: #000000;
text-align: left;
line-height: 21px;
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CreateCustomerCardComponent } from './create-customer-card.component';
describe('CreateCustomerCardComponent', () => {
let component: CreateCustomerCardComponent;
let fixture: ComponentFixture<CreateCustomerCardComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CreateCustomerCardComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CreateCustomerCardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,132 @@
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators, FormControl } from '@angular/forms';
import { Store, Select } from '@ngxs/store';
import { AddUser, ChangeCurrentRoute, AddProcess } from '../../../../core/store/actions/process.actions';
import { User, Address } from '../../../../core/models/user.model';
import { Router } from '@angular/router';
import { Process } from '../../../../core/models/process.model';
import { Breadcrumb } from '../../../../core/models/breadcrumb.model';
import { getRandomPic } from '../../../../core/utils/process.util';
import { ProcessState } from '../../../../core/store/state/process.state';
import { Observable } from 'rxjs';
@Component({
selector: 'app-create-customer-card',
templateUrl: './create-customer-card.component.html',
styleUrls: ['./create-customer-card.component.scss']
})
export class CreateCustomerCardComponent implements OnInit {
@Select(ProcessState.getProcesses) processes$: Observable<Process[]>;
userForm: FormGroup;
submitted = false;
processes: Process[];
constructor(
private fb: FormBuilder,
private store: Store,
private router: Router,
) { }
ngOnInit() {
this.userForm = this.buildForm(this.fb);
}
createUser() {
if (this.userForm.valid) {
this.createProcessIfDosntExists();
const address = new Address(
this.userForm.get('firstName').value + ' ' + this.userForm.get('lastName').value,
this.userForm.get('address').value,
+this.userForm.get('zipCode').value,
this.userForm.get('city').value
);
const data: User = {
id: Date.now() + Math.random(),
name: this.userForm.get('firstName').value + ' ' + this.userForm.get('lastName').value,
date_of_birth: this.userForm.get('dateOfBirth').value,
delivery_addres: address,
poossible_addresses: [
address
],
invoice_address: address,
email: 'random@gmail.com',
shop: (Math.floor(Math.random() * 6) + 1) > 3 ? true : false
};
this.store.dispatch(new AddUser(data));
this.store.dispatch(new ChangeCurrentRoute('customer-search'));
this.router.navigate(['customer-search']);
this.userForm.reset();
} else {
this.submitted = true;
}
}
loadProcesses() {
this.processes$.subscribe(
(data: Process[]) => this.processes = data
);
}
createProcess() {
const newProcess = <Process>{
id: 1,
name: '# 1',
selected: true,
icon: getRandomPic(),
breadcrumbs: <Breadcrumb[]>[{
name: 'Kundendaten erfassen',
path: '/customer-search'
}],
currentRoute: 'customer-search'
};
this.store.dispatch(new AddProcess(newProcess));
}
createProcessIfDosntExists() {
this.loadProcesses();
if (this.processes.length === 0) {
this.createProcess();
}
}
get f() { return this.userForm.controls; }
private buildForm(fb: FormBuilder) {
return fb.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
dateOfBirth: ['', Validators.required],
address: ['', Validators.required],
zipCode: ['', Validators.required],
city: ['', Validators.required]
}, { validators: this.validateAllFormFields });
}
validateAllFormFields(formGroup: FormGroup) {
Object.keys(formGroup.controls).forEach(field => {
const control = formGroup.get(field);
if (control instanceof FormControl) {
control.markAsTouched({ onlySelf: true });
if (field === 'dateOfBirth') {
const regexpDate: RegExp = /^\s*(3[01]|[12][0-9]|0?[1-9])\.(1[012]|0?[1-9])\.((?:19|20)\d{2})\s*$/;
const validDate = regexpDate.test(control.value);
if ((control.errors && control.errors.invalidDate) && validDate) {
control.setErrors(null);
} else if (!control.errors) {
const regexpDate: RegExp = /^\s*(3[01]|[12][0-9]|0?[1-9])\.(1[012]|0?[1-9])\.((?:19|20)\d{2})\s*$/;
const validDate = regexpDate.test(control.value);
if (!validDate) {
control.setErrors({ invalidDate: true });
}
}
}
} else if (control instanceof FormGroup) {
this.validateAllFormFields(control);
}
});
}
}

View File

@@ -0,0 +1,15 @@
<div class="customer-search-container">
<div class="customer-section" (click)="changeCard()">
<ng-container *ngIf="displayUserSearch">
<div><span class="customer-section-header">Kundendaten erfassen</span></div>
</ng-container>
<ng-container *ngIf="!displayUserSearch">
<div><span class="customer-section-header">Kundensuche</span></div>
</ng-container>
</div>
<div class="customer-section">
<div><span class="customer-section-header">Kundenkarte scannen</span></div>
</div>
<app-search-customer-card *ngIf="displayUserSearch"></app-search-customer-card>
<app-create-customer-card *ngIf="!displayUserSearch"></app-create-customer-card>
</div>

View File

@@ -0,0 +1,37 @@
@import "../../../../../assets/scss/variables";
.customer-search-container {
background-color: white;
margin-left: 5px;
margin-right: 5px;
margin-top: 5px;
width: 99%;
border-radius: 5px;
font-family: 'Open Sans';
line-height: 21px;
}
.customer-section {
padding: 10px;
border-radius: 4px;
display: grid;
grid-template-columns: auto;
height: 40px;
padding-top: 28px;
border-radius: 5px;
background-image: -webkit-linear-gradient(-180deg, #ffffff 0%, #ffffff 100%);
background-image: -moz-linear-gradient(-180deg, #ffffff 0%, #ffffff 100%);
background-image: -o-linear-gradient(-180deg, #ffffff 0%, #ffffff 100%);
background-image: linear-gradient(-180deg, #ffffff 0%, #ffffff 100%);
box-shadow: 0px -2px 24px 0px #dce2e9;
}
.customer-section-header {
font-size: 20px;
font-weight: bold;
color: #5a728a;
text-align: left;
line-height: 21px;
}

View File

@@ -0,0 +1,22 @@
import { Component, OnInit, AfterViewInit } from '@angular/core';
import { Store } from '@ngxs/store';
@Component({
selector: 'app-customer-search',
templateUrl: './customer-search.component.html',
styleUrls: ['./customer-search.component.scss']
})
export class CustomerSearchComponent implements OnInit {
displayUserSearch = true;
animate = false;
constructor(private store: Store) { }
ngOnInit() {}
changeCard() {
this.animate = true;
this.displayUserSearch = !this.displayUserSearch;
this.animate = false;
}
}

View File

@@ -0,0 +1,14 @@
<div class="customer-section-search">
<div class="customer-search-title">
<span>Kundensuche</span>
</div>
<div class="customer-search-description">
<span>Wie lautet Ihr Name, Ihre E-Mail, Adresse oder Ihre PLZ?</span>
</div>
<div class="search-wrapper">
<input [(ngModel)]="searchParams" (keypress)="keyHandler($event)" class="search-box" placeholder="Name, E-Mail, PLZ" type="text" autofocus [ngClass]="{'error-visible' : searchError}">
<span *ngIf="searchError" class="error-message">Ergibt keine Suchergebnisse</span>
<img (click)="search()" class="search-icon" src="../../../assets/images/search.svg" alt="search">
<img (click)="clear()" *ngIf="searchParams" class="clear-icon" src="../../../assets/images/close.svg" alt="close">
</div>
</div>

View File

@@ -1,80 +1,32 @@
@import "../../../assets/scss/variables";
.customer-search-container {
background-color: white;
margin-left: 5px;
margin-right: 5px;
margin-top: 5px;
width: 99%;
border-radius: 5px;
box-shadow: 0px 0px 10px 0px #dce2e9;
-moz-box-shadow: 0px 0px 10px 0px #dce2e9;;
-webkit-box-shadow: 0px 0px 10px 0px #dce2e9;
box-shadow: 0px 0px 10px 0px #dce2e9;
font-family: 'Open Sans';
line-height: 21px;
}
@media screen and (max-width: 485px) {
.article-search-container {
width: 95%;
}
}
.customer-section {
padding: 10px;
border-radius: 4px;
}
.customer-scan {
display: grid;
grid-template-columns: auto;
height: 40px;
padding-top: 28px;
}
.customer-scan-header {
font-size: 20px;
font-weight: bold;
color: #5a728a;
text-align: left;
line-height: 21px;
}
@import "../../../../../assets/scss/variables";
.customer-section-search {
border-radius: 5px;
box-shadow: 0px -2px 24px 0px #dce2e9;
border-radius: 4px;
padding-top: 45px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
height: 45vh;
}
.customer-scan-content {
font-size: 10pt;
}
.customer-scan-content-div {
margin-top: 2px;
}
.customer-search {
display: grid;
grid-template-columns: auto;
padding-top: 30px;
background-image: -webkit-linear-gradient(-180deg, #ffffff 0%, #ffffff 100%);
background-image: -moz-linear-gradient(-180deg, #ffffff 0%, #ffffff 100%);
background-image: -o-linear-gradient(-180deg, #ffffff 0%, #ffffff 100%);
background-image: linear-gradient(-180deg, #ffffff 0%, #ffffff 100%);
box-shadow: 0px -2px 24px 0px #dce2e9;
}
.customer-search-title {
font-weight: bold;
font-size: 26px;
text-align: center;
}
.customer-search-description {
font-size: 22px;
width: 359px;
padding-top: 12px;
padding-top: 15px;
text-align: center;
}
// SEARCH
@@ -85,6 +37,7 @@
display: flex;
justify-content: center;
align-items: center;
text-align: center;
.search-box {
width: 100%;
@@ -225,13 +178,6 @@ and (max-width: 768px) {
}
}
.show-all-label {
font-size: 16px;
font-weight: bold;
color: #f70400;
}
@keyframes shake {
10% {
transform: translate3d(-1px, 0, 0);

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SearchCustomerCardComponent } from './search-customer-card.component';
describe('SearchCustomerCardComponent', () => {
let component: SearchCustomerCardComponent;
let fixture: ComponentFixture<SearchCustomerCardComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SearchCustomerCardComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SearchCustomerCardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,28 +1,27 @@
import { Component, OnInit, Input } from '@angular/core';
import { Observable } from 'rxjs';
import { RecentArticleSearch } from '../../core/models/recent-article-search.model';
import { Product } from '../../core/models/product.model';
import { Filter } from '../../core/models/filter.model';
import { Process } from '../../core/models/process.model';
import { RecentArticleSearch } from '../../../../core/models/recent-article-search.model';
import { Product } from '../../../../core/models/product.model';
import { Filter } from '../../../../core/models/filter.model';
import { Process } from '../../../../core/models/process.model';
import { Router } from '@angular/router';
import { Breadcrumb } from '../../core/models/breadcrumb.model';
import { getRandomPic } from '../../core/utils/process.util';
import { Breadcrumb } from '../../../../core/models/breadcrumb.model';
import { getRandomPic } from '../../../../core/utils/process.util';
import { Store, Select } from '@ngxs/store';
import { SearchUser, ChangeCurrentRoute, AddProcess } from '../../core/store/actions/process.actions';
import { ProcessState } from '../../core/store/state/process.state';
import { User } from '../../core/models/user.model';
import { SearchUser, ChangeCurrentRoute, AddProcess } from '../../../../core/store/actions/process.actions';
import { ProcessState } from '../../../../core/store/state/process.state';
import { User } from '../../../../core/models/user.model';
@Component({
selector: 'app-customer-search',
templateUrl: './customer-search.component.html',
styleUrls: ['./customer-search.component.scss']
selector: 'app-search-customer-card',
templateUrl: './search-customer-card.component.html',
styleUrls: ['./search-customer-card.component.scss']
})
export class CustomerSearchComponent implements OnInit {
export class SearchCustomerCardComponent implements OnInit {
@Select(ProcessState.getProcesses) processes$: Observable<Process[]>;
@Select(ProcessState.getUsers) users$: Observable<User[]>;
recentArticles$: Observable<RecentArticleSearch[]>;
recentArticles: RecentArticleSearch[];
products$: Observable<Product[]>;
@@ -40,7 +39,6 @@ export class CustomerSearchComponent implements OnInit {
ngOnInit() {}
search() {
if (!this.searchParams) {
this.searchError = false;

View File

@@ -5,15 +5,15 @@
<h1 class="user-title">{{ user.name }}</h1>
<div class="user-location">
<span class="user-label">PLZ und Wohnort</span>
<span class="user-data">{{ user.name }}</span>
<span class="user-data">{{ user.delivery_addres.zip }} {{ user.delivery_addres.city }}</span>
</div>
<div class="user-email">
<span class="user-label">E-Mail</span>
<span class="user-data">{{ user.email }}</span>
<span class="user-label">E-Mail</span>
<span class="user-data">{{ user.email }}</span>
</div>
<div class="user-shop" *ngIf="user.shop">
<span class="user-label">Onlineshop</span>
<span class="user-data"><img src="../../../assets/images/Check.svg" alt="check" class="check-icon">Registriert</span>
<span class="user-label">Onlineshop</span>
<span class="user-data"><img src="../../../assets/images/Check.svg" alt="check" class="check-icon">Registriert</span>
</div>
</div>
<div class="user-actions">
@@ -23,8 +23,9 @@
</div>
</div>
<div class="note">
<span class="note-text">
Hinweis: Aus Datenschutzgründen werden nur Teilinformationen dargestellt. Tab auf einen Kunden um mehr zu erfahren.
</span>
<span class="note-text">
Hinweis: Aus Datenschutzgründen werden nur Teilinformationen dargestellt. Tab auf einen Kunden um mehr zu erfahren.
</span>
</div>
</div>

View File

@@ -1,4 +1,4 @@
@import "../../../assets/scss/variables";
@import "../../../../../assets/scss/variables";
.customer-search-result-container {
font-family: 'Open Sans';

View File

@@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CustomerSearchResultComponent } from './customer-search-result.component';
import { SearchCustomerResultComponent } from './search-customer-result.component';
describe('CustomerSearchResultComponent', () => {
let component: CustomerSearchResultComponent;
let fixture: ComponentFixture<CustomerSearchResultComponent>;
describe('SearchCustomerResultComponent', () => {
let component: SearchCustomerResultComponent;
let fixture: ComponentFixture<SearchCustomerResultComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CustomerSearchResultComponent ]
declarations: [ SearchCustomerResultComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CustomerSearchResultComponent);
fixture = TestBed.createComponent(SearchCustomerResultComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@@ -1,17 +1,16 @@
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { ProcessState } from '../../core/store/state/process.state';
import { ProcessState } from '../../../../core/store/state/process.state';
import { Observable } from 'rxjs';
import { User } from '../../core/models/user.model';
import { User } from '../../../../core/models/user.model';
import { SethUserName } from 'src/app/core/store/actions/process.actions';
@Component({
selector: 'app-customer-search-result',
templateUrl: './customer-search-result.component.html',
styleUrls: ['./customer-search-result.component.scss']
selector: 'app-search-customer-result',
templateUrl: './search-customer-result.component.html',
styleUrls: ['./search-customer-result.component.scss']
})
export class CustomerSearchResultComponent implements OnInit {
export class SearchCustomerResultComponent implements OnInit {
@Select(ProcessState.getUsers) users$: Observable<User[]>;
@@ -32,4 +31,5 @@ export class CustomerSearchResultComponent implements OnInit {
approve(name: string): void {
this.store.dispatch(new SethUserName(name));
}
}

View File

@@ -1,19 +0,0 @@
<div class="customer-search-container">
<div class="customer-section customer-scan">
<div><span class="customer-scan-header">Kundenkarte scannen</span></div>
</div>
<div class="customer-section-search">
<div class="align-center">
<span class="customer-search-title">Kundensuche</span>
</div>
<div class="align-center customer-search-description">
<span>Wie lautet Ihr Name, Ihre E-Mail, Adresse oder Ihre PLZ?</span>
</div>
<div class="align-center search-wrapper">
<input [(ngModel)]="searchParams" (keypress)="keyHandler($event)" class="search-box" placeholder="Name, E-Mail, PLZ" type="text" autofocus [ngClass]="{'error-visible' : searchError}">
<span *ngIf="searchError" class="error-message">Ergibt keine Suchergebnisse</span>
<img (click)="search()" class="search-icon" src="../../../assets/images/search.svg" alt="search">
<img (click)="clear()" *ngIf="searchParams" class="clear-icon" src="../../../assets/images/close.svg" alt="close">
</div>
</div>
</div>

View File

@@ -0,0 +1,29 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { SearchCustomerResultComponent } from './components/search-customer-result/search-customer-result.component';
import { SearchCustomerCardComponent } from './components/search-customer-card/search-customer-card.component';
import { CreateCustomerCardComponent } from './components/create-customer-card/create-customer-card.component';
import { CustomerSearchComponent } from './components/customer-search/customer-search.component';
@NgModule({
declarations: [
SearchCustomerResultComponent,
SearchCustomerCardComponent,
CreateCustomerCardComponent,
CustomerSearchComponent,
],
exports: [
SearchCustomerResultComponent,
SearchCustomerCardComponent,
CreateCustomerCardComponent,
CustomerSearchComponent,
],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
],
})
export class CustomerSearchModule {}

View File

@@ -14,6 +14,11 @@
<span class="process-cart-number">{{ cartCount }}</span>
</div>
</ng-container>
<ng-container *ngIf="!process.cart">
<div class="pt-3">
<span class="">&nbsp;</span>
</div>
</ng-container>
<div>
<a (click)="deleteProcess(process)">
<img class="process-delete-icon" src="/assets/images/close.svg">

View File

@@ -1,15 +1,14 @@
import { Product } from './product.model';
export interface User {
id: number;
name: string;
email: string;
phone_number: string;
delivery_addres: Address;
invoice_address: Address;
shop: boolean;
payement_method: string;
poossible_addresses: Address[];
date_of_birth: string;
email?: string;
phone_number?: string;
delivery_addres?: Address;
invoice_address?: Address;
shop?: boolean;
payement_method?: string;
poossible_addresses?: Address[];
}
export class Address {

View File

@@ -8,19 +8,39 @@ import { Observable, of } from 'rxjs';
})
export class UserService {
constructor() { }
constructor() {
localStorage.setItem('users', JSON.stringify(usersMock));
}
searchUser(params: string): Observable<User[]> {
const filteredUsers = usersMock.filter((user: User) => {
// Params should be contained in name, zip or city
if (user.name.toLowerCase().indexOf(params.toLowerCase()) >= 0 ||
user.delivery_addres.city.toString().toLowerCase().indexOf(params.toLowerCase()) >= 0 ||
user.delivery_addres.zip.toString().toLowerCase().indexOf(params.toLowerCase()) >= 0
) {
return user;
}
});
const mockedUsers = JSON.parse(localStorage.getItem('users'));
let foundUsers: User[] = [];
const splitedParams = params.split(' ');
return of(filteredUsers);
mockedUsers.forEach((user: User) => {
splitedParams.forEach((word: string) => {
if (word) {
if (user.name.toLowerCase().indexOf(word.toLowerCase()) >= 0 ||
user.delivery_addres.city.toString().toLowerCase().indexOf(word.toLowerCase()) >= 0 ||
user.delivery_addres.zip.toString().toLowerCase().indexOf(params.toLowerCase()) >= 0
) {
const userExists = foundUsers.filter((userAdded: User) => userAdded.id === user.id);
if (userExists.length === 0) {
foundUsers.push(user);
}
}
}
});
});
return of(foundUsers);
}
addUser(user: User) {
const mockedUsers = JSON.parse(localStorage.getItem('users'));
localStorage.setItem('users',
JSON.stringify([...mockedUsers, user])
);
}
}

View File

@@ -2,6 +2,7 @@ import { Process } from '../../models/process.model';
import { Search } from '../../models/search.model';
import { ItemDTO } from 'cat-service';
import { Breadcrumb } from '../../models/breadcrumb.model';
import { User } from '../../models/user.model';
export const ADD_PROCESS = '[PROCESS] Add';
export const DELETE_PROCESS = '[PROCESS] Delete';
@@ -13,6 +14,7 @@ export const SET_USER_NAME = '[PROCESS] Set the users name in tab';
export const SET_CART = '[PROCESS] Set cart data for user';
export const PREVENT_PRODUCT_LOAD = '[POCESS] Prevent product load';
export const ALLOW_PRODUCT_LOAD = '[POCESS] Allow product load';
export const ADD_USER = '[PROCESS] Add new user to store';
export class AddProcess {
static readonly type = ADD_PROCESS;
@@ -69,3 +71,8 @@ export class PreventProductLoad {
export class AllowProductLoad {
static readonly type = ALLOW_PRODUCT_LOAD;
}
export class AddUser {
static readonly type = ADD_USER;
constructor(public payload: User) {}
}

View File

@@ -9,7 +9,7 @@ import { ProductService } from '../../services/product.service';
import { RecentArticleSearch } from '../../models/recent-article-search.model';
import { GetProducts, LoadRecentProducts, AddSelectedProduct } from '../actions/product.actions';
import { ItemDTO } from 'dist/cat-service/lib/dtos';
import { getCurrentProcess } from '../../utils/process.util';
import { getCurrentProcess } from '../../utils/process.util'
export class ProcessStateModel {
processes: Process[];
@@ -464,4 +464,9 @@ export class ProcessState {
});
return newProcessState;
}
@Action(actions.AddUser)
addUser(ctx: StateContext<ProcessStateModel>, { payload }: actions.AddUser) {
this.usersService.addUser(payload);
}
}

View File

@@ -0,0 +1,7 @@
<div class="container">
<h1>Artikel scannen</h1>
<caption>
Scannen Sie den Artikel um Informationen zu erhalten.
</caption>
<app-barcode-scanner></app-barcode-scanner>
</div>

View File

@@ -0,0 +1,34 @@
.container {
display: flex;
align-items: center;
padding: 36px;
flex-direction: column;
background-color: rgba(255, 255, 255, 1);
border-radius: 5px;
box-shadow: 0px -2px 24px 0px rgba(220, 226, 233, 0.8);
user-select: none;
h1 {
font-family: 'Open Sans';
font-size: 26px;
font-weight: 600;
color: rgba(0, 0, 0, 1);
margin: 0;
margin-bottom: 15px;
}
caption {
width: 360px;
font-family: 'Open Sans';
font-size: 22px;
color: rgba(0, 0, 0, 1);
text-align: center;
margin-bottom: 25px;
}
}
app-barcode-scanner {
width: 550px;
height: 300px;
border: #e1ebf5 2px solid;
}

View File

@@ -0,0 +1,12 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-barcode-search',
templateUrl: 'barcode-search.component.html',
styleUrls: ['barcode-search.component.scss']
})
export class BarcodeSearchComponent implements OnInit {
constructor() {}
ngOnInit() {}
}

View File

@@ -0,0 +1,14 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ZXingScannerModule } from '@zxing/ngx-scanner';
import { BarcodeSearchComponent } from './barcode-search.component';
import { BarcodeScannerComponent } from './components/barcode-scanner/barcode-scanner.component';
@NgModule({
imports: [ZXingScannerModule, CommonModule],
exports: [BarcodeSearchComponent],
declarations: [BarcodeSearchComponent, BarcodeScannerComponent],
providers: []
})
export class BarcodeSearchModule {}

View File

@@ -0,0 +1,51 @@
<div class="w-100" [hidden]="!hasDevices">
<zxing-scanner
#scanner
[scannerEnabled]="enabled"
(scanError)="scanErrorHandler($event)"
(scanFailure)="scanFailureHandler($event)"
(camerasFound)="camerasFoundHandler($event)"
[formats]="allowedFormats"
></zxing-scanner>
</div>
<div class="scanner-controls" *ngIf="!enabled">
<div class="result">
<span
(click)="scanToggle()"
class="action"
*ngIf="hasDevices && hasPermission"
>
<i class="icon-placeholder"></i>
<span>Scan</span>
</span>
<span (click)="search()" *ngIf="code" class="action">
<i class="icon-placeholder"></i>
<span>Suchen</span>
</span>
</div>
<div class="scan-result">
<span>{{ code }}</span>
</div>
</div>
<div class="debug" *ngIf="!(hasDevices && hasPermission)">
<table class="table-scanner-state">
<thead>
<tr>
<th>Status</th>
<th>Property</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ stateToEmoji(hasDevices) }}</td>
<td>Devices</td>
</tr>
<tr>
<td>{{ stateToEmoji(hasPermission) }}</td>
<td>Permissions</td>
</tr>
</tbody>
</table>
</div>

View File

@@ -0,0 +1,52 @@
:host {
overflow: hidden;
align-items: center;
flex-direction: column;
display: flex;
position: relative;
}
.scanner-controls {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.action {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 20px;
}
.result {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.scan-result {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
font-weight: 600;
margin-bottom: 50px;
font-size: 26px;
}
.icon-placeholder {
$buttonSize: 30px;
border-radius: 60px;
border: #e1ebf5 2px solid;
background-color: white;
width: $buttonSize;
height: $buttonSize;
display: block;
padding: $buttonSize;
}

View File

@@ -0,0 +1,91 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { BarcodeFormat, Result } from '@zxing/library';
import { ZXingScannerComponent } from '@zxing/ngx-scanner';
@Component({
selector: 'app-barcode-scanner',
templateUrl: 'barcode-scanner.component.html',
styleUrls: ['barcode-scanner.component.scss']
})
export class BarcodeScannerComponent implements OnInit {
allowedFormats = [
BarcodeFormat.QR_CODE,
BarcodeFormat.EAN_13,
BarcodeFormat.CODE_128,
BarcodeFormat.UPC_A
];
public enabled = false;
public debugMode = true;
public code = '';
@ViewChild('scanner')
scanner: ZXingScannerComponent;
hasDevices;
availableDevices: MediaDeviceInfo[];
currentDevice: MediaDeviceInfo;
hasPermission;
constructor() {}
ngOnInit() {
this.scanner.camerasNotFound.subscribe(() => (this.hasDevices = false));
this.scanner.scanComplete.subscribe(result => this.scanSuccess(result));
this.scanner.permissionResponse.subscribe((perm: boolean) => {
this.hasPermission = perm;
});
}
scanSuccess(result: Result) {
console.log(event);
this.code = result.getText();
this.enabled = false;
}
camerasFoundHandler(devices: MediaDeviceInfo[]) {
this.hasDevices = true;
this.availableDevices = devices;
this.currentDevice = devices[0];
for (const device of devices) {
if (/back|rear|environment/gi.test(device.label)) {
this.scanner.changeDevice(device);
this.currentDevice = device;
break;
}
}
}
scanToggle() {
this.enabled = !this.enabled;
if (this.enabled) {
this.scanner.scan(this.currentDevice.deviceId);
}
}
search() {}
scanErrorHandler($event) {
console.error($event);
}
scanFailureHandler($event) {
console.error($event);
}
stateToEmoji(state: boolean): string {
const states = {
// not checked
undefined: '❔',
// failed to check
null: '⭕',
// success
true: '✔',
// can't touch that
false: '❌'
};
return states['' + state];
}
}

View File

@@ -1,4 +1,5 @@
import { NewsletterSignupModule } from './newsletter-signup/newsletter-signup.module';
import { BarcodeSearchModule } from './barcode-search/barcode-search.module';
import { NgModule } from '@angular/core';
import { HeaderComponent } from 'src/app/components/header/header.component';
import { ProcessHeaderComponent } from 'src/app/components/process-header/process-header.component';
@@ -41,7 +42,8 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll';
AppRoutingModule,
FormsModule,
NewsletterSignupModule,
InfiniteScrollModule
InfiniteScrollModule,
BarcodeSearchModule
],
exports: [
HeaderComponent,

View File

@@ -1,19 +1,21 @@
import { NewsletterSignupComponent } from './../../modules/newsletter-signup/newsletter-signup.component';
import { BarcodeSearchComponent } from './../../modules/barcode-search/barcode-search.component';
import { Routes } from '@angular/router';
import { DashboardComponent } from 'src/app/components/dashboard/dashboard.component';
import { ArticleSearchComponent } from 'src/app/components/article-search/article-search.component';
import { SearchResultsComponent } from 'src/app/components/search-results/search-results.component';
import { ProductDetailsComponent } from 'src/app/components/product-details/product-details.component';
import { CustomerSearchComponent } from 'src/app/components/customer-search/customer-search.component';
import { CustomerSearchResultComponent } from 'src/app/components/customer-search-result/customer-search-result.component';
import { NewsletterSignupComponent } from 'src/app/modules/newsletter-signup/newsletter-signup.component';
import { DashboardComponent } from '../../components/dashboard/dashboard.component';
import { ArticleSearchComponent } from '../../components/article-search/article-search.component';
import { SearchResultsComponent } from '../../components/search-results/search-results.component';
import { ProductDetailsComponent } from '../../components/product-details/product-details.component';
import { CustomerSearchComponent } from '../../components/customer-search/components/customer-search/customer-search.component';
import { SearchCustomerResultComponent } from '../../components/customer-search/components/search-customer-result/search-customer-result.component';
export const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'article-search', component: ArticleSearchComponent },
{ path: 'customer-search', component: CustomerSearchComponent },
{ path: 'customer-search-result', component: CustomerSearchResultComponent },
{ path: 'customer-search-result', component: SearchCustomerResultComponent },
{ path: 'search-results#start', component: SearchResultsComponent },
{ path: 'product-details/:id', component: ProductDetailsComponent },
{ path: 'article-scan', component: BarcodeSearchComponent },
{ path: 'newsletter', component: NewsletterSignupComponent }
];

View File

@@ -518,6 +518,21 @@
version "1.1.0"
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
"@zxing/library@0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@zxing/library/-/library-0.7.0.tgz#14969fd2dde384fda06cec829bfcf560bea19a98"
dependencies:
ts-custom-error "^2.2.1"
optionalDependencies:
text-encoding "^0.6.4"
"@zxing/ngx-scanner@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@zxing/ngx-scanner/-/ngx-scanner-1.3.0.tgz#15f2061348ef853a681888df70b4b74b454470e5"
dependencies:
"@zxing/library" "0.7.0"
tslib "^1.9.0"
JSONStream@^1.3.4:
version "1.3.5"
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
@@ -4382,7 +4397,6 @@ ng-packagr@^4.2.0:
ngx-infinite-scroll@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/ngx-infinite-scroll/-/ngx-infinite-scroll-7.0.1.tgz#59472108f5b6960519c269d6997fe3ae0961be07"
integrity sha512-be9DAAuabV7VGI06/JUnS6pXC6mcBOzA4+SBCwOcP9WwJ2r5GjdZyOa34ls9hi1MnCOj3zrXLvPKQ/UDp6csIw==
dependencies:
opencollective "^1.0.3"
@@ -6464,6 +6478,10 @@ terser@^3.8.1:
source-map "~0.6.1"
source-map-support "~0.5.6"
text-encoding@^0.6.4:
version "0.6.4"
resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
through2@^2.0.0:
version "2.0.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
@@ -6564,6 +6582,10 @@ trim-right@^1.0.1:
dependencies:
glob "^7.1.2"
ts-custom-error@^2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/ts-custom-error/-/ts-custom-error-2.2.2.tgz#ee769cd6a9cf35dc2e9fedefbb3842f3a2fbceae"
ts-node@~7.0.0:
version "7.0.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-7.0.1.tgz#9562dc2d1e6d248d24bc55f773e3f614337d9baf"