Merged PR 1564: Customer RD

This commit is contained in:
Lorenz Hilpert
2023-06-13 11:45:47 +00:00
committed by Nino Righi
parent c9a90211ee
commit 5c9f4c5b21
332 changed files with 8054 additions and 1075 deletions

View File

@@ -930,6 +930,5 @@ export class DomainCheckoutService {
private updateProcessCount(processId: number, count: number) {
this.applicationService.patchProcessData(processId, { count });
}
//#endregion
}

View File

@@ -18,14 +18,15 @@ import {
NotificationChannel,
PayerDTO,
PayerService,
QueryTokenDTO,
ResponseArgsOfIEnumerableOfBonusCardInfoDTO,
ShippingAddressDTO,
ShippingAddressService,
} from '@swagger/crm';
import { isArray } from '@utils/common';
import { isArray, memorize } from '@utils/common';
import { PagedResult, Result } from 'apps/domain/defs/src/public-api';
import { Observable, of, ReplaySubject } from 'rxjs';
import { catchError, map, mergeMap, retry } from 'rxjs/operators';
import { catchError, map, mergeMap, retry, shareReplay } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CrmCustomerService {
@@ -38,6 +39,14 @@ export class CrmCustomerService {
private loyaltyCardService: LoyaltyCardService
) {}
@memorize()
filterSettings() {
return this.customerService.CustomerCustomerQuerySettings().pipe(
map((res) => res.result),
shareReplay(1)
);
}
complete(queryString: string, filter?: { [key: string]: string }): Observable<Result<AutocompleteDTO[]>> {
return this.customerService.CustomerCustomerAutocomplete({
input: queryString,
@@ -66,6 +75,15 @@ export class CrmCustomerService {
});
}
getCustomersWithQueryToken(queryToken: QueryTokenDTO) {
if (queryToken.skip === undefined) queryToken.skip = 0;
if (queryToken.take === undefined) queryToken.take = 20;
if (queryToken.input === undefined) queryToken.input = { qs: '' };
if (queryToken.filter === undefined) queryToken.filter = {};
return this.customerService.CustomerListCustomers(queryToken);
}
getCustomersByCustomerCardNumber(queryString: string): Observable<PagedResult<CustomerInfoDTO>> {
return this.customerService.CustomerGetCustomerByBonuscard(!!queryString ? queryString : undefined);
}

View File

@@ -32,11 +32,11 @@ import { IsaErrorHandler } from './providers/isa.error-handler';
import { ScanAdapterModule, ScanAdapterService, ScanditScanAdapterModule } from '@adapter/scan';
import { RootStateService } from './store/root-state.service';
import * as Commands from './commands';
import { UiIconModule, UI_ICON_CFG } from '@ui/icon';
import { PreviewComponent } from './preview';
import { NativeContainerService } from 'native-container';
import { ShellModule } from '@shared/shell';
import { MainComponent } from './main.component';
import { IconModule } from '@shared/components/icon';
registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);
@@ -106,7 +106,7 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
ScanAdapterModule.forRoot(),
ScanditScanAdapterModule.forRoot(),
PlatformModule,
UiIconModule.forRoot(),
IconModule.forRoot(),
],
providers: [
{
@@ -135,11 +135,6 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
useClass: IsaErrorHandler,
},
{ provide: LOCALE_ID, useValue: 'de-DE' },
{
provide: UI_ICON_CFG,
useFactory: (config: Config) => config.get('@ui/icon'),
deps: [Config],
},
],
bootstrap: [AppComponent],
})

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

@@ -69,5 +69,6 @@
"checkForUpdates": 3600000,
"licence": {
"scandit": "AQZyKCc+BEkNL00Y3h3FjawGLF+INUj7cVb0My91hl8ffiW873T8FTV1k4TIZJx5RwcJlYxhgsxHVcnM4AJgSwJhbAfxJmP/3XGijLlLp3XUIRjQwFtf7UlZAFZ7Vrt1/WSf7kxxrFQ2SE2AQwLqPg9DL+hHEfd4xT/15n8p2q7qUlCKLsV6jF12Pd7koFNSWNL3ZIkRtd1ma99/321dnwAJHFGXqWg5nprJ7sYtqUqNQ8Er9SlvKbhnw3AipHzKpz0O3oNfUsr6NlZivRBhMhCZLo5WpXo1m9uIU8zLEWMNDJ+wGUctcGxE3eCptP2zLXUgxxjB+0EXOUtT/GWUc/Ip61CMiyUf7Paz026E2eYil2yWgfkTP5CUgDMNGZFuAA1T5PhB9FRW51CjAIvwOKVMCvfixJiVoUsXHnWH2ZnXqtbDR/uEZBE7OKoBlaPL4G3Lvgdqym5EjROAztUXb6wOmVDiGzzqgizyZnIcxFBSKJAownGj9Vh4/Y/Ag1xzGzNtjz3ngSRfMfIIq/q2Q51uiLiv7mBVliPvPWMUTfTjnqnK/OSBlR2ID+COJqnUKpQMedPyOT3IMznmM6gQCmyYO5KE0MkfhFh6+pdNi6oJM2iZsxK1Z1V+GRSOIwrJEoajjDJkh439XjXk8NExFvplrLjK/oL/dsHIZiG6U5GVWW92kGkuXkJCeUz1CET3paxbGqwrd53r5d6gFABbC12CtcP2JeH4YYCpHYyPQacf0prj9Hdq3wDztShC9tH+4UQS/GbaDHKcS1ANIyPuTxHmBFtPuCJ9Uagy5QBEc8eAz2nfsbfaUxYzco6u/zhNsFbqp6zgQIxs5OcqDQ=="
}
},
"@shared/icon": "/assets/icons.json"
}

View File

File diff suppressed because one or more lines are too long

View File

@@ -70,5 +70,6 @@
"checkForUpdates": 3600000,
"licence": {
"scandit": "AfHi/mY+RbwJD5nC7SuWn3I14pFUOfSbQ2QG//4aV3zWQjwix30kHqsqraA8ZiipDBql8YlwIyV6VPBMUiAX4s9YHDxHHsWwq2BUB3ImzDEcU1jmMH/5yakGUYpCQ68D0iZ8SG9sS0QBb3iFdCHc1r9DFr1cMTxM7zOvb/AUoIVmieHZXnx9ioUgCvczsLiuX3hwvTW3lhbvJ4uUyqTWK4sWFVwoY4AIWSFrPwwrkV2DksMKT5fMJT3GWgPypvTIGwWvpRfLWwKlc1Z3ckyb84khsnaWD2wr+hdgu/K8YIMmgGszm5KIZ/G05YfDNZtQ4jby+5RZvQwWR8rxM35rJgf73OkMSpuL9jw3T0TTAlvpkGRLzVVuCw9VjlBLqfPNEZ6VsEwFuAla9IYUvFHCsjypg2J6UpxHXrTYmbsSu5Jm8frVfS5znPPTO9D/4rF6ZVv2PxY9PgUgJUvwMa/VMc/nse3RRRf8RGT4rUItfJDFO8pujD76vVEWq/KixQRoMdLgDLyxhsFVftkxqhZhyEfFZzsEy49LSojJ28vpHpBWLeCQBmnZ7JZ4C5yOQiqSQV/assBq2zJN2q+vCDp8qy5j1rED1SX5Ec7JpgpgnU4chLIf5Zn7bP/hNGT3pEYBuXeDXXN8ke1pcc3fc3m0FysDG0o56XVCUqImZ8Ezi8eujZciKDrWbtljhKTj7cnfuJx0sVHF6Bh5i4YfgA/Z+NL+MtH2EVIF67e6hEz6PWYTcoh3ybBaJfxb2FNvGJutNKg04GwMhYq6K2IddBt0fDiBt0SGM0oSBlUP3DKCUmXcf2a6ASbrcqv6Wz1jHt0pY4U8bEpg7qSbW3VDyvdPgyQ="
}
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -70,5 +70,6 @@
"checkForUpdates": 3600000,
"licence": {
"scandit": "AfHi/mY+RbwJD5nC7SuWn3I14pFUOfSbQ2QG//4aV3zWQjwix30kHqsqraA8ZiipDBql8YlwIyV6VPBMUiAX4s9YHDxHHsWwq2BUB3ImzDEcU1jmMH/5yakGUYpCQ68D0iZ8SG9sS0QBb3iFdCHc1r9DFr1cMTxM7zOvb/AUoIVmieHZXnx9ioUgCvczsLiuX3hwvTW3lhbvJ4uUyqTWK4sWFVwoY4AIWSFrPwwrkV2DksMKT5fMJT3GWgPypvTIGwWvpRfLWwKlc1Z3ckyb84khsnaWD2wr+hdgu/K8YIMmgGszm5KIZ/G05YfDNZtQ4jby+5RZvQwWR8rxM35rJgf73OkMSpuL9jw3T0TTAlvpkGRLzVVuCw9VjlBLqfPNEZ6VsEwFuAla9IYUvFHCsjypg2J6UpxHXrTYmbsSu5Jm8frVfS5znPPTO9D/4rF6ZVv2PxY9PgUgJUvwMa/VMc/nse3RRRf8RGT4rUItfJDFO8pujD76vVEWq/KixQRoMdLgDLyxhsFVftkxqhZhyEfFZzsEy49LSojJ28vpHpBWLeCQBmnZ7JZ4C5yOQiqSQV/assBq2zJN2q+vCDp8qy5j1rED1SX5Ec7JpgpgnU4chLIf5Zn7bP/hNGT3pEYBuXeDXXN8ke1pcc3fc3m0FysDG0o56XVCUqImZ8Ezi8eujZciKDrWbtljhKTj7cnfuJx0sVHF6Bh5i4YfgA/Z+NL+MtH2EVIF67e6hEz6PWYTcoh3ybBaJfxb2FNvGJutNKg04GwMhYq6K2IddBt0fDiBt0SGM0oSBlUP3DKCUmXcf2a6ASbrcqv6Wz1jHt0pY4U8bEpg7qSbW3VDyvdPgyQ="
}
},
"@shared/icon": "/assets/icons.json"
}

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,4 @@
export const environment = {
production: true,
debug: false,
};

