mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
RD fur customer bereich
This commit is contained in:
@@ -69,12 +69,12 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'customer',
|
||||
loadChildren: () => import('@page/customer').then((m) => m.PageCustomerModule),
|
||||
loadChildren: () => import('@page/customer-rd').then((m) => m.CustomerModule),
|
||||
canActivate: [CanActivateCustomerGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/customer',
|
||||
loadChildren: () => import('@page/customer').then((m) => m.PageCustomerModule),
|
||||
loadChildren: () => import('@page/customer-rd').then((m) => m.CustomerModule),
|
||||
canActivate: [CanActivateCustomerWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
debug: false,
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
export const environment = {
|
||||
production: false,
|
||||
debug: false,
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -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)
|
||||
|
||||
7
apps/page/customer-rd/ng-package.json
Normal file
7
apps/page/customer-rd/ng-package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../../dist/page/customer",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply flex flex-col bg-surface text-surface-content h-[11.313rem] mb-[0.625rem] p-4;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<div
|
||||
class="customer-result-list-header bg-surface-2 text-surface-2-content flex flex-row justify-between pb-4"
|
||||
[class.flex-row]="!compact"
|
||||
[class.flex-col]="compact"
|
||||
>
|
||||
<div class="grid grid-flow-col gap-3 items-center justify-start grow-0">
|
||||
<shared-searchbox class="w-[20.892rem]">
|
||||
<input type="text" sharedSearchboxInput />
|
||||
</shared-searchbox>
|
||||
<button type="button" class="btn btn-light w-[5.813rem]">
|
||||
Filter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<span class="mr-5 self-end text-sm" [class.mt-4]="compact">
|
||||
xxx Treffer
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<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 })"
|
||||
>
|
||||
<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.225rem)]" *ngIf="compact">
|
||||
<a
|
||||
*cdkVirtualFor="let customer of customers$ | async; trackBy: trackByFn"
|
||||
[routerLink]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })"
|
||||
>
|
||||
<page-customer-result-list-item [customer]="customer" (click)="select(customer)"></page-customer-result-list-item>
|
||||
</a>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
@@ -0,0 +1,49 @@
|
||||
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) {}
|
||||
|
||||
select(customer: CustomerInfoDTO) {
|
||||
this.selected = customer;
|
||||
this.selectedChange.emit(customer);
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -0,0 +1,3 @@
|
||||
<shared-breadcrumb [key]="processId$ | async" [tags]="['customer']" class="mb-9"></shared-breadcrumb>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
15
apps/page/customer-rd/src/lib/customer-page.component.ts
Normal file
15
apps/page/customer-rd/src/lib/customer-page.component.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer',
|
||||
templateUrl: 'customer-page.component.html',
|
||||
styleUrls: ['customer-page.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerComponent {
|
||||
processId$ = this._activatedRoute.data.pipe(map((data) => data.processId));
|
||||
|
||||
constructor(private _activatedRoute: ActivatedRoute) {}
|
||||
}
|
||||
16
apps/page/customer-rd/src/lib/customer-page.module.ts
Normal file
16
apps/page/customer-rd/src/lib/customer-page.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { CustomerComponent } from './customer-page.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { routes } from './routes';
|
||||
|
||||
import { BreadcrumbModule } from '@shared/components/breadcrumb';
|
||||
import { CustomerSearchModule } from './customer-search/customer-search.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, RouterModule.forChild(routes), BreadcrumbModule, CustomerSearchModule],
|
||||
exports: [CustomerComponent],
|
||||
declarations: [CustomerComponent],
|
||||
})
|
||||
export class CustomerModule {}
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="grid grid-cols-[27.5rem_auto] max-h-[calc(100vh-13.875rem)] h-[calc(100vh-13.875rem)] gap-6">
|
||||
<div *ngIf="showSide$ | async" [ngSwitch]="side$ | async">
|
||||
<page-customer-results-side-view *ngSwitchCase="'results'"></page-customer-results-side-view>
|
||||
</div>
|
||||
<div [class.col-span-2]="hideSide$ | async" class="overflow-y-scroll">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,108 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||
import { combineLatest, BehaviorSubject, Subscription } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||
import { CustomerSearchStore } from './store/customer-search.store';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-search',
|
||||
templateUrl: 'customer-search.component.html',
|
||||
styleUrls: ['customer-search.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [CustomerSearchStore],
|
||||
})
|
||||
export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
isTablet$ = this._breakpointObserver.observe('(max-width: 1024px)').pipe(map((result) => result.matches));
|
||||
|
||||
side$ = new BehaviorSubject<string | undefined>(undefined);
|
||||
|
||||
get side() {
|
||||
return this.side$.value;
|
||||
}
|
||||
|
||||
showSide$ = combineLatest([this.isTablet$, this.side$]).pipe(map(([isTablet, side]) => !isTablet && side));
|
||||
|
||||
hideSide$ = this.showSide$.pipe(map((showSide) => !showSide));
|
||||
|
||||
get snapshot() {
|
||||
return this._activatedRoute.snapshot;
|
||||
}
|
||||
|
||||
get parentSnapshot() {
|
||||
return this._activatedRoute.parent?.snapshot;
|
||||
}
|
||||
|
||||
get firstChildSnapshot() {
|
||||
return this._activatedRoute.firstChild?.snapshot;
|
||||
}
|
||||
|
||||
private _eventsSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private _store: CustomerSearchStore,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _breakpointObserver: BreakpointObserver,
|
||||
private _router: Router
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.checkAndUpdateProcessId();
|
||||
this.checkAndUpdateSide();
|
||||
this.checkAndUpdateCustomerId();
|
||||
|
||||
this._eventsSubscription = this._router.events.subscribe((event) => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
this.checkAndUpdateProcessId();
|
||||
this.checkAndUpdateSide();
|
||||
this.checkAndUpdateCustomerId();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._eventsSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
checkAndUpdateProcessId() {
|
||||
let processId: number;
|
||||
|
||||
processId = this.snapshot.data?.processId;
|
||||
|
||||
if (!processId) {
|
||||
processId = this.parentSnapshot?.data?.processId;
|
||||
}
|
||||
|
||||
if (processId !== this._store.processId) {
|
||||
this._store.setProcessId(processId);
|
||||
}
|
||||
}
|
||||
|
||||
checkAndUpdateSide() {
|
||||
let side: string;
|
||||
|
||||
side = this.snapshot.data?.side;
|
||||
|
||||
if (!side) {
|
||||
side = this.firstChildSnapshot.data?.side;
|
||||
}
|
||||
|
||||
if (side !== this.side) {
|
||||
this.side$.next(side);
|
||||
}
|
||||
}
|
||||
|
||||
checkAndUpdateCustomerId() {
|
||||
let customerId: number;
|
||||
|
||||
customerId = this.snapshot.params.customerId;
|
||||
|
||||
if (!customerId) {
|
||||
customerId = this.firstChildSnapshot?.params.customerId;
|
||||
}
|
||||
|
||||
if (customerId !== this._store.customer?.id) {
|
||||
this._store.selectCustomer(customerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { CustomerSearchComponent } from './customer-search.component';
|
||||
import { CustomerResultsSideViewModule } from './results-side-view/results-side-view.module';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { CustomerResultsMainViewModule } from './results-main-view/results-main-view.module';
|
||||
import { CustomerDetailsViewMainModule } from './details-main-view/details-main-view.module';
|
||||
import { CustomerHistoryMainViewModule } from './history-main-view/history-main-view.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule,
|
||||
CustomerResultsSideViewModule,
|
||||
CustomerResultsMainViewModule,
|
||||
CustomerDetailsViewMainModule,
|
||||
CustomerHistoryMainViewModule,
|
||||
],
|
||||
exports: [CustomerSearchComponent],
|
||||
declarations: [CustomerSearchComponent],
|
||||
})
|
||||
export class CustomerSearchModule {}
|
||||
@@ -0,0 +1,19 @@
|
||||
:host {
|
||||
@apply block bg-surface text-surface-content rounded-[0.313rem] mb-3;
|
||||
}
|
||||
|
||||
.data-label {
|
||||
@apply w-[10.75rem];
|
||||
}
|
||||
|
||||
.data-value {
|
||||
@apply grow font-bold;
|
||||
}
|
||||
|
||||
.customer-details-customer-main-row {
|
||||
@apply px-5 py-3 bg-surface text-surface-content border-t-2 border-solid border-surface-2 flex flex-row items-center;
|
||||
}
|
||||
|
||||
.customer-details-customer-main-row .data-label {
|
||||
@apply w-[6.875rem];
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<ng-container *ngIf="fetching$ | async; else customerTemplate"></ng-container>
|
||||
|
||||
<ng-template #customerTemplate>
|
||||
<div class="customer-details-header grid grid-flow-row pt-1 px-1 pb-6">
|
||||
<div class="customer-details-header-actions flex flex-row justify-end pt-1 px-1">
|
||||
<a *ngIf="historyRoute$ | async; let historyRoute" class="btn btn-label font-bold text-brand" [routerLink]="historyRoute">Historie</a>
|
||||
</div>
|
||||
<div class="customer-details-header-body text-center -mt-3">
|
||||
<h1 class="text-[1.625rem] font-bold">Kundendetails</h1>
|
||||
<p>Sind Ihre Kundendaten korrekt?</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-type flex flex-row justify-between items-center bg-surface-2 text-surface-2-content">
|
||||
<div class="pl-4 font-bold">{{ customerType$ | async }}</div>
|
||||
<button class="btn btn-label font-bold text-brand" type="button">Bearbeiten</button>
|
||||
</div>
|
||||
|
||||
<div class="customer-details-customer-main-data px-5 py-3 grid grid-flow-row gap-3">
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Erstellungsdatum</div>
|
||||
<div class="data-value">{{ created$ | async | date: 'dd.MM.yyyy' }} | {{ created$ | async | date: 'hh:mm' }} Uhr</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Kundennummer</div>
|
||||
<div class="data-value">{{ customerNumber$ | async }}</div>
|
||||
</div>
|
||||
<div class="flex flex-row" *ngIf="customerNumberDig$ | async; let customerNumberDig">
|
||||
<div class="data-label">Kundennummer-DIG</div>
|
||||
<div class="data-value">{{ customerNumberDig }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Anrede</div>
|
||||
<div class="data-value">{{ gender$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Titel</div>
|
||||
<div class="data-value">{{ title$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Nachname</div>
|
||||
<div class="data-value">{{ lastName$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Vorname</div>
|
||||
<div class="data-value">{{ firstName$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">E-Mail</div>
|
||||
<div class="data-value">{{ email$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Straße</div>
|
||||
<div class="data-value">{{ street$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Hausnr.</div>
|
||||
<div class="data-value">{{ streetNumber$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">PLZ</div>
|
||||
<div class="data-value">{{ zipCode$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Ort</div>
|
||||
<div class="data-value">{{ city$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Adresszusatz</div>
|
||||
<div class="data-value">{{ info$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Land</div>
|
||||
<div class="data-value">{{ country$ | async | country }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Festnetznr.</div>
|
||||
<div class="data-value">{{ landline$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Mobilnr.</div>
|
||||
<div class="data-value">{{ mobile$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Abteilung</div>
|
||||
<div class="data-value">{{ department$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">USt-ID</div>
|
||||
<div class="data-value">{{ vatId$ | async }}</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { CustomerSearchNavigation } from '../../navigations';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
|
||||
const GENDER_MAP = {
|
||||
2: 'Herr',
|
||||
4: 'Frau',
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-details-main-view',
|
||||
templateUrl: 'details-main-view.component.html',
|
||||
styleUrls: ['details-main-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerDetailsViewMainComponent {
|
||||
fetching$ = this._store.fetchingCustomer$;
|
||||
|
||||
processId$ = this._store.processId$;
|
||||
|
||||
customerId$ = this._store.customerId$;
|
||||
|
||||
historyRoute$ = combineLatest([this.processId$, this.customerId$]).pipe(
|
||||
map(([processId, customerId]) => this._navigation.historyRoute({ processId, customerId }))
|
||||
);
|
||||
|
||||
customerType$ = this._store.select((s) => s.customer?.features?.find((f) => f.enabled)?.description);
|
||||
|
||||
created$ = this._store.select((s) => s.customer?.created);
|
||||
|
||||
customerNumber$ = this._store.select((s) => s.customer?.customerNumber);
|
||||
|
||||
customerNumberDig$ = this._store.select((s) => s.customer?.linkedRecords?.find((r) => r.repository === 'dig')?.number);
|
||||
|
||||
gender$ = this._store.select((s) => GENDER_MAP[s.customer?.gender]);
|
||||
|
||||
title$ = this._store.select((s) => s.customer?.title);
|
||||
|
||||
lastName$ = this._store.select((s) => s.customer?.lastName);
|
||||
|
||||
firstName$ = this._store.select((s) => s.customer?.firstName);
|
||||
|
||||
email$ = this._store.select((s) => s.customer?.communicationDetails?.email);
|
||||
|
||||
street$ = this._store.select((s) => s.customer?.address?.street);
|
||||
|
||||
streetNumber$ = this._store.select((s) => s.customer?.address?.streetNumber);
|
||||
|
||||
zipCode$ = this._store.select((s) => s.customer?.address?.zipCode);
|
||||
|
||||
city$ = this._store.select((s) => s.customer?.address?.city);
|
||||
|
||||
country$ = this._store.select((s) => s.customer?.address?.country);
|
||||
|
||||
info$ = this._store.select((s) => s.customer?.address?.info);
|
||||
|
||||
landline$ = this._store.select((s) => s.customer?.communicationDetails?.phone);
|
||||
|
||||
mobile$ = this._store.select((s) => s.customer?.communicationDetails?.mobile);
|
||||
|
||||
department$ = this._store.select((s) => s.customer?.organisation?.department);
|
||||
|
||||
vatId$ = this._store.select((s) => s.customer?.organisation?.vatId);
|
||||
|
||||
constructor(private _store: CustomerSearchStore, private _navigation: CustomerSearchNavigation) {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { CustomerDetailsViewMainComponent } from './details-main-view.component';
|
||||
import { CountryPipe } from '@shared/pipes/country';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, CountryPipe, RouterModule],
|
||||
exports: [CustomerDetailsViewMainComponent],
|
||||
declarations: [CustomerDetailsViewMainComponent],
|
||||
})
|
||||
export class CustomerDetailsViewMainModule {}
|
||||
@@ -0,0 +1,7 @@
|
||||
:host {
|
||||
@apply block bg-surface text-surface-content rounded-[0.313rem] mb-3;
|
||||
}
|
||||
|
||||
::ng-deep page-customer-history-main-view shared-history-list .scroll-container {
|
||||
@apply h-[calc(100vh-24rem)];
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<ng-container *ngIf="fetching$ | async; else historyTemplate"></ng-container>
|
||||
|
||||
<ng-template #historyTemplate>
|
||||
<div>
|
||||
<div class="customer-history-header">
|
||||
<div class="customer-history-header-actions flex flex-row justify-end pt-1 px-1">
|
||||
<a [routerLink]="detailsRoute$ | async" class="btn btn-label">
|
||||
<ui-icon [icon]="'close'"></ui-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="customer-history-header-body text-center -mt-3">
|
||||
<h1 class="text-[1.625rem] font-bold">Historie</h1>
|
||||
</div>
|
||||
<div class="customer-history-header-info flex flex-row justify-evenly items-center my-5">
|
||||
<div class="flex flex-row">
|
||||
<div class="w-36">Kundenname</div>
|
||||
<div class="grow font-bold">{{ customerName$ | async }}</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="w-36">Kundennummer</div>
|
||||
<div class="grow font-bold">{{ customerNumber$ | async }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-3 bg-surface-2 text-surface-2-content">
|
||||
<shared-history-list [history]="history$ | async"> </shared-history-list>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,76 @@
|
||||
import { coerceNumberProperty } from '@angular/cdk/coercion';
|
||||
import { Component, ChangeDetectionStrategy, AfterViewInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { HistoryDTO } from '@swagger/crm';
|
||||
import { Observable, combineLatest } from 'rxjs';
|
||||
import { map, switchMap, tap } from 'rxjs/operators';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { CustomerSearchNavigation } from '../../navigations';
|
||||
|
||||
export interface CustomerHistoryViewMainState {
|
||||
history?: HistoryDTO[];
|
||||
fetching?: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-history-main-view',
|
||||
templateUrl: 'history-main-view.component.html',
|
||||
styleUrls: ['history-main-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerHistoryMainViewComponent extends ComponentStore<CustomerHistoryViewMainState> implements AfterViewInit {
|
||||
fetching$ = this.select((s) => s.fetching);
|
||||
|
||||
history$ = this.select((s) => s.history);
|
||||
|
||||
processId$ = this._store.processId$;
|
||||
|
||||
customerId$ = this._store.customerId$;
|
||||
|
||||
customer$ = this._store.customer$;
|
||||
|
||||
detailsRoute$ = combineLatest([this.processId$, this.customerId$]).pipe(
|
||||
map(([processId, customerId]) => this._navigation.detailsRoute({ processId, customerId }))
|
||||
);
|
||||
|
||||
customerName$ = this.customer$.pipe(map((customer) => `${customer?.lastName}, ${customer?.firstName}`));
|
||||
|
||||
customerNumber$ = this.customer$.pipe(map((customer) => customer?.customerNumber));
|
||||
|
||||
constructor(
|
||||
private _store: CustomerSearchStore,
|
||||
private _customerService: CrmCustomerService,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
) {
|
||||
super({});
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.fetchHistory(this.customerId$);
|
||||
}
|
||||
|
||||
fetchHistory = this.effect((customerId$: Observable<number>) =>
|
||||
customerId$.pipe(
|
||||
tap(() => this.patchState({ fetching: true })),
|
||||
switchMap((customerId) =>
|
||||
this._customerService
|
||||
.getCustomerHistory(customerId)
|
||||
.pipe(tapResponse(this.handleFetchHistoryResponse, this.handleFetchHistoryError, this.handleFetchHistoryComplete))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
handleFetchHistoryResponse = (history: HistoryDTO[]) => {
|
||||
this.patchState({ history });
|
||||
};
|
||||
|
||||
handleFetchHistoryError = (err: any) => {
|
||||
console.error(err);
|
||||
};
|
||||
|
||||
handleFetchHistoryComplete = () => {
|
||||
this.patchState({ fetching: false });
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SharedHistoryListModule } from '@shared/components/history';
|
||||
|
||||
import { CustomerHistoryMainViewComponent } from './history-main-view.component';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, RouterModule, SharedHistoryListModule, UiIconModule],
|
||||
exports: [CustomerHistoryMainViewComponent],
|
||||
declarations: [CustomerHistoryMainViewComponent],
|
||||
})
|
||||
export class CustomerHistoryMainViewModule {}
|
||||
@@ -0,0 +1 @@
|
||||
<page-customer-result-list [processId]="processId$ | async"> </page-customer-result-list>
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NumberInput } from '@angular/cdk/coercion';
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { CustomerInfoDTO } from '@swagger/crm';
|
||||
import { CustomerSearchStore } from '../store/customer-search.store';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-results-main-view',
|
||||
templateUrl: 'results-main-view.component.html',
|
||||
styleUrls: ['results-main-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerResultsMainViewComponent {
|
||||
processId$ = this._store.processId$;
|
||||
|
||||
constructor(private _store: CustomerSearchStore) {}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { CustomerResultsMainViewComponent } from './results-main-view.component';
|
||||
import { CustomerResultListModule } from '../../components/customer-result-list/customer-result-list.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, CustomerResultListModule],
|
||||
exports: [CustomerResultsMainViewComponent],
|
||||
declarations: [CustomerResultsMainViewComponent],
|
||||
})
|
||||
export class CustomerResultsMainViewModule {}
|
||||
@@ -0,0 +1 @@
|
||||
<page-customer-result-list [processId]="processId$ | async" compact="true"> </page-customer-result-list>
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NumberInput } from '@angular/cdk/coercion';
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { CustomerInfoDTO } from '@swagger/crm';
|
||||
import { CustomerSearchStore } from '../store/customer-search.store';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-results-side-view',
|
||||
templateUrl: 'results-side-view.component.html',
|
||||
styleUrls: ['results-side-view.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerResultsSideViewComponent {
|
||||
processId$ = this._store.processId$;
|
||||
|
||||
constructor(private _store: CustomerSearchStore) {}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { CustomerResultsSideViewComponent } from './results-side-view.component';
|
||||
import { CustomerResultListModule } from '../../components/customer-result-list/customer-result-list.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, CustomerResultListModule],
|
||||
exports: [CustomerResultsSideViewComponent],
|
||||
declarations: [CustomerResultsSideViewComponent],
|
||||
})
|
||||
export class CustomerResultsSideViewModule {}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { CustomerDTO } from '@swagger/crm';
|
||||
|
||||
export interface CustomerSearchState {
|
||||
processId?: number;
|
||||
customer?: CustomerDTO;
|
||||
fetchingCustomer?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { CustomerSearchState } from './customer-search.state';
|
||||
import * as S from './selectors';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, switchMap, tap } from 'rxjs/operators';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { Result } from '@domain/defs';
|
||||
import { CustomerDTO } from '@swagger/crm';
|
||||
|
||||
@Injectable()
|
||||
export class CustomerSearchStore extends ComponentStore<CustomerSearchState> {
|
||||
get processId() {
|
||||
return this.get(S.selectProcessId);
|
||||
}
|
||||
|
||||
processId$ = this.select(S.selectProcessId);
|
||||
|
||||
get fetchingCustomer() {
|
||||
return this.get(S.selectFetchingCustomer);
|
||||
}
|
||||
|
||||
fetchingCustomer$ = this.select(S.selectFetchingCustomer);
|
||||
|
||||
get customerId() {
|
||||
return this.get(S.selectCustomerId);
|
||||
}
|
||||
|
||||
customerId$ = this.select(S.selectCustomerId);
|
||||
|
||||
get customer() {
|
||||
return this.get(S.selectCustomer);
|
||||
}
|
||||
|
||||
customer$ = this.select(S.selectCustomer);
|
||||
|
||||
constructor(private _customerService: CrmCustomerService) {
|
||||
super({});
|
||||
}
|
||||
|
||||
setProcessId = this.updater((state, processId: number) => ({ ...state, processId }));
|
||||
|
||||
selectCustomer = this.effect((customerId$: Observable<number>) =>
|
||||
customerId$.pipe(
|
||||
distinctUntilChanged(),
|
||||
tap((custoemrId) => this.patchState({ fetchingCustomer: true, customer: { id: custoemrId } })),
|
||||
switchMap((customerId) =>
|
||||
this._customerService
|
||||
.getCustomer(customerId)
|
||||
.pipe(tapResponse(this.handleSelectCustomerResponse, this.handleSelectCustomerError, this.handleSelectCustomerComplete))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
handleSelectCustomerResponse = (result: Result<CustomerDTO>) => {
|
||||
this.patchState({ customer: result.result });
|
||||
};
|
||||
|
||||
handleSelectCustomerError = (err: any) => {
|
||||
console.error(err);
|
||||
};
|
||||
|
||||
handleSelectCustomerComplete = () => {
|
||||
this.patchState({ fetchingCustomer: false });
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './customer-search.state';
|
||||
export * from './customer-search.store';
|
||||
export * from './selectors';
|
||||
@@ -0,0 +1,18 @@
|
||||
import { coerceNumberProperty } from '@angular/cdk/coercion';
|
||||
import { CustomerSearchState } from './customer-search.state';
|
||||
|
||||
export function selectProcessId(s: CustomerSearchState): number {
|
||||
return coerceNumberProperty(s.processId);
|
||||
}
|
||||
|
||||
export function selectFetchingCustomer(s: CustomerSearchState) {
|
||||
return s.fetchingCustomer;
|
||||
}
|
||||
|
||||
export function selectCustomer(s: CustomerSearchState) {
|
||||
return s.customer;
|
||||
}
|
||||
|
||||
export function selectCustomerId(s: CustomerSearchState) {
|
||||
return selectCustomer(s)?.id;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { NumberInput, coerceNumberProperty } from '@angular/cdk/coercion';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CustomerSearchNavigation {
|
||||
constructor(private _router: Router) {}
|
||||
|
||||
detailsRoute(params: { processId: NumberInput; customerId: NumberInput }): any[] {
|
||||
return ['/kunde', coerceNumberProperty(params.processId), 'customer', coerceNumberProperty(params.customerId)];
|
||||
}
|
||||
|
||||
navigateToDetails(params: { processId: NumberInput; customerId: NumberInput }): Promise<boolean> {
|
||||
return this._router.navigate(this.detailsRoute(params));
|
||||
}
|
||||
|
||||
historyRoute(params: { processId: NumberInput; customerId: NumberInput }): any[] {
|
||||
return ['/kunde', coerceNumberProperty(params.processId), 'customer', coerceNumberProperty(params.customerId), 'history'];
|
||||
}
|
||||
|
||||
navigateToHistory(params: { processId: NumberInput; customerId: NumberInput }): Promise<boolean> {
|
||||
return this._router.navigate(this.historyRoute(params));
|
||||
}
|
||||
}
|
||||
1
apps/page/customer-rd/src/lib/navigations/index.ts
Normal file
1
apps/page/customer-rd/src/lib/navigations/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './customer-search.navigation';
|
||||
26
apps/page/customer-rd/src/lib/routes.ts
Normal file
26
apps/page/customer-rd/src/lib/routes.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { CustomerComponent } from './customer-page.component';
|
||||
import { CustomerSearchComponent } from './customer-search/customer-search.component';
|
||||
import { CustomerResultsMainViewComponent } from './customer-search/results-main-view/results-main-view.component';
|
||||
import { CustomerDetailsViewMainComponent } from './customer-search/details-main-view/details-main-view.component';
|
||||
import { CustomerHistoryMainViewComponent } from './customer-search/history-main-view/history-main-view.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: CustomerComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: CustomerSearchComponent,
|
||||
children: [
|
||||
{ path: '', component: CustomerResultsMainViewComponent },
|
||||
{ path: ':customerId', component: CustomerDetailsViewMainComponent, data: { side: 'results' } },
|
||||
{ path: ':customerId/history', component: CustomerHistoryMainViewComponent, data: { side: 'results' } },
|
||||
// { path: ':customerId/edit', component: CustomerSearchComponent, data: { side: 'results' } },
|
||||
// { path: ':customerId/orders', component: CustomerSearchComponent, data: { side: 'orderItems' } },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
2
apps/page/customer-rd/src/public-api.ts
Normal file
2
apps/page/customer-rd/src/public-api.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './lib/customer-page.module';
|
||||
export * from './lib/customer-page.component';
|
||||
6
apps/shared/components/searchbox/ng-package.json
Normal file
6
apps/shared/components/searchbox/ng-package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export interface AutocompleteSource<T> {
|
||||
complete(searchText: string): T[] | Promise<T[]> | Observable<T[]>;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Directive, EventEmitter, HostBinding, HostListener, Input, Output } from '@angular/core';
|
||||
import { ControlValueAccessor } from '@angular/forms';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Directive({ selector: 'input[type=text][sharedSearchboxInput]' })
|
||||
export class SearchboxInputDirective implements ControlValueAccessor {
|
||||
value$ = new BehaviorSubject<string>('');
|
||||
|
||||
@Input()
|
||||
@HostBinding('value')
|
||||
value: string = '';
|
||||
|
||||
@Output()
|
||||
valueChange = new EventEmitter<string>();
|
||||
|
||||
@Input()
|
||||
@HostBinding('disabled')
|
||||
disabled: boolean;
|
||||
|
||||
onChange = (_: string) => {};
|
||||
|
||||
onTouched = () => {};
|
||||
|
||||
onSearch = () => {};
|
||||
|
||||
constructor() {}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
this.value = obj ?? '';
|
||||
this.value$.next(this.value);
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
regsiterOnSearch(fn: any): void {
|
||||
this.onSearch = fn;
|
||||
}
|
||||
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
}
|
||||
|
||||
@HostListener('input', ['$event.target.value'])
|
||||
onInput(value: string) {
|
||||
this.value = value;
|
||||
this.value$.next(value);
|
||||
this.valueChange.emit(value);
|
||||
this.onChange(value);
|
||||
this.onTouched();
|
||||
}
|
||||
|
||||
@HostListener('blur')
|
||||
onBlur() {
|
||||
this.onTouched();
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.value = '';
|
||||
this.value$.next('');
|
||||
this.valueChange.emit('');
|
||||
this.onChange('');
|
||||
this.onTouched();
|
||||
}
|
||||
|
||||
@HostListener('keydown.enter')
|
||||
onEnter() {
|
||||
this.onSearch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
.shared-searchbox {
|
||||
@apply inline-block;
|
||||
}
|
||||
|
||||
.shared-searchbox-input-wrapper {
|
||||
@apply bg-surface text-surface-content flex flex-row items-center rounded-button;
|
||||
}
|
||||
|
||||
input[sharedSearchboxInput] {
|
||||
@apply bg-transparent text-surface-content text-lg font-bold h-12 px-4 outline-transparent grow;
|
||||
}
|
||||
|
||||
.shared-searchbox-input-clear {
|
||||
@apply text-surface-content h-12 w-12 outline-transparent inline-grid justify-center items-center grow-0;
|
||||
}
|
||||
|
||||
.shared-searchbox-input-search {
|
||||
@apply text-white h-12 w-12 outline-transparent inline-grid justify-center items-center bg-brand rounded-button grow-0;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="shared-searchbox-input-wrapper">
|
||||
<ng-content select="[sharedSearchboxInput]"></ng-content>
|
||||
<span class="shared-searchbox-input-warning"></span>
|
||||
<button class="shared-searchbox-input-clear" *ngIf="input.value$ | async" type="button" (click)="input.clear()">
|
||||
<ui-svg-icon icon="close" [size]="24"></ui-svg-icon>
|
||||
</button>
|
||||
<button
|
||||
class="shared-searchbox-input-search"
|
||||
type="button"
|
||||
(keypress.enter)="search.emit(input.value)"
|
||||
(click)="search.emit(input.value)"
|
||||
>
|
||||
<ui-icon icon="search"></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ViewEncapsulation,
|
||||
ContentChild,
|
||||
AfterContentInit,
|
||||
Input,
|
||||
} from '@angular/core';
|
||||
import { SearchboxInputDirective } from './searchbox-input.directive';
|
||||
import { AutocompleteSource } from './autocomplete-source';
|
||||
|
||||
@Component({
|
||||
selector: 'shared-searchbox',
|
||||
templateUrl: 'searchbox.component.html',
|
||||
styleUrls: ['searchbox.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: {
|
||||
class: 'shared-searchbox',
|
||||
},
|
||||
})
|
||||
export class SearchboxComponent implements AfterContentInit {
|
||||
@Output()
|
||||
search = new EventEmitter<string>();
|
||||
|
||||
@ContentChild(SearchboxInputDirective, { static: true })
|
||||
input: SearchboxInputDirective;
|
||||
|
||||
@Input()
|
||||
autocompleteSource: AutocompleteSource<any>;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
this.input.regsiterOnSearch((value) => this.search.emit(value));
|
||||
}
|
||||
}
|
||||
13
apps/shared/components/searchbox/src/lib/searchbox.module.ts
Normal file
13
apps/shared/components/searchbox/src/lib/searchbox.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { SearchboxComponent } from './searchbox.component';
|
||||
import { SearchboxInputDirective } from './searchbox-input.directive';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiIconModule],
|
||||
exports: [SearchboxComponent, SearchboxInputDirective],
|
||||
declarations: [SearchboxComponent, SearchboxInputDirective],
|
||||
})
|
||||
export class SearchboxModule {}
|
||||
2
apps/shared/components/searchbox/src/public-api.ts
Normal file
2
apps/shared/components/searchbox/src/public-api.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './lib/searchbox.component';
|
||||
export * from './lib/searchbox.module';
|
||||
6
apps/shared/pipes/country/ng-package.json
Normal file
6
apps/shared/pipes/country/ng-package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
40
apps/shared/pipes/country/src/lib/country.pipe.ts
Normal file
40
apps/shared/pipes/country/src/lib/country.pipe.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { ChangeDetectorRef, OnDestroy, Pipe, PipeTransform } from '@angular/core';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { BehaviorSubject, combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Pipe({
|
||||
name: 'country',
|
||||
standalone: true,
|
||||
pure: false,
|
||||
})
|
||||
export class CountryPipe implements PipeTransform, OnDestroy {
|
||||
private result: string;
|
||||
private value$ = new BehaviorSubject<string>(undefined);
|
||||
|
||||
countries$ = this.customerService.getCountries().pipe(map((res) => res.result));
|
||||
|
||||
subscriptions = combineLatest([this.value$, this.countries$]).subscribe(([value, countries]) => {
|
||||
if (!!value && countries?.length > 0) {
|
||||
const country = countries.find((c) => c.isO3166_A_3 === value);
|
||||
|
||||
if (country && this.result !== country.name) {
|
||||
this.result = String(country.name);
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
constructor(private customerService: CrmCustomerService, private cdr: ChangeDetectorRef) {}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.unsubscribe();
|
||||
}
|
||||
|
||||
transform(value: string): any {
|
||||
if (this.value$.value !== value) {
|
||||
this.value$.next(value);
|
||||
}
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
1
apps/shared/pipes/country/src/public-api.ts
Normal file
1
apps/shared/pipes/country/src/public-api.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './lib/country.pipe';
|
||||
@@ -4,6 +4,6 @@
|
||||
[class.translate-x-0]="sideMenuOpen$ | async"
|
||||
></shell-side-menu>
|
||||
<shell-process-bar class="fixed z-[149] top-[4.625rem] inset-x-0 desktop:left-[10.5rem]"></shell-process-bar>
|
||||
<main class="ml-0 desktop:ml-[10.5rem] p-4" (click)="closeSideMenu()">
|
||||
<main class="ml-0 desktop:ml-[10.5rem] pt-2 px-6" (click)="closeSideMenu()">
|
||||
<ng-content></ng-content>
|
||||
</main>
|
||||
|
||||
@@ -23,6 +23,11 @@ module.exports = plugin(function ({ addComponents, theme, addBase, addUtilities
|
||||
'--btn-background-color': theme('colors.button.light.DEFAULT'),
|
||||
'--btn-color': theme('colors.button.light.content'),
|
||||
},
|
||||
'.btn-label': {
|
||||
'--btn-background-color': 'transparent',
|
||||
'--btn-hover-background-color': 'transparent',
|
||||
'--btn-hover-color': 'inherit',
|
||||
},
|
||||
});
|
||||
|
||||
for (const key in theme('colors.accent')) {
|
||||
|
||||
@@ -97,12 +97,16 @@ module.exports = {
|
||||
'accent-darkblue': '#557596',
|
||||
|
||||
background: {
|
||||
DEFAULT: '#F5F5F5',
|
||||
DEFAULT: '#EDEFF0',
|
||||
content: '#000000',
|
||||
},
|
||||
surface: {
|
||||
DEFAULT: '#ffffff',
|
||||
content: '#000000',
|
||||
2: {
|
||||
DEFAULT: '#F5F7FA',
|
||||
content: '#000000',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
menu: {
|
||||
@@ -170,6 +174,7 @@ module.exports = {
|
||||
card: '0px -2px 24px 0px rgba(220, 226, 233, 0.8)',
|
||||
cta: '0px 0px 15px 0px rgba(0, 0, 0, 0.5)',
|
||||
action: '0 0 20px 0 #596470',
|
||||
s: '0px 6px 24px rgba(206, 212, 219, 0.8)',
|
||||
},
|
||||
borderRadius: {
|
||||
button: '5px',
|
||||
|
||||
Reference in New Issue
Block a user