mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
#2274 Benachrichtigungskanal hinzufügen
This commit is contained in:
40
angular.json
40
angular.json
@@ -3256,6 +3256,46 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@shared/notification-channel-control": {
|
||||
"projectType": "library",
|
||||
"root": "apps/shared/notification-channel-control",
|
||||
"sourceRoot": "apps/shared/notification-channel-control/src",
|
||||
"prefix": "shared",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:ng-packagr",
|
||||
"options": {
|
||||
"tsConfig": "apps/shared/notification-channel-control/tsconfig.lib.json",
|
||||
"project": "apps/shared/notification-channel-control/ng-package.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"tsConfig": "apps/shared/notification-channel-control/tsconfig.lib.prod.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "apps/shared/notification-channel-control/src/test.ts",
|
||||
"tsConfig": "apps/shared/notification-channel-control/tsconfig.spec.json",
|
||||
"karmaConfig": "apps/shared/notification-channel-control/karma.conf.js"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"apps/shared/notification-channel-control/tsconfig.lib.json",
|
||||
"apps/shared/notification-channel-control/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "sales"
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ChangeStockStatusCodeValues,
|
||||
HistoryDTO,
|
||||
NotificationChannel,
|
||||
OrderCheckoutService,
|
||||
OrderDTO,
|
||||
OrderItemDTO,
|
||||
@@ -15,7 +16,7 @@ import {
|
||||
} from '@swagger/oms';
|
||||
import { memorize } from '@utils/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import { map, mergeMap, shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class DomainOmsService {
|
||||
@@ -162,4 +163,33 @@ export class DomainOmsService {
|
||||
orderItemSubsetId,
|
||||
});
|
||||
}
|
||||
|
||||
getNotifications(orderId: number): Observable<{ selected: NotificationChannel; email: string; mobile: string }> {
|
||||
return this.getOrder(orderId).pipe(
|
||||
map((order) => ({
|
||||
selected: order.notificationChannels,
|
||||
email: order.buyer?.communicationDetails?.email,
|
||||
mobile: order.buyer?.communicationDetails?.mobile,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
updateNotifications(orderId: number, changes: { selected: NotificationChannel; email: string; mobile: string }) {
|
||||
return this.getOrder(orderId).pipe(
|
||||
map((order) => ({
|
||||
...order,
|
||||
notificationChannels: changes.selected,
|
||||
buyer: {
|
||||
...order.buyer,
|
||||
communicationDetails: {
|
||||
...(order.buyer.communicationDetails ?? {}),
|
||||
email: changes.email,
|
||||
mobile: changes.mobile,
|
||||
},
|
||||
},
|
||||
})),
|
||||
mergeMap((order) => this.orderService.OrderUpdateOrder({ orderId: order.id, order })),
|
||||
map((res) => res.result)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,4 +205,5 @@
|
||||
<g id="dashboard" transform="matrix(1.31094,0,0,1.31094,-0.207962,-1.09593)">
|
||||
<path d="M23.017,15.072L19.649,15.072C19.649,15.072 19.649,1.924 19.649,1.924C19.649,1.315 19.188,0.836 18.638,0.836L1.71,0.836C1.161,0.836 0.7,1.315 0.7,1.924L0.7,21.301C0.702,23.487 2.316,25.244 4.288,25.246L20.83,25.246C21.684,25.245 22.503,24.87 23.106,24.199C23.698,23.539 24.03,22.647 24.027,21.717C24.027,21.718 24.027,16.159 24.027,16.159C24.027,15.55 23.566,15.072 23.017,15.072ZM4.289,23.055C3.419,23.054 2.729,22.262 2.72,21.299C2.72,21.299 2.72,3.011 2.72,3.011C2.72,3.011 17.628,3.011 17.628,3.011C17.628,3.011 17.628,21.718 17.628,21.718C17.628,22.179 17.711,22.633 17.871,23.055C17.871,23.055 4.289,23.055 4.289,23.055L4.289,23.055ZM22.007,21.71C21.973,22.414 21.464,22.979 20.828,22.979C20.194,22.979 19.685,22.417 19.649,21.716C19.649,21.71 19.649,17.246 19.649,17.246C19.649,17.246 22.007,17.246 22.007,17.246L22.007,21.71ZM15.174,17.136L15.174,17.135C15.178,16.848 15.076,16.572 14.894,16.368C14.702,16.151 14.438,16.032 14.164,16.032C14.164,16.032 5.311,16.032 5.311,16.032C4.762,16.032 4.301,16.511 4.301,17.12C4.301,17.728 4.762,18.207 5.311,18.207L14.164,18.207C14.708,18.207 15.166,17.737 15.174,17.136ZM15.174,12.013L15.174,12C15.174,11.718 15.071,11.448 14.893,11.248C14.7,11.033 14.437,10.914 14.164,10.914C14.164,10.914 5.311,10.914 5.311,10.914C4.762,10.914 4.301,11.393 4.301,12.002C4.301,12.61 4.762,13.089 5.311,13.089C5.311,13.089 14.164,13.089 14.164,13.089C14.71,13.089 15.169,12.616 15.174,12.013ZM14.833,12.268C14.83,12.276 14.827,12.284 14.824,12.292C14.842,12.297 14.857,12.299 14.865,12.301L14.833,12.268ZM14.835,12.261L14.835,12.262L14.842,12.269L14.835,12.261ZM15.174,6.884C15.174,6.275 14.713,5.797 14.164,5.797C14.164,5.797 5.311,5.797 5.311,5.797C4.762,5.797 4.301,6.275 4.301,6.884C4.301,7.493 4.762,7.971 5.311,7.971L14.164,7.971C14.713,7.971 15.174,7.493 15.174,6.884Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<path id="checked" d="M25.151,8.013C24.854,8.055 24.58,8.197 24.374,8.415C19.929,12.869 16.602,16.545 12.361,20.844L7.533,16.766C7.169,16.457 6.667,16.366 6.218,16.527C5.768,16.689 5.44,17.079 5.356,17.549C5.272,18.019 5.447,18.498 5.813,18.805L11.584,23.688C12.115,24.134 12.899,24.098 13.387,23.605C18.15,18.832 21.553,15.005 26.26,10.288C26.674,9.887 26.782,9.265 26.53,8.748C26.277,8.231 25.721,7.934 25.151,8.013Z"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
@@ -3,13 +3,13 @@ import { Component, ChangeDetectionStrategy, ContentChildren, QueryList } from '
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { CommandService } from '@core/command';
|
||||
import { OrderItemsContext } from '@domain/oms';
|
||||
import { SharedGoodsInOutOrderDetailsTagsComponent } from '@shared/goods-in-out';
|
||||
import { KeyValueDTOOfStringAndString, OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { BehaviorSubject, combineLatest, merge, of, Subscription } from 'rxjs';
|
||||
import { first, switchMap } from 'rxjs/operators';
|
||||
import { SharedGoodsInOutOrderDetailsCoversComponent } from './goods-in-out-order-details-covers';
|
||||
import { SharedGoodsInOutOrderDetailsItemComponent } from './goods-in-out-order-details-item';
|
||||
import { SharedGoodsInOutOrderDetailsTagsComponent } from './goods-in-out-order-details-tags';
|
||||
import { SharedGoodsInOutOrderDetailsStore } from './goods-in-out-order-details.store';
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<input uiInput formControlName="clientChannel" />
|
||||
</ui-form-control>
|
||||
|
||||
<shared-notification-channel-control formGroupName="notificationChannel"></shared-notification-channel-control>
|
||||
|
||||
<ui-form-control label="Kundennummer" variant="inline" statusLabel="Nicht Änderbar">
|
||||
<input uiInput formControlName="buyerNumber" />
|
||||
</ui-form-control>
|
||||
|
||||
@@ -10,10 +10,12 @@ import {
|
||||
Output,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { DomainOmsService } from '@domain/oms';
|
||||
import { OrderItemListItemDTO, StockStatusCodeDTO, VATDTO } from '@swagger/oms';
|
||||
import { emailNotificationValidator, mobileNotificationValidator } from '@shared/notification-channel-control';
|
||||
import { NotificationChannel, OrderDTO, OrderItemListItemDTO, StockStatusCodeDTO, VATDTO } from '@swagger/oms';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { UiSelectOptionComponent } from '@ui/select';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { first, shareReplay } from 'rxjs/operators';
|
||||
@@ -36,12 +38,21 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
@Input()
|
||||
items: OrderItemListItemDTO[];
|
||||
|
||||
@Input()
|
||||
order: OrderDTO;
|
||||
|
||||
expanded: boolean[];
|
||||
|
||||
showTagsComponent: boolean[];
|
||||
|
||||
control: FormGroup;
|
||||
|
||||
notificationsGroup = new FormGroup({
|
||||
selected: new FormControl(1),
|
||||
email: new FormControl('lorenz.hilpert@gmail.com', emailNotificationValidator),
|
||||
mobile: new FormControl('', mobileNotificationValidator),
|
||||
});
|
||||
|
||||
minDate = this.dateAdapter.addCalendarDays(new Date(), -1);
|
||||
|
||||
vats$: Observable<VATDTO[]> = this.omsService.getVATs().pipe(shareReplay());
|
||||
@@ -67,7 +78,8 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
private datePipe: DatePipe,
|
||||
private omsService: DomainOmsService,
|
||||
private dateAdapter: DateAdapter,
|
||||
private cdr: ChangeDetectorRef
|
||||
private cdr: ChangeDetectorRef,
|
||||
private _modal: UiModalService
|
||||
) {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -75,12 +87,13 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
}
|
||||
|
||||
ngOnChanges({ items }: SimpleChanges) {
|
||||
if (items.currentValue) {
|
||||
if (items?.currentValue) {
|
||||
this.expanded = new Array(items.currentValue.length);
|
||||
this.expanded[0] = true;
|
||||
|
||||
this.showTagsComponent = items.currentValue?.map((item) => !!item.compartmentCode);
|
||||
this.initForm(items.currentValue);
|
||||
this.updateNotificationsGroup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +106,7 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
clientChannel: fb.control({ value: this.environmentChannelPipe.transform(items[0].clientChannel), disabled: true }),
|
||||
buyerNumber: fb.control({ value: items[0].buyerNumber, disabled: true }),
|
||||
items: fb.array([]),
|
||||
notificationChannel: this.notificationsGroup,
|
||||
});
|
||||
|
||||
items.forEach(async (item, index) => {
|
||||
@@ -148,6 +162,19 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
});
|
||||
}
|
||||
|
||||
async updateNotificationsGroup() {
|
||||
const control = this.control.getRawValue();
|
||||
const orderId = control.orderId;
|
||||
|
||||
try {
|
||||
const notifications = await this.omsService.getNotifications(+orderId).toPromise();
|
||||
|
||||
this.notificationsGroup.reset(notifications);
|
||||
} catch (error) {
|
||||
this._modal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim abrufen der Benachrichtigung' });
|
||||
}
|
||||
}
|
||||
|
||||
changeEstimatedDeliveryDate(date: Date, item: OrderItemListItemDTO) {
|
||||
if (!date) {
|
||||
return;
|
||||
@@ -170,44 +197,64 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
try {
|
||||
const control = this.control.getRawValue();
|
||||
const orderId = control.orderId;
|
||||
|
||||
if (this.notificationsGroup.dirty) {
|
||||
try {
|
||||
await this.omsService.updateNotifications(orderId, this.notificationsGroup.getRawValue()).toPromise();
|
||||
} catch (error) {
|
||||
this._modal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim aktualisieren der Benachrichtigung' });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
for (const itemCtrl of control.items) {
|
||||
const orderItemId = itemCtrl.orderItemId;
|
||||
const orderItemSubsetId = itemCtrl.orderItemSubsetId;
|
||||
await this.omsService
|
||||
.patchOrderItem({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItem: {
|
||||
product: { ean: itemCtrl.ean },
|
||||
grossPrice: {
|
||||
vat: { vatType: itemCtrl.vat },
|
||||
value: { value: Number(String(itemCtrl.price).replace(',', '.')) },
|
||||
try {
|
||||
await this.omsService
|
||||
.patchOrderItem({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItem: {
|
||||
product: { ean: itemCtrl.ean },
|
||||
grossPrice: {
|
||||
vat: { vatType: itemCtrl.vat },
|
||||
value: { value: Number(String(itemCtrl.price).replace(',', '.')) },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
} catch (error) {
|
||||
this._modal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim aktualisieren des Bestellpostens' });
|
||||
throw error;
|
||||
}
|
||||
|
||||
await this.omsService
|
||||
.patchOrderItemSubset({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
orderItemSubset: {
|
||||
compartmentCode:
|
||||
itemCtrl.compartmentInfo && itemCtrl.compartmentCode
|
||||
? itemCtrl.compartmentCode.replace('_' + itemCtrl.compartmentInfo, '')
|
||||
: itemCtrl.compartmentCode,
|
||||
compartmentInfo: itemCtrl.compartmentInfo || '',
|
||||
estimatedShippingDate: itemCtrl.estimatedShippingDate || null,
|
||||
compartmentStop: itemCtrl.pickUpDeadline || null,
|
||||
specialComment: itemCtrl.specialComment || '',
|
||||
ssc: itemCtrl.ssc,
|
||||
sscText: itemCtrl.sscText !== '' ? itemCtrl.sscText.substring(3) : '',
|
||||
},
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
try {
|
||||
await this.omsService
|
||||
.patchOrderItemSubset({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
orderItemSubset: {
|
||||
compartmentCode:
|
||||
itemCtrl.compartmentInfo && itemCtrl.compartmentCode
|
||||
? itemCtrl.compartmentCode.replace('_' + itemCtrl.compartmentInfo, '')
|
||||
: itemCtrl.compartmentCode,
|
||||
compartmentInfo: itemCtrl.compartmentInfo || '',
|
||||
estimatedShippingDate: itemCtrl.estimatedShippingDate || null,
|
||||
compartmentStop: itemCtrl.pickUpDeadline || null,
|
||||
specialComment: itemCtrl.specialComment || '',
|
||||
ssc: itemCtrl.ssc,
|
||||
sscText: itemCtrl.sscText !== '' ? itemCtrl.sscText.substring(3) : '',
|
||||
},
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
} catch (error) {
|
||||
this._modal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim aktualisieren des Bestellpostens' });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
this.navigateBack();
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { SharedGoodsInOutOrderDetailsModule } from '@shared/goods-in-out';
|
||||
import { NotificationChannelControlModule } from '@shared/notification-channel-control';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { UiDatepickerModule } from '@ui/datepicker';
|
||||
import { UiDropdownModule } from '@ui/dropdown';
|
||||
@@ -10,6 +10,7 @@ import { UiIconModule } from '@ui/icon';
|
||||
import { UiInputModule } from '@ui/input';
|
||||
import { UiSelectModule } from '@ui/select';
|
||||
import { ProductImageModule } from 'apps/cdn/product-image/src/public-api';
|
||||
import { SharedGoodsInOutOrderDetailsModule } from '../goods-in-out-order-details';
|
||||
import { PipesModule } from '../pipes/pipes.module';
|
||||
import { SharedGoodsInOutOrderEditComponent } from './goods-in-out-order-edit.component';
|
||||
|
||||
@@ -28,6 +29,7 @@ import { SharedGoodsInOutOrderEditComponent } from './goods-in-out-order-edit.co
|
||||
UiDatepickerModule,
|
||||
UiDropdownModule,
|
||||
SharedGoodsInOutOrderDetailsModule,
|
||||
NotificationChannelControlModule,
|
||||
],
|
||||
exports: [SharedGoodsInOutOrderEditComponent],
|
||||
declarations: [SharedGoodsInOutOrderEditComponent],
|
||||
|
||||
25
apps/shared/notification-channel-control/README.md
Normal file
25
apps/shared/notification-channel-control/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# NotificationChannelControl
|
||||
|
||||
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.2.4.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name --project notification-channel-control` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project notification-channel-control`.
|
||||
|
||||
> Note: Don't forget to add `--project notification-channel-control` or else it will be added to the default project in your `angular.json` file.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build notification-channel-control` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Publishing
|
||||
|
||||
After building your library with `ng build notification-channel-control`, go to the dist folder `cd dist/notification-channel-control` and run `npm publish`.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test notification-channel-control` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||
32
apps/shared/notification-channel-control/karma.conf.js
Normal file
32
apps/shared/notification-channel-control/karma.conf.js
Normal file
@@ -0,0 +1,32 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma'),
|
||||
],
|
||||
client: {
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, '../../../coverage/shared/notification-channel-control'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true,
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true,
|
||||
});
|
||||
};
|
||||
7
apps/shared/notification-channel-control/ng-package.json
Normal file
7
apps/shared/notification-channel-control/ng-package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../../dist/shared/notification-channel-control",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
11
apps/shared/notification-channel-control/package.json
Normal file
11
apps/shared/notification-channel-control/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@shared/notification-channel-control",
|
||||
"version": "0.0.1",
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^10.2.4",
|
||||
"@angular/core": "^10.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// start:ng42.barrel
|
||||
export * from './notification-channel-control.component';
|
||||
export * from './notification-channel-control.module';
|
||||
export * from './validators';
|
||||
// end:ng42.barrel
|
||||
@@ -0,0 +1,39 @@
|
||||
<div class="nc-heading">
|
||||
<label for="notificationChannel">
|
||||
Benachrichtigung
|
||||
</label>
|
||||
<ui-checkbox-group
|
||||
[ngModel]="notificationChannels$ | async"
|
||||
(ngModelChange)="setNotificationChannels($event)"
|
||||
[disabled]="notificationChannelControl?.disabled"
|
||||
>
|
||||
<ui-checkbox [value]="1" design="filled">E-Mail</ui-checkbox>
|
||||
<ui-checkbox [value]="2" design="filled">SMS</ui-checkbox>
|
||||
</ui-checkbox-group>
|
||||
<div class="expand"></div>
|
||||
<button *ngIf="displayToggle" type="button" class="more-toggle" (click)="toggle()" [class.open]="open$ | async">
|
||||
<ui-icon icon="arrow_head" size="16px"></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="nc-content" [class.open]="open$ | async">
|
||||
<div class="nc-control-wrapper" *ngIf="displayEmail">
|
||||
<label for="email">E-Mail</label>
|
||||
<div class="input-wrapper" [class.has-error]="emailControl.touched && emailControl?.errors">
|
||||
<input type="email" name="email" id="email" [formControl]="emailControl" placeholder="E-Mail*" />
|
||||
<ng-container *ngIf="emailControl.touched && emailControl?.errors; let errors">
|
||||
<span class="error" *ngIf="errors.required">Das Fehld E-Mail ist ein Pflichtfeld</span>
|
||||
<span class="error" *ngIf="errors.pattern">Keine gültige E-Mail Adresse</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nc-control-wrapper" *ngIf="displayMobile">
|
||||
<label for="mobile">SMS</label>
|
||||
<div class="input-wrapper" [class.has-error]="mobileControl.touched && mobileControl?.errors">
|
||||
<input type="tel" name="mobile" id="mobile" [formControl]="mobileControl" placeholder="SMS*" />
|
||||
<ng-container *ngIf="mobileControl.touched && mobileControl?.errors; let errors">
|
||||
<span class="error" *ngIf="errors.required">Das Fehld SMS ist ein Pflichtfeld</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,90 @@
|
||||
:host {
|
||||
@apply block;
|
||||
}
|
||||
|
||||
.nc-heading {
|
||||
@apply flex flex-row items-center px-5 py-6 bg-white;
|
||||
|
||||
label {
|
||||
width: 154px;
|
||||
}
|
||||
|
||||
.expand {
|
||||
@apply flex-grow;
|
||||
}
|
||||
|
||||
.more-toggle {
|
||||
@apply bg-transparent outline-none border-none;
|
||||
|
||||
ui-icon {
|
||||
@apply transition-all transform rotate-90;
|
||||
}
|
||||
|
||||
&.open {
|
||||
ui-icon {
|
||||
@apply -rotate-90;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nc-content {
|
||||
@apply h-0 overflow-hidden transition-all bg-white px-5 py-0;
|
||||
|
||||
&.open {
|
||||
@apply h-auto overflow-auto;
|
||||
}
|
||||
}
|
||||
|
||||
.nc-control-wrapper {
|
||||
@apply flex flex-row items-start pb-6;
|
||||
|
||||
label {
|
||||
@apply mt-1;
|
||||
width: 154px;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
@apply flex flex-col flex-grow pb-1;
|
||||
|
||||
input {
|
||||
@apply flex-grow outline-none text-base font-bold border-0 border-b-2 border-solid pb-px-2;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
@apply bg-white;
|
||||
}
|
||||
|
||||
&.has-error input {
|
||||
@apply border-brand;
|
||||
}
|
||||
|
||||
.error {
|
||||
@apply text-right text-sm font-bold text-brand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .customer shared-notification-channel-control {
|
||||
.nc-control-wrapper {
|
||||
.input-wrapper {
|
||||
input {
|
||||
@apply border-glitter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .branch shared-notification-channel-control {
|
||||
.nc-control-wrapper {
|
||||
.input-wrapper {
|
||||
input {
|
||||
@apply border-munsell;
|
||||
}
|
||||
|
||||
ui-icon {
|
||||
@apply text-white !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { ControlContainer, FormGroup } from '@angular/forms';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { NotificationChannel } from '@swagger/oms';
|
||||
import { NEVER, Observable } from 'rxjs';
|
||||
import { map, startWith, tap } from 'rxjs/operators';
|
||||
|
||||
export interface NotificationChannelControlComponentState {
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'shared-notification-channel-control',
|
||||
templateUrl: './notification-channel-control.component.html',
|
||||
styleUrls: ['./notification-channel-control.component.scss'],
|
||||
})
|
||||
export class NotificationChannelControlComponent extends ComponentStore<NotificationChannelControlComponentState> implements OnInit {
|
||||
notificationGroup: FormGroup;
|
||||
|
||||
get notificationChannelControl() {
|
||||
return this.notificationGroup.get('selected');
|
||||
}
|
||||
|
||||
get emailControl() {
|
||||
return this.notificationGroup.get('email');
|
||||
}
|
||||
|
||||
get displayEmail() {
|
||||
return !!(this.notificationChannelControl.value & 1) && this.emailControl;
|
||||
}
|
||||
|
||||
get mobileControl() {
|
||||
return this.notificationGroup.get('mobile');
|
||||
}
|
||||
|
||||
get displayMobile() {
|
||||
return !!(this.notificationChannelControl.value & 2) && this.mobileControl;
|
||||
}
|
||||
|
||||
get displayToggle() {
|
||||
return this.displayEmail || this.displayMobile;
|
||||
}
|
||||
|
||||
get hasError() {
|
||||
return !!(this.mobileControl?.errors || this.emailControl?.errors);
|
||||
}
|
||||
|
||||
readonly open$ = this.select((s) => s.open);
|
||||
|
||||
readonly options: NotificationChannel[] = [1, 2];
|
||||
|
||||
get notificationChannels() {
|
||||
const value = this.notificationChannelControl?.value;
|
||||
|
||||
let values: NotificationChannel[] = [];
|
||||
|
||||
this.options?.forEach((option) => {
|
||||
if (value & option) {
|
||||
values.push(option);
|
||||
}
|
||||
});
|
||||
return values;
|
||||
}
|
||||
|
||||
notificationChannels$: Observable<NotificationChannel[]>;
|
||||
|
||||
constructor(private _notificationsGroup: ControlContainer, private _cdr: ChangeDetectorRef) {
|
||||
super({
|
||||
open: false,
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this._notificationsGroup.control instanceof FormGroup) {
|
||||
this.notificationGroup = this._notificationsGroup.control;
|
||||
}
|
||||
this.initNotificationChannels$();
|
||||
}
|
||||
|
||||
initNotificationChannels$() {
|
||||
if (this.notificationGroup) {
|
||||
this.notificationChannels$ = this.notificationChannelControl.valueChanges.pipe(startWith(this.notificationChannelControl.value)).pipe(
|
||||
map((value) => {
|
||||
let values = [];
|
||||
|
||||
this.options?.forEach((option) => {
|
||||
if (value & option) {
|
||||
values.push(option);
|
||||
}
|
||||
});
|
||||
return values;
|
||||
}),
|
||||
tap(() => {
|
||||
this.updateValidity();
|
||||
|
||||
if (this.hasError) {
|
||||
this.toggle(true);
|
||||
}
|
||||
|
||||
this._cdr.markForCheck();
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.notificationChannels$ = NEVER;
|
||||
}
|
||||
}
|
||||
|
||||
setNotificationChannels(notificationChannels: NotificationChannel[]) {
|
||||
const notificationChannel = notificationChannels.reduce((val, current) => val | current, 0) as NotificationChannel;
|
||||
this.notificationChannelControl.setValue(notificationChannel);
|
||||
}
|
||||
|
||||
toggle(value?: boolean) {
|
||||
this.patchState({ open: value ?? !this.get((s) => s.open) });
|
||||
}
|
||||
|
||||
updateValidity() {
|
||||
this.emailControl?.updateValueAndValidity();
|
||||
this.mobileControl?.updateValueAndValidity();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiCheckboxModule } from '@ui/checkbox';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { NotificationChannelControlComponent } from './notification-channel-control.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [NotificationChannelControlComponent],
|
||||
imports: [CommonModule, UiCheckboxModule, FormsModule, ReactiveFormsModule, UiIconModule],
|
||||
exports: [NotificationChannelControlComponent],
|
||||
})
|
||||
export class NotificationChannelControlModule {}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { AbstractControl, ValidatorFn, Validators } from '@angular/forms';
|
||||
|
||||
// RFC 5322 Official Standard
|
||||
const emailRegexPattern = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
|
||||
|
||||
export const emailNotificationValidator: ValidatorFn = (control: AbstractControl) => {
|
||||
if (control.parent) {
|
||||
const notificationChannel = control.parent.get('selected').value;
|
||||
if (notificationChannel & 1) {
|
||||
return Validators.required(control) ?? Validators.pattern(emailRegexPattern)(control);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const mobileNotificationValidator: ValidatorFn = (control: AbstractControl) => {
|
||||
if (control.parent) {
|
||||
const notificationChannel = control.parent.get('selected').value;
|
||||
if (notificationChannel & 2) {
|
||||
return Validators.required(control);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* Public API Surface of notification-channel-control
|
||||
*/
|
||||
|
||||
export * from './lib';
|
||||
24
apps/shared/notification-channel-control/src/test.ts
Normal file
24
apps/shared/notification-channel-control/src/test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/zone';
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(
|
||||
path: string,
|
||||
deep?: boolean,
|
||||
filter?: RegExp
|
||||
): {
|
||||
keys(): string[];
|
||||
<T>(id: string): T;
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
25
apps/shared/notification-channel-control/tsconfig.lib.json
Normal file
25
apps/shared/notification-channel-control/tsconfig.lib.json
Normal file
@@ -0,0 +1,25 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../out-tsc/lib",
|
||||
"target": "es2015",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"types": [],
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2018"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"skipTemplateCodegen": true,
|
||||
"strictMetadataEmit": true,
|
||||
"enableResourceInlining": true
|
||||
},
|
||||
"exclude": [
|
||||
"src/test.ts",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.lib.json",
|
||||
"compilerOptions": {
|
||||
"declarationMap": false
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": false
|
||||
}
|
||||
}
|
||||
17
apps/shared/notification-channel-control/tsconfig.spec.json
Normal file
17
apps/shared/notification-channel-control/tsconfig.spec.json
Normal file
@@ -0,0 +1,17 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
17
apps/shared/notification-channel-control/tslint.json
Normal file
17
apps/shared/notification-channel-control/tslint.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "../../../tslint.json",
|
||||
"rules": {
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
"shared",
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
"shared",
|
||||
"kebab-case"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -51,14 +51,23 @@ export class UiCheckboxGroupComponent implements AfterContentInit, ControlValueA
|
||||
}
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
this.setDisabledStateOfChildren(isDisabled);
|
||||
}
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
this.registerOnCheckboxChange();
|
||||
this.updateValuesOfChildren();
|
||||
this.setDisabledStateOfChildren(this.disabled);
|
||||
this.checkboxes.changes.subscribe(() => {
|
||||
this.registerOnCheckboxChange();
|
||||
this.updateValuesOfChildren();
|
||||
this.setDisabledStateOfChildren(this.disabled);
|
||||
});
|
||||
}
|
||||
|
||||
setDisabledStateOfChildren(isDisabled: boolean) {
|
||||
this.checkboxes?.forEach((checkbox) => {
|
||||
checkbox.setDisabledState(isDisabled);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,45 @@
|
||||
@apply font-bold;
|
||||
}
|
||||
|
||||
&.disabled ::ng-deep {
|
||||
@apply text-ucla-blue cursor-not-allowed;
|
||||
&.disabled {
|
||||
@apply cursor-not-allowed;
|
||||
}
|
||||
|
||||
&.style-filled {
|
||||
ui-icon[icon='checked'] {
|
||||
@apply rounded-sm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .customer ui-checkbox {
|
||||
ui-icon {
|
||||
@apply text-active-customer;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
@apply text-ucla-blue;
|
||||
}
|
||||
|
||||
&.style-filled {
|
||||
ui-icon[icon='checked'] {
|
||||
@apply text-white bg-active-customer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .branch ui-checkbox {
|
||||
ui-icon {
|
||||
@apply text-active-branch;
|
||||
}
|
||||
|
||||
&.disabled ::ng-deep {
|
||||
@apply text-cool-grey;
|
||||
}
|
||||
|
||||
&.style-filled {
|
||||
ui-icon[icon='checked'] {
|
||||
@apply text-white bg-active-branch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,23 @@ export class UiCheckboxComponent implements ControlValueAccessor {
|
||||
|
||||
get icon() {
|
||||
if (this.showCheckbox) {
|
||||
return this.checked ? 'checkbox_checked' : 'checkbox';
|
||||
if (this.design === 'default') {
|
||||
return this.checked ? 'checkbox_checked' : 'checkbox';
|
||||
}
|
||||
if (this.design === 'filled') {
|
||||
return this.checked ? 'checked' : 'checkbox';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Input()
|
||||
design: 'default' | 'filled' = 'default';
|
||||
|
||||
@HostBinding('class')
|
||||
get styleClass() {
|
||||
return `style-${this.design}`;
|
||||
}
|
||||
|
||||
private onChange = (v: any) => {};
|
||||
private onChangeForParent = (checked: boolean, value: any) => {};
|
||||
private onTouched = () => {};
|
||||
@@ -54,6 +67,7 @@ export class UiCheckboxComponent implements ControlValueAccessor {
|
||||
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
|
||||
selectValue(value: any, emitEvent = true) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, Optional, Inject } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, Input, Optional, Inject, HostBinding } from '@angular/core';
|
||||
import { UI_ICON_HREF, UI_ICON_VIEW_BOX } from './tokens';
|
||||
|
||||
@Component({
|
||||
@@ -9,6 +9,7 @@ import { UI_ICON_HREF, UI_ICON_VIEW_BOX } from './tokens';
|
||||
})
|
||||
export class UiIconComponent {
|
||||
@Input()
|
||||
@HostBinding('attr.icon')
|
||||
icon: string;
|
||||
|
||||
@Input()
|
||||
|
||||
@@ -7,7 +7,6 @@ import { UiModalRef } from '../defs';
|
||||
@Component({
|
||||
selector: 'ui-error-modal',
|
||||
template: `
|
||||
<h1 class="title">Es ist ein Fehler aufgetreten</h1>
|
||||
<p class="message">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
|
||||
Binary file not shown.
@@ -280,7 +280,10 @@
|
||||
"apps/core/signalr/src/public-api.ts"
|
||||
],
|
||||
"@hub/notifications": [
|
||||
"apps/hub/notifications/src/public-api.ts",
|
||||
"apps/hub/notifications/src/public-api.ts"
|
||||
],
|
||||
"@shared/notification-channel-control": [
|
||||
"apps/shared/notification-channel-control/src/public-api.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user