View File

@@ -4,6 +4,7 @@
export const environment = {
production: false,
debug: false,
};
/*

View File

@@ -14,26 +14,28 @@ if (environment.production) {
const debugService = new DebugService();
const consoleLog = console.log;
if (environment.debug) {
const consoleLog = console.log;
console.log = (...args) => {
debugService.add({ type: 'log', args });
consoleLog(...args);
};
console.log = (...args) => {
debugService.add({ type: 'log', args });
consoleLog(...args);
};
const consoleWarn = console.warn;
const consoleWarn = console.warn;
console.warn = (...args) => {
debugService.add({ type: 'warn', args });
consoleWarn(...args);
};
console.warn = (...args) => {
debugService.add({ type: 'warn', args });
consoleWarn(...args);
};
const consoleError = console.error;
const consoleError = console.error;
console.error = (...args) => {
debugService.add({ type: 'error', args });
consoleError(...args);
};
console.error = (...args) => {
debugService.add({ type: 'error', args });
consoleError(...args);
};
}
platformBrowserDynamic([{ provide: DebugService, useValue: debugService }])
.bootstrapModule(AppModule)

View File

@@ -5,7 +5,7 @@
class="absolute right-0 top-0 h-14 rounded px-5 text-lg bg-cadet-blue flex flex-row flex-nowrap items-center justify-center"
type="button"
>
<ui-svg-icon class="mr-2" icon="filter-variant"></ui-svg-icon>
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
Filter
</button>
</div>
@@ -16,7 +16,7 @@
<shell-filter-overlay #filterOverlay class="relative">
<div class="relative">
<button type="button" class="absolute top-4 right-4 text-cadet" (click)="closeFilterOverlay()">
<ui-svg-icon [icon]="'close'" [size]="28"></ui-svg-icon>
<shared-icon [icon]="'close'" [size]="28"></shared-icon>
</button>
</div>

View File

@@ -2,13 +2,13 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiFilterNextModule } from '@ui/filter';
import { UiIconModule } from '@ui/icon';
import { UiSpinnerModule } from '@ui/spinner';
import { PriceUpdateListModule } from './price-update-list';
import { PriceUpdateComponent } from './price-update.component';
import { IconComponent } from '@shared/components/icon';
@NgModule({
imports: [CommonModule, PriceUpdateListModule, UiIconModule, UiFilterNextModule, SharedFilterOverlayModule, UiSpinnerModule],
imports: [CommonModule, PriceUpdateListModule, UiFilterNextModule, SharedFilterOverlayModule, UiSpinnerModule, IconComponent],
exports: [PriceUpdateComponent],
declarations: [PriceUpdateComponent],
providers: [],

View File

@@ -8,7 +8,7 @@
[routerLink]="closeFilterRoute"
queryParamsHandling="preserve"
>
<ui-svg-icon icon="close" [size]="25"></ui-svg-icon>
<shared-icon icon="close" [size]="25"></shared-icon>
</a>
</div>

View File

@@ -1,13 +1,13 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { UiIconModule } from '@ui/icon';
import { UiSpinnerModule } from '@ui/spinner';
import { ArticleSearchFilterComponent } from './search-filter.component';
import { FilterNextModule } from 'apps/shared/components/filter/src/lib';
import { FilterModule } from '@shared/components/filter';
import { IconComponent } from '@shared/components/icon';
@NgModule({
imports: [CommonModule, RouterModule, FilterNextModule, UiIconModule, UiSpinnerModule],
imports: [CommonModule, RouterModule, FilterModule, UiSpinnerModule, IconComponent],
exports: [ArticleSearchFilterComponent],
declarations: [ArticleSearchFilterComponent],
providers: [],

View File

@@ -26,7 +26,7 @@
[routerLink]="openFilterRoute"
queryParamsHandling="preserve"
>
<ui-svg-icon class="mr-2" icon="filter-variant"></ui-svg-icon>
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
Filter
</a>
</div>

View File

@@ -1,12 +1,13 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { UiIconModule } from '@ui/icon';
import { ArticleSearchMainComponent } from './search-main.component';
import { FilterNextModule } from 'apps/shared/components/filter/src/lib';
import { FilterModule } from '@shared/components/filter';
import { RouterModule } from '@angular/router';
import { IconComponent } from '@shared/components/icon';
import { UiIconModule } from '@ui/icon';
@NgModule({
imports: [CommonModule, RouterModule, UiIconModule, FilterNextModule],
imports: [CommonModule, RouterModule, IconComponent, FilterModule, UiIconModule],
exports: [ArticleSearchMainComponent],
declarations: [ArticleSearchMainComponent],
providers: [],

View File

@@ -22,7 +22,7 @@
[routerLink]="filterRoute"
queryParamsHandling="preserve"
>
<ui-svg-icon class="mr-2" icon="filter-variant"></ui-svg-icon>
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
Filter
</a>
</div>

View File

@@ -15,9 +15,10 @@ import { SearchResultItemLoadingComponent } from './search-result-item-loading.c
import { SearchResultItemComponent } from './search-result-item.component';
import { ArticleSearchResultsComponent } from './search-results.component';
import { SearchResultSelectedPipe } from './selected/search-result-selected.pipe';
import { FilterAutocompleteProvider, FilterNextModule, OrderByFilterModule } from 'apps/shared/components/filter/src/lib';
import { FilterAutocompleteProvider, FilterModule, OrderByFilterModule } from '@shared/components/filter';
import { FocusSearchboxEvent } from '../focus-searchbox.event';
import { ArticleSearchMainAutocompleteProvider } from '../providers';
import { IconComponent } from '@shared/components/icon';
@NgModule({
imports: [
@@ -32,7 +33,8 @@ import { ArticleSearchMainAutocompleteProvider } from '../providers';
OrderByFilterModule,
ScrollingModule,
UiTooltipModule,
FilterNextModule,
FilterModule,
IconComponent,
],
exports: [ArticleSearchResultsComponent, SearchResultItemComponent],
declarations: [

View File

@@ -5,7 +5,7 @@
[routerLink]="filterRoute"
queryParamsHandling="preserve"
>
<ui-svg-icon class="mr-2" icon="filter-variant"></ui-svg-icon>
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
Filter
</a>
</div>

View File

@@ -9,6 +9,7 @@ import { UiFilterNextModule } from '@ui/filter';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiSpinnerModule } from '@ui/spinner';
import { CustomerOrderSearchStore } from './customer-order-search.store';
import { IconComponent } from '@shared/components/icon';
@NgModule({
imports: [
@@ -19,6 +20,7 @@ import { CustomerOrderSearchStore } from './customer-order-search.store';
SharedFilterOverlayModule,
UiSpinnerModule,
OrderBranchIdInputComponent,
IconComponent,
],
exports: [CustomerOrderSearchComponent],
providers: [CustomerOrderSearchStore],

View File

@@ -5,7 +5,7 @@
[routerLink]="filterRoute"
queryParamsHandling="preserve"
>
<ui-svg-icon class="mr-2" icon="filter-variant"></ui-svg-icon>
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
Filter
</a> -->

View File

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

View File

@@ -0,0 +1,3 @@
:host {
@apply grid grid-flow-row gap-[5px] h-[6.125rem] bg-surface text-surface-content overflow-hidden mb-px-2 px-4 py-3;
}

View File

@@ -0,0 +1,24 @@
<div class="flex items-start justify-between">
<div class="isa-label">
{{ label?.description }}
</div>
<div class="mr-20">
{{ customer?.created | date: 'dd.MM.yy' }}
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<span class="text-[22px] font-bold"> {{ customer?.lastName }} {{ customer?.firstName }} </span>
<div>
<div class="flex flex-col">
<div class="flex flex-row items-center">
<span class="w-32">PLZ und Ort</span>
<span class="font-bold grow-1">{{ customer?.address?.zipCode }} {{ customer?.address?.city }}</span>
</div>
<div class="flex flex-row items-center">
<span class="w-32">E-Mail</span>
<span class="font-bold grow-1">{{ customer?.communicationDetails?.email }}</span>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,19 @@
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
import { CustomerInfoDTO } from '@swagger/crm';
@Component({
selector: 'page-customer-result-list-item-full',
templateUrl: 'customer-result-list-item-full.component.html',
styleUrls: ['customer-result-list-item-full.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerResultListItemFullComponent {
get label() {
return this.customer?.features?.find((f) => f.enabled);
}
@Input()
customer: CustomerInfoDTO;
constructor() {}
}

View File

@@ -0,0 +1,3 @@
:host {
@apply flex flex-col bg-surface text-surface-content h-[11.313rem] mb-[0.625rem] p-4;
}

View File

@@ -0,0 +1,24 @@
<div class="flex items-start justify-between">
<div class="isa-label">
{{ label?.description }}
</div>
<div>
{{ customer?.created | date: 'dd.MM.yy' }}
</div>
</div>
<div class="flex flex-col justify-between grow">
<span class="text-[22px] font-bold"> {{ customer?.lastName }} {{ customer?.firstName }} </span>
<div>
<div class="flex flex-col">
<div class="flex flex-row items-center">
<span class="w-32">PLZ und Ort</span>
<span class="font-bold grow-1">{{ customer?.address?.zipCode }} {{ customer?.address?.city }}</span>
</div>
<div class="flex flex-row items-center">
<span class="w-32">E-Mail</span>
<span class="font-bold grow-1">{{ customer?.communicationDetails?.email }}</span>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,19 @@
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
import { CustomerInfoDTO } from '@swagger/crm';
@Component({
selector: 'page-customer-result-list-item',
templateUrl: 'customer-result-list-item.component.html',
styleUrls: ['customer-result-list-item.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerResultListItemComponent {
get label() {
return this.customer?.features?.find((f) => f.enabled);
}
@Input()
customer: CustomerInfoDTO;
constructor() {}
}

View File

@@ -0,0 +1,17 @@
:host {
--shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
--border-radius: 0.313rem;
@apply grid grid-flow-row;
border-radius: var(--border-radius);
}
.customer-result-list-header {
box-shadow: var(--shadow);
border-radius: var(--border-radius) var(--border-radius) 0 0;
}
page-customer-result-list-item-full,
page-customer-result-list-item {
cursor: pointer;
border-radius: var(--border-radius);
}

View File

@@ -0,0 +1,19 @@
<cdk-virtual-scroll-viewport itemSize="98" class="h-[calc(100vh-18.875rem)]" *ngIf="!compact">
<a
*cdkVirtualFor="let customer of customers$ | async; trackBy: trackByFn"
[routerLink]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.path"
[queryParams]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.queryParams"
>
<page-customer-result-list-item-full [customer]="customer"></page-customer-result-list-item-full>
</a>
</cdk-virtual-scroll-viewport>
<cdk-virtual-scroll-viewport itemSize="98" class="h-[calc(100vh-20.75rem)]" *ngIf="compact">
<a
*cdkVirtualFor="let customer of customers$ | async; trackBy: trackByFn"
[routerLink]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.path"
[queryParams]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.queryParams"
>
<page-customer-result-list-item [customer]="customer"></page-customer-result-list-item>
</a>
</cdk-virtual-scroll-viewport>

View File

@@ -0,0 +1,44 @@
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output } from '@angular/core';
import { CustomerInfoDTO } from '@swagger/crm';
import { CrmCustomerService } from '@domain/crm';
import { map } from 'rxjs/operators';
import { BooleanInput, NumberInput, coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';
import { CustomerSearchNavigation } from '../../navigations';
@Component({
selector: 'page-customer-result-list',
templateUrl: 'customer-result-list.component.html',
styleUrls: ['customer-result-list.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerResultListComponent {
private _compact: boolean;
@Input()
get compact() {
return this._compact;
}
set compact(value: BooleanInput) {
this._compact = coerceBooleanProperty(value);
}
customers$ = this._customerService.getCustomers('Lorenz Hilpert').pipe(map((res) => res.result));
@Input()
selected: CustomerInfoDTO;
@Output()
selectedChange = new EventEmitter<CustomerInfoDTO>();
private _processId: NumberInput;
@Input()
get processId() {
return this._processId;
}
set processId(value: NumberInput) {
this._processId = coerceNumberProperty(value);
}
trackByFn = (_: number, item: CustomerInfoDTO) => item?.id;
constructor(private _customerService: CrmCustomerService, public customerSearchNavigation: CustomerSearchNavigation) {}
}

View File

@@ -0,0 +1,22 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { SearchboxModule } from '@shared/components/searchbox';
import { CustomerResultListComponent } from './customer-result-list.component';
import { CustomerResultListItemFullComponent } from './customer-result-list-item-full/customer-result-list-item-full.component';
import { CustomerResultListItemComponent } from './customer-result-list-item/customer-result-list-item.component';
import { RouterModule } from '@angular/router';
@NgModule({
imports: [CommonModule, SearchboxModule, ScrollingModule, RouterModule],
exports: [CustomerResultListComponent, CustomerResultListComponent, CustomerResultListItemFullComponent, CustomerResultListItemComponent],
declarations: [
CustomerResultListComponent,
CustomerResultListComponent,
CustomerResultListItemFullComponent,
CustomerResultListItemComponent,
],
})
export class CustomerResultListModule {}

View File

@@ -0,0 +1,19 @@
<shared-checkbox
*ngIf="customerType !== 'b2b'"
[ngModel]="p4mUser"
(ngModelChange)="setValue({ p4mUser: !p4mUser })"
[disabled]="p4mReadonly || readonly"
>
Kundenkarte
</shared-checkbox>
<ng-container *ngFor="let option of filteredOptions$ | async">
<shared-checkbox
*ngIf="option?.enabled !== false"
[ngModel]="option.value === customerType"
(ngModelChange)="setValue({ customerType: $event ? option.value : undefined })"
[disabled]="readonly"
[name]="option.value"
>
{{ option.label }}
</shared-checkbox>
</ng-container>

View File

@@ -0,0 +1,11 @@
:host {
@apply flex flex-row flex-wrap justify-start items-center;
}
shared-checkbox {
@apply mr-10 mb-5;
}
shared-checkbox.disabled.checked {
@apply text-black;
}

View File

@@ -0,0 +1,240 @@
import {
Component,
ChangeDetectionStrategy,
forwardRef,
OnInit,
Input,
ChangeDetectorRef,
OnDestroy,
Output,
EventEmitter,
ViewChildren,
QueryList,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { CacheService } from '@core/cache';
import { DomainCheckoutService } from '@domain/checkout';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { OptionDTO } from '@swagger/checkout';
import { UiCheckboxComponent } from '@ui/checkbox';
import { first, isBoolean, isString } from 'lodash';
import { combineLatest, Observable, Subject } from 'rxjs';
import { distinctUntilChanged, filter, map, shareReplay, switchMap } from 'rxjs/operators';
export interface CustomerTypeSelectorState {
processId: number;
customerType: string;
p4mUser: boolean;
options: OptionDTO[];
}
@Component({
selector: 'app-customer-type-selector',
templateUrl: 'customer-type-selector.component.html',
styleUrls: ['customer-type-selector.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomerTypeSelectorComponent),
multi: true,
},
],
})
export class CustomerTypeSelectorComponent extends ComponentStore<CustomerTypeSelectorState>
implements OnInit, OnDestroy, ControlValueAccessor {
@ViewChildren(UiCheckboxComponent)
checkboxes: QueryList<UiCheckboxComponent>;
private _onDestroy$ = new Subject<void>();
@Input()
readonly: boolean;
@Input()
get value() {
if (this.p4mUser) {
return `${this.customerType}-p4m`;
}
return this.customerType;
}
set value(value: string) {
if (value.includes('-p4m')) {
this.p4mUser = true;
this.customerType = value.replace('-p4m', '');
} else {
this.customerType = value;
}
}
@Output()
valueChanges = new EventEmitter<string>();
@Input()
disabled: boolean;
@Input()
set processId(val: number) {
if (this.processId !== val) {
this.patchState({ processId: val });
this.getOptions(val);
}
}
get processId() {
return this.get((s) => s.processId);
}
@Input()
get p4mUser() {
return this.get((s) => s.p4mUser);
}
set p4mUser(val: boolean) {
this.patchState({ p4mUser: val ?? false });
}
@Input()
get customerType() {
return this.get((s) => s.customerType);
}
set customerType(val: string) {
this.patchState({ customerType: val });
}
@Input()
p4mReadonly = false;
get filteredOptions$() {
const options$ = this.select((s) => s.options).pipe(distinctUntilChanged());
const p4mUser$ = this.select((s) => s.p4mUser).pipe(distinctUntilChanged());
const customerType$ = this.select((s) => s.customerType).pipe(distinctUntilChanged());
return combineLatest([options$, p4mUser$, customerType$]).pipe(
filter(([options]) => options?.length > 0),
map(([options, p4mUser, customerType]) => {
const initial = { p4mUser: this.p4mUser, customerType: this.customerType };
let result: OptionDTO[] = options;
if (p4mUser) {
result = result.filter((o) => o.value === 'store' || (o.value === 'webshop' && o.enabled !== false));
result = result.map((o) => {
if (o.value === 'store') {
return { ...o, enabled: false };
}
return o;
});
}
if (customerType === 'b2b' && this.p4mUser) {
this.p4mUser = false;
}
if (initial.p4mUser !== this.p4mUser || initial.customerType !== this.customerType) {
this.setValue({ customerType: this.customerType, p4mUser: this.p4mUser });
}
return result;
}),
shareReplay(1)
);
}
get enabledOptions() {
return this.get((s) => s.options.filter((o) => o.enabled !== false));
}
onChange = (value: string) => {};
onTouched = () => {};
constructor(private _checkoutService: DomainCheckoutService, private _cache: CacheService, private _cdr: ChangeDetectorRef) {
super({
processId: undefined,
customerType: undefined,
p4mUser: false,
options: _cache.get('customerTypeOptions') ?? [],
});
}
ngOnInit(): void {}
getOptions = this.effect((processId$: Observable<number>) => {
return processId$.pipe(
switchMap((pid) =>
this._checkoutService.canSetCustomer({ processId: pid }).pipe(
tapResponse(
(res) => {
const options = res.create.options.values;
this.patchState({ options });
this._cache.set('customerTypeOptions', options);
},
(err) => {}
)
)
)
);
});
ngOnDestroy(): void {
this._onDestroy$.next();
this._onDestroy$.complete();
}
writeValue(obj: any): void {
this.value = obj;
this._cdr.markForCheck();
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}
setValue(value: { p4mUser?: boolean; customerType?: string } | string) {
const initial = { p4mUser: this.p4mUser, customerType: this.customerType };
if (isString(value)) {
this.value = value;
} else {
if (isBoolean(value.p4mUser)) {
this.p4mUser = value.p4mUser;
}
if (isString(value.customerType)) {
this.customerType = value.customerType;
} else if (this.p4mUser) {
// Implementierung wie im PBI #3467 beschrieben
// wenn customerType nicht gesetzt wird und p4mUser true ist,
// dann customerType auf store setzen.
// wenn dies nicht möglich ist da der Warenkob keinen store Kunden zulässt,
// dann customerType auf webshop setzen.
// wenn dies nicht möglich ist da der Warenkob keinen webshop Kunden zulässt,
// dann customerType auf den ersten verfügbaren setzen und p4mUser auf false setzen.
if (this.enabledOptions.some((o) => o.value === 'store')) {
this.customerType = 'store';
} else if (this.enabledOptions.some((o) => o.value === 'webshop')) {
this.customerType = 'webshop';
} else {
this.p4mUser = false;
const includesGuest = this.enabledOptions.some((o) => o.value === 'guest');
this.customerType = includesGuest ? 'guest' : first(this.enabledOptions)?.value;
}
} else {
// wenn customerType nicht gesetzt wird und p4mUser false ist,
// dann customerType auf den ersten verfügbaren setzen der nicht mit dem aktuellen customerType übereinstimmt.
this.customerType = first(this.enabledOptions.filter((o) => o.value === this.customerType))?.value ?? this.customerType;
}
}
if (this.customerType !== initial.customerType || this.p4mUser !== initial.p4mUser) {
this.onChange(this.value);
this.onTouched();
this.valueChanges.emit(this.value);
}
this.checkboxes?.find((c) => c.name === this.customerType)?.writeValue(true);
}
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CustomerTypeSelectorComponent } from './customer-type-selector.component';
import { FormsModule } from '@angular/forms';
import { CheckboxComponent } from '@shared/components/checkbox';
@NgModule({
imports: [CommonModule, FormsModule, CheckboxComponent],
exports: [CustomerTypeSelectorComponent],
declarations: [CustomerTypeSelectorComponent],
})
export class CustomerTypeSelectorModule {}

View File

@@ -0,0 +1,2 @@
export * from './customer-type-selector.component';
export * from './customer-type-selector.module';

View File

@@ -0,0 +1,4 @@
<ui-checkbox [formControl]="control" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly">
Bitte unterschreiben Sie die Teilnahmebedingungen im Kundenkartenformular, um die Prämiennutzung zu ermöglichen.
{{ requiredMark ? '*' : '' }}
</ui-checkbox>

View File

@@ -0,0 +1,11 @@
:host {
@apply block;
}
ui-checkbox {
@apply font-semibold;
}
::ng-deep app-accept-agb-form-block ui-checkbox {
align-items: flex-start !important;
}

View File

@@ -0,0 +1,36 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { UntypedFormControl } from '@angular/forms';
import { FormBlockControl } from '../form-block';
@Component({
selector: 'app-accept-agb-form-block',
templateUrl: 'accept-agb-form-block.component.html',
styleUrls: ['accept-agb-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'appAcceptAGBFormBlock',
})
export class AcceptAGBFormBlockComponent extends FormBlockControl<boolean> {
get tabIndexEnd(): number {
return this.tabIndexStart;
}
constructor() {
super();
}
initializeControl(data?: boolean): void {
this.control = new UntypedFormControl(data ?? false, this.getValidatorFn(), this.getAsyncValidatorFn());
}
_patchValue(update: { previous: boolean; current: boolean }): void {
this.control.patchValue(update.current);
}
updateValidators(): void {
this.control.setValidators(this.getValidatorFn());
}
setValue(value: boolean): void {
this.control.setValue(value);
}
}

View File

@@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AcceptAGBFormBlockComponent } from './accept-agb-form-block.component';
import { UiCheckboxModule } from '@ui/checkbox';
import { ReactiveFormsModule } from '@angular/forms';
import { UiCommonModule } from '@ui/common';
@NgModule({
imports: [CommonModule, UiCommonModule, UiCheckboxModule, ReactiveFormsModule],
exports: [AcceptAGBFormBlockComponent],
declarations: [AcceptAGBFormBlockComponent],
})
export class AcceptAGBFormBlockModule {}

View File

@@ -0,0 +1,2 @@
export * from './accept-agb-form-block.component';
export * from './accept-agb-form-block.module';

View File

@@ -0,0 +1,8 @@
export interface AddressFormBlockData {
street?: string;
streetNumber?: string;
zipCode?: string;
city?: string;
info?: string;
country?: string;
}

View File

@@ -0,0 +1,25 @@
<ng-container [formGroup]="control">
<ui-form-control label="Straße" [requiredMark]="requiredMarks.includes('street') ? '*' : ''">
<input uiInput type="text" formControlName="street" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly" />
</ui-form-control>
<ui-form-control label="Hausnummer" [requiredMark]="requiredMarks.includes('streetNumber') ? '*' : ''">
<input uiInput type="text" formControlName="streetNumber" [tabindex]="tabIndexStart + 1" [readonly]="readonly" />
</ui-form-control>
<ui-form-control label="PLZ" [requiredMark]="requiredMarks.includes('zipCode') ? '*' : ''">
<input uiInput type="text" formControlName="zipCode" [tabindex]="tabIndexStart + 2" [readonly]="readonly" />
</ui-form-control>
<ui-form-control label="Ort" [requiredMark]="requiredMarks.includes('city') ? '*' : ''">
<input uiInput type="text" formControlName="city" [tabindex]="tabIndexStart + 3" [readonly]="readonly" />
</ui-form-control>
<ui-form-control class="col-span-2" label="Adresszusatz" [clearable]="false" [requiredMark]="requiredMarks.includes('info') ? '*' : ''">
<input uiInput type="text" formControlName="info" [tabindex]="tabIndexStart + 4" [readonly]="readonly" />
</ui-form-control>
<ui-form-control class="col-span-2" label="Land" [clearable]="true" [requiredMark]="requiredMarks.includes('country') ? '*' : ''">
<ui-select formControlName="country" [tabindex]="tabIndexStart + 5" [readonly]="readonly">
<ui-select-option *ngFor="let country of countries || (countries$ | async)" [label]="country.name" [value]="country.isO3166_A_3">
</ui-select-option>
</ui-select>
</ui-form-control>
</ng-container>

View File

@@ -0,0 +1,3 @@
:host {
@apply grid grid-cols-2 gap-x-8;
}

View File

@@ -0,0 +1,78 @@
import { Component, ChangeDetectionStrategy, Input, OnInit, ChangeDetectorRef } from '@angular/core';
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { CrmCustomerService } from '@domain/crm';
import { CountryDTO } from '@swagger/crm';
import { camelCase } from 'lodash';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { FormBlockGroup } from '../form-block';
import { AddressFormBlockData } from './address-form-block-data';
@Component({
selector: 'app-address-form-block',
templateUrl: 'address-form-block.component.html',
styleUrls: ['address-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AddressFormBlockComponent extends FormBlockGroup<AddressFormBlockData> implements OnInit {
@Input() countries: CountryDTO[];
@Input() defaults: Partial<AddressFormBlockData>;
get tabIndexEnd(): number {
return this.tabIndexStart + 5;
}
countries$: Observable<CountryDTO[]>;
constructor(private readonly _customerService: CrmCustomerService, private _cdr: ChangeDetectorRef) {
super();
}
ngOnInit(): void {
super.ngOnInit();
this.countries$ = this._customerService.getCountries().pipe(map((r) => r.result));
}
initializeControl(data?: AddressFormBlockData): void {
this.control = new UntypedFormGroup({
country: new UntypedFormControl(data?.country ?? this.defaults?.country ?? '', this.getValidatorFn('country')),
city: new UntypedFormControl(data?.city ?? this.defaults?.city ?? '', this.getValidatorFn('city')),
street: new UntypedFormControl(data?.street ?? this.defaults?.street ?? '', this.getValidatorFn('street')),
streetNumber: new UntypedFormControl(data?.streetNumber ?? this.defaults?.streetNumber ?? '', this.getValidatorFn('streetNumber')),
zipCode: new UntypedFormControl(data?.zipCode ?? this.defaults?.zipCode ?? '', this.getValidatorFn('zipCode')),
info: new UntypedFormControl(data?.info ?? this.defaults?.info ?? '', this.getValidatorFn('info')),
});
}
_patchValue(update: { previous: AddressFormBlockData; current: AddressFormBlockData }): void {
this.control.patchValue({
country: update.current.country ?? '',
city: update.current.city ?? '',
street: update.current.street ?? '',
streetNumber: update.current.streetNumber ?? '',
zipCode: update.current.zipCode ?? '',
info: update.current.info ?? '',
});
}
setAddressValidationError(invalidProperties: Record<keyof AddressFormBlockData, string>) {
const keys = Object.keys(invalidProperties);
for (const key of keys) {
this.control.get(camelCase(key))?.setErrors({ validateAddress: invalidProperties[key] });
}
this.control.markAllAsTouched();
this._cdr.markForCheck();
}
updateValidators(): void {
this.control.get('country')?.setValidators(this.getValidatorFn('country'));
this.control.get('city')?.setValidators(this.getValidatorFn('city'));
this.control.get('street')?.setValidators(this.getValidatorFn('street'));
this.control.get('streetNumber')?.setValidators(this.getValidatorFn('streetNumber'));
this.control.get('zipCode')?.setValidators(this.getValidatorFn('zipCode'));
this.control.get('info')?.setValidators(this.getValidatorFn('info'));
this.control.updateValueAndValidity();
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AddressFormBlockComponent } from './address-form-block.component';
import { UiFormControlModule } from '@ui/form-control';
import { UiInputModule } from '@ui/input';
import { UiSelectModule } from '@ui/select';
import { ReactiveFormsModule } from '@angular/forms';
import { UiCommonModule } from '@ui/common';
@NgModule({
imports: [CommonModule, UiCommonModule, UiFormControlModule, UiInputModule, UiSelectModule, ReactiveFormsModule],
exports: [AddressFormBlockComponent],
declarations: [AddressFormBlockComponent],
})
export class AddressFormBlockModule {}

View File

@@ -0,0 +1,3 @@
export * from './address-form-block-data';
export * from './address-form-block.component';
export * from './address-form-block.module';

View File

@@ -0,0 +1,3 @@
<ui-form-control label="Geburtsdatum (TT.MM.JJJJ)" [requiredMark]="requiredMark ? '*' : ''">
<input uiDateInput type="text" [formControl]="control" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly" />
</ui-form-control>

View File

@@ -0,0 +1,3 @@
:host {
@apply block;
}

View File

@@ -0,0 +1,29 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { UntypedFormControl, Validators } from '@angular/forms';
import { UiValidators } from '@ui/validators';
import { FormBlockControl } from '../form-block';
@Component({
selector: 'app-birth-date-form-block',
templateUrl: 'birth-date-form-block.component.html',
styleUrls: ['birth-date-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BirthDateFormBlockComponent extends FormBlockControl<Date | string> {
get tabIndexEnd() {
return this.tabIndexStart;
}
initializeControl(data?: Date): void {
this.control = new UntypedFormControl(data, [...this.getValidatorFn(), UiValidators.date]);
}
_patchValue(update: { previous: Date; current: Date }): void {
this.control.patchValue(update.current);
}
updateValidators(): void {
this.control.setValidators([...this.getValidatorFn(), UiValidators.date]);
this.control.updateValueAndValidity();
}
}

View File

@@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BirthDateFormBlockComponent } from './birth-date-form-block.component';
import { UiFormControlModule } from '@ui/form-control';
import { UiInputModule } from '@ui/input';
import { ReactiveFormsModule } from '@angular/forms';
import { UiCommonModule } from '@ui/common';
@NgModule({
imports: [CommonModule, ReactiveFormsModule, UiFormControlModule, UiInputModule, UiCommonModule],
exports: [BirthDateFormBlockComponent],
declarations: [BirthDateFormBlockComponent],
})
export class BirthDateFormBlockModule {}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './birth-date-form-block.component';
export * from './birth-date-form-block.module';
// end:ng42.barrel

View File

@@ -0,0 +1,12 @@
import { AddressFormBlockData } from '../address';
import { NameFormBlockData } from '../name/name-form-block-data';
import { OrganisationFormBlockData } from '../organisation/organisation-form-block-data';
export interface DeviatingAddressFormBlockData {
deviatingAddress?: boolean;
organisation?: OrganisationFormBlockData;
name?: NameFormBlockData;
address?: AddressFormBlockData;
email?: string;
phoneNumbers?: { mobile?: string; phone?: string };
}

View File

@@ -0,0 +1,61 @@
<ui-checkbox [formControl]="deviatingAddress" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly">
<ng-content></ng-content>
</ui-checkbox>
<div class="address-block" *ngIf="control.value.deviatingAddress">
<div class="wrapper">
<app-organisation-form-block
*ngIf="organisation"
[tabIndexStart]="tabIndexStart + 1"
#orgaBlock
(onInit)="addOrganisationGroup($event)"
(onDestroy)="removeOrganisationGroup()"
[data]="data?.organisation"
#nameFormBlock
[tabIndexStart]="tabIndexStart + 1"
[requiredMarks]="organisationRequiredMarks"
[validatorFns]="organisationValidatorFns"
[readonly]="readonly"
>
</app-organisation-form-block>
<app-name-form-block
(onInit)="addNameGroup($event)"
(onDestroy)="removeNameGroup()"
[data]="data?.name"
#nameFormBlock
[requiredMarks]="nameRequiredMarks"
[validatorFns]="nameValidatorFns"
[readonly]="readonly"
>
</app-name-form-block>
<app-address-form-block
#addressFormBlock
(onInit)="addAddressGroup($event)"
(onDestroy)="removeAddressGroup()"
[data]="data?.address"
[tabIndexStart]="nameFormBlock?.tabIndexEnd + 1"
[requiredMarks]="addressRequiredMarks"
[validatorFns]="addressValidatorFns"
[readonly]="readonly"
>
</app-address-form-block>
<app-email-form-block
*ngIf="email"
#emailFormBlock
(onInit)="addEmailGroup($event)"
(onDestroy)="removeEmailGroup()"
[data]="data?.email"
[requiredMark]="emailRequiredMark"
[validatorFns]="emailValidationFns"
[readonly]="readonly"
>
</app-email-form-block>
<app-phone-numbers-form-block
*ngIf="phoneNumbers"
(onInit)="addPhoneNumbersGroup($event)"
(onDestroy)="removePhoneNumbersGroup()"
[readonly]="readonly"
>
[tabIndexStart]="emailFormBlock?.tabIndexEnd+1" [requiredMarks]="phoneNumbersRequiredMarks" [validatorFns]="phoneNumbersValidatorFns">
</app-phone-numbers-form-block>
</div>
</div>

View File

@@ -0,0 +1,7 @@
:host {
@apply block;
}
ui-checkbox {
@apply font-semibold;
}

View File

@@ -0,0 +1,174 @@
import { Component, ChangeDetectionStrategy, ViewChild, ElementRef, Self, AfterViewInit, ChangeDetectorRef, Input } from '@angular/core';
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, ValidatorFn } from '@angular/forms';
import { FormBlock, FormBlockGroup } from '../form-block';
import { DeviatingAddressFormBlockData } from './deviating-address-form-block-data';
import { NameFormBlockComponent } from '../name';
import { AddressFormBlockComponent, AddressFormBlockData } from '../address';
import { NameFormBlockData } from '../name/name-form-block-data';
import { OrganisationFormBlockComponent } from '../organisation';
import { OrganisationFormBlockData } from '../organisation/organisation-form-block-data';
import { EmailFormBlockComponent } from '../email';
import { PhoneNumbersFormBlockComponent, PhoneNumbersFormBlockData } from '../phone-numbers';
@Component({
selector: 'app-deviating-address-form-block',
templateUrl: 'deviating-address-form-block.component.html',
styleUrls: ['deviating-address-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'appDeviatingAddressFormBlock',
})
export class DeviatingAddressFormBlockComponent extends FormBlockGroup<DeviatingAddressFormBlockData> implements AfterViewInit {
@Input()
organisation = false;
@Input()
email = false;
@Input()
phoneNumbers = false;
@ViewChild(AddressFormBlockComponent, { static: false })
private readonly _addressFormBlock: AddressFormBlockComponent;
@ViewChild(EmailFormBlockComponent, { static: false })
private readonly _emailFormBlock: AddressFormBlockComponent;
@ViewChild(PhoneNumbersFormBlockComponent, { static: false })
private readonly _phoneNumbersFormBlock: AddressFormBlockComponent;
get tabIndexEnd() {
return this.tabIndexStart + 10;
}
@Input() defaults: Partial<DeviatingAddressFormBlockData>;
@Input()
nameRequiredMarks: Array<keyof NameFormBlockData>;
@Input()
nameValidatorFns: Record<keyof NameFormBlockData, ValidatorFn[]>;
@Input()
addressRequiredMarks: Array<keyof AddressFormBlockData>;
@Input()
addressValidatorFns: Record<keyof AddressFormBlockData, ValidatorFn[]>;
@Input()
organisationRequiredMarks: Array<keyof OrganisationFormBlockData>;
@Input()
organisationValidatorFns: Record<keyof OrganisationFormBlockData, ValidatorFn[]>;
@Input()
emailRequiredMark = false;
@Input()
emailValidationFns: ValidatorFn[] = [];
@Input()
phoneNumbersRequiredMarks: Array<keyof PhoneNumbersFormBlockData>;
@Input()
phoneNumbersValidatorFns: Record<keyof PhoneNumbersFormBlockData, ValidatorFn[]>;
get deviatingAddress() {
return this.control.get('deviatingAddress') as UntypedFormControl;
}
constructor(private readonly _fb: UntypedFormBuilder, @Self() private _elementRef: ElementRef, private _cdr: ChangeDetectorRef) {
super();
}
ngAfterViewInit(): void {}
initializeControl(data?: DeviatingAddressFormBlockData): void {
this.control = this._fb.group({
deviatingAddress: this._fb.control(data?.deviatingAddress ?? false, this.getValidatorFn('deviatingAddress')),
});
}
_patchValue(update: { previous: DeviatingAddressFormBlockData; current: DeviatingAddressFormBlockData }): void {
this.control.patchValue({
deviatingAddress: update.current?.deviatingAddress ?? false,
});
}
addOrganisationGroup(cmp: FormBlock<OrganisationFormBlockData, UntypedFormGroup>) {
if (!this.control.contains('organisation')) {
this.control.addControl('organisation', cmp.control);
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
removeOrganisationGroup() {
if (this.control.contains('organisation')) {
this.control.removeControl('organisation');
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
addNameGroup(cmp: FormBlock<NameFormBlockData, UntypedFormGroup>) {
if (!this.control.contains('name')) {
this.control.addControl('name', cmp.control);
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
removeNameGroup() {
if (this.control.contains('name')) {
this.control.removeControl('name');
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
addAddressGroup(cmp: FormBlock<AddressFormBlockData, UntypedFormGroup>) {
if (!this.control.contains('address')) {
this.control.addControl('address', cmp.control);
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
removeAddressGroup() {
if (this.control.contains('address')) {
this.control.removeControl('address');
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
addEmailGroup(cmp: FormBlock<string, UntypedFormControl>) {
if (!this.control.contains('email')) {
this.control.addControl('email', cmp.control);
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
removeEmailGroup() {
if (this.control.contains('email')) {
this.control.removeControl('email');
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
addPhoneNumbersGroup(cmp: FormBlock<PhoneNumbersFormBlockData, UntypedFormGroup>) {
if (!this.control.contains('phoneNumbers')) {
this.control.addControl('phoneNumbers', cmp.control);
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
removePhoneNumbersGroup() {
if (this.control.contains('phoneNumbers')) {
this.control.removeControl('phoneNumbers');
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
setAddressValidationError(invalidProperties: Record<keyof AddressFormBlockData, string>) {
this._addressFormBlock?.setAddressValidationError(invalidProperties);
}
updateValidators(): void {
throw new Error('Method not implemented.');
}
}

View File

@@ -0,0 +1,29 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DeviatingAddressFormBlockComponent } from './deviating-address-form-block.component';
import { UiCheckboxModule } from '@ui/checkbox';
import { AddressFormBlockModule } from '../address';
import { NameFormBlockModule } from '../name';
import { ReactiveFormsModule } from '@angular/forms';
import { OrganisationFormBlockModule } from '../organisation';
import { EmailFormBlockModule } from '../email';
import { PhoneNumbersFormBlockModule } from '../phone-numbers';
import { UiCommonModule } from '@ui/common';
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
UiCheckboxModule,
NameFormBlockModule,
AddressFormBlockModule,
OrganisationFormBlockModule,
EmailFormBlockModule,
PhoneNumbersFormBlockModule,
UiCommonModule,
],
exports: [DeviatingAddressFormBlockComponent],
declarations: [DeviatingAddressFormBlockComponent],
})
export class DeviatingAddressFormBlockComponentModule {}

View File

@@ -0,0 +1,3 @@
export * from './deviating-address-form-block-data';
export * from './deviating-address-form-block.component';
export * from './deviating-address-form-block.module';

View File

@@ -0,0 +1,3 @@
<ui-form-control label="E-Mail" [requiredMark]="requiredMark ? '*' : ''">
<input uiInput type="mail" [formControl]="control" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly" />
</ui-form-control>

View File

@@ -0,0 +1,34 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { UntypedFormControl } from '@angular/forms';
import { FormBlockControl } from '../form-block';
import { validateEmail } from '../../../validators/email-validator';
@Component({
selector: 'app-email-form-block',
templateUrl: 'email-form-block.component.html',
styleUrls: ['email-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EmailFormBlockComponent extends FormBlockControl<string> {
get tabIndexEnd() {
return this.tabIndexStart;
}
constructor() {
super();
}
updateValidators(): void {
this.control.setValidators([...this.getValidatorFn(), validateEmail]);
this.control.setAsyncValidators(this.getAsyncValidatorFn());
this.control.updateValueAndValidity();
}
initializeControl(data?: string): void {
this.control = new UntypedFormControl(data, [...this.getValidatorFn(), validateEmail], this.getAsyncValidatorFn());
}
_patchValue(update: { previous: string; current: string }): void {
this.control.patchValue(update.current);
}
}

View File

@@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { EmailFormBlockComponent } from './email-form-block.component';
import { ReactiveFormsModule } from '@angular/forms';
import { UiFormControlModule } from '@ui/form-control';
import { UiInputModule } from '@ui/input';
import { UiCommonModule } from '@ui/common';
@NgModule({
imports: [CommonModule, UiCommonModule, ReactiveFormsModule, UiFormControlModule, UiInputModule],
exports: [EmailFormBlockComponent],
declarations: [EmailFormBlockComponent],
})
export class EmailFormBlockModule {}

View File

@@ -0,0 +1,2 @@
export * from './email-form-block.component';
export * from './email-form-block.module';

View File

@@ -0,0 +1,179 @@
import { BooleanInput, coerceBooleanProperty, NumberInput, coerceNumberProperty } from '@angular/cdk/coercion';
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { AbstractControl, AsyncValidatorFn, UntypedFormControl, UntypedFormGroup, ValidatorFn } from '@angular/forms';
import { isEqual } from 'lodash';
import { Subject } from 'rxjs';
import { distinctUntilChanged, takeUntil } from 'rxjs/operators';
@Directive()
export abstract class FormBlock<TD, TC extends AbstractControl> implements OnInit, OnDestroy {
private _readonly = false;
@Input()
get readonly(): boolean {
return this._readonly;
}
set readonly(value: BooleanInput) {
this._readonly = coerceBooleanProperty(value);
}
private _focusAfterInit = false;
@Input()
get focusAfterInit(): boolean {
return this._focusAfterInit;
}
set focusAfterInit(value: BooleanInput) {
this._focusAfterInit = coerceBooleanProperty(value);
}
readonly onDestroy$ = new Subject<void>();
private _tabIndexStart: number;
@Input()
get tabIndexStart(): number {
return this._tabIndexStart;
}
set tabIndexStart(value: NumberInput) {
this._tabIndexStart = coerceNumberProperty(value);
}
abstract tabIndexEnd: NumberInput;
private _data: TD;
@Input()
get data(): TD | undefined {
return this._data;
}
set data(value: TD) {
const previous = this._data;
this._data = value;
if (this.control && !isEqual(previous, value)) {
this._patchValue({
previous,
current: value,
});
}
}
@Output()
dataChanges = new EventEmitter<TD>();
@Output()
onInit = new EventEmitter<FormBlock<TD, TC>>();
@Output()
onDestroy = new EventEmitter<void>();
control: TC;
get value(): TD {
return this.control.value;
}
get valid(): boolean {
return this.control.valid;
}
ngOnInit(): void {
this.initializeControl(this.data);
this.control.valueChanges
.pipe(takeUntil(this.onDestroy$), distinctUntilChanged(isEqual))
.subscribe((value) => this.dataChanges.emit(value));
this.onInit.emit(this);
}
ngOnDestroy(): void {
this.onDestroy$.next();
this.onDestroy$.complete();
this.onDestroy.emit();
}
abstract initializeControl(data?: TD): void;
abstract _patchValue(update: { previous: TD; current: TD }): void;
}
@Directive()
export abstract class FormBlockControl<TD> extends FormBlock<TD, UntypedFormControl> {
@Input()
requiredMark: boolean;
private _validatorFns: ValidatorFn[];
@Input()
get validatorFns() {
return this._validatorFns;
}
set validatorFns(value: ValidatorFn[]) {
this._validatorFns = value;
if (this.control) {
this.updateValidators();
}
}
private _asyncValidatorFns: AsyncValidatorFn[];
@Input()
get asyncValidatorFns() {
return this._asyncValidatorFns;
}
set asyncValidatorFns(value: AsyncValidatorFn[]) {
this._asyncValidatorFns = value;
if (this.control) {
this.updateValidators();
}
}
constructor() {
super();
}
abstract updateValidators(): void;
getValidatorFn(): ValidatorFn[] {
return this.validatorFns ?? [];
}
getAsyncValidatorFn(): AsyncValidatorFn[] {
return this.asyncValidatorFns ?? [];
}
}
@Directive()
export abstract class FormBlockGroup<TD> extends FormBlock<TD, UntypedFormGroup> {
private _requiredMarks: Array<keyof TD>;
@Input()
get requiredMarks() {
return this._requiredMarks ?? [];
}
set requiredMarks(value: Array<keyof TD>) {
this._requiredMarks = value;
}
private _validatorFns: Record<keyof TD, ValidatorFn[]>;
@Input()
get validatorFns() {
return this._validatorFns ?? ({} as Record<keyof TD, ValidatorFn[]>);
}
set validatorFns(value: Record<keyof TD, ValidatorFn[]>) {
this._validatorFns = value;
if (this.control) {
this.updateValidators();
}
}
constructor() {
super();
}
abstract updateValidators(): void;
getValidatorFn(key: keyof TD): ValidatorFn[] {
return this.validatorFns[key] ?? [];
}
}

View File

@@ -0,0 +1,12 @@
export * from './accept-agb';
export * from './address';
export * from './birth-date';
export * from './deviating-address';
export * from './email';
export * from './interests';
export * from './name';
export * from './newsletter';
export * from './organisation';
export * from './p4m-number';
export * from './phone-numbers';
export * from './form-block';

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './interests-form-block.component';
export * from './interests-form-block.module';
// end:ng42.barrel

View File

@@ -0,0 +1 @@
export type InterestsFormBlockData = Record<string, boolean>;

View File

@@ -0,0 +1,12 @@
<div class="interests-description">Geben Sie Interessen an, um Ihre persönlichen Kontoangaben zu verfeinern.</div>
<div class="interests-wrapper" [formGroup]="control">
<ui-checkbox
*ngFor="let pair of interests | keyvalue; let idx = index"
[formControlName]="pair.key"
[tabindex]="tabIndexStart + idx"
[autofocus]="focusAfterInit"
[readonly]="readonly"
>
{{ pair.value }}
</ui-checkbox>
</div>

View File

@@ -0,0 +1,11 @@
:host {
@apply grid grid-flow-row gap-6;
}
.interests-description {
@apply font-bold;
}
.interests-wrapper {
@apply grid grid-cols-2 gap-4 font-semibold;
}

View File

@@ -0,0 +1,88 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { FormBlock } from '../form-block';
import { InterestsFormBlockData } from './interests-form-block-data';
import { LoyaltyCardService } from '@swagger/crm';
import { shareReplay } from 'rxjs/operators';
import { isEqual } from 'lodash';
import { memorize } from '@utils/common';
@Component({
selector: 'app-interests-form-block',
templateUrl: 'interests-form-block.component.html',
styleUrls: ['interests-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class InterestsFormBlockComponent extends FormBlock<InterestsFormBlockData, UntypedFormGroup> {
private _interests: Map<string, string>;
get interests(): Map<string, string> {
return this._interests;
}
set interests(value: Map<string, string>) {
if (!isEqual(this._interests, value)) {
this._interests = value;
if (this.control) {
this.updateInterestControls();
}
}
}
get tabIndexEnd() {
return this.tabIndexStart + this.interests?.keys.length;
}
constructor(private _fb: UntypedFormBuilder, private _LoyaltyCardService: LoyaltyCardService) {
super();
this.getInterests().subscribe({
next: (response) => {
const interests = new Map<string, string>();
response.result.forEach((preference) => {
interests.set(preference.key, preference.value);
});
this.interests = interests;
},
error: (error) => {
console.error(error);
},
});
}
@memorize({ ttl: 28800000 })
getInterests() {
return this._LoyaltyCardService.LoyaltyCardListInteressen().pipe(shareReplay(1));
}
updateInterestControls() {
const fData = this.data ?? {};
this.interests?.forEach((value, key) => {
if (!this.control.contains(key)) {
this.control.addControl(key, new UntypedFormControl(fData[key] ?? false));
}
});
Object.keys(this.control.controls).forEach((key) => {
if (!this.interests.has(key)) {
this.control.removeControl(key);
}
});
}
initializeControl(data?: InterestsFormBlockData): void {
const fData = data ?? {};
this.control = this._fb.group({});
this.interests?.forEach((value, key) => {
this.control.addControl(key, new UntypedFormControl(fData[key] ?? false));
});
}
_patchValue(update: { previous: InterestsFormBlockData; current: InterestsFormBlockData }): void {
const fData = update.current ?? {};
this.interests?.forEach((value, key) => {
this.control.get(key).patchValue(fData[key] ?? false);
});
}
}

View File

@@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { InterestsFormBlockComponent } from './interests-form-block.component';
import { UiCheckboxModule } from '@ui/checkbox';
import { ReactiveFormsModule } from '@angular/forms';
import { UiCommonModule } from '@ui/common';
@NgModule({
imports: [CommonModule, UiCommonModule, ReactiveFormsModule, UiCheckboxModule],
exports: [InterestsFormBlockComponent],
declarations: [InterestsFormBlockComponent],
})
export class InterestsFormBlockModule {}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './name-form-block.component';
export * from './name-form-block.module';
// end:ng42.barrel

View File

@@ -0,0 +1,8 @@
import { Gender } from '@swagger/crm';
export interface NameFormBlockData {
gender?: Gender;
title?: string;
firstName?: string;
lastName?: string;
}

View File

@@ -0,0 +1,47 @@
<ng-container [formGroup]="control">
<label class="grid grid-flow-row gap-1">
<span>Anrede</span>
<shared-select formControlName="gender" placeholder="Anrede" [readonly]="readonly" [tabindex]="tabIndexStart">
<shared-select-option [value]="2">Herr</shared-select-option>
<shared-select-option [value]="4">Frau</shared-select-option>
</shared-select>
</label>
<label class="grid grid-flow-row gap-1">
<span>Titel</span>
<shared-select formControlName="title" placeholder="Titel" [readonly]="readonly" [tabindex]="tabIndexStart + 1">
<shared-select-option value="Dipl.-Ing.">Dipl.-Ing.</shared-select-option>
<shared-select-option value="Dr.">Dr.</shared-select-option>
<shared-select-option value="Dr. med.">Dr. med.</shared-select-option>
<shared-select-option value="Prof.">Prof.</shared-select-option>
<shared-select-option value="Prof. Dr.">Prof. Dr.</shared-select-option>
<shared-select-option value="RA">RA</shared-select-option>
</shared-select>
</label>
<!-- <ui-form-control [clearable]="!readonly" label="Anrede" [requiredMark]="requiredMarks.includes('gender') ? '*' : ''">
<ui-select formControlName="gender" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly">
<ui-select-option [value]="2" label="Herr"></ui-select-option>
<ui-select-option [value]="4" label="Frau"></ui-select-option>
</ui-select>
</ui-form-control> -->
<!-- <ui-form-control [clearable]="!readonly" label="Titel" [requiredMark]="requiredMarks.includes('title') ? '*' : ''">
<ui-select formControlName="title" [tabindex]="tabIndexStart + 1" [readonly]="readonly">
<ui-select-option value="Dipl.-Ing." label="Dipl.-Ing."></ui-select-option>
<ui-select-option value="Dr." label="Dr."></ui-select-option>
<ui-select-option value="Dr. med." label="Dr. med."></ui-select-option>
<ui-select-option value="Prof." label="Prof."></ui-select-option>
<ui-select-option value="Prof. Dr." label="Prof. Dr."></ui-select-option>
<ui-select-option value="RA" label="RA"></ui-select-option>
</ui-select>
</ui-form-control> -->
<ui-form-control label="Nachname" [requiredMark]="requiredMarks.includes('firstName') ? '*' : ''">
<input uiInput type="text" formControlName="lastName" [tabindex]="tabIndexStart + 2" [readonly]="readonly" />
</ui-form-control>
<ui-form-control label="Vorname" [requiredMark]="requiredMarks.includes('lastName') ? '*' : ''">
<input uiInput type="text" formControlName="firstName" [tabindex]="tabIndexStart + 3" [readonly]="readonly" />
</ui-form-control>
</ng-container>

View File

@@ -0,0 +1,3 @@
:host {
@apply grid grid-cols-2 gap-x-8;
}

View File

@@ -0,0 +1,56 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { FormBlockGroup } from '../form-block';
import { NameFormBlockData } from './name-form-block-data';
import { Gender } from '@swagger/crm';
@Component({
selector: 'app-name-form-block',
templateUrl: 'name-form-block.component.html',
styleUrls: ['name-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NameFormBlockComponent extends FormBlockGroup<NameFormBlockData> {
get tabIndexEnd() {
return this.tabIndexStart + 3;
}
displayGenderNameFn = (gender: Gender) => {
if (gender == 2) {
return 'Herr';
}
if (gender == 4) {
return 'Frau';
}
return undefined;
};
constructor() {
super();
}
initializeControl(data?: NameFormBlockData): void {
this.control = new UntypedFormGroup({
gender: new UntypedFormControl(data?.gender, this.getValidatorFn('gender')),
title: new UntypedFormControl(data?.title, this.getValidatorFn('title')),
firstName: new UntypedFormControl(data?.firstName ?? '', this.getValidatorFn('firstName')),
lastName: new UntypedFormControl(data?.lastName ?? '', this.getValidatorFn('lastName')),
});
}
_patchValue(update: { previous: NameFormBlockData; current: NameFormBlockData }): void {
this.control.patchValue({
gender: update.current?.gender ?? 0,
title: update.current?.title ?? '',
firstName: update.current?.firstName ?? '',
lastName: update.current?.lastName ?? '',
});
}
updateValidators(): void {
this.control.get('gender').setValidators(this.getValidatorFn('gender'));
this.control.get('title').setValidators(this.getValidatorFn('title'));
this.control.get('firstName').setValidators(this.getValidatorFn('firstName'));
this.control.get('lastName').setValidators(this.getValidatorFn('lastName'));
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NameFormBlockComponent } from './name-form-block.component';
import { UiFormControlModule } from '@ui/form-control';
import { UiInputModule } from '@ui/input';
import { ReactiveFormsModule } from '@angular/forms';
import { UiCommonModule } from '@ui/common';
import { SelectModule } from '@shared/components/select';
@NgModule({
imports: [CommonModule, UiCommonModule, ReactiveFormsModule, UiFormControlModule, UiInputModule, SelectModule],
exports: [NameFormBlockComponent],
declarations: [NameFormBlockComponent],
})
export class NameFormBlockModule {}

View File

@@ -0,0 +1,2 @@
export * from './newsletter-form-block.component';
export * from './newsletter-form-block.module';

View File

@@ -0,0 +1,3 @@
<ui-checkbox [formControl]="control" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly">
Erhalten Sie 250 Lesepunkte mit der Anmeldung zum Newsletter und eine Geburtstagsüberraschung
</ui-checkbox>

View File

@@ -0,0 +1,11 @@
:host {
@apply block;
}
ui-checkbox {
@apply font-semibold;
}
::ng-deep app-newsletter-form-block ui-checkbox {
align-items: flex-start !important;
}

View File

@@ -0,0 +1,27 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { UntypedFormControl } from '@angular/forms';
import { FormBlock } from '../form-block';
@Component({
selector: 'app-newsletter-form-block',
templateUrl: 'newsletter-form-block.component.html',
styleUrls: ['newsletter-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NewsletterFormBlockComponent extends FormBlock<boolean, UntypedFormControl> {
get tabIndexEnd() {
return this.tabIndexStart;
}
constructor() {
super();
}
initializeControl(data?: boolean): void {
this.control = new UntypedFormControl(data ?? false);
}
_patchValue(update: { previous: boolean; current: boolean }): void {
this.control.patchValue(update.current);
}
}

View File

@@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NewsletterFormBlockComponent } from './newsletter-form-block.component';
import { UiCheckboxModule } from '@ui/checkbox';
import { ReactiveFormsModule } from '@angular/forms';
import { UiCommonModule } from '@ui/common';
@NgModule({
imports: [CommonModule, UiCommonModule, ReactiveFormsModule, UiCheckboxModule],
exports: [NewsletterFormBlockComponent],
declarations: [NewsletterFormBlockComponent],
})
export class NewsletterFormBlockModule {}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './organisation-form-block.component';
export * from './organisation-form-block.module';
// end:ng42.barrel

View File

@@ -0,0 +1,5 @@
export interface OrganisationFormBlockData {
name?: string;
department?: string;
vatId?: string;
}

View File

@@ -0,0 +1,13 @@
<ng-container [formGroup]="control">
<ui-form-control class="col-span-2" label="Firmenname" [requiredMark]="requiredMarks.includes('name') ? '*' : ''">
<input uiInput type="text" formControlName="name" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly" />
</ui-form-control>
<ng-container *ngIf="appearence === 'default'">
<ui-form-control label="Abteilung" [requiredMark]="requiredMarks.includes('department') ? '*' : ''">
<input uiInput type="text" formControlName="department" [tabindex]="tabIndexStart + 1" [readonly]="readonly" />
</ui-form-control>
<ui-form-control label="USt-ID" [requiredMark]="requiredMarks.includes('vatId') ? '*' : ''">
<input uiInput type="text" formControlName="vatId" [tabindex]="tabIndexStart + 2" [readonly]="readonly" />
</ui-form-control>
</ng-container>
</ng-container>

View File

@@ -0,0 +1,3 @@
:host {
@apply grid grid-cols-2 gap-x-8;
}

View File

@@ -0,0 +1,47 @@
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { FormBlockGroup } from '../form-block';
import { OrganisationFormBlockData } from './organisation-form-block-data';
@Component({
selector: 'app-organisation-form-block',
templateUrl: 'organisation-form-block.component.html',
styleUrls: ['organisation-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OrganisationFormBlockComponent extends FormBlockGroup<OrganisationFormBlockData> {
@Input()
appearence: 'default' | 'compact' = 'default';
get tabIndexEnd() {
return this.tabIndexStart + 2;
}
constructor() {
super();
}
initializeControl(data?: OrganisationFormBlockData): void {
this.control = new UntypedFormGroup({
name: new UntypedFormControl(data?.name ?? '', this.getValidatorFn('name')),
department: new UntypedFormControl(data?.department ?? '', this.getValidatorFn('department')),
vatId: new UntypedFormControl(data?.vatId ?? '', this.getValidatorFn('vatId')),
});
}
_patchValue(update: { previous: OrganisationFormBlockData; current: OrganisationFormBlockData }): void {
this.control.patchValue({
name: update.current?.name ?? '',
department: update.current?.department ?? '',
vatId: update.current?.vatId ?? '',
});
}
updateValidators(): void {
this.control.setValidators(this.getValidatorFn('name'));
this.control.get('name').updateValueAndValidity();
this.control.setValidators(this.getValidatorFn('department'));
this.control.get('department').updateValueAndValidity();
this.control.setValidators(this.getValidatorFn('vatId'));
this.control.get('vatId').updateValueAndValidity();
}
}

View File

@@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { OrganisationFormBlockComponent } from './organisation-form-block.component';
import { UiFormControlModule } from '@ui/form-control';
import { UiInputModule } from '@ui/input';
import { ReactiveFormsModule } from '@angular/forms';
import { UiCommonModule } from '@ui/common';
@NgModule({
imports: [CommonModule, UiCommonModule, ReactiveFormsModule, UiFormControlModule, UiInputModule],
exports: [OrganisationFormBlockComponent],
declarations: [OrganisationFormBlockComponent],
})
export class OrganisationFormBlockModule {}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './p4m-number-form-block.component';
export * from './p4m-number-form-block.module';
// end:ng42.barrel

View File

@@ -0,0 +1,6 @@
<ui-form-control label="Kundenkartencode" requiredMark="*" class="flex-grow">
<input uiInput type="text" [formControl]="control" [tabindex]="tabIndexStart" [readonly]="readonly" [autofocus]="focusAfterInit" />
</ui-form-control>
<button type="button" *ngIf="!readonly && canScan()" (click)="scan()">
<shared-icon icon="barcode-scan" [size]="32"></shared-icon>
</button>

View File

@@ -0,0 +1,11 @@
:host {
@apply block relative;
}
button {
@apply absolute -right-2 top-0 h-14 w-14 border-none outline-none bg-transparent items-center justify-center rounded-full bg-brand;
shared-icon {
@apply flex justify-center items-center text-white;
}
}

View File

@@ -0,0 +1,45 @@
import { Component, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { UntypedFormControl, Validators } from '@angular/forms';
import { FormBlockControl } from '../form-block';
import { ScanAdapterService } from '@adapter/scan';
@Component({
selector: 'app-p4m-number-form-block',
templateUrl: 'p4m-number-form-block.component.html',
styleUrls: ['p4m-number-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class P4mNumberFormBlockComponent extends FormBlockControl<string> {
get tabIndexEnd() {
return this.tabIndexStart;
}
constructor(private scanAdapter: ScanAdapterService, private changeDetectorRef: ChangeDetectorRef) {
super();
}
updateValidators(): void {
this.control.setValidators([...this.getValidatorFn()]);
this.control.setAsyncValidators(this.getAsyncValidatorFn());
this.control.updateValueAndValidity();
}
initializeControl(data?: string): void {
this.control = new UntypedFormControl(data ?? '', [Validators.required], this.getAsyncValidatorFn());
}
_patchValue(update: { previous: string; current: string }): void {
this.control.patchValue(update.current);
}
scan() {
this.scanAdapter.scan().subscribe((result) => {
this.control.patchValue(result);
this.changeDetectorRef.markForCheck();
});
}
canScan() {
return this.scanAdapter.isReady();
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { P4mNumberFormBlockComponent } from './p4m-number-form-block.component';
import { ReactiveFormsModule } from '@angular/forms';
import { UiFormControlModule } from '@ui/form-control';
import { UiInputModule } from '@ui/input';
import { UiCommonModule } from '@ui/common';
import { IconComponent } from '@shared/components/icon';
@NgModule({
imports: [CommonModule, UiCommonModule, ReactiveFormsModule, UiFormControlModule, UiInputModule, IconComponent],
exports: [P4mNumberFormBlockComponent],
declarations: [P4mNumberFormBlockComponent],
})
export class P4mNumberFormBlockModule {}

View File

@@ -0,0 +1,3 @@
export * from './phone-numbers-form-block-data';
export * from './phone-numbers-form-block.component';
export * from './phone-numbers-form-block.module';

View File

@@ -0,0 +1,4 @@
export interface PhoneNumbersFormBlockData {
phone: string;
mobile: string;
}

View File

@@ -0,0 +1,8 @@
<ng-container [formGroup]="control">
<ui-form-control label="Festnetznummer" [requiredMark]="requiredMarks.includes('phone') ? '*' : ''">
<input uiInput type="tel" formControlName="phone" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly" />
</ui-form-control>
<ui-form-control label="Mobilnummer" [clearable]="false" [requiredMark]="requiredMarks.includes('mobile') ? '*' : ''">
<input uiInput type="tel" formControlName="mobile" [tabindex]="tabIndexStart + 1" [readonly]="readonly" />
</ui-form-control>
</ng-container>

Some files were not shown because too many files have changed in this diff Show More