Added error handling and now possible to post employees
This commit is contained in:
@@ -14,3 +14,4 @@
|
|||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
<dafa-toast-list></dafa-toast-list>
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { DigiNgNavigationBreadcrumbsModule } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
|
import { DigiNgNavigationBreadcrumbsModule } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
import { NgModule } from '@angular/core';
|
import { ErrorHandler, NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { CustomErrorHandler } from '@dafa-interceptors/custom-error-handler.module';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { NavigationModule } from './components/navigation/navigation.module';
|
import { NavigationModule } from './components/navigation/navigation.module';
|
||||||
import { SidebarModule } from './components/sidebar/sidebar.module';
|
import { SidebarModule } from './components/sidebar/sidebar.module';
|
||||||
import { SkipToContentModule } from './components/skip-to-content/skip-to-content.module';
|
import { SkipToContentModule } from './components/skip-to-content/skip-to-content.module';
|
||||||
|
import { ToastListModule } from './components/toast-list/toast-list.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
@@ -19,9 +21,15 @@ import { SkipToContentModule } from './components/skip-to-content/skip-to-conten
|
|||||||
SkipToContentModule,
|
SkipToContentModule,
|
||||||
NavigationModule,
|
NavigationModule,
|
||||||
SidebarModule,
|
SidebarModule,
|
||||||
|
ToastListModule,
|
||||||
DigiNgNavigationBreadcrumbsModule,
|
DigiNgNavigationBreadcrumbsModule,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [
|
||||||
|
{
|
||||||
|
provide: ErrorHandler,
|
||||||
|
useClass: CustomErrorHandler,
|
||||||
|
},
|
||||||
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<section [ngClass]="className" *ngIf="errors$ | async as errors">
|
||||||
|
<ul class="toast-list__list" *ngIf="errors.length">
|
||||||
|
<li class="toast-list__item" *ngFor="let error of errors">
|
||||||
|
<dafa-toast [error]="error" (closeToast)="removeError($event)"></dafa-toast>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="dafa__a11y-sr-only" [attr.aria-live]="ariaLivePoliteness" [attr.aria-atomic]="ariaAtomic">
|
||||||
|
<ng-container *ngIf="errors.length">
|
||||||
|
<h3>{{ errors.length }} fel har uppstått!</h3>
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let error of errors">{{ error.name }}: {{ error.message }}</li>
|
||||||
|
</ul>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
@import 'mixins/list';
|
||||||
|
|
||||||
|
.toast-list {
|
||||||
|
position: fixed;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 9999;
|
||||||
|
margin: var(--digi--layout--gutter);
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&--top-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
&--top-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
&--top-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
&--center-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
&--center-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
&--center-center {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
&--bottom-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
&--bottom-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
&--bottom-center {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__list {
|
||||||
|
@include dafa__reset-list;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item:not(:first-child) {
|
||||||
|
margin-top: var(--digi--layout--gutter);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/* tslint:disable:no-unused-variable */
|
||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { ToastListComponent } from './toast-list.component';
|
||||||
|
|
||||||
|
|
||||||
|
describe('ToastListComponent', () => {
|
||||||
|
let component: ToastListComponent;
|
||||||
|
let fixture: ComponentFixture<ToastListComponent>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ToastListComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ToastListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { AriaLivePoliteness } from '@angular/cdk/a11y';
|
||||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||||
|
import { ToastPosition } from '@dafa-enums/toast-position.enum';
|
||||||
|
import { CustomError } from '@dafa-models/error/custom-error';
|
||||||
|
import { ErrorService } from '@dafa-services/error.service';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'dafa-toast-list',
|
||||||
|
templateUrl: './toast-list.component.html',
|
||||||
|
styleUrls: ['./toast-list.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class ToastListComponent {
|
||||||
|
@Input() ariaLivePoliteness: AriaLivePoliteness = 'assertive';
|
||||||
|
@Input() ariaAtomic = true;
|
||||||
|
@Input() position: ToastPosition = ToastPosition.TOP_RIGHT;
|
||||||
|
|
||||||
|
errors$: Observable<CustomError[]> = this.errorService.errors$;
|
||||||
|
|
||||||
|
constructor(private errorService: ErrorService) {}
|
||||||
|
|
||||||
|
get className(): string {
|
||||||
|
return `toast-list toast-list--${this.position}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeError(error: CustomError): void {
|
||||||
|
this.errorService.remove(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { ToastListComponent } from './toast-list.component';
|
||||||
|
import { ToastModule } from './toast/toast.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [ToastListComponent],
|
||||||
|
imports: [CommonModule, ToastModule],
|
||||||
|
exports: [ToastListComponent],
|
||||||
|
})
|
||||||
|
export class ToastListModule {}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<div [ngClass]="className">
|
||||||
|
<div class="toast__icon-wrapper">
|
||||||
|
<digi-icon-exclamation-circle *ngIf="error.severity === errorSeverity.HIGH"></digi-icon-exclamation-circle>
|
||||||
|
<digi-icon-exclamation-triangle *ngIf="error.severity === errorSeverity.MEDIUM"></digi-icon-exclamation-triangle>
|
||||||
|
<digi-icon-check-circle-reg *ngIf="error.severity === errorSeverity.LOW"></digi-icon-check-circle-reg>
|
||||||
|
</div>
|
||||||
|
<div class="toast__content">
|
||||||
|
<button class="toast__close-button" aria-label="Stäng meddelandet" (click)="emitCloseEvent()">
|
||||||
|
<digi-icon-x></digi-icon-x>
|
||||||
|
</button>
|
||||||
|
<h3 class="toast__heading">{{ error.name }}</h3>
|
||||||
|
<p class="toast__message">{{ error.message }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
@import 'variables/shadows';
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
background-color: var(--digi--ui--color--informative);
|
||||||
|
border: 2px solid var(--digi--ui--color--informative);
|
||||||
|
box-shadow: $dafa__shadow;
|
||||||
|
pointer-events: auto;
|
||||||
|
color: var(--digi--typography--color--text);
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
&--high {
|
||||||
|
background-color: var(--digi--ui--color--danger);
|
||||||
|
border-color: var(--digi--ui--color--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--medium {
|
||||||
|
background-color: var(--digi--ui--color--warning);
|
||||||
|
border-color: var(--digi--ui--color--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--digi--layout--gutter);
|
||||||
|
color: var(--digi--typography--color--text--light);
|
||||||
|
font-size: 2rem;
|
||||||
|
|
||||||
|
.toast--medium & {
|
||||||
|
color: var(--digi--typography--color--text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
background-color: var(--digi--ui--color--background);
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--digi--layout--gutter--s);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__heading {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__message {
|
||||||
|
max-width: 400px !important;
|
||||||
|
margin: 0;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close-button {
|
||||||
|
background-color: transparent;
|
||||||
|
border-width: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: var(--digi--layout--gutter--s);
|
||||||
|
color: var(--digi--typography--color--text);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/* tslint:disable:no-unused-variable */
|
||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { ToastComponent } from './toast.component';
|
||||||
|
|
||||||
|
|
||||||
|
describe('ToastComponent', () => {
|
||||||
|
let component: ToastComponent;
|
||||||
|
let fixture: ComponentFixture<ToastComponent>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ToastComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ToastComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { AfterViewInit, ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { ErrorSeverity } from '@dafa-enums/error-severity.enum';
|
||||||
|
import { CustomError } from '@dafa-models/error/custom-error';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'dafa-toast',
|
||||||
|
templateUrl: './toast.component.html',
|
||||||
|
styleUrls: ['./toast.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class ToastComponent implements AfterViewInit {
|
||||||
|
@Input() error: CustomError;
|
||||||
|
@Output() closeToast: EventEmitter<CustomError> = new EventEmitter();
|
||||||
|
|
||||||
|
errorSeverity = ErrorSeverity;
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
if (this.error.removeAfter) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.closeToast.emit(this.error);
|
||||||
|
}, this.error.removeAfter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get className(): string {
|
||||||
|
return `toast toast--${this.error.severity.toLowerCase()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitCloseEvent(): void {
|
||||||
|
this.closeToast.emit(this.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { DigiNgIconExclamationCircleModule } from '@af/digi-ng/_icon/icon-exclamation-circle';
|
||||||
|
import { DigiNgIconExclamationTriangleModule } from '@af/digi-ng/_icon/icon-exclamation-triangle';
|
||||||
|
import { DigiNgIconInfoCircleRegModule } from '@af/digi-ng/_icon/icon-info-circle-reg';
|
||||||
|
import { DigiNgIconXModule } from '@af/digi-ng/_icon/icon-x';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
|
import { ToastComponent } from './toast.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
|
declarations: [ToastComponent],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
DigiNgIconXModule,
|
||||||
|
DigiNgIconExclamationCircleModule,
|
||||||
|
DigiNgIconExclamationTriangleModule,
|
||||||
|
DigiNgIconInfoCircleRegModule,
|
||||||
|
],
|
||||||
|
exports: [ToastComponent],
|
||||||
|
})
|
||||||
|
export class ToastModule {}
|
||||||
5
apps/dafa-web/src/app/data/enums/error-severity.enum.ts
Normal file
5
apps/dafa-web/src/app/data/enums/error-severity.enum.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export enum ErrorSeverity {
|
||||||
|
HIGH = 'High',
|
||||||
|
MEDIUM = 'Medium',
|
||||||
|
LOW = 'Low'
|
||||||
|
}
|
||||||
6
apps/dafa-web/src/app/data/enums/error-type.enum.ts
Normal file
6
apps/dafa-web/src/app/data/enums/error-type.enum.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export enum ErrorType {
|
||||||
|
API = 'API Error',
|
||||||
|
NETWORK = 'Network Error',
|
||||||
|
APP = 'Application Error',
|
||||||
|
UNKNOWN = 'Unknown Error'
|
||||||
|
}
|
||||||
11
apps/dafa-web/src/app/data/enums/toast-position.enum.ts
Normal file
11
apps/dafa-web/src/app/data/enums/toast-position.enum.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export enum ToastPosition {
|
||||||
|
TOP_RIGHT = 'top-right',
|
||||||
|
TOP_CENTER = 'top-center',
|
||||||
|
TOP_LEFT = 'top-left',
|
||||||
|
BOTTOM_RIGHT = 'bottom-right',
|
||||||
|
BOTTOM_CENTER = 'bottom-center',
|
||||||
|
BOTTOM_LEFT = 'bottom-left',
|
||||||
|
CENTER_RIGHT = 'center-right',
|
||||||
|
CENTER_CENTER = 'center-center',
|
||||||
|
CENTER_LEFT = 'center-left',
|
||||||
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
export interface EmployeesApiResponse {
|
|
||||||
pxMore: string;
|
|
||||||
pxObjClass: string;
|
|
||||||
pxPageCount: string;
|
|
||||||
pxQueryTimeStamp: string;
|
|
||||||
pxResultCount: string;
|
|
||||||
pxTotalResultCount: string;
|
|
||||||
pyMaxRecords: string;
|
|
||||||
pyObjClass: string;
|
|
||||||
pxResults: EmployeeResponse[];
|
|
||||||
pzPerformanceSettings: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EmployeeResponse {
|
|
||||||
pxInsHandle: string;
|
|
||||||
pxObjClass: string;
|
|
||||||
pyAccessGroup: string;
|
|
||||||
pyFirstName: string;
|
|
||||||
pyLastName: string;
|
|
||||||
pyOrganization: string;
|
|
||||||
pyOrgDivision: string;
|
|
||||||
pyOrgUnit: string;
|
|
||||||
pyTelephone: string;
|
|
||||||
pyUserIdentifier: string;
|
|
||||||
pyUserName: string;
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
export interface UserResponse {
|
|
||||||
pxInsName: string;
|
|
||||||
pxLimitedAccess: string;
|
|
||||||
pxObjClass: string;
|
|
||||||
pyAccessGroup: string;
|
|
||||||
pyFirstName: string;
|
|
||||||
pyLastName: string;
|
|
||||||
pyLastSignon: string;
|
|
||||||
pyOrganization: string;
|
|
||||||
pyOrgDivision: string;
|
|
||||||
pyOrgUnit: string;
|
|
||||||
pyTelephone: string;
|
|
||||||
pyUserIdentifier: string;
|
|
||||||
pyUserName: string;
|
|
||||||
pyAccessGroupsAdditional: string[];
|
|
||||||
pyAddresses: {
|
|
||||||
Email: {
|
|
||||||
pxObjClass: string;
|
|
||||||
pxSubscript: string;
|
|
||||||
pyEmailAddress: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,27 +1,72 @@
|
|||||||
import { mapPegaAccessGroupToAccessGroups, PegaAccessGroup } from '@dafa-enums/access-group.enum';
|
import { mapPegaAccessGroupToAccessGroups, PegaAccessGroup } from '@dafa-enums/access-group.enum';
|
||||||
import { Agency } from '@dafa-models/agency.model';
|
import { Agency } from '@dafa-models/agency.model';
|
||||||
import { EmployeeResponse } from './api/employee-response.model';
|
|
||||||
import { Participant } from './participant.model';
|
import { Participant } from './participant.model';
|
||||||
import { User } from './user.model';
|
import { User } from './user.model';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
export interface Employee extends User {}
|
export interface Employee extends User {
|
||||||
|
|
||||||
export interface EmployeeDetail extends Employee {
|
|
||||||
languages: string[];
|
languages: string[];
|
||||||
outOfOffice: {
|
outOfOffice: {
|
||||||
start: Date;
|
start: Date;
|
||||||
end: Date;
|
end: Date;
|
||||||
}[];
|
}[];
|
||||||
authorisations: string[];
|
|
||||||
phone: string;
|
|
||||||
email: string;
|
|
||||||
ssn: string;
|
ssn: string;
|
||||||
agencies: Agency[];
|
agencies: Agency[];
|
||||||
participants: Participant[];
|
participants: Participant[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapEmployeeReponseToEmployee(data: EmployeeResponse): Employee {
|
export interface EmployeesApiResponse {
|
||||||
|
pxMore: string;
|
||||||
|
pxObjClass: string;
|
||||||
|
pxPageCount: string;
|
||||||
|
pxQueryTimeStamp: string;
|
||||||
|
pxResultCount: string;
|
||||||
|
pxTotalResultCount: string;
|
||||||
|
pyMaxRecords: string;
|
||||||
|
pyObjClass: string;
|
||||||
|
pxResults: EmployeeApiResponse[];
|
||||||
|
pzPerformanceSettings: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmployeeApiResponse {
|
||||||
|
pxInsHandle: string;
|
||||||
|
pxObjClass: string;
|
||||||
|
pyAccessGroup: string;
|
||||||
|
pyFirstName: string;
|
||||||
|
pyLastName: string;
|
||||||
|
pyOrganization: string;
|
||||||
|
pyOrgDivision: string;
|
||||||
|
pyOrgUnit: string;
|
||||||
|
pyTelephone: string;
|
||||||
|
pyUserIdentifier: string;
|
||||||
|
pyUserName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmployeeApiRequestData {
|
||||||
|
pyFirstName: string;
|
||||||
|
pyLastName: string;
|
||||||
|
pyTelephone: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmployeeApiPostResponse {
|
||||||
|
pxObjClass: string;
|
||||||
|
pyErrorMessage: string;
|
||||||
|
pyFirstName: string;
|
||||||
|
pyHasError: 'true' | 'false';
|
||||||
|
pyLastName: string;
|
||||||
|
pyTelephone: string;
|
||||||
|
pyUserIdentifier: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapEmployeeToEmployeeApiRequestData(data: Employee): EmployeeApiRequestData {
|
||||||
|
return {
|
||||||
|
pyFirstName: data.firstName,
|
||||||
|
pyLastName: data.lastName,
|
||||||
|
pyTelephone: data.phone,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapEmployeeReponseToEmployee(data: EmployeeApiResponse): Employee {
|
||||||
return {
|
return {
|
||||||
id: data.pyUserIdentifier,
|
id: data.pyUserIdentifier,
|
||||||
lastName: data.pyLastName,
|
lastName: data.pyLastName,
|
||||||
@@ -35,5 +80,10 @@ export function mapEmployeeReponseToEmployee(data: EmployeeResponse): Employee {
|
|||||||
accessGroups: mapPegaAccessGroupToAccessGroups(data.pyAccessGroup as PegaAccessGroup),
|
accessGroups: mapPegaAccessGroupToAccessGroups(data.pyAccessGroup as PegaAccessGroup),
|
||||||
utforandeverksamhet: '',
|
utforandeverksamhet: '',
|
||||||
service: '',
|
service: '',
|
||||||
|
languages: [],
|
||||||
|
outOfOffice: null,
|
||||||
|
ssn: '',
|
||||||
|
agencies: [],
|
||||||
|
participants: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
58
apps/dafa-web/src/app/data/models/error/custom-error.ts
Normal file
58
apps/dafa-web/src/app/data/models/error/custom-error.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { ErrorSeverity } from '@dafa-enums/error-severity.enum';
|
||||||
|
import { ErrorType } from '@dafa-enums/error-type.enum';
|
||||||
|
|
||||||
|
export class CustomError implements Error {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
message: string;
|
||||||
|
stack: string;
|
||||||
|
type: ErrorType;
|
||||||
|
severity: ErrorSeverity;
|
||||||
|
timestamp: Date;
|
||||||
|
error: Error;
|
||||||
|
removeAfter: number;
|
||||||
|
|
||||||
|
constructor(args: { error: Error; type: ErrorType; message?: string; severity?: ErrorSeverity; stack?: string }) {
|
||||||
|
this.timestamp = new Date();
|
||||||
|
this.id = this.timestamp.getTime().toString();
|
||||||
|
this.type = this.name = args.type;
|
||||||
|
this.message = args.message ? args.message : args.error.message;
|
||||||
|
this.severity = args.severity ? args.severity : ErrorSeverity.HIGH;
|
||||||
|
this.stack = args.stack ? args.stack : CustomError.getStack(args.error);
|
||||||
|
this.error = args.error;
|
||||||
|
this.removeAfter =
|
||||||
|
this.severity === ErrorSeverity.LOW ? 5000 : this.severity === ErrorSeverity.MEDIUM ? 10000 : 20000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getStack(error: Error): string {
|
||||||
|
if (!error) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (error.stack) {
|
||||||
|
return error.stack;
|
||||||
|
} else if ((error as any).error) {
|
||||||
|
return this.getStack((error as any).error);
|
||||||
|
} else {
|
||||||
|
return error as any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function errorToCustomError(error: Error): CustomError {
|
||||||
|
console.log(error);
|
||||||
|
const type = (error as any).type || ErrorType.UNKNOWN;
|
||||||
|
const message = error.message || (error as any);
|
||||||
|
const severity = ErrorSeverity.HIGH;
|
||||||
|
|
||||||
|
// this is done to avoid circular references while running in debug mode
|
||||||
|
if ((error as any).ngDebugContext) {
|
||||||
|
(error as any).ngDebugContext = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CustomError({
|
||||||
|
error,
|
||||||
|
type,
|
||||||
|
severity,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { AccessGroup, mapPegaAccessGroupToAccessGroups, PegaAccessGroup } from '@dafa-enums/access-group.enum';
|
import { AccessGroup, mapPegaAccessGroupToAccessGroups, PegaAccessGroup } from '@dafa-enums/access-group.enum';
|
||||||
import { UserResponse } from './api/user-response.model';
|
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -16,7 +15,31 @@ export interface User {
|
|||||||
service: string;
|
service: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapUserReponseToUser(data: UserResponse): User {
|
export interface UserApiResponse {
|
||||||
|
pxInsName: string;
|
||||||
|
pxLimitedAccess: string;
|
||||||
|
pxObjClass: string;
|
||||||
|
pyAccessGroup: string;
|
||||||
|
pyFirstName: string;
|
||||||
|
pyLastName: string;
|
||||||
|
pyLastSignon: string;
|
||||||
|
pyOrganization: string;
|
||||||
|
pyOrgDivision: string;
|
||||||
|
pyOrgUnit: string;
|
||||||
|
pyTelephone: string;
|
||||||
|
pyUserIdentifier: string;
|
||||||
|
pyUserName: string;
|
||||||
|
pyAccessGroupsAdditional: string[];
|
||||||
|
pyAddresses: {
|
||||||
|
Email: {
|
||||||
|
pxObjClass: string;
|
||||||
|
pxSubscript: string;
|
||||||
|
pyEmailAddress: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapUserApiReponseToUser(data: UserApiResponse): User {
|
||||||
return {
|
return {
|
||||||
id: data.pyUserIdentifier,
|
id: data.pyUserIdentifier,
|
||||||
lastName: data.pyLastName,
|
lastName: data.pyLastName,
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { ErrorHandler, Injectable } from '@angular/core';
|
||||||
|
import { CustomError, errorToCustomError } from '@dafa-models/error/custom-error';
|
||||||
|
import { ErrorService } from '@dafa-services/error.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CustomErrorHandler implements ErrorHandler {
|
||||||
|
constructor(private errorService: ErrorService) {}
|
||||||
|
|
||||||
|
handleError(error: any): void {
|
||||||
|
const customError: CustomError = errorToCustomError(error);
|
||||||
|
this.errorService.add(customError);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,6 +34,14 @@
|
|||||||
[afDisableValidStyle]="true"
|
[afDisableValidStyle]="true"
|
||||||
[afInvalid]="ssnControl.invalid && ssnControl.dirty"
|
[afInvalid]="ssnControl.invalid && ssnControl.dirty"
|
||||||
></digi-ng-form-input>
|
></digi-ng-form-input>
|
||||||
|
<digi-ng-form-input
|
||||||
|
class="create-account__input"
|
||||||
|
formControlName="phone"
|
||||||
|
afLabel="Telefonnummer"
|
||||||
|
[afInvalidMessage]="phoneControl.errors?.message || ''"
|
||||||
|
[afDisableValidStyle]="true"
|
||||||
|
[afInvalid]="phoneControl.invalid && phoneControl.dirty"
|
||||||
|
></digi-ng-form-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="create-account__block">
|
<div class="create-account__block">
|
||||||
<h2>Tjänst</h2>
|
<h2>Tjänst</h2>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Router } from '@angular/router';
|
|||||||
import { Service } from '@dafa-enums/service.enum';
|
import { Service } from '@dafa-enums/service.enum';
|
||||||
import { EmployeeService } from '@dafa-services/api/employee.service';
|
import { EmployeeService } from '@dafa-services/api/employee.service';
|
||||||
import { RequiredValidator } from '@dafa-validators/required.validator';
|
import { RequiredValidator } from '@dafa-validators/required.validator';
|
||||||
import { SocialSecurityNumberValidator } from '@dafa-validators/social-security-number.validator';
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -39,8 +38,11 @@ export class CreateAccountComponent {
|
|||||||
this.formGroup = this.formBuilder.group({
|
this.formGroup = this.formBuilder.group({
|
||||||
firstName: this.formBuilder.control('', [RequiredValidator('Förnamn')]),
|
firstName: this.formBuilder.control('', [RequiredValidator('Förnamn')]),
|
||||||
lastName: this.formBuilder.control('', [RequiredValidator('Efternamn')]),
|
lastName: this.formBuilder.control('', [RequiredValidator('Efternamn')]),
|
||||||
ssn: this.formBuilder.control('', [RequiredValidator('Personnummer'), SocialSecurityNumberValidator()]),
|
phone: this.formBuilder.control('', [RequiredValidator('Telefonnummer')]),
|
||||||
employeeId: this.formBuilder.control('', [RequiredValidator('Personal-ID')]),
|
// ssn: this.formBuilder.control('', [RequiredValidator('Personnummer'), SocialSecurityNumberValidator()]),
|
||||||
|
// employeeId: this.formBuilder.control('', [RequiredValidator('Personal-ID')]),
|
||||||
|
ssn: this.formBuilder.control(''),
|
||||||
|
employeeId: this.formBuilder.control(''),
|
||||||
service: this.formBuilder.control(''),
|
service: this.formBuilder.control(''),
|
||||||
permissions: this.formBuilder.control(false),
|
permissions: this.formBuilder.control(false),
|
||||||
participant: this.formBuilder.control(false),
|
participant: this.formBuilder.control(false),
|
||||||
@@ -83,6 +85,9 @@ export class CreateAccountComponent {
|
|||||||
get ssnControl(): AbstractControl {
|
get ssnControl(): AbstractControl {
|
||||||
return this.formGroup.get('ssn');
|
return this.formGroup.get('ssn');
|
||||||
}
|
}
|
||||||
|
get phoneControl(): AbstractControl {
|
||||||
|
return this.formGroup.get('phone');
|
||||||
|
}
|
||||||
|
|
||||||
private _markFormAsDirty(): void {
|
private _markFormAsDirty(): void {
|
||||||
Object.keys(this.formGroup.controls).forEach(control => {
|
Object.keys(this.formGroup.controls).forEach(control => {
|
||||||
@@ -101,7 +106,7 @@ export class CreateAccountComponent {
|
|||||||
delete submittableValues.outOfOfficeStart;
|
delete submittableValues.outOfOfficeStart;
|
||||||
delete submittableValues.outOfOfficeEnd;
|
delete submittableValues.outOfOfficeEnd;
|
||||||
|
|
||||||
const post = this.employeeService.createAccount(submittableValues).subscribe({
|
const post = this.employeeService.postNewEmployee(submittableValues).subscribe({
|
||||||
next: id => {
|
next: id => {
|
||||||
this.router.navigate(['/administration', 'personal', id]);
|
this.router.navigate(['/administration', 'personal', id]);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { environment } from '@dafa-environment';
|
|
||||||
import { Employee, EmployeeDetail } from '@dafa-models/employee.model';
|
|
||||||
import { SortBy } from '@dafa-models/sort-by.model';
|
|
||||||
import { sort } from '@dafa-utils/sort.util';
|
|
||||||
import { BehaviorSubject, combineLatest, Observable, throwError } from 'rxjs';
|
|
||||||
import { catchError, map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
function filterEmployees(employees: Employee[], searchFilter: string): Employee[] {
|
|
||||||
return employees.filter(person => {
|
|
||||||
const searchValueExistsInName = person.fullName.toLowerCase().includes(searchFilter.toLowerCase());
|
|
||||||
|
|
||||||
return searchValueExistsInName;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class EmployeeService {
|
|
||||||
private _employeesApiUrl = `${environment.apiBase}/employees`;
|
|
||||||
private _allEmployees$: Observable<Employee[]> = this.httpClient.get<Employee[]>(this._employeesApiUrl, {
|
|
||||||
params: { _embed: 'participants' },
|
|
||||||
});
|
|
||||||
|
|
||||||
private _employeesSortBy$ = new BehaviorSubject<SortBy | null>({ key: 'fullName', reverse: false });
|
|
||||||
public employeesSortBy$: Observable<SortBy> = this._employeesSortBy$.asObservable();
|
|
||||||
private _searchFilter$ = new BehaviorSubject<string>('');
|
|
||||||
public searchFilter$: Observable<string> = this._searchFilter$.asObservable();
|
|
||||||
|
|
||||||
private _filteredEmployees$: Observable<Employee[]> = combineLatest([this._allEmployees$, this._searchFilter$]).pipe(
|
|
||||||
map(([employees, searchFilter]) => filterEmployees(employees, searchFilter))
|
|
||||||
);
|
|
||||||
|
|
||||||
public filteredEmployees$: Observable<Employee[]> = combineLatest([
|
|
||||||
this._filteredEmployees$,
|
|
||||||
this._employeesSortBy$,
|
|
||||||
]).pipe(
|
|
||||||
map(([employees, sortBy]) => {
|
|
||||||
return sortBy ? sort(employees, sortBy) : employees;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient) {}
|
|
||||||
|
|
||||||
public getDetailedEmployeeData(id: string): Observable<Employee> {
|
|
||||||
return this.httpClient.get<Employee>(`${this._employeesApiUrl}/${id}`, { params: { _embed: 'participants' } });
|
|
||||||
}
|
|
||||||
|
|
||||||
public setSearchFilter(value: string) {
|
|
||||||
this._searchFilter$.next(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEmployeesSortKey(key: keyof Employee) {
|
|
||||||
const currentSortBy = this._employeesSortBy$.getValue();
|
|
||||||
const reverse = currentSortBy?.key === key ? !currentSortBy.reverse : false;
|
|
||||||
this._employeesSortBy$.next({ key, reverse });
|
|
||||||
}
|
|
||||||
|
|
||||||
public createAccount(employeesData: EmployeeDetail): Observable<string> {
|
|
||||||
return this.httpClient.post<EmployeeDetail>(this._employeesApiUrl, employeesData).pipe(
|
|
||||||
map(data => data.id),
|
|
||||||
catchError(error => {
|
|
||||||
return throwError(error);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ErrorType } from '@dafa-enums/error-type.enum';
|
||||||
import { environment } from '@dafa-environment';
|
import { environment } from '@dafa-environment';
|
||||||
import { EmployeesApiResponse } from '@dafa-models/api/employee-response.model';
|
import {
|
||||||
import { Employee, EmployeeDetail, mapEmployeeReponseToEmployee } from '@dafa-models/employee.model';
|
Employee,
|
||||||
|
EmployeeApiPostResponse,
|
||||||
|
EmployeesApiResponse,
|
||||||
|
mapEmployeeReponseToEmployee,
|
||||||
|
mapEmployeeToEmployeeApiRequestData,
|
||||||
|
} from '@dafa-models/employee.model';
|
||||||
import { SortBy } from '@dafa-models/sort-by.model';
|
import { SortBy } from '@dafa-models/sort-by.model';
|
||||||
import { sort } from '@dafa-utils/sort.util';
|
import { sort } from '@dafa-utils/sort.util';
|
||||||
import { BehaviorSubject, combineLatest, Observable, throwError } from 'rxjs';
|
import { BehaviorSubject, combineLatest, Observable, throwError } from 'rxjs';
|
||||||
@@ -64,12 +70,21 @@ export class EmployeeService {
|
|||||||
this._employeesSortBy$.next({ key, reverse });
|
this._employeesSortBy$.next({ key, reverse });
|
||||||
}
|
}
|
||||||
|
|
||||||
public createAccount(employeesData: EmployeeDetail): Observable<string> {
|
public postNewEmployee(employeeData: Employee): Observable<string> {
|
||||||
return this.httpClient.post<EmployeeDetail>(this._employeesApiUrl, employeesData).pipe(
|
return this.httpClient
|
||||||
map(data => data.id),
|
.post<EmployeeApiPostResponse>(
|
||||||
catchError(error => {
|
this._employeeApiUrl,
|
||||||
return throwError(error);
|
mapEmployeeToEmployeeApiRequestData(employeeData),
|
||||||
})
|
API_HEADERS
|
||||||
);
|
)
|
||||||
|
.pipe(
|
||||||
|
map(data => {
|
||||||
|
if (data.pyHasError === 'true') {
|
||||||
|
throw new Error(data.pyErrorMessage);
|
||||||
|
}
|
||||||
|
return data.pyUserIdentifier;
|
||||||
|
}),
|
||||||
|
catchError(error => throwError({ message: error, type: ErrorType.API }))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { environment } from '@dafa-environment';
|
import { environment } from '@dafa-environment';
|
||||||
import { UserResponse } from '@dafa-models/api/user-response.model';
|
import { mapUserApiReponseToUser, User, UserApiResponse } from '@dafa-models/user.model';
|
||||||
import { mapUserReponseToUser, User } from '@dafa-models/user.model';
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
@@ -12,10 +11,10 @@ import { map } from 'rxjs/operators';
|
|||||||
export class UserService {
|
export class UserService {
|
||||||
private _userApiUrl = `${environment.api.default}/D_OperatorID`;
|
private _userApiUrl = `${environment.api.default}/D_OperatorID`;
|
||||||
public currentUser$: Observable<User> = this.httpClient
|
public currentUser$: Observable<User> = this.httpClient
|
||||||
.get<UserResponse>(this._userApiUrl, {
|
.get<UserApiResponse>(this._userApiUrl, {
|
||||||
headers: environment.api.headers,
|
headers: environment.api.headers,
|
||||||
})
|
})
|
||||||
.pipe(map(response => mapUserReponseToUser(response)));
|
.pipe(map(response => mapUserApiReponseToUser(response)));
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient) {}
|
constructor(private httpClient: HttpClient) {}
|
||||||
}
|
}
|
||||||
|
|||||||
34
apps/dafa-web/src/app/services/error.service.ts
Normal file
34
apps/dafa-web/src/app/services/error.service.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { ApplicationRef, Injectable, Injector } from '@angular/core';
|
||||||
|
import { CustomError } from '@dafa-models/error/custom-error';
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ErrorService {
|
||||||
|
private appRef: any;
|
||||||
|
private errorQueue$: BehaviorSubject<CustomError[]> = new BehaviorSubject([]);
|
||||||
|
|
||||||
|
public errors$: Observable<CustomError[]> = this.errorQueue$.pipe(
|
||||||
|
map(errors => errors.sort((a, b) => (a.timestamp > b.timestamp ? -1 : 1)))
|
||||||
|
);
|
||||||
|
|
||||||
|
constructor(private injector: Injector) {
|
||||||
|
// Workaround to fix change-detection when using Error interceptor
|
||||||
|
// See https://stackoverflow.com/a/37793791
|
||||||
|
setTimeout(() => (this.appRef = this.injector.get(ApplicationRef)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public add(error: CustomError) {
|
||||||
|
console.error(error);
|
||||||
|
this.errorQueue$.next([...this.errorQueue$.value, error]);
|
||||||
|
this.appRef.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
public remove(error: CustomError) {
|
||||||
|
const newErrorQueue = this.errorQueue$.value.filter(currentError => currentError.id !== error.id);
|
||||||
|
this.errorQueue$.next(newErrorQueue);
|
||||||
|
this.appRef.tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
1
apps/dafa-web/src/styles/variables/_shadows.scss
Normal file
1
apps/dafa-web/src/styles/variables/_shadows.scss
Normal file
@@ -0,0 +1 @@
|
|||||||
|
$dafa__shadow: 0 0.2rem 0.6rem 0 var(--digi--ui--color--shadow);
|
||||||
26604
package-lock.json
generated
26604
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -45,6 +45,7 @@
|
|||||||
"@af/auth": "^11.1.0",
|
"@af/auth": "^11.1.0",
|
||||||
"@af/digi-ng": "^14.0.0",
|
"@af/digi-ng": "^14.0.0",
|
||||||
"@angular/animations": "^11.2.0",
|
"@angular/animations": "^11.2.0",
|
||||||
|
"@angular/cdk": "^11.2.12",
|
||||||
"@angular/common": "^11.2.0",
|
"@angular/common": "^11.2.0",
|
||||||
"@angular/compiler": "^11.2.0",
|
"@angular/compiler": "^11.2.0",
|
||||||
"@angular/core": "^11.2.0",
|
"@angular/core": "^11.2.0",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"@dafa-directives/*": ["apps/dafa-web/src/app/directives/*"],
|
"@dafa-directives/*": ["apps/dafa-web/src/app/directives/*"],
|
||||||
"@dafa-enums/*": ["apps/dafa-web/src/app/data/enums/*"],
|
"@dafa-enums/*": ["apps/dafa-web/src/app/data/enums/*"],
|
||||||
"@dafa-services/*": ["apps/dafa-web/src/app/services/*"],
|
"@dafa-services/*": ["apps/dafa-web/src/app/services/*"],
|
||||||
|
"@dafa-interceptors/*": ["apps/dafa-web/src/app/interceptors/*"],
|
||||||
"@dafa-utils/*": ["apps/dafa-web/src/app/utils/*"],
|
"@dafa-utils/*": ["apps/dafa-web/src/app/utils/*"],
|
||||||
"@dafa-validators/*": ["apps/dafa-web/src/app/utils/validators/*"],
|
"@dafa-validators/*": ["apps/dafa-web/src/app/utils/validators/*"],
|
||||||
"@dafa-environment": ["apps/dafa-web/src/environments/environment"]
|
"@dafa-environment": ["apps/dafa-web/src/environments/environment"]
|
||||||
|
|||||||
Reference in New Issue
Block a user