Added error handling and now possible to post employees
This commit is contained in:
@@ -14,3 +14,4 @@
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
</div>
|
||||
<dafa-toast-list></dafa-toast-list>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { DigiNgNavigationBreadcrumbsModule } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { ErrorHandler, NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { CustomErrorHandler } from '@dafa-interceptors/custom-error-handler.module';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { NavigationModule } from './components/navigation/navigation.module';
|
||||
import { SidebarModule } from './components/sidebar/sidebar.module';
|
||||
import { SkipToContentModule } from './components/skip-to-content/skip-to-content.module';
|
||||
import { ToastListModule } from './components/toast-list/toast-list.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
@@ -19,9 +21,15 @@ import { SkipToContentModule } from './components/skip-to-content/skip-to-conten
|
||||
SkipToContentModule,
|
||||
NavigationModule,
|
||||
SidebarModule,
|
||||
ToastListModule,
|
||||
DigiNgNavigationBreadcrumbsModule,
|
||||
],
|
||||
providers: [],
|
||||
providers: [
|
||||
{
|
||||
provide: ErrorHandler,
|
||||
useClass: CustomErrorHandler,
|
||||
},
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
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 { Agency } from '@dafa-models/agency.model';
|
||||
import { EmployeeResponse } from './api/employee-response.model';
|
||||
import { Participant } from './participant.model';
|
||||
import { User } from './user.model';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface Employee extends User {}
|
||||
|
||||
export interface EmployeeDetail extends Employee {
|
||||
export interface Employee extends User {
|
||||
languages: string[];
|
||||
outOfOffice: {
|
||||
start: Date;
|
||||
end: Date;
|
||||
}[];
|
||||
authorisations: string[];
|
||||
phone: string;
|
||||
email: string;
|
||||
ssn: string;
|
||||
agencies: Agency[];
|
||||
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 {
|
||||
id: data.pyUserIdentifier,
|
||||
lastName: data.pyLastName,
|
||||
@@ -35,5 +80,10 @@ export function mapEmployeeReponseToEmployee(data: EmployeeResponse): Employee {
|
||||
accessGroups: mapPegaAccessGroupToAccessGroups(data.pyAccessGroup as PegaAccessGroup),
|
||||
utforandeverksamhet: '',
|
||||
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 { UserResponse } from './api/user-response.model';
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
@@ -16,7 +15,31 @@ export interface User {
|
||||
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 {
|
||||
id: data.pyUserIdentifier,
|
||||
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"
|
||||
[afInvalid]="ssnControl.invalid && ssnControl.dirty"
|
||||
></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 class="create-account__block">
|
||||
<h2>Tjänst</h2>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Router } from '@angular/router';
|
||||
import { Service } from '@dafa-enums/service.enum';
|
||||
import { EmployeeService } from '@dafa-services/api/employee.service';
|
||||
import { RequiredValidator } from '@dafa-validators/required.validator';
|
||||
import { SocialSecurityNumberValidator } from '@dafa-validators/social-security-number.validator';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
@@ -39,8 +38,11 @@ export class CreateAccountComponent {
|
||||
this.formGroup = this.formBuilder.group({
|
||||
firstName: this.formBuilder.control('', [RequiredValidator('Förnamn')]),
|
||||
lastName: this.formBuilder.control('', [RequiredValidator('Efternamn')]),
|
||||
ssn: this.formBuilder.control('', [RequiredValidator('Personnummer'), SocialSecurityNumberValidator()]),
|
||||
employeeId: this.formBuilder.control('', [RequiredValidator('Personal-ID')]),
|
||||
phone: this.formBuilder.control('', [RequiredValidator('Telefonnummer')]),
|
||||
// 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(''),
|
||||
permissions: this.formBuilder.control(false),
|
||||
participant: this.formBuilder.control(false),
|
||||
@@ -83,6 +85,9 @@ export class CreateAccountComponent {
|
||||
get ssnControl(): AbstractControl {
|
||||
return this.formGroup.get('ssn');
|
||||
}
|
||||
get phoneControl(): AbstractControl {
|
||||
return this.formGroup.get('phone');
|
||||
}
|
||||
|
||||
private _markFormAsDirty(): void {
|
||||
Object.keys(this.formGroup.controls).forEach(control => {
|
||||
@@ -101,7 +106,7 @@ export class CreateAccountComponent {
|
||||
delete submittableValues.outOfOfficeStart;
|
||||
delete submittableValues.outOfOfficeEnd;
|
||||
|
||||
const post = this.employeeService.createAccount(submittableValues).subscribe({
|
||||
const post = this.employeeService.postNewEmployee(submittableValues).subscribe({
|
||||
next: 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 { Injectable } from '@angular/core';
|
||||
import { ErrorType } from '@dafa-enums/error-type.enum';
|
||||
import { environment } from '@dafa-environment';
|
||||
import { EmployeesApiResponse } from '@dafa-models/api/employee-response.model';
|
||||
import { Employee, EmployeeDetail, mapEmployeeReponseToEmployee } from '@dafa-models/employee.model';
|
||||
import {
|
||||
Employee,
|
||||
EmployeeApiPostResponse,
|
||||
EmployeesApiResponse,
|
||||
mapEmployeeReponseToEmployee,
|
||||
mapEmployeeToEmployeeApiRequestData,
|
||||
} 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';
|
||||
@@ -64,12 +70,21 @@ export class EmployeeService {
|
||||
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);
|
||||
})
|
||||
);
|
||||
public postNewEmployee(employeeData: Employee): Observable<string> {
|
||||
return this.httpClient
|
||||
.post<EmployeeApiPostResponse>(
|
||||
this._employeeApiUrl,
|
||||
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 { Injectable } from '@angular/core';
|
||||
import { environment } from '@dafa-environment';
|
||||
import { UserResponse } from '@dafa-models/api/user-response.model';
|
||||
import { mapUserReponseToUser, User } from '@dafa-models/user.model';
|
||||
import { mapUserApiReponseToUser, User, UserApiResponse } from '@dafa-models/user.model';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@@ -12,10 +11,10 @@ import { map } from 'rxjs/operators';
|
||||
export class UserService {
|
||||
private _userApiUrl = `${environment.api.default}/D_OperatorID`;
|
||||
public currentUser$: Observable<User> = this.httpClient
|
||||
.get<UserResponse>(this._userApiUrl, {
|
||||
.get<UserApiResponse>(this._userApiUrl, {
|
||||
headers: environment.api.headers,
|
||||
})
|
||||
.pipe(map(response => mapUserReponseToUser(response)));
|
||||
.pipe(map(response => mapUserApiReponseToUser(response)));
|
||||
|
||||
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/digi-ng": "^14.0.0",
|
||||
"@angular/animations": "^11.2.0",
|
||||
"@angular/cdk": "^11.2.12",
|
||||
"@angular/common": "^11.2.0",
|
||||
"@angular/compiler": "^11.2.0",
|
||||
"@angular/core": "^11.2.0",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@dafa-directives/*": ["apps/dafa-web/src/app/directives/*"],
|
||||
"@dafa-enums/*": ["apps/dafa-web/src/app/data/enums/*"],
|
||||
"@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-validators/*": ["apps/dafa-web/src/app/utils/validators/*"],
|
||||
"@dafa-environment": ["apps/dafa-web/src/environments/environment"]
|
||||
|
||||
Reference in New Issue
Block a user