diff --git a/src/app/graph/components/data-analysis/data-analysis.component.html b/src/app/graph/components/data-analysis/data-analysis.component.html
index a401e82..04b207e 100644
--- a/src/app/graph/components/data-analysis/data-analysis.component.html
+++ b/src/app/graph/components/data-analysis/data-analysis.component.html
@@ -40,25 +40,25 @@
@for (account of accounts; track $index) {
-
- {{ account.entityName }}
-
-
-
- info
-
-
+
+ {{ account.entityName }}
+
+
+
+ info
+
+
}
-
+
diff --git a/src/app/graph/components/data-analysis/data-analysis.component.ts b/src/app/graph/components/data-analysis/data-analysis.component.ts
index b9ec158..2fdcdd0 100644
--- a/src/app/graph/components/data-analysis/data-analysis.component.ts
+++ b/src/app/graph/components/data-analysis/data-analysis.component.ts
@@ -69,7 +69,7 @@ export class DataAnalysisComponent implements AfterViewInit {
private loadGraphService: LoadGraphService,
private dialog: MatDialog,
private changeDetector: ChangeDetectorRef,
- private loadingService: LoadingService,
+ private loadingService: LoadingService
) {}
handlePageEvent(e: PageEvent) {
@@ -109,7 +109,7 @@ export class DataAnalysisComponent implements AfterViewInit {
this.networkInstance = new Network(
this.el.nativeElement,
this.data,
- getOptions(),
+ getOptions()
);
// Listen for the context menu event (right-click)
@@ -127,7 +127,7 @@ export class DataAnalysisComponent implements AfterViewInit {
this.changeDetector.detectChanges();
const rightClickNodeInfoElem = document.getElementById(
- 'right-click-node-info',
+ 'right-click-node-info'
) as HTMLElement;
rightClickNodeInfoElem.dataset['nodeid'] = nodeId.toString();
@@ -208,17 +208,15 @@ export class DataAnalysisComponent implements AfterViewInit {
console.log('edge click: ', edgeId);
}
- getInfo(
- account: { id: number; entityName: string } = { id: 0, entityName: 'test' },
- ) {
+ getInfo(account?: number) {
// todo: fix this
- // if (!account) {
- // account = (
- // document.getElementById('right-click-node-info') as HTMLElement
- // ).dataset['nodeid'];
- // }
+ if (!account) {
+ account = (
+ document.getElementById('right-click-node-info') as HTMLElement
+ ).dataset['nodeid'] as unknown as number;
+ }
- this.loadGraphService.getNodeInfo(account.id).subscribe({
+ this.loadGraphService.getNodeInfo(account).subscribe({
next: (data) => {
this.dialog.open(InfoDialogComponent, {
width: '105rem',
@@ -293,7 +291,7 @@ export class DataAnalysisComponent implements AfterViewInit {
const dialogRef = this.dialog.open(
ColorPickerDialogComponent,
- dialogConfig,
+ dialogConfig
);
dialogRef.afterClosed().subscribe((result) => {
diff --git a/src/app/user/components/dashboard/manage-account/manage-account.component.html b/src/app/user/components/dashboard/manage-account/manage-account.component.html
index 7dfb28b..fc8ca43 100644
--- a/src/app/user/components/dashboard/manage-account/manage-account.component.html
+++ b/src/app/user/components/dashboard/manage-account/manage-account.component.html
@@ -1,74 +1,76 @@
-
diff --git a/src/app/user/components/dashboard/manage-account/manage-account.component.scss b/src/app/user/components/dashboard/manage-account/manage-account.component.scss
index 2963768..aca6962 100644
--- a/src/app/user/components/dashboard/manage-account/manage-account.component.scss
+++ b/src/app/user/components/dashboard/manage-account/manage-account.component.scss
@@ -1,28 +1,34 @@
-.add-user-form {
+.container {
display: flex;
flex-direction: column;
gap: 1rem;
-}
-.container {
- width: 100%;
- display: flex;
- gap: 1rem;
+ .add-user-form {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ .info-container {
+ width: 100%;
+ display: flex;
+ gap: 1rem;
- .form {
- width: 50%;
+ .form {
+ width: 50%;
- .btn_container {
- display: flex;
- gap: 1rem;
+ .btn_container {
+ display: flex;
+ gap: 1rem;
- .btn {
- width: 25%;
+ .btn {
+ width: 25%;
+ }
}
}
- }
- .validation {
- width: 50%;
+ .validation {
+ width: 50%;
+ }
}
}
diff --git a/src/app/user/components/dashboard/manage-account/manage-account.component.spec.ts b/src/app/user/components/dashboard/manage-account/manage-account.component.spec.ts
index 054c822..351299e 100644
--- a/src/app/user/components/dashboard/manage-account/manage-account.component.spec.ts
+++ b/src/app/user/components/dashboard/manage-account/manage-account.component.spec.ts
@@ -1,47 +1,246 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
-
import { ManageAccountComponent } from './manage-account.component';
-import { DashboardHeaderComponent } from '../../../../shared/components/dashboard-header/dashboard-header.component';
-import { CardComponent } from '../../../../shared/components/card/card.component';
-import { MatIconModule } from '@angular/material/icon';
-import { MatInputModule } from '@angular/material/input';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
-import { RouterModule } from '@angular/router';
+import { MatInputModule } from '@angular/material/input';
+import { MatButtonModule } from '@angular/material/button';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
-import { ReactiveFormsModule } from '@angular/forms';
-import { ValidationStatusComponent } from '../../../../shared/components/validation-status/validation-status.component';
+import { SharedModule } from '../../../../shared/shared.module';
+import { UserInformation } from '../../../models/ManageUsers';
+import { of, throwError } from 'rxjs';
+import { UserService } from '../../../services/user/user.service';
+import { MatSnackBar } from '@angular/material/snack-bar';
+import { LoadingService } from '../../../../shared/services/loading.service';
+import { DangerSuccessNotificationComponent } from '../../../../shared/components/danger-success-notification/danger-success-notification.component';
+import { ProfileHeaderComponent } from './profile-header/profile-header.component';
+import { MatIconModule } from '@angular/material/icon';
describe('ManageAccountComponent', () => {
let component: ManageAccountComponent;
let fixture: ComponentFixture;
+ let userServiceSpy: jasmine.SpyObj;
+ let snackBarSpy: jasmine.SpyObj;
+ let loadingServiceSpy: jasmine.SpyObj;
beforeEach(async () => {
+ const userServiceMock = jasmine.createSpyObj('UserService', [
+ 'updateUser',
+ 'getLoginUserInfo',
+ ]);
+ const snackBarMock = jasmine.createSpyObj('MatSnackBar', [
+ 'openFromComponent',
+ ]);
+ const loadingServiceMock = jasmine.createSpyObj('LoadingService', [
+ 'setLoading',
+ ]);
+
await TestBed.configureTestingModule({
- declarations: [
- ManageAccountComponent,
- DashboardHeaderComponent,
- CardComponent,
- ValidationStatusComponent,
- ],
+ declarations: [ManageAccountComponent, ProfileHeaderComponent],
imports: [
+ SharedModule,
MatIconModule,
+ ReactiveFormsModule,
+ FormsModule,
MatFormFieldModule,
MatInputModule,
- RouterModule.forRoot([]),
+ MatButtonModule,
BrowserAnimationsModule,
- ReactiveFormsModule,
],
- providers: [provideHttpClient(), provideHttpClientTesting()],
+ providers: [
+ provideHttpClient(),
+ provideHttpClientTesting(),
+ { provide: UserService, useValue: userServiceMock },
+ { provide: MatSnackBar, useValue: snackBarMock },
+ { provide: LoadingService, useValue: loadingServiceMock },
+ ],
}).compileComponents();
+ userServiceSpy = TestBed.inject(UserService) as jasmine.SpyObj;
+ snackBarSpy = TestBed.inject(MatSnackBar) as jasmine.SpyObj;
+ loadingServiceSpy = TestBed.inject(
+ LoadingService,
+ ) as jasmine.SpyObj;
+ });
+
+ beforeEach(() => {
fixture = TestBed.createComponent(ManageAccountComponent);
component = fixture.componentInstance;
+ const mockUserInfo: UserInformation = {
+ firstName: 'John',
+ lastName: 'Doe',
+ email: 'john.doe@example.com',
+ phoneNumber: '09123456789',
+ image: 'image.jpg',
+ };
+
+ userServiceSpy.getLoginUserInfo.and.returnValue(of(mockUserInfo));
fixture.detectChanges();
});
- it('should create', () => {
+ it('should create the component', () => {
expect(component).toBeTruthy();
});
+
+ it('should populate the form with user information on init', () => {
+ const mockUserInfo: UserInformation = {
+ firstName: 'John',
+ lastName: 'Doe',
+ email: 'john.doe@example.com',
+ phoneNumber: '09123456789',
+ image: 'image.jpg',
+ };
+
+ userServiceSpy.getLoginUserInfo.and.returnValue(of(mockUserInfo));
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ expect(component.userInfo).toEqual(mockUserInfo);
+
+ expect(component.myForm.get('firstName')?.value).toBe('John');
+ expect(component.myForm.get('lastName')?.value).toBe('Doe');
+ expect(component.myForm.get('email')?.value).toBe('john.doe@example.com');
+ expect(component.myForm.get('phoneNumber')?.value).toBe('09123456789');
+ });
+
+ it('should create a form with controls', () => {
+ expect(component.myForm.contains('firstName')).toBeTruthy();
+ expect(component.myForm.contains('lastName')).toBeTruthy();
+ expect(component.myForm.contains('email')).toBeTruthy();
+ expect(component.myForm.contains('phoneNumber')).toBeTruthy();
+ });
+
+ it('should make the email control required', () => {
+ const emailControl = component.myForm.get('email');
+ emailControl?.setValue('');
+ expect(emailControl?.valid).toBeFalsy();
+ });
+
+ it('should submit the form when valid', () => {
+ spyOn(component, 'onSubmit');
+
+ component.myForm.get('firstName')?.setValue('John');
+ component.myForm.get('lastName')?.setValue('Doe');
+ component.myForm.get('email')?.setValue('john.doe@example.com');
+ component.myForm.get('phoneNumber')?.setValue('1234567890');
+
+ const form = fixture.nativeElement.querySelector('form');
+ form.dispatchEvent(new Event('submit'));
+
+ expect(component.onSubmit).toHaveBeenCalled();
+ });
+
+ it('should reset the form when the reset button is clicked', () => {
+ component.userInfo = {
+ firstName: 'John',
+ lastName: 'Doe',
+ email: 'john.doe@example.com',
+ phoneNumber: '09123456789',
+ image: 'image.jpg',
+ };
+
+ component.myForm.patchValue({
+ firstName: 'Jane',
+ lastName: 'Smith',
+ email: 'jane.smith@example.com',
+ phoneNumber: '09876543210',
+ });
+
+ component.resetUserInfo();
+
+ expect(component.myForm.get('firstName')?.value).toBe(
+ component.userInfo.firstName,
+ );
+ expect(component.myForm.get('lastName')?.value).toBe(
+ component.userInfo.lastName,
+ );
+ expect(component.myForm.get('email')?.value).toBe(component.userInfo.email);
+ expect(component.myForm.get('phoneNumber')?.value).toBe(
+ component.userInfo.phoneNumber,
+ );
+ });
+
+ it('should set focusedField when input is focused', () => {
+ const firstNameInput = fixture.nativeElement.querySelector(
+ 'input[name="firstName"]',
+ );
+ firstNameInput.dispatchEvent(new Event('focus'));
+
+ expect(component.focusedField).toBe('firstName');
+ });
+
+ it('should call updateUser and show success notification when form is valid and update is successful', () => {
+ // Arrange
+ component.myForm.patchValue({
+ firstName: 'John',
+ lastName: 'Doe',
+ email: 'john.doe@example.com',
+ phoneNumber: '09123456789',
+ });
+ component.myForm.markAsDirty();
+ userServiceSpy.updateUser.and.returnValue(of({}));
+
+ // Act
+ component.onSubmit();
+
+ // Assert
+ expect(userServiceSpy.updateUser).toHaveBeenCalledWith(
+ component.myForm.value,
+ );
+ expect(snackBarSpy.openFromComponent).toHaveBeenCalledWith(
+ DangerSuccessNotificationComponent,
+ {
+ data: 'User information updated successfully!',
+ panelClass: ['notification-class-success'],
+ duration: 2000,
+ },
+ );
+ expect(loadingServiceSpy.setLoading).toHaveBeenCalledWith(false);
+ expect(component.userInfo).toEqual(component.myForm.value);
+ });
+
+ it('should show error notification when updateUser fails', () => {
+ component.myForm.patchValue({
+ firstName: 'John',
+ lastName: 'Doe',
+ email: 'john.doe@example.com',
+ phoneNumber: '09123456789',
+ });
+ component.myForm.markAsDirty();
+
+ const mockError = { error: { message: 'Update failed' } };
+ userServiceSpy.updateUser.and.returnValue(throwError(() => mockError));
+
+ component.onSubmit();
+
+ expect(userServiceSpy.updateUser).toHaveBeenCalledWith(
+ component.myForm.value,
+ );
+ expect(snackBarSpy.openFromComponent).toHaveBeenCalledWith(
+ DangerSuccessNotificationComponent,
+ {
+ data: 'Update failed',
+ panelClass: ['notification-class-danger'],
+ duration: 2000,
+ },
+ );
+ expect(loadingServiceSpy.setLoading).toHaveBeenCalledWith(false);
+ });
+
+ it('should not call updateUser if form is invalid', () => {
+ // Arrange
+ component.myForm.patchValue({
+ firstName: '',
+ lastName: 'Doe',
+ email: 'john.doe@example.com',
+ phoneNumber: '09123456789',
+ });
+
+ // Act
+ component.onSubmit();
+
+ // Assert
+ expect(userServiceSpy.updateUser).not.toHaveBeenCalled();
+ });
});
diff --git a/src/app/user/components/dashboard/manage-account/profile-header/profile-header.component.html b/src/app/user/components/dashboard/manage-account/profile-header/profile-header.component.html
new file mode 100644
index 0000000..b8cd91b
--- /dev/null
+++ b/src/app/user/components/dashboard/manage-account/profile-header/profile-header.component.html
@@ -0,0 +1,33 @@
+
+
+
+
{{ userInfo.firstName }} {{ userInfo.lastName }}
+
+ email
+ {{ userInfo.email }}
+
+
+ phone
+ {{ userInfo.phoneNumber }}
+
+
+
diff --git a/src/app/user/components/dashboard/manage-account/profile-header/profile-header.component.scss b/src/app/user/components/dashboard/manage-account/profile-header/profile-header.component.scss
new file mode 100644
index 0000000..7749136
--- /dev/null
+++ b/src/app/user/components/dashboard/manage-account/profile-header/profile-header.component.scss
@@ -0,0 +1,90 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.header {
+ position: relative;
+ background-image: url("https://i.postimg.cc/J4fR2XfB/header-bg.jpg");
+ background-size: cover;
+ background-position: center;
+ height: 19rem;
+ width: 100%;
+ border-radius: 0.5rem;
+}
+
+.profile-info {
+ display: flex;
+ align-items: center;
+ position: absolute;
+ bottom: -120px;
+ left: 30px;
+}
+
+.profile-pic {
+ width: 15.5rem;
+ height: 15.5rem;
+ border-radius: 50%;
+ overflow: hidden;
+ border: 0.3rem solid var(--card-background-color);
+}
+
+.edit-btn {
+ top: -40px;
+ left: 100px;
+ position: relative;
+ color: var(--mat-app-text-color);
+ background-color: var(--mat-app-background-color);
+ border-radius: 50%;
+ padding: 5px 5.75px 0 5.75px;
+}
+
+.profile-pic img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.profile-details {
+ margin-left: 20px;
+ display: flex;
+ flex-direction: column;
+ padding-left: 270px;
+ gap: 0.5rem;
+
+ > p {
+ display: flex;
+ gap: 0.5rem;
+ }
+}
+
+.profile-details h1 {
+ font-size: 1.6rem;
+}
+
+.profile-actions {
+ position: absolute;
+ top: 20px;
+ right: 30px;
+}
+
+/* Adjust for mobile */
+@media (max-width: 600px) {
+ .profile-info {
+ flex-direction: column;
+ align-items: center;
+ bottom: -70px;
+ left: 20px;
+ }
+
+ .profile-details h1 {
+ font-size: 1.25rem;
+ text-align: center;
+ }
+
+ .profile-actions {
+ top: 10px;
+ right: 20px;
+ }
+}
diff --git a/src/app/user/components/dashboard/manage-account/profile-header/profile-header.component.spec.ts b/src/app/user/components/dashboard/manage-account/profile-header/profile-header.component.spec.ts
new file mode 100644
index 0000000..d9c1031
--- /dev/null
+++ b/src/app/user/components/dashboard/manage-account/profile-header/profile-header.component.spec.ts
@@ -0,0 +1,37 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ProfileHeaderComponent } from './profile-header.component';
+import { CardComponent } from '../../../../../shared/components/card/card.component';
+import { MatIconModule } from '@angular/material/icon';
+import { provideHttpClient } from '@angular/common/http';
+import { provideHttpClientTesting } from '@angular/common/http/testing';
+
+describe('ProfileHeaderComponent', () => {
+ let component: ProfileHeaderComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ProfileHeaderComponent, CardComponent],
+ imports: [MatIconModule],
+ providers: [provideHttpClient(), provideHttpClientTesting()],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ProfileHeaderComponent);
+ component = fixture.componentInstance;
+
+ component.userInfo = {
+ firstName: 'Kevin',
+ lastName: 'Smith',
+ email: 'kevin.smith@example.com',
+ phoneNumber: '+123456789',
+ image: 'image.jpg',
+ };
+
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/user/components/dashboard/manage-account/profile-header/profile-header.component.ts b/src/app/user/components/dashboard/manage-account/profile-header/profile-header.component.ts
new file mode 100644
index 0000000..95780a0
--- /dev/null
+++ b/src/app/user/components/dashboard/manage-account/profile-header/profile-header.component.ts
@@ -0,0 +1,54 @@
+import { Component, Input } from '@angular/core';
+import { UserInformation } from '../../../../models/ManageUsers';
+import { UserService } from '../../../../services/user/user.service';
+import { DangerSuccessNotificationComponent } from '../../../../../shared/components/danger-success-notification/danger-success-notification.component';
+import { MatSnackBar } from '@angular/material/snack-bar';
+import { LoadingService } from '../../../../../shared/services/loading.service';
+
+@Component({
+ selector: 'app-profile-header',
+ templateUrl: './profile-header.component.html',
+ styleUrl: './profile-header.component.scss',
+})
+export class ProfileHeaderComponent {
+ @Input({ required: true }) userInfo!: UserInformation;
+
+ constructor(
+ private userService: UserService,
+ private _snackBar: MatSnackBar,
+ private loadingService: LoadingService,
+ ) {}
+
+ onFileSelected(event: Event): void {
+ const input = event.target as HTMLInputElement;
+ if (input.files && input.files.length > 0) {
+ const file = input.files[0];
+
+ console.log('Selected file:', file);
+ this.userService.uploadImage(file).subscribe({
+ next: () => {
+ this._snackBar.openFromComponent(DangerSuccessNotificationComponent, {
+ data: 'User profile image updated successfully!',
+ panelClass: ['notification-class-success'],
+ duration: 2000,
+ });
+ this.loadingService.setLoading(false);
+ this.userService
+ .getLoginUserInfo()
+ .subscribe((data: UserInformation) => {
+ this.userInfo = data;
+ console.log(11, this.userInfo);
+ });
+ },
+ error: (error) => {
+ this._snackBar.openFromComponent(DangerSuccessNotificationComponent, {
+ data: error.error.message,
+ panelClass: ['notification-class-danger'],
+ duration: 2000,
+ });
+ this.loadingService.setLoading(false);
+ },
+ });
+ }
+ }
+}
diff --git a/src/app/user/components/dashboard/manage-users/add-user/add-user.component.html b/src/app/user/components/dashboard/manage-users/add-user/add-user.component.html
index 1c63cd7..e01706f 100644
--- a/src/app/user/components/dashboard/manage-users/add-user/add-user.component.html
+++ b/src/app/user/components/dashboard/manage-users/add-user/add-user.component.html
@@ -31,8 +31,8 @@ Add User
diff --git a/src/app/user/components/dashboard/manage-users/add-user/add-user.component.spec.ts b/src/app/user/components/dashboard/manage-users/add-user/add-user.component.spec.ts
index 4b605d7..37999d7 100644
--- a/src/app/user/components/dashboard/manage-users/add-user/add-user.component.spec.ts
+++ b/src/app/user/components/dashboard/manage-users/add-user/add-user.component.spec.ts
@@ -55,7 +55,7 @@ describe('AddUserComponent', () => {
component.myForm = new FormGroup({
firstName: new FormControl(''),
lastName: new FormControl(''),
- username: new FormControl(''),
+ userName: new FormControl(''),
password: new FormControl(''),
confirmPassword: new FormControl(''),
email: new FormControl(''),
@@ -73,7 +73,7 @@ describe('AddUserComponent', () => {
it('should initialize the form with controls', () => {
expect(component.myForm.contains('firstName')).toBeTruthy();
expect(component.myForm.contains('lastName')).toBeTruthy();
- expect(component.myForm.contains('username')).toBeTruthy();
+ expect(component.myForm.contains('userName')).toBeTruthy();
expect(component.myForm.contains('password')).toBeTruthy();
expect(component.myForm.contains('confirmPassword')).toBeTruthy();
expect(component.myForm.contains('email')).toBeTruthy();
@@ -87,7 +87,7 @@ describe('AddUserComponent', () => {
component.myForm.setValue({
firstName: 'John',
lastName: 'Doe',
- username: 'johndoe',
+ userName: 'johndoe',
password: 'passwordAa@12',
confirmPassword: 'passwordAa@12',
email: 'john.doe@example.com',
diff --git a/src/app/user/components/dashboard/manage-users/add-user/add-user.component.ts b/src/app/user/components/dashboard/manage-users/add-user/add-user.component.ts
index ff9af95..926dda0 100644
--- a/src/app/user/components/dashboard/manage-users/add-user/add-user.component.ts
+++ b/src/app/user/components/dashboard/manage-users/add-user/add-user.component.ts
@@ -37,7 +37,7 @@ export class AddUserComponent implements OnInit {
this.myForm = new FormGroup({
firstName: new FormControl('', Validators.required),
lastName: new FormControl('', Validators.required),
- username: new FormControl('', Validators.required),
+ userName: new FormControl('', Validators.required),
password: new FormControl('', [
Validators.required,
Validators.pattern(
diff --git a/src/app/user/components/dashboard/manage-users/edit-user/edit-user.component.html b/src/app/user/components/dashboard/manage-users/edit-user/edit-user.component.html
index d13492a..d0addd2 100644
--- a/src/app/user/components/dashboard/manage-users/edit-user/edit-user.component.html
+++ b/src/app/user/components/dashboard/manage-users/edit-user/edit-user.component.html
@@ -31,8 +31,8 @@ Edit User
@@ -55,13 +55,11 @@ Edit User
class="radio-buttons"
formControlName="roleName"
>
- Admin
- Data Manager
- Data Analyst
+ @for (role of roles; track role.id) {
+ {{ role.name.replace("-", " ") }}
+
+ }