#2274 Benachrichtigungskanal hinzufügen

This commit is contained in:
Lorenz Hilpert
2021-10-19 16:02:03 +02:00
parent af2ab8f104
commit bf768b970f
30 changed files with 698 additions and 46 deletions

View File

@@ -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"

View File

@@ -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)
);
}
}

View File

@@ -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

View File

@@ -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({

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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],

View 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.

View File

@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../../coverage/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,
});
};

View 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"
}
}

View 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"
}
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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;
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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 {}

View File

@@ -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;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
});
}

View File

@@ -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;
}
}
}

View File

@@ -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) {

View File

@@ -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()

View File

@@ -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>

View File

Binary file not shown.

View File

@@ -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"
]
}
}