diff --git a/src/app/models/project.ts b/src/app/models/project.ts index 780607f..6eae557 100644 --- a/src/app/models/project.ts +++ b/src/app/models/project.ts @@ -1,11 +1,15 @@ +import { User } from './user'; + export class Project { id: Number; title: string; color: string; + members: User[]; - public constructor(id=null, title=null, color=null) { + public constructor(id=null, title=null, color=null, members: User[]=null) { this.id = id; this.title = title; this.color = color; + this.members = members; } } diff --git a/src/app/pages/activities/modal-activities-filter/modal-activities-filter.component.ts b/src/app/pages/activities/modal-activities-filter/modal-activities-filter.component.ts index 5557e5a..586ab48 100644 --- a/src/app/pages/activities/modal-activities-filter/modal-activities-filter.component.ts +++ b/src/app/pages/activities/modal-activities-filter/modal-activities-filter.component.ts @@ -23,7 +23,7 @@ export class ModalActivitiesFilterComponent implements OnInit { ) { } ngOnInit() { - const projectListNextObs = this.projectService.getProjects() + const projectListNextObs = this.projectService.getUserProjects() .subscribe(projects => { this.projectService.announceProjectList(projects); }); diff --git a/src/app/pages/activities/modal-add-activity/modal-add-activity.component.ts b/src/app/pages/activities/modal-add-activity/modal-add-activity.component.ts index d27a125..362ed4e 100644 --- a/src/app/pages/activities/modal-add-activity/modal-add-activity.component.ts +++ b/src/app/pages/activities/modal-add-activity/modal-add-activity.component.ts @@ -162,7 +162,7 @@ export class ModalAddActivityComponent implements OnInit { } getProjectSelectData() { - this.projectService.getProjects() + this.projectService.getUserProjects() .subscribe( result => { this.projects = result; diff --git a/src/app/pages/projects/members-chips/members-chips.component.html b/src/app/pages/projects/members-chips/members-chips.component.html deleted file mode 100644 index 9c1bbc5..0000000 --- a/src/app/pages/projects/members-chips/members-chips.component.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - -
- - - - ({{ index }}) {{ item.id }}: {{ item.value }} - - - -
diff --git a/src/app/pages/projects/members-chips/members-chips.component.scss b/src/app/pages/projects/members-chips/members-chips.component.scss deleted file mode 100644 index bf942f5..0000000 --- a/src/app/pages/projects/members-chips/members-chips.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -//::ng-deep .div { -// z-index: 999999; -//} diff --git a/src/app/pages/projects/members-chips/members-chips.component.ts b/src/app/pages/projects/members-chips/members-chips.component.ts deleted file mode 100644 index 1a1fa02..0000000 --- a/src/app/pages/projects/members-chips/members-chips.component.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Input, Component, NgZone, OnInit, ChangeDetectorRef, NgModule } from '@angular/core'; - -import { ViewCell } from 'ng2-smart-table'; - -import { TagInputModule } from 'ngx-chips'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { FormBuilder, FormGroup, Validators, FormControl, AbstractControl } from '@angular/forms'; - -@Component({ - selector: 'projects-members-chips', - templateUrl: './members-chips.component.html', - styleUrls: ['./members-chips.component.scss'] -}) -export class MembersChipsComponent implements ViewCell, OnInit { - - renderValue: string; - form: FormGroup; - @Input() value: string | number; - @Input() rowData: any; - - constructor(private zone:NgZone, private ref: ChangeDetectorRef) { - } - - autocompleteItemsAsObjects = [ - {value: 'Item1', id: 0, extra: 0}, - {value: 'item2', id: 1, extra: 1}, - 'item3' - ]; - - ngOnInit() { - this.renderValue = this.value.toString().toUpperCase(); - - // creating form - this.form = new FormGroup({ - tags: new FormControl('', []) // add validators here - }); - - setTimeout(() => { - this.ref.markForCheck(); - }); - } - -} diff --git a/src/app/pages/projects/modal-add-project/form-add-project/form-add-project.component.html b/src/app/pages/projects/modal-add-project/form-add-project/form-add-project.component.html index 28232f6..b9a5af9 100644 --- a/src/app/pages/projects/modal-add-project/form-add-project/form-add-project.component.html +++ b/src/app/pages/projects/modal-add-project/form-add-project/form-add-project.component.html @@ -1,31 +1,38 @@ - - - 1. General - -
- +
+ + + + + Project title is required + + - - - - - - 2. Invite Members - -

There you could add members to your project

- -
-
- + + + + + {{ + user.firstName + " " + user.lastName + " (" + user.email + ")" + }} + + + + + + + +
diff --git a/src/app/pages/projects/modal-add-project/form-add-project/form-add-project.component.scss b/src/app/pages/projects/modal-add-project/form-add-project/form-add-project.component.scss index e69de29..07d20b1 100644 --- a/src/app/pages/projects/modal-add-project/form-add-project/form-add-project.component.scss +++ b/src/app/pages/projects/modal-add-project/form-add-project/form-add-project.component.scss @@ -0,0 +1,21 @@ +::ng-deep .cdk-overlay-container { + z-index: 1051; +} + +::ng-deep .modal-dialog { + max-width: 580px; +} + +.create-project-form-container { + display: flex; + justify-content: center; +} + +.create-project-form-container > form { + width: 90%; +} + +mat-form-field { + display: block; + padding-bottom: 15px; +} diff --git a/src/app/pages/projects/modal-add-project/form-add-project/form-add-project.component.ts b/src/app/pages/projects/modal-add-project/form-add-project/form-add-project.component.ts index 28d83d5..0b9e6f0 100644 --- a/src/app/pages/projects/modal-add-project/form-add-project/form-add-project.component.ts +++ b/src/app/pages/projects/modal-add-project/form-add-project/form-add-project.component.ts @@ -1,31 +1,129 @@ -import { Component, OnInit } from '@angular/core'; -import { FormGroup, FormBuilder, FormControl } from '@angular/forms'; -import { Project } from '../../../../models/project'; -import { ProjectService } from '../../../../services/project.service'; +import { Component, OnInit, Input } from "@angular/core"; +import { FormGroup, FormBuilder, Validators } from "@angular/forms"; +import { Project } from "../../../../models/project"; +import { ProjectService } from "../../../../services/project.service"; +import { User } from "../../../../models/user"; +import { Subscription, Observable } from "rxjs"; +import { UserService } from "../../../../services/user.service"; +import { map } from 'rxjs/operators'; @Component({ - selector: 'form-add-project', - templateUrl: './form-add-project.component.html', - styleUrls: ['./form-add-project.component.scss'] + selector: "form-add-project", + templateUrl: "./form-add-project.component.html", + styleUrls: ["./form-add-project.component.scss"] }) export class FormAddProjectComponent implements OnInit { - createProjectForm: FormGroup; + @Input() editData: any + projectForm: FormGroup; - constructor(private formBuilder: FormBuilder, private projectService: ProjectService) { - } + protected users: User[] = []; + protected filteredUsers: User[] = []; + protected selectedMembers: User[] = []; + + private sub: Subscription = new Subscription(); + + constructor( + private formBuilder: FormBuilder, + private projectService: ProjectService, + private userService: UserService, + ) {} ngOnInit() { - this.createProjectForm = this.formBuilder.group({ - title: '', - color: '' - }) + this.createProjectForm(); + this.addSubscriptions(); } - submitCreateProjectForm() { - const projectData: Project = this.createProjectForm.value as Project; + ngOnDestroy() { + if (this.sub) { + this.sub.unsubscribe(); + } + } - this.projectService.createProject(projectData).subscribe(newProject => { - this.projectService.announceNewProject(newProject) + submitProjectForm() { + if (this.projectForm.valid) { + let projectData: Project = this.projectForm.value as Project; + projectData.members = this.selectedMembers; + + if (this.editData) { + projectData.id = this.editData.id; + + this.projectService.updateProject(projectData).subscribe(updatedProject => { + this.projectService.announceNewProject(updatedProject); + }); + } + else { + this.projectService.createProject(projectData).subscribe(newProject => { + this.projectService.announceNewProject(newProject); + }); + } + } + else { + this.projectForm.markAllAsTouched(); + } + } + + protected displayInputWith() { + return ""; + } + + protected addMember({ option: { value } }) { + this.selectedMembers.push(value); + this.users.splice(this.users.indexOf(value), 1); + } + + protected removeMember(member) { + this.selectedMembers.splice(this.selectedMembers.indexOf(member), 1); + this.users.push(member); + } + + private createProjectForm(): void { + this.projectForm = this.formBuilder.group({ + title: ["", [Validators.required]], + color: [""], + members: [[]], + }); + + this.projectForm.controls.members.valueChanges.subscribe( + filterValue => { + if (typeof filterValue === "string") { + filterValue = filterValue.toLowerCase(); + + this.filteredUsers = this.users.filter(user => + user.firstName.toLowerCase().includes(filterValue) + ); + } + } + ); + + if (this.editData) { + this.patchValues(); + } + } + + private patchValues(): void { + this.projectForm.patchValue({ + title: this.editData.title, }); + + this.selectedMembers = this.editData.members; + } + + private addSubscriptions(): void{ + const getUsersSub = this.userService.getAllUsers() + .pipe( + map(users => { + if (this.editData) { + users = users.filter(user => !this.editData.members.some(member => user.id === member.id)) + } + + return users + }) + ) + .subscribe(users => { + this.users = users; + this.filteredUsers = users; + }) + + this.sub.add(getUsersSub); } } diff --git a/src/app/pages/projects/modal-add-project/form-add-project/project-members/project-members-view-cell.component.ts b/src/app/pages/projects/modal-add-project/form-add-project/project-members/project-members-view-cell.component.ts new file mode 100644 index 0000000..138eece --- /dev/null +++ b/src/app/pages/projects/modal-add-project/form-add-project/project-members/project-members-view-cell.component.ts @@ -0,0 +1,25 @@ +import { Component} from "@angular/core"; +import { ViewCell } from 'ng2-smart-table'; +import { MatDialog } from '@angular/material/dialog'; +import { ProjectMembersComponent } from './project-members.component'; +import { User } from '../../../../../models/user'; + +@Component({ + selector: "project-members-dialog", + template: `See members...`, + styles: ['span { cursor: pointer; color: blue; text-decoration: underline; }'] +}) +export class ProjectMembersViewCell implements ViewCell { + value: any; + rowData: any; + + constructor(private membersDialog: MatDialog) { } + + openDialog() { + const dialogInstance = this.membersDialog.open(ProjectMembersComponent); + const componentInstance = dialogInstance.componentInstance; + + componentInstance.viewMode = true; + componentInstance.selectedMembers = this.value as User[]; + } +} diff --git a/src/app/pages/projects/modal-add-project/form-add-project/project-members/project-members.component.html b/src/app/pages/projects/modal-add-project/form-add-project/project-members/project-members.component.html new file mode 100644 index 0000000..44dc874 --- /dev/null +++ b/src/app/pages/projects/modal-add-project/form-add-project/project-members/project-members.component.html @@ -0,0 +1,13 @@ + + No members selected + + + + + + + + diff --git a/src/app/pages/projects/modal-add-project/form-add-project/project-members/project-members.component.scss b/src/app/pages/projects/modal-add-project/form-add-project/project-members/project-members.component.scss new file mode 100644 index 0000000..0183017 --- /dev/null +++ b/src/app/pages/projects/modal-add-project/form-add-project/project-members/project-members.component.scss @@ -0,0 +1,18 @@ +.no-members-message { + margin: auto; +} + +nb-list-item { + display: flex; + justify-content: space-between; +} + +nb-icon { + font-size: 1.65rem; + margin: auto 0 auto; + cursor: pointer; +} + +nb-user { + display: inline-flex; +} diff --git a/src/app/pages/projects/modal-add-project/form-add-project/project-members/project-members.component.ts b/src/app/pages/projects/modal-add-project/form-add-project/project-members/project-members.component.ts new file mode 100644 index 0000000..3f2e965 --- /dev/null +++ b/src/app/pages/projects/modal-add-project/form-add-project/project-members/project-members.component.ts @@ -0,0 +1,22 @@ +import { Component, OnInit, Input, Output, EventEmitter } from "@angular/core"; +import { User } from '../../../../../models/user'; + +@Component({ + selector: "project-members", + templateUrl: "./project-members.component.html", + styleUrls: ["./project-members.component.scss"] +}) +export class ProjectMembersComponent implements OnInit { + @Input() selectedMembers: User[]; + @Input() viewMode: boolean = false; + + @Output() removeMemberEvent: EventEmitter = new EventEmitter(); + + constructor() {} + + ngOnInit() {} + + removeMember(member) { + this.removeMemberEvent.emit(member); + } +} diff --git a/src/app/pages/projects/modal-add-project/modal-add-project.component.html b/src/app/pages/projects/modal-add-project/modal-add-project.component.html index ae4f24a..57ca6b2 100644 --- a/src/app/pages/projects/modal-add-project/modal-add-project.component.html +++ b/src/app/pages/projects/modal-add-project/modal-add-project.component.html @@ -1,6 +1,6 @@ diff --git a/src/app/pages/projects/modal-add-project/modal-add-project.component.ts b/src/app/pages/projects/modal-add-project/modal-add-project.component.ts index 2eebf49..04e6b78 100644 --- a/src/app/pages/projects/modal-add-project/modal-add-project.component.ts +++ b/src/app/pages/projects/modal-add-project/modal-add-project.component.ts @@ -16,6 +16,8 @@ export class ModalAddProjectComponent { public dialogRef: NgbModalRef; + public editData: Project; + @ViewChild("modalAddProject", { static: false }) modalAddProject: ElementRef; @@ -42,8 +44,10 @@ export class ModalAddProjectComponent { } } - open() { + open(editData = null) { this.dialogRef = this.modalService.open(this.modalAddProject); + + this.editData = editData; } closeModal(data = null) { diff --git a/src/app/pages/projects/projects.component.html b/src/app/pages/projects/projects.component.html index e04ce6c..92e616b 100644 --- a/src/app/pages/projects/projects.component.html +++ b/src/app/pages/projects/projects.component.html @@ -2,7 +2,7 @@
@@ -12,9 +12,8 @@
+ (delete)="onDelete($event)" + (edit)="onEdit($event)">
diff --git a/src/app/pages/projects/projects.component.ts b/src/app/pages/projects/projects.component.ts index c776639..ac95040 100644 --- a/src/app/pages/projects/projects.component.ts +++ b/src/app/pages/projects/projects.component.ts @@ -5,10 +5,8 @@ import { MatSnackBar } from "@angular/material/snack-bar"; import { Project } from "../../models/project"; import { ProjectService } from "../../services/project.service"; import { LocalDataSource } from "ng2-smart-table"; -import { MembersChipsComponent } from "./members-chips/members-chips.component"; -import { ColorRenderComponent } from "./color-render/color-render.component"; -import { ColorEditorRenderComponent } from "./color-editor-render/color-editor-render.component"; import { ConfigurationConstants } from '../../constants/configuration-constants'; +import { ProjectMembersViewCell } from './modal-add-project/form-add-project/project-members/project-members-view-cell.component'; @Component({ selector: 'ngx-app-projects', @@ -26,7 +24,7 @@ export class ProjectsComponent implements OnInit { constructor( private projectService: ProjectService, - private snackBar: MatSnackBar + private snackBar: MatSnackBar, ) { } @@ -53,8 +51,17 @@ export class ProjectsComponent implements OnInit { } }); - const newProjectSub = this.projectService.newProject$.subscribe(project => { - this.dataSource.append(project); + const newProjectSub = this.projectService.newProject$.subscribe(newProject => { + if (this.modalAddProject) { + const projectToUpdate = this.modalAddProject.editData; + + if (projectToUpdate) { + this.updateProject(projectToUpdate, newProject); + } + else { + this.addProject(newProject); + } + } }) this.sub.add(getProjectsObsSub); @@ -62,20 +69,12 @@ export class ProjectsComponent implements OnInit { this.sub.add(newProjectSub); } - open() { - this.modalAddProject.open(); - this.modalAddProject.dialogRef.result.then(result => { - if (result) { - this.snackBar.open(`Project "${result.title}" created.`, '', ConfigurationConstants.DEFAULT_MATSNACKBACK_CONFIGURATION); - } - }) - } - settings = { + mode: "external", actions: { edit: true, delete: true, - add: true, + add: false, position: 'right', }, @@ -83,12 +82,6 @@ export class ProjectsComponent implements OnInit { display: true, }, - add: { - addButtonContent: '', - createButtonContent: '', - cancelButtonContent: '', - confirmCreate: true - }, edit: { editButtonContent: '', saveButtonContent: '', @@ -108,46 +101,21 @@ export class ProjectsComponent implements OnInit { members: { title: 'Members', type: 'custom', - renderComponent: MembersChipsComponent, - - }, - color: { - title: 'Color', - type: 'custom', - renderComponent: ColorRenderComponent, - - editor: { - type: 'custom', - component: ColorEditorRenderComponent, - }, + renderComponent: ProjectMembersViewCell }, }, + hideSubHeader: true, }; - onEditConfirm(event): void { - if (event.data !== event.newData && window.confirm('Are you sure you want to save?')) { - const project = new Project(event.data.id, event.newData.title, event.newData.color.value) - - this.projectService.updateProject(project) - .subscribe(res => { - if (res) { - event.confirm.resolve(event.newData); - this.snackBar.open(`Project with id"${event.data.id}" was successfully updated.`, - 'OK', ConfigurationConstants.DEFAULT_MATSNACKBACK_CONFIGURATION); - } - else { - event.resolve(event.Data); - this.snackBar.open(`Project with id"${event.data.id}" was not updated.`, - 'OK', ConfigurationConstants.DEFAULT_MATSNACKBACK_CONFIGURATION); - } - }); - } - else { - event.confirm.reject(); - } + onAdd(): void { + this.modalAddProject.open(); + } + + onEdit($event): void { + this.modalAddProject.open($event.data); } - onDeleteConfirm(event): void { + onDelete(event): void { if (window.confirm('Are you sure you want to delete?')) { this.projectService.deleteProject(event.data.id).subscribe(res => { if (res) { @@ -162,31 +130,16 @@ export class ProjectsComponent implements OnInit { } }); - event.confirm.resolve(); - } else { - event.confirm.reject(); } } - // the function below is created for ngx + button - onCreateConfirm(event) { - if (window.confirm('Are you sure you want to create a new project?')) { - const data = event.newData; - const project = new Project(null, data.title, data.color['currentValue']['hex']); - this.projectService.createProject(project) - .subscribe(r => { - if (r) { - event.confirm.resolve(event.newData); - this.snackBar.open(`Project "${event.data.title}" was successfully created.`, - 'OK', ConfigurationConstants.DEFAULT_MATSNACKBACK_CONFIGURATION); - } else { - event.resolve(event.Data); - this.snackBar.open(`ERROR! Project "${event.data.title}" was not created.`, - 'OK', ConfigurationConstants.DEFAULT_MATSNACKBACK_CONFIGURATION); - } - }); - } else { - event.confirm.reject(); - } + private addProject(project: Project): void { + this.dataSource.append(project); + this.snackBar.open(`Project "${project.title}" created.`, '', ConfigurationConstants.DEFAULT_MATSNACKBACK_CONFIGURATION); + } + + private updateProject(projectToUpdate: Project, project: Project): void { + this.dataSource.update(projectToUpdate, project); + this.snackBar.open(`Project with id: ${project.id} updated.`, '', ConfigurationConstants.DEFAULT_MATSNACKBACK_CONFIGURATION); } } diff --git a/src/app/pages/projects/projects.module.ts b/src/app/pages/projects/projects.module.ts index d69384f..5597caf 100644 --- a/src/app/pages/projects/projects.module.ts +++ b/src/app/pages/projects/projects.module.ts @@ -1,7 +1,6 @@ import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {ColorPickerModule} from "@syncfusion/ej2-angular-inputs"; -import {MembersChipsComponent} from "./members-chips/members-chips.component"; import {ColorRenderComponent} from "./color-render/color-render.component"; import {ColorEditorRenderComponent} from "./color-editor-render/color-editor-render.component"; import {ColorPickerComponent} from "./color-picker/color-picker.component"; @@ -15,7 +14,13 @@ import {FormAddProjectComponent} from "./modal-add-project/form-add-project/form import {TagInputModule} from "ngx-chips"; import {FormsModule, ReactiveFormsModule} from "@angular/forms"; import {Ng2SmartTableModule} from "ng2-smart-table"; -import {NbCardModule} from "@nebular/theme"; +import {NbCardModule, NbListModule, NbUserModule, NbIconModule} from "@nebular/theme"; +import {MatAutocompleteModule} from '@angular/material/autocomplete' +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { ProjectMembersComponent } from './modal-add-project/form-add-project/project-members/project-members.component'; +import { ProjectMembersViewCell } from './modal-add-project/form-add-project/project-members/project-members-view-cell.component'; +import { MatDialogModule } from '@angular/material/dialog'; @NgModule({ imports: [ @@ -26,16 +31,24 @@ import {NbCardModule} from "@nebular/theme"; Ng2SmartTableModule, NbCardModule, NgbTabsetModule, - ReactiveFormsModule + ReactiveFormsModule, + MatAutocompleteModule, + MatFormFieldModule, + MatInputModule, + MatDialogModule, + NbListModule, + NbUserModule, + NbIconModule, ], declarations: [ ColorPickerComponent, ColorRenderComponent, ColorEditorRenderComponent, - MembersChipsComponent, ProjectsComponent, ModalAddProjectComponent, FormAddProjectComponent, + ProjectMembersComponent, + ProjectMembersViewCell ], providers: [ ProjectService, @@ -49,9 +62,10 @@ import {NbCardModule} from "@nebular/theme"; ColorPickerComponent ], entryComponents: [ - MembersChipsComponent, ColorRenderComponent, ColorEditorRenderComponent, + ProjectMembersViewCell, + ProjectMembersComponent, ] }) export class ProjectsModule { diff --git a/src/app/services/project.service.ts b/src/app/services/project.service.ts index 11baee4..276f9bd 100644 --- a/src/app/services/project.service.ts +++ b/src/app/services/project.service.ts @@ -32,7 +32,19 @@ export class ProjectService { } getProjects() { - return this.commonHttp.get('project/getList/') + return this.commonHttp.get('project/getProjects') + .pipe( + catchError((err) => { + this.snackBar.open("An error occured while trying to load your projects", + 'OK', ConfigurationConstants.DEFAULT_MATSNACKBACK_CONFIGURATION); + + return of(null); + }) + ) + } + + getUserProjects() { + return this.commonHttp.get('project/getUserProjects') .pipe( catchError((err) => { this.snackBar.open("An error occured while trying to load your projects", diff --git a/src/app/services/user.service.ts b/src/app/services/user.service.ts new file mode 100644 index 0000000..958cb19 --- /dev/null +++ b/src/app/services/user.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; +import { User } from '../models/user'; +import { CommonHttpService } from './common-http.service'; +import { Observable } from 'rxjs'; + +@Injectable({ providedIn: "root" }) +export class UserService { + + constructor(private commonHttp: CommonHttpService) { } + + public getAllUsers(): Observable { + return this.commonHttp.get("users/getAll") + } +}