feat: added update project option and refactored project-dialog
This commit is contained in:
parent
203b376ef1
commit
282d66efa6
|
@ -45,7 +45,7 @@
|
||||||
status="primary"
|
status="primary"
|
||||||
size="small"
|
size="small"
|
||||||
class="project-button"
|
class="project-button"
|
||||||
(click)="onClickEditProject()">
|
(click)="onClickEditProject(project)">
|
||||||
<fa-icon [icon]="fa.faPencilAlt"></fa-icon>
|
<fa-icon [icon]="fa.faPencilAlt"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
<button nbButton
|
<button nbButton
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="projects.getValue().length === 0 && !isLoading()" fxLayout="row" fxLayoutAlign="center center">
|
<div *ngIf="projects.getValue().length === 0 || !isLoading()" fxLayout="row" fxLayoutAlign="center center">
|
||||||
<p class="error-text">
|
<p class="error-text">
|
||||||
{{'project.overview.no.projects' | translate}}
|
{{'project.overview.no.projects' | translate}}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -36,9 +36,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-card:hover {
|
.project-card:hover {
|
||||||
background-color: nb-theme(color-info-transparent-default);
|
background-color: nb-theme(color-basic-transparent-focus);
|
||||||
|
// Increases element size on hover
|
||||||
|
// Decreases usability which is why it is commented out
|
||||||
|
/*
|
||||||
margin-top: +0.625rem;
|
margin-top: +0.625rem;
|
||||||
transform: scale(1.025)
|
transform: scale(1.025);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-link:hover {
|
.project-link:hover {
|
||||||
|
|
|
@ -24,6 +24,8 @@ import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-s
|
||||||
import {KeycloakService} from 'keycloak-angular';
|
import {KeycloakService} from 'keycloak-angular';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
||||||
|
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
||||||
|
import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
|
||||||
|
|
||||||
describe('ProjectOverviewComponent', () => {
|
describe('ProjectOverviewComponent', () => {
|
||||||
let component: ProjectOverviewComponent;
|
let component: ProjectOverviewComponent;
|
||||||
|
@ -63,6 +65,7 @@ describe('ProjectOverviewComponent', () => {
|
||||||
providers: [
|
providers: [
|
||||||
KeycloakService,
|
KeycloakService,
|
||||||
{provide: ProjectService, useValue: new ProjectServiceMock()},
|
{provide: ProjectService, useValue: new ProjectServiceMock()},
|
||||||
|
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
|
||||||
{provide: DialogService, useClass: DialogServiceMock},
|
{provide: DialogService, useClass: DialogServiceMock},
|
||||||
{provide: NotificationService, useValue: new NotificationServiceMock()}
|
{provide: NotificationService, useValue: new NotificationServiceMock()}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||||
import * as FA from '@fortawesome/free-solid-svg-icons';
|
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
import {Project, SaveProjectDialogBody} from '@shared/models/project.model';
|
import {Project, ProjectDialogBody} from '@shared/models/project.model';
|
||||||
import {BehaviorSubject, Observable} from 'rxjs';
|
import {BehaviorSubject, Observable} from 'rxjs';
|
||||||
import {untilDestroyed} from 'ngx-take-until-destroy';
|
import {untilDestroyed} from 'ngx-take-until-destroy';
|
||||||
import {ProjectService} from '@shared/services/project.service';
|
import {ProjectService} from '@shared/services/project.service';
|
||||||
|
@ -8,6 +8,7 @@ import {NotificationService, PopupType} from '@shared/services/notification.serv
|
||||||
import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
||||||
|
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-project-overview',
|
selector: 'app-project-overview',
|
||||||
|
@ -24,6 +25,7 @@ export class ProjectOverviewComponent implements OnInit, OnDestroy {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly projectService: ProjectService,
|
private readonly projectService: ProjectService,
|
||||||
private readonly dialogService: DialogService,
|
private readonly dialogService: DialogService,
|
||||||
|
private readonly projectDialogService: ProjectDialogService,
|
||||||
private readonly notificationService: NotificationService) {
|
private readonly notificationService: NotificationService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,17 +53,18 @@ export class ProjectOverviewComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickAddProject(): void {
|
onClickAddProject(): void {
|
||||||
this.dialogService.openCustomDialog(
|
this.projectDialogService.openProjectDialog(
|
||||||
ProjectDialogComponent,
|
ProjectDialogComponent,
|
||||||
|
null,
|
||||||
{
|
{
|
||||||
closeOnEsc: false,
|
closeOnEsc: false,
|
||||||
hasScroll: false,
|
hasScroll: false,
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
closeOnBackdropClick: false
|
closeOnBackdropClick: false
|
||||||
}
|
}
|
||||||
).onClose.pipe(
|
).pipe(
|
||||||
filter(value => !!value),
|
filter(value => !!value),
|
||||||
mergeMap((value: SaveProjectDialogBody) => this.projectService.saveProject(value)),
|
mergeMap((value: ProjectDialogBody) => this.projectService.saveProject(value)),
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
|
@ -75,8 +78,30 @@ export class ProjectOverviewComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickEditProject(): void {
|
onClickEditProject(project: Project): void {
|
||||||
console.log('to be implemented...');
|
this.projectDialogService.openProjectDialog(
|
||||||
|
ProjectDialogComponent,
|
||||||
|
project,
|
||||||
|
{
|
||||||
|
closeOnEsc: false,
|
||||||
|
hasScroll: false,
|
||||||
|
autoFocus: false,
|
||||||
|
closeOnBackdropClick: false
|
||||||
|
}
|
||||||
|
).pipe(
|
||||||
|
filter(value => !!value),
|
||||||
|
mergeMap((value: ProjectDialogBody) => this.projectService.updateProject(project.id, value)),
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.loadProjects();
|
||||||
|
this.notificationService.showPopup('project.popup.update.success', PopupType.SUCCESS);
|
||||||
|
},
|
||||||
|
error: error => {
|
||||||
|
console.error(error);
|
||||||
|
this.notificationService.showPopup('project.popup.update.failed', PopupType.FAILURE);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickDeleteProject(project: Project): void {
|
onClickDeleteProject(project: Project): void {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"action.login": "Einloggen",
|
"action.login": "Einloggen",
|
||||||
"action.retry": "Erneut Versuchen",
|
"action.retry": "Erneut Versuchen",
|
||||||
"action.save": "Speichern",
|
"action.save": "Speichern",
|
||||||
|
"action.update": "Aktualisieren",
|
||||||
"action.confirm": "Bestätigen",
|
"action.confirm": "Bestätigen",
|
||||||
"action.cancel": "Abbrechen",
|
"action.cancel": "Abbrechen",
|
||||||
"action.yes": "Ja",
|
"action.yes": "Ja",
|
||||||
|
@ -14,7 +15,7 @@
|
||||||
"success": "✔",
|
"success": "✔",
|
||||||
"failure": "✘",
|
"failure": "✘",
|
||||||
"warning": "!",
|
"warning": "!",
|
||||||
"info": "",
|
"info": "ⓘ",
|
||||||
"error.position": {
|
"error.position": {
|
||||||
"permissionDenied": "Berechtigung verweigert",
|
"permissionDenied": "Berechtigung verweigert",
|
||||||
"timeout": "Zeitüberschreitung"
|
"timeout": "Zeitüberschreitung"
|
||||||
|
@ -29,6 +30,13 @@
|
||||||
"unauthorized": "Benutzer nicht gefunden. Bitte registrieren und erneut versuchen"
|
"unauthorized": "Benutzer nicht gefunden. Bitte registrieren und erneut versuchen"
|
||||||
},
|
},
|
||||||
"project": {
|
"project": {
|
||||||
|
"title.label": "Projekt Titel",
|
||||||
|
"client.label": "Name des Auftraggebers",
|
||||||
|
"tester.label": "Name des Pentester",
|
||||||
|
"title": "Titel",
|
||||||
|
"client": "Klient",
|
||||||
|
"tester": "Tester",
|
||||||
|
"createdAt": "Erstellt am",
|
||||||
"overview": {
|
"overview": {
|
||||||
"add.project": "Projekt hinzufügen",
|
"add.project": "Projekt hinzufügen",
|
||||||
"no.projects": "Keine Projekte verfügbar"
|
"no.projects": "Keine Projekte verfügbar"
|
||||||
|
@ -36,23 +44,26 @@
|
||||||
"create": {
|
"create": {
|
||||||
"header": "Neues Projekt erstellen"
|
"header": "Neues Projekt erstellen"
|
||||||
},
|
},
|
||||||
|
"edit": {
|
||||||
|
"header": "Projekt editieren"
|
||||||
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"title": "Projekt löschen",
|
"title": "Projekt löschen",
|
||||||
"key": "Möchten Sie das Projekt \"{{name}}\" unwiderruflich löschen?"
|
"key": "Möchten Sie das Projekt \"{{name}}\" unwiderruflich löschen?"
|
||||||
},
|
},
|
||||||
|
"validationMessage": {
|
||||||
|
"titleRequired": "Titel ist erforderlich.",
|
||||||
|
"clientRequired": "Klient ist erforderlich.",
|
||||||
|
"testerRequired": "Tester ist erforderlich."
|
||||||
|
},
|
||||||
"popup": {
|
"popup": {
|
||||||
"not.found": "Keine Projekte gefunden",
|
"not.found": "Keine Projekte gefunden",
|
||||||
"save.success": "Projekt erfolgreich gespeichert",
|
"save.success": "Projekt erfolgreich gespeichert",
|
||||||
"save.failed": "Projekt konnte nicht gespeichert werden",
|
"save.failed": "Projekt konnte nicht gespeichert werden",
|
||||||
|
"update.success": "Projekt erfolgreich aktualisiert",
|
||||||
|
"update.failed": "Projekt konnte nicht aktualisiert werden",
|
||||||
"delete.success": "Projekt erfolgreich gelöscht",
|
"delete.success": "Projekt erfolgreich gelöscht",
|
||||||
"delete.failed": "Projekt konnte nicht gelöscht werden"
|
"delete.failed": "Projekt konnte nicht gelöscht werden"
|
||||||
},
|
}
|
||||||
"title.label": "Projekt Titel",
|
|
||||||
"client.label": "Name des Auftraggebers",
|
|
||||||
"tester.label": "Name des Pentester",
|
|
||||||
"title": "Titel",
|
|
||||||
"client": "Klient",
|
|
||||||
"tester": "Tester",
|
|
||||||
"createdAt": "Erstellt am"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"action.retry": "Try again",
|
"action.retry": "Try again",
|
||||||
"action.confirm": "Confirm",
|
"action.confirm": "Confirm",
|
||||||
"action.save": "Save",
|
"action.save": "Save",
|
||||||
|
"action.update": "Update",
|
||||||
"action.cancel": "Cancel",
|
"action.cancel": "Cancel",
|
||||||
"action.yes": "Yes",
|
"action.yes": "Yes",
|
||||||
"action.no": "No",
|
"action.no": "No",
|
||||||
|
@ -14,7 +15,7 @@
|
||||||
"success": "✔",
|
"success": "✔",
|
||||||
"failure": "✘",
|
"failure": "✘",
|
||||||
"warning": "!",
|
"warning": "!",
|
||||||
"info": "",
|
"info": "ⓘ",
|
||||||
"error.position": {
|
"error.position": {
|
||||||
"permissionDenied": "Permission denied",
|
"permissionDenied": "Permission denied",
|
||||||
"timeout": "Timeout"
|
"timeout": "Timeout"
|
||||||
|
@ -29,30 +30,40 @@
|
||||||
"unauthorized": "User not found. Please register and try again"
|
"unauthorized": "User not found. Please register and try again"
|
||||||
},
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"overview": {
|
|
||||||
"add.project": "Add project",
|
|
||||||
"no.projects": "No projects available"
|
|
||||||
},
|
|
||||||
"create": {
|
|
||||||
"header": "Create New Project"
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"title": "Delete Project",
|
|
||||||
"key": "Do you want to permanently delete the project \"{{name}}\"?"
|
|
||||||
},
|
|
||||||
"popup": {
|
|
||||||
"not.found": "No projects found",
|
|
||||||
"save.success": "Project saved successfully",
|
|
||||||
"save.failed": "Project could not be saved",
|
|
||||||
"delete.success": "Project deleted successfully",
|
|
||||||
"delete.failed": "Project could not be deleted"
|
|
||||||
},
|
|
||||||
"title.label": "Project Title",
|
"title.label": "Project Title",
|
||||||
"client.label": "Name of Client",
|
"client.label": "Name of Client",
|
||||||
"tester.label": "Name of Pentester",
|
"tester.label": "Name of Pentester",
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"client": "Client",
|
"client": "Client",
|
||||||
"tester": "Tester",
|
"tester": "Tester",
|
||||||
"createdAt": "Created at"
|
"createdAt": "Created at",
|
||||||
|
"overview": {
|
||||||
|
"add.project": "Add Project",
|
||||||
|
"no.projects": "No projects available"
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"header": "Create New Project"
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"header": "Edit Project"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"title": "Delete Project",
|
||||||
|
"key": "Do you want to permanently delete the project \"{{name}}\"?"
|
||||||
|
},
|
||||||
|
"validationMessage": {
|
||||||
|
"titleRequired": "Title is required.",
|
||||||
|
"clientRequired": "Client is required.",
|
||||||
|
"testerRequired": "Tester is required."
|
||||||
|
},
|
||||||
|
"popup": {
|
||||||
|
"not.found": "No projects found",
|
||||||
|
"save.success": "Project saved successfully",
|
||||||
|
"save.failed": "Project could not be saved",
|
||||||
|
"update.success": "Project updated successfully",
|
||||||
|
"update.failed": "Project could not be updated",
|
||||||
|
"delete.success": "Project deleted successfully",
|
||||||
|
"delete.failed": "Project could not be deleted"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>SecurityC4POAngular</title>
|
<title>Security C4PO</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/x-icon" href="assets/images/favicons/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="assets/images/favicons/corporate_favicon.ico">
|
||||||
</head>
|
</head>
|
||||||
<body id="loader-wrapper">
|
<body id="loader-wrapper">
|
||||||
<app-root id="loader"></app-root>
|
<app-root id="loader"></app-root>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const GlobalTitlesVariables = {
|
export const GlobalTitlesVariables = {
|
||||||
SECURITYC4PO_TITLE: 'SecurityC4PO',
|
SECURITYC4PO_TITLE: 'Security C4PO',
|
||||||
NOVATEC_NAME: 'Novatec'
|
NOVATEC_NAME: 'Novatec'
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
export interface ProjectDialogData {
|
||||||
|
form: {
|
||||||
|
[key: string]: GenericFormFieldConfig // key is property name, e.g. title
|
||||||
|
};
|
||||||
|
options?: GenericFormFieldOption[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericFormFieldConfig {
|
||||||
|
fieldName: string;
|
||||||
|
type: string; // text, password, email
|
||||||
|
labelKey: string; // translation key of field label
|
||||||
|
placeholder: string; // translation key of placeholder text
|
||||||
|
controlsConfig: { [key: string]: any };
|
||||||
|
errors: GenericFormFieldError[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericFormFieldError {
|
||||||
|
errorCode: string;
|
||||||
|
translationKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericFormFieldOption {
|
||||||
|
headerLabelKey: string;
|
||||||
|
buttonKey: string;
|
||||||
|
accentColor: string;
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ export class Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SaveProjectDialogBody {
|
export interface ProjectDialogBody {
|
||||||
title: string;
|
title: string;
|
||||||
client: string;
|
client: string;
|
||||||
tester: string;
|
tester: string;
|
||||||
|
|
|
@ -1,48 +1,43 @@
|
||||||
<nb-card #dialog accent="primary" class="project-dialog">
|
<nb-card #dialog accent="{{dialogData?.options[0].accentColor}}" class="project-dialog">
|
||||||
<nb-card-header fxLayoutAlign="start center" class="dialog-header">
|
<nb-card-header fxLayoutAlign="start center" class="dialog-header">
|
||||||
{{ 'project.create.header' | translate }}
|
{{ dialogData?.options[0].headerLabelKey | translate }}
|
||||||
</nb-card-header>
|
</nb-card-header>
|
||||||
<nb-card-body>
|
<nb-card-body>
|
||||||
<form [formGroup]="projectFormGroup" fxLayout="column" fxLayoutGap="1rem" fxLayoutAlign="start start">
|
<form *ngIf="formArray" [formGroup]="projectFormGroup" fxLayout="column" fxLayoutGap="1rem"
|
||||||
<nb-form-field class="project-form-field">
|
fxLayoutAlign="start start">
|
||||||
<label for="projectTitleInput" class="label">
|
<ng-template ngFor let-fieldConfig [ngForOf]="formArray">
|
||||||
{{'project.title.label' | translate}}:
|
<!-- TYPE select -->
|
||||||
</label>
|
<ng-container [ngSwitch]="fieldConfig.type">
|
||||||
<input formControlName="projectTitle" required
|
<!-- Default styles -->
|
||||||
id="projectTitleInput" nbInput
|
<nb-form-field *ngSwitchCase="'text'" class="project-form-field">
|
||||||
class="input" type="text" fullWidth
|
<label for="{{fieldConfig.fieldName}}" class="label">
|
||||||
status="{{formCtrlStatus}}"
|
{{fieldConfig.labelKey | translate}}
|
||||||
placeholder="{{'project.title' | translate}} *">
|
</label>
|
||||||
</nb-form-field>
|
<input formControlName="{{fieldConfig.fieldName}}"
|
||||||
|
type="text" required fullWidth
|
||||||
<nb-form-field class="project-form-field">
|
id="{{fieldConfig.fieldName}}" nbInput
|
||||||
<label for="projectClientInput" class="label">
|
class="input"
|
||||||
{{'project.client.label' | translate}}:
|
[status]="projectFormGroup.get(fieldConfig.fieldName).dirty ? (projectFormGroup.get(fieldConfig.fieldName).invalid ? 'danger' : 'basic') : 'basic'"
|
||||||
</label>
|
placeholder="{{fieldConfig.placeholder | translate}} *">
|
||||||
<input formControlName="projectClient" required
|
<!-- FIXME: when the bug (https://github.com/angular/components/issues/7739) is fixed -->
|
||||||
id="projectClientInput" nbInput
|
<ng-template ngFor let-error [ngForOf]="fieldConfig.errors"
|
||||||
class="input" type="text" fullWidth
|
*ngIf="projectFormGroup.get(fieldConfig.fieldName).dirty">
|
||||||
status="{{formCtrlStatus}}"
|
<span class="error-text"
|
||||||
placeholder="{{'project.client' | translate}} *">
|
*ngIf="projectFormGroup.get(fieldConfig.fieldName)?.hasError(error.errorCode) && error.errorCode === 'required'">
|
||||||
</nb-form-field>
|
{{error.translationKey | translate}}
|
||||||
|
</span>
|
||||||
<nb-form-field class="project-form-field">
|
</ng-template>
|
||||||
<label for="projectTesterInput" class="label">
|
</nb-form-field>
|
||||||
{{'project.tester.label' | translate}}:
|
</ng-container>
|
||||||
</label>
|
</ng-template>
|
||||||
<input formControlName="projectTester" required
|
|
||||||
id="projectTesterInput" nbInput
|
|
||||||
class="input" type="text" fullWidth
|
|
||||||
status="{{formCtrlStatus}}"
|
|
||||||
placeholder="{{'project.tester' | translate}} *">
|
|
||||||
</nb-form-field>
|
|
||||||
</form>
|
</form>
|
||||||
</nb-card-body>
|
</nb-card-body>
|
||||||
<nb-card-footer fxLayout="row" fxLayoutGap="1.5rem" fxLayoutAlign="end end">
|
<nb-card-footer fxLayout="row" fxLayoutGap="1.5rem" fxLayoutAlign="end end">
|
||||||
<button nbButton status="success" size="small" class="dialog-button" [disabled]="formIsEmptyOrInvalid()" (click)="onClickSave(projectFormGroup.value)">
|
<button nbButton status="success" size="small" class="dialog-button" [disabled]="!allowSave()"
|
||||||
{{ 'global.action.save' | translate}}
|
(click)="onClickSave(projectFormGroup.value)">
|
||||||
|
{{ dialogData?.options[0].buttonKey | translate}}
|
||||||
</button>
|
</button>
|
||||||
<button nbButton status="danger" size="small" class="dialog-button"(click)="onClickClose()">
|
<button nbButton status="danger" size="small" class="dialog-button" (click)="onClickClose()">
|
||||||
{{ 'global.action.cancel' | translate }}
|
{{ 'global.action.cancel' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</nb-card-footer>
|
</nb-card-footer>
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
@import "../../../assets/@theme/styles/_dialog.scss";
|
@import "../../../assets/@theme/styles/_dialog.scss";
|
||||||
|
@import '../../../assets/@theme/styles/themes';
|
||||||
|
|
||||||
.project-dialog {
|
.project-dialog {
|
||||||
width: 24rem;
|
width: 25.25rem;
|
||||||
height: 31rem;
|
height: 35rem;
|
||||||
|
|
||||||
.project-dialog-header {
|
.project-dialog-header {
|
||||||
height: 8vh;
|
height: 8vh;
|
||||||
|
@ -20,6 +21,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
width: 15rem;
|
width: 18rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
float: left;
|
||||||
|
color: nb-theme(color-danger-default);;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
import {ProjectDialogComponent} from './project-dialog.component';
|
import {ProjectDialogComponent} from './project-dialog.component';
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {NbButtonModule, NbCardModule, NbDialogRef, NbFormFieldModule, NbInputModule, NbLayoutModule} from '@nebular/theme';
|
import {
|
||||||
|
NB_DIALOG_CONFIG,
|
||||||
|
NbButtonModule,
|
||||||
|
NbCardModule,
|
||||||
|
NbDialogRef,
|
||||||
|
NbFormFieldModule,
|
||||||
|
NbInputModule,
|
||||||
|
NbLayoutModule
|
||||||
|
} from '@nebular/theme';
|
||||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
import {ThemeModule} from '@assets/@theme/theme.module';
|
import {ThemeModule} from '@assets/@theme/theme.module';
|
||||||
|
@ -14,13 +21,18 @@ import {NotificationService} from '@shared/services/notification.service';
|
||||||
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
||||||
import {ReactiveFormsModule} from '@angular/forms';
|
import {ReactiveFormsModule, Validators} from '@angular/forms';
|
||||||
|
import {Project} from '@shared/models/project.model';
|
||||||
|
import Mock = jest.Mock;
|
||||||
|
import deepEqual from 'deep-equal';
|
||||||
|
|
||||||
describe('ProjectDialogComponent', () => {
|
describe('ProjectDialogComponent', () => {
|
||||||
let component: ProjectDialogComponent;
|
let component: ProjectDialogComponent;
|
||||||
let fixture: ComponentFixture<ProjectDialogComponent>;
|
let fixture: ComponentFixture<ProjectDialogComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
const dialogSpy = createSpyObj('NbDialogRef', ['close']);
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
ProjectDialogComponent
|
ProjectDialogComponent
|
||||||
|
@ -49,12 +61,14 @@ describe('ProjectDialogComponent', () => {
|
||||||
providers: [
|
providers: [
|
||||||
{provide: NotificationService, useValue: new NotificationServiceMock()},
|
{provide: NotificationService, useValue: new NotificationServiceMock()},
|
||||||
{provide: DialogService, useClass: DialogServiceMock},
|
{provide: DialogService, useClass: DialogServiceMock},
|
||||||
{provide: NbDialogRef, useValue: {}}
|
{provide: NbDialogRef, useValue: dialogSpy},
|
||||||
|
{provide: NB_DIALOG_CONFIG, useValue: mockedDialogData}
|
||||||
]
|
]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedDialogData});
|
||||||
fixture = TestBed.createComponent(ProjectDialogComponent);
|
fixture = TestBed.createComponent(ProjectDialogComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
@ -64,3 +78,71 @@ describe('ProjectDialogComponent', () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const createSpyObj = (baseName, methodNames): { [key: string]: Mock<any> } => {
|
||||||
|
const obj: any = {};
|
||||||
|
for (const i of methodNames) {
|
||||||
|
obj[i] = jest.fn();
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockProject: Project = {
|
||||||
|
id: '11-22-33',
|
||||||
|
title: 'Test Project',
|
||||||
|
client: 'Testclient',
|
||||||
|
tester: 'Testpentester',
|
||||||
|
createdAt: new Date(),
|
||||||
|
createdBy: 'UID-11-12-13'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockedDialogData = {
|
||||||
|
form: {
|
||||||
|
projectTitle: {
|
||||||
|
fieldName: 'projectTitle',
|
||||||
|
type: 'text',
|
||||||
|
labelKey: 'project.title.label',
|
||||||
|
placeholder: 'project.title',
|
||||||
|
controlsConfig: [
|
||||||
|
{value: mockProject ? mockProject.title : '', disabled: false},
|
||||||
|
[Validators.required]
|
||||||
|
],
|
||||||
|
errors: [
|
||||||
|
{errorCode: 'required', translationKey: 'project.validationMessage.titleRequired'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
projectClient: {
|
||||||
|
fieldName: 'projectClient',
|
||||||
|
type: 'text',
|
||||||
|
labelKey: 'project.client.label',
|
||||||
|
placeholder: 'project.client',
|
||||||
|
controlsConfig: [
|
||||||
|
{value: mockProject ? mockProject.client : '', disabled: false},
|
||||||
|
[Validators.required]
|
||||||
|
],
|
||||||
|
errors: [
|
||||||
|
{errorCode: 'required', translationKey: 'project.validationMessage.clientRequired'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
projectTester: {
|
||||||
|
fieldName: 'projectTester',
|
||||||
|
type: 'text',
|
||||||
|
labelKey: 'project.tester.label',
|
||||||
|
placeholder: 'project.tester',
|
||||||
|
controlsConfig: [
|
||||||
|
{value: mockProject ? mockProject.tester : '', disabled: false},
|
||||||
|
[Validators.required]
|
||||||
|
],
|
||||||
|
errors: [
|
||||||
|
{errorCode: 'required', translationKey: 'project.validationMessage.testerRequired'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
headerLabelKey: 'project.edit.header',
|
||||||
|
buttonKey: 'global.action.update',
|
||||||
|
accentColor: 'warning'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
import {Component, Inject, OnDestroy, OnInit} from '@angular/core';
|
||||||
import {NbDialogRef} from '@nebular/theme';
|
import {NB_DIALOG_CONFIG, NbDialogRef} from '@nebular/theme';
|
||||||
import {AbstractControl, FormBuilder, FormGroup, Validators} from '@angular/forms';
|
import {FormBuilder, FormGroup} from '@angular/forms';
|
||||||
import {FieldStatus} from '@shared/models/form-field-status.model';
|
import {GenericFormFieldConfig, ProjectDialogData} from '@shared/models/project-dialog-data';
|
||||||
import {untilDestroyed} from 'ngx-take-until-destroy';
|
import deepEqual from 'deep-equal';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-project-dialog',
|
selector: 'app-project-dialog',
|
||||||
|
@ -12,40 +12,29 @@ import {untilDestroyed} from 'ngx-take-until-destroy';
|
||||||
export class ProjectDialogComponent implements OnInit, OnDestroy {
|
export class ProjectDialogComponent implements OnInit, OnDestroy {
|
||||||
// form control elements
|
// form control elements
|
||||||
projectFormGroup: FormGroup;
|
projectFormGroup: FormGroup;
|
||||||
projectTitleCtrl: AbstractControl;
|
formArray: GenericFormFieldConfig[];
|
||||||
projectClientCtrl: AbstractControl;
|
|
||||||
projectTesterCtrl: AbstractControl;
|
|
||||||
|
|
||||||
formCtrlStatus = FieldStatus.BASIC;
|
dialogData: ProjectDialogData;
|
||||||
|
|
||||||
invalidProjectTitle: string;
|
|
||||||
invalidProjectClient: string;
|
|
||||||
invalidProjectTester: string;
|
|
||||||
|
|
||||||
readonly MIN_LENGTH: number = 2;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(NB_DIALOG_CONFIG) private data: ProjectDialogData,
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
protected dialogRef: NbDialogRef<ProjectDialogComponent>
|
protected dialogRef: NbDialogRef<ProjectDialogComponent>
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.projectFormGroup = this.fb.group({
|
this.projectFormGroup = this.generateFormCreationFieldArray();
|
||||||
projectTitle: ['', [Validators.required, Validators.minLength(this.MIN_LENGTH)]],
|
this.dialogData = this.data;
|
||||||
projectClient: ['', [Validators.required, Validators.minLength(this.MIN_LENGTH)]],
|
}
|
||||||
projectTester: ['', [Validators.required, Validators.minLength(this.MIN_LENGTH)]]
|
|
||||||
});
|
|
||||||
|
|
||||||
this.projectTitleCtrl = this.projectFormGroup.get('projectTitle');
|
generateFormCreationFieldArray(): FormGroup {
|
||||||
this.projectClientCtrl = this.projectFormGroup.get('projectClient');
|
this.formArray = Object.values(this.data.form);
|
||||||
this.projectTesterCtrl = this.projectFormGroup.get('projectTester');
|
const config = this.formArray?.reduce((accumulator: {}, currentValue: GenericFormFieldConfig) => ({
|
||||||
|
...accumulator,
|
||||||
this.projectFormGroup.valueChanges
|
[currentValue?.fieldName]: currentValue?.controlsConfig
|
||||||
.pipe(untilDestroyed(this))
|
}), {});
|
||||||
.subscribe(() => {
|
return this.fb.group(config);
|
||||||
this.formCtrlStatus = FieldStatus.BASIC;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickSave(value): void {
|
onClickSave(value): void {
|
||||||
|
@ -60,23 +49,41 @@ export class ProjectDialogComponent implements OnInit, OnDestroy {
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
formIsEmptyOrInvalid(): boolean {
|
allowSave(): boolean {
|
||||||
return this.isEmpty(this.projectTitleCtrl.value)
|
return this.projectFormGroup.valid && this.projectDataChanged();
|
||||||
|| this.isEmpty(this.projectClientCtrl.value)
|
|
||||||
|| this.isEmpty(this.projectTesterCtrl.value)
|
|
||||||
|| this.projectTitleCtrl.invalid
|
|
||||||
|| this.projectClientCtrl.invalid
|
|
||||||
|| this.projectTesterCtrl.invalid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ctrlValue of type string
|
* @return true if project data is different from initial value
|
||||||
* @return if ctrlValue is empty or not
|
|
||||||
*/
|
*/
|
||||||
isEmpty(ctrlValue: string): boolean {
|
private projectDataChanged(): boolean {
|
||||||
return ctrlValue === '';
|
const oldProjectData = this.parseInitializedProjectDialogData(this.dialogData);
|
||||||
|
const newProjectData = this.projectFormGroup.getRawValue();
|
||||||
|
Object.entries(newProjectData).forEach(entry => {
|
||||||
|
const [key, value] = entry;
|
||||||
|
if (value === null) {
|
||||||
|
newProjectData[key] = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const didChange = !deepEqual(oldProjectData, newProjectData);
|
||||||
|
return didChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dialogData of type ProjectDialogData
|
||||||
|
* @return parsed projectData
|
||||||
|
*/
|
||||||
|
private parseInitializedProjectDialogData(dialogData: ProjectDialogData): any {
|
||||||
|
const projectData = {};
|
||||||
|
Object.entries(dialogData.form).forEach(entry => {
|
||||||
|
const [key, value] = entry;
|
||||||
|
projectData[key] = value.controlsConfig[0] ?
|
||||||
|
(value.controlsConfig[0].value ? value.controlsConfig[0].value : value.controlsConfig[0]) : '';
|
||||||
|
});
|
||||||
|
return projectData;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
// ToDo: Remove this after Angular upgrade and use @UnitDestroy() instead
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
import {TranslateModule} from '@ngx-translate/core';
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {ReactiveFormsModule} from '@angular/forms';
|
import {ReactiveFormsModule} from '@angular/forms';
|
||||||
|
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -25,6 +26,7 @@ import {ReactiveFormsModule} from '@angular/forms';
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DialogService,
|
DialogService,
|
||||||
|
ProjectDialogService,
|
||||||
NbDialogService
|
NbDialogService
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import {ComponentType} from '@angular/cdk/overlay';
|
||||||
|
import {NbDialogConfig} from '@nebular/theme';
|
||||||
|
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
||||||
|
import {Project} from '@shared/models/project.model';
|
||||||
|
import {Observable, of} from 'rxjs';
|
||||||
|
|
||||||
|
export class ProjectDialogServiceMock implements Required<ProjectDialogService> {
|
||||||
|
|
||||||
|
dialog: any;
|
||||||
|
|
||||||
|
openProjectDialog(
|
||||||
|
componentOrTemplateRef: ComponentType<any>,
|
||||||
|
project: Project | undefined,
|
||||||
|
config: Partial<NbDialogConfig<Partial<any> | string>> | undefined): Observable<any> {
|
||||||
|
return of(undefined);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ProjectDialogService } from './project-dialog.service';
|
||||||
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
|
import {NbDialogModule, NbDialogRef} from '@nebular/theme';
|
||||||
|
import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
|
||||||
|
|
||||||
|
describe('ProjectDialogService', () => {
|
||||||
|
let service: ProjectDialogService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
HttpClientTestingModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
NbDialogModule.forRoot()
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
|
||||||
|
{provide: NbDialogRef, useValue: {}},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
service = TestBed.inject(ProjectDialogService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,102 @@
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {NbDialogConfig, NbDialogService} from '@nebular/theme';
|
||||||
|
import {ComponentType} from '@angular/cdk/overlay';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
import {Project} from '@shared/models/project.model';
|
||||||
|
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
||||||
|
import {Validators} from '@angular/forms';
|
||||||
|
import {ProjectDialogData} from '@shared/models/project-dialog-data';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ProjectDialogService {
|
||||||
|
|
||||||
|
constructor(private readonly dialog: NbDialogService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly MIN_LENGTH: number = 4;
|
||||||
|
|
||||||
|
static addDataToDialogConfig(
|
||||||
|
dialogOptions?: Partial<NbDialogConfig<Partial<any> | string>>,
|
||||||
|
projectData?: ProjectDialogData
|
||||||
|
): Partial<NbDialogConfig<Partial<any> | string>> {
|
||||||
|
return {
|
||||||
|
context: {data: projectData},
|
||||||
|
closeOnEsc: dialogOptions?.closeOnEsc || false,
|
||||||
|
hasScroll: dialogOptions?.hasScroll || false,
|
||||||
|
autoFocus: dialogOptions?.autoFocus || false,
|
||||||
|
closeOnBackdropClick: dialogOptions?.closeOnBackdropClick || false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public openProjectDialog(componentOrTemplateRef: ComponentType<any>,
|
||||||
|
project?: Project,
|
||||||
|
config?: Partial<NbDialogConfig<Partial<any> | string>>): Observable<any> {
|
||||||
|
let dialogOptions: Partial<NbDialogConfig<Partial<any> | string>>;
|
||||||
|
let dialogData: ProjectDialogData;
|
||||||
|
// Setup ProjectDialogData
|
||||||
|
dialogData = {
|
||||||
|
form: {
|
||||||
|
projectTitle: {
|
||||||
|
fieldName: 'projectTitle',
|
||||||
|
type: 'text',
|
||||||
|
labelKey: 'project.title.label',
|
||||||
|
placeholder: 'project.title',
|
||||||
|
controlsConfig: [
|
||||||
|
{value: project ? project.title : '', disabled: false},
|
||||||
|
[Validators.required]
|
||||||
|
],
|
||||||
|
errors: [
|
||||||
|
{errorCode: 'required', translationKey: 'project.validationMessage.titleRequired'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
projectClient: {
|
||||||
|
fieldName: 'projectClient',
|
||||||
|
type: 'text',
|
||||||
|
labelKey: 'project.client.label',
|
||||||
|
placeholder: 'project.client',
|
||||||
|
controlsConfig: [
|
||||||
|
{value: project ? project.client : '', disabled: false},
|
||||||
|
[Validators.required]
|
||||||
|
],
|
||||||
|
errors: [
|
||||||
|
{errorCode: 'required', translationKey: 'project.validationMessage.clientRequired'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
projectTester: {
|
||||||
|
fieldName: 'projectTester',
|
||||||
|
type: 'text',
|
||||||
|
labelKey: 'project.tester.label',
|
||||||
|
placeholder: 'project.tester',
|
||||||
|
controlsConfig: [
|
||||||
|
{value: project ? project.tester : '', disabled: false},
|
||||||
|
[Validators.required]
|
||||||
|
],
|
||||||
|
errors: [
|
||||||
|
{errorCode: 'required', translationKey: 'project.validationMessage.testerRequired'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: []
|
||||||
|
};
|
||||||
|
if (project) {
|
||||||
|
dialogData.options = [
|
||||||
|
{
|
||||||
|
headerLabelKey: 'project.edit.header',
|
||||||
|
buttonKey: 'global.action.update',
|
||||||
|
accentColor: 'warning'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
dialogData.options = [
|
||||||
|
{
|
||||||
|
headerLabelKey: 'project.create.header',
|
||||||
|
buttonKey: 'global.action.save',
|
||||||
|
accentColor: 'primary'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// Merge dialog config with project data
|
||||||
|
dialogOptions = ProjectDialogService.addDataToDialogConfig(config, dialogData);
|
||||||
|
return this.dialog.open(ProjectDialogComponent, dialogOptions).onClose;
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ export class DialogService {
|
||||||
closeOnEsc: config?.closeOnEsc || false,
|
closeOnEsc: config?.closeOnEsc || false,
|
||||||
hasScroll: config?.hasScroll || false,
|
hasScroll: config?.hasScroll || false,
|
||||||
autoFocus: config?.autoFocus || false,
|
autoFocus: config?.autoFocus || false,
|
||||||
closeOnBackdropClick: config?.closeOnBackdropClick || false,
|
closeOnBackdropClick: config?.closeOnBackdropClick || false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export class DialogService {
|
||||||
*/
|
*/
|
||||||
openConfirmDialog(message: DialogMessage): NbDialogRef<ConfirmDialogComponent> {
|
openConfirmDialog(message: DialogMessage): NbDialogRef<ConfirmDialogComponent> {
|
||||||
return this.dialog.open(ConfirmDialogComponent, {
|
return this.dialog.open(ConfirmDialogComponent, {
|
||||||
closeOnEsc: false,
|
closeOnEsc: true,
|
||||||
hasScroll: false,
|
hasScroll: false,
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
closeOnBackdropClick: false,
|
closeOnBackdropClick: false,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {ProjectService} from '@shared/services/project.service';
|
import {ProjectService} from '@shared/services/project.service';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {Observable, of} from 'rxjs';
|
import {Observable, of} from 'rxjs';
|
||||||
import {Project, SaveProjectDialogBody} from '@shared/models/project.model';
|
import {Project, ProjectDialogBody} from '@shared/models/project.model';
|
||||||
|
|
||||||
|
|
||||||
export class ProjectServiceMock implements Required<ProjectService> {
|
export class ProjectServiceMock implements Required<ProjectService> {
|
||||||
|
@ -12,7 +12,11 @@ export class ProjectServiceMock implements Required<ProjectService> {
|
||||||
return of([]);
|
return of([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveProject(saveProject: SaveProjectDialogBody): Observable<Project> {
|
saveProject(saveProject: ProjectDialogBody): Observable<Project> {
|
||||||
|
return of();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProject(projectId: string, project: ProjectDialogBody): Observable<Project> {
|
||||||
return of();
|
return of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {ProjectService} from './project.service';
|
||||||
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
||||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
import {KeycloakService} from 'keycloak-angular';
|
import {KeycloakService} from 'keycloak-angular';
|
||||||
import {Project, SaveProjectDialogBody} from '@shared/models/project.model';
|
import {Project, ProjectDialogBody} from '@shared/models/project.model';
|
||||||
import {environment} from '../../environments/environment';
|
import {environment} from '../../environments/environment';
|
||||||
|
|
||||||
describe('ProjectService', () => {
|
describe('ProjectService', () => {
|
||||||
|
@ -76,7 +76,7 @@ describe('ProjectService', () => {
|
||||||
|
|
||||||
describe('saveProject', () => {
|
describe('saveProject', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const mockSaveProjectDialogBody: SaveProjectDialogBody = {
|
const mockSaveProjectDialogBody: ProjectDialogBody = {
|
||||||
client: 'E Corp',
|
client: 'E Corp',
|
||||||
title: 'Some Mock API (v1.0) Scanning',
|
title: 'Some Mock API (v1.0) Scanning',
|
||||||
tester: 'Novatester',
|
tester: 'Novatester',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {environment} from '../../environments/environment';
|
import {environment} from '../../environments/environment';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {Project, SaveProjectDialogBody} from '../models/project.model';
|
import {Project, ProjectDialogBody} from '../models/project.model';
|
||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
|
@ -25,10 +25,20 @@ export class ProjectService {
|
||||||
* Save Project
|
* Save Project
|
||||||
* @param project the information of the project
|
* @param project the information of the project
|
||||||
*/
|
*/
|
||||||
public saveProject(project: SaveProjectDialogBody): Observable<Project> {
|
public saveProject(project: ProjectDialogBody): Observable<Project> {
|
||||||
return this.http.post<Project>(`${this.apiBaseURL}`, project);
|
return this.http.post<Project>(`${this.apiBaseURL}`, project);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update Project
|
||||||
|
* @param projectId the id of the project
|
||||||
|
* @param project the information of the project
|
||||||
|
*/
|
||||||
|
public updateProject(projectId: string, project: ProjectDialogBody): Observable<Project> {
|
||||||
|
console.log('update Project');
|
||||||
|
return this.http.patch<Project>(`${this.apiBaseURL}/${projectId}`, project);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete Project
|
* Delete Project
|
||||||
* @param projectId the id of the project
|
* @param projectId the id of the project
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"downlevelIteration": true,
|
"downlevelIteration": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"jest"
|
"jest"
|
||||||
],
|
],
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,7 @@ services:
|
||||||
- ../cfg/keycloak.env
|
- ../cfg/keycloak.env
|
||||||
c4po-keycloak-postgress:
|
c4po-keycloak-postgress:
|
||||||
container_name: c4po-keycloak-postgres
|
container_name: c4po-keycloak-postgres
|
||||||
image: postgres:latest
|
image: postgres:10.16-alpine
|
||||||
env_file:
|
env_file:
|
||||||
- ../cfg/keycloakdb.env
|
- ../cfg/keycloakdb.env
|
||||||
ports:
|
ports:
|
||||||
|
|
Loading…
Reference in New Issue