stable after blogs

This commit is contained in:
Dhanraj
2024-08-31 19:39:59 +05:30
parent 7b153f4cf6
commit 9c6c3abc32
52 changed files with 10990 additions and 5416 deletions

View File

@ -0,0 +1,7 @@
[[ -s $HOME/.nvm/nvm.sh ]] && . $HOME/.nvm/nvm.sh # This loads NVM
nvm use 12

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@
"@angular/platform-browser-dynamic": "~12.2.0",
"@angular/router": "~12.2.0",
"@auth0/angular-jwt": "^5.0.2",
"@josipv/angular-editor-k2": "^2.20.0",
"angular-notifier": "^9.1.0",
"rxjs": "~6.6.0",
"subsink": "^1.0.2",

View File

@ -11,25 +11,36 @@ import {ProfileComponent} from "./component/management/profile/profile.component
import {UserEditComponent} from "./component/management/users/user-edit/user-edit.component";
import {UserViewComponent} from "./component/management/users/user-view/user-view.component";
import {UserResolver} from "./component/management/users/user-resolver.service";
import { ProfessorComponent } from './component/professor/professor.component';
import { HomeComponent } from './component/home/home.component';
import { EventComponent } from './component/event/event.component';
import { BlogComponent } from './component/blog/blog.component';
export const routes: Routes = [
{path: 'login', component: LoginComponent},
{path: 'register', component: RegisterComponent},
{path: 'user/management', component: UserComponent, canActivate: [AuthenticationGuard]},
{
path: 'management', component: ManagementComponent, canActivate: [AuthenticationGuard],
children: [
{path: 'settings', component: SettingsComponent},
{path: 'profile', component: ProfileComponent},
{
path: 'users', component: UsersComponent,
children: [
{path: ':id/view', component: UserViewComponent, resolve: {user: UserResolver}},
{path: ':id/edit', component: UserEditComponent}
]
}
]
},
{ path: 'home', component: HomeComponent },
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent },
{ path: 'settings', component: SettingsComponent, canActivate: [AuthenticationGuard] },
{ path: 'profile', component: ProfileComponent, canActivate: [AuthenticationGuard] },
{ path: 'events', component: EventComponent, canActivate: [AuthenticationGuard] },
{ path: 'blogs', component: BlogComponent, canActivate: [AuthenticationGuard] },
{ path: 'user/management', component: UserComponent, canActivate: [AuthenticationGuard] },
{ path: 'professor/management', component: ProfessorComponent, canActivate: [AuthenticationGuard] },
// {
// path: 'management', component: ManagementComponent, canActivate: [AuthenticationGuard],
// children: [
// {path: 'settings', component: SettingsComponent},
// {path: 'profile', component: ProfileComponent},
// {
// path: 'users', component: UsersComponent,
// children: [
// {path: ':id/view', component: UserViewComponent, resolve: {user: UserResolver}},
// {path: ':id/edit', component: UserEditComponent}
// ]
// }
// ]
// },
{path: '', redirectTo: '/login', pathMatch: 'full'}
];

View File

@ -7,12 +7,11 @@ import {AuthenticationService} from "./service/authentication.service";
import {UserService} from "./service/user.service";
import {AuthInterceptor} from "./interceptor/auth.interceptor";
import {AuthenticationGuard} from "./guard/authentication.guard";
import {NotificationModule} from "./notification/notification.module";
import {LoginComponent} from './component/login/login.component';
import {RegisterComponent} from './component/register/register.component';
import {UserComponent} from './component/user/user.component';
import {AppRoutingModule} from './app-routing.module';
import {FormsModule} from "@angular/forms";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {ManagementComponent} from './component/management/management.component';
import {UsersComponent} from './component/management/users/users.component';
import {SettingsComponent} from './component/management/settings/settings.component';
@ -20,6 +19,18 @@ import {ProfileComponent} from './component/management/profile/profile.component
import {UsersTableComponent} from './component/management/users/users-table/users-table.component';
import {UserViewComponent} from './component/management/users/user-view/user-view.component';
import {UserEditComponent} from './component/management/users/user-edit/user-edit.component';
import { ProfessorComponent } from './component/professor/professor.component';
import { MenuComponent } from './component/menu/menu.component';
import { HomeComponent } from './component/home/home.component';
import { BlogComponent } from './component/blog/blog.component';
import { EventComponent } from './component/event/event.component';
import { BlogService } from './service/blog.service';
import { AngularEditorModule } from '@josipv/angular-editor-k2';
import { NotificationModule } from './notification/notification.module';
@NgModule({
declarations: [
@ -33,16 +44,24 @@ import {UserEditComponent} from './component/management/users/user-edit/user-edi
ProfileComponent,
UsersTableComponent,
UserViewComponent,
UserEditComponent
UserEditComponent,
ProfessorComponent,
MenuComponent,
HomeComponent,
BlogComponent,
EventComponent
],
imports: [
BrowserModule,
HttpClientModule,
NotificationModule,
AppRoutingModule,
FormsModule
FormsModule,
ReactiveFormsModule,
AngularEditorModule
],
providers: [AuthenticationGuard, AuthenticationService, UserService,
providers: [AuthenticationGuard, AuthenticationService, UserService,BlogService,
{provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true}
],
bootstrap: [AppComponent]

View File

@ -0,0 +1,31 @@
/* Add these styles to your component's CSS file or a global stylesheet */
.blog-container {
display: flex;
justify-content: space-between;
}
.blog-list {
flex: 1;
margin-right: 20px; /* Adjust spacing as needed */
}
.blog-form {
flex: 2; /* Adjust this if you want the form to be wider or narrower */
}
.blog-form form {
width: 100%;
}
.result-container {
border: 1px solid #ddd;
padding: 1rem;
border-radius: 4px;
background-color: #f9f9f9;
}
.result-container h4 {
margin-bottom: 1rem;
}

View File

@ -0,0 +1,118 @@
<app-menu></app-menu>
<div class="container mt-4">
<div class="d-flex justify-content-between mb-3">
<!-- Button to Toggle to Blog Form -->
<button *ngIf="!isShowForm" class="btn btn-primary" (click)="showForm()">New Blog</button>
<!-- Button to Toggle to Blog List -->
<button *ngIf="isShowForm" class="btn btn-secondary" (click)="showTable()">Back to List</button>
</div>
<!-- Blog Form -->
<div *ngIf="isShowForm" class="mb-4">
<form [formGroup]="blogForm" (ngSubmit)="saveBlog()">
<div class="container">
<button type="submit" class="btn btn-primary mx-2">{{ editing ? 'Update Blog' : 'Create Blog' }}</button>
<button type="button" class="btn btn-secondary ml-2" (click)="resetForm()">Cancel</button>
</div>
<div class="form-group m-2 ">
<label class="text-primary" for="title">Title</label>
<input type="text" id="title" class="form-control" formControlName="title" placeholder="Enter blog title">
<div *ngIf="blogForm?.get('title')?.invalid && blogForm.get('title')?.touched" class="text-danger">
Title is required.
</div>
</div>
<div class="form-group m-2">
<label class="text-primary" for="professors">Professors</label>
<select id="professors" formControlName="professors" class="form-control" multiple>
<option *ngFor="let professor of allProfessors" [value]="professor.id">
{{ professor.firstName }}
</option>
</select>
<div *ngIf="blogForm.get('professors')?.invalid && blogForm.get('professors')?.touched" class="text-danger mt-1">
At least one professor must be selected.
</div>
</div>
<div class="form-group m-2">
<label class="text-primary" for="tags">Tags</label>
<input type="text" id="tags" class="form-control" formControlName="tags" placeholder="Enter tags separated by commas">
<div *ngIf="blogForm.get('tags')?.invalid && blogForm.get('tags')?.touched" class="text-danger mt-1">
Tags are required.
</div>
</div>
<div class="form-group m-2">
<input type="checkbox" class="mx-2" id="posted" formControlName="posted">
<label class="text-primary" for="posted">Posted</label>
</div>
<div class="form-group m-2">
<label class="text-primary m-1" for="content">Content</label>
<angular-editor [config]="editorConfig" [placeholder]="'Enter text here...'" formControlName="content"></angular-editor>
<div *ngIf="blogForm.get('content')?.invalid && blogForm.get('content')?.touched" class="text-danger mt-1">
Content is required.
</div>
</div>
<div class="result-container mt-4">
<h4 class="text-primary">Preview</h4>
<div [innerHTML]="blogForm.get('content')?.value || ''"></div>
</div>
</form>
</div>
<!-- Blog List -->
<div *ngIf="!isShowForm">
<div *ngIf="blogs.length > 0">
<table class="table table-striped">
<thead>
<tr>
<th>Title</th>
<!-- <th>Content</th> -->
<th>Authors</th>
<th>Tags</th>
<th>Posted</th> <!-- New column for posted status -->
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let blog of blogs">
<td>{{ blog.title }}</td>
<!-- <td>{{ blog.content | slice:0:50 }}...</td> -->
<td>
<span *ngFor="let professor of blog.professors">{{ professor.firstName }}<br></span>
</td>
<td>
<span *ngFor="let tag of blog.tags">{{ tag }}<br></span>
</td>
<td>
<span *ngIf="blog.posted" class="text-success">&#10003;</span> <!-- Check mark for posted -->
<span *ngIf="!blog.posted" class="text-danger">&#10007;</span> <!-- Cross mark for not posted -->
</td>
<td>
<button class="btn btn-info btn-sm mr-2" (click)="editBlog(blog)"> <!-- Added margin-right for spacing -->
Edit
</button>
<button class="btn btn-danger btn-sm" (click)="deleteBlog(blog)">
Delete
</button>
</td>
</tr>
</tbody>
</table>
</div>
<div *ngIf="blogs.length === 0" class="alert alert-info">
No blogs available. Please create a new blog.
</div>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BlogComponent } from './blog.component';
describe('BlogComponent', () => {
let component: BlogComponent;
let fixture: ComponentFixture<BlogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ BlogComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(BlogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,187 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AngularEditorConfig } from '@josipv/angular-editor-k2';
import { AuthenticationService } from 'src/app/service/authentication.service';
;
import { BlogService } from 'src/app/service/blog.service';
import { ProfessorService } from 'src/app/service/professor.service';
@Component({
selector: 'app-blog',
templateUrl: './blog.component.html',
styleUrls: ['./blog.component.css'],
})
export class BlogComponent implements OnInit {
blogs: any[] = [];
allProfessors: any[] = [];
selectedBlog: any = null;
blogForm: FormGroup;
editing: boolean = false;
loggedInUser: any;
currentBlog: any = null; // Holds the blog being edited
isShowForm = false; // Controls visibility of form vs. table
content = '';
constructor(
private blogService: BlogService,
private authService: AuthenticationService,
private professorService: ProfessorService,
private fb: FormBuilder
) {
// Initialize form with form controls
this.blogForm = this.fb.group({
title: ['', Validators.required],
content: ['', Validators.required],
professors: [[], Validators.required], // To hold selected professor IDs
tags: ['', Validators.required], // To hold tags as a comma-separated string
posted: [true], // Initialize checkbox with default value false
});
}
editorConfig: AngularEditorConfig = {
editable: true,
spellcheck: true,
height: '10',
minHeight: '0',
maxHeight: 'auto',
width: 'auto',
minWidth: '0',
translate: 'yes',
enableToolbar: true,
showToolbar: true,
placeholder: 'Enter text here...',
defaultParagraphSeparator: '',
defaultFontName: '',
defaultFontSize: '',
// headers: [{
// }],
fonts: [
{ class: 'arial', name: 'Arial' },
{ class: 'times-new-roman', name: 'Times New Roman' },
{ class: 'calibri', name: 'Calibri' },
{ class: 'comic-sans-ms', name: 'Comic Sans MS' },
],
customClasses: [
{
name: 'quote',
class: 'quote',
},
{
name: 'redText',
class: 'redText',
},
{
name: 'titleText',
class: 'titleText',
tag: 'h1',
},
],
// uploadUrl: 'v1/image',
// upload: (file: File) => { ... }
// uploadWithCredentials: false,
sanitize: true,
toolbarPosition: 'top',
toolbarHiddenButtons: [['bold', 'italic'], ['fontSize']],
};
ngOnInit(): void {
this.loggedInUser = this.authService.getUserFromLocalStorage();
this.professorService
.getAllProfessors()
.subscribe((res) => (this.allProfessors = res.content));
this.loadBlogs();
// Subscribe to form value changes to update content for preview
this.blogForm.get('content')?.valueChanges.subscribe((value) => {
this.content = value;
});
}
showForm() {
this.isShowForm = true;
this.resetForm(); // Ensure form is reset when showing
}
showTable() {
this.isShowForm = false;
this.resetForm(); // Ensure form is reset when switching back
}
loadBlogs() {
this.blogService.getBlogs().subscribe(data => {
this.blogs = data;
});
}
createBlog() {
this.showForm(); // Show form to create a new blog
this.blogForm.reset();
this.selectedBlog = null;
this.editing = false;
}
editBlog(blog: any) {
this.selectedBlog = blog;
this.blogForm.patchValue({
title: blog.title,
content: blog.content,
posted: blog.posted,
professors: blog.professors.map((prof: any) => prof.id), // Assuming professors are an array of objects
tags: blog.tags.join(', ') // Convert tags array back to comma-separated string
});
this.editing = true;
this.isShowForm = true;
}
saveBlog() {
if (this.blogForm.valid) {
const blogData = this.blogForm.value;
// Convert tags to array and professors to array of IDs
blogData.tags = blogData.tags.split(',').map((tag: string) => tag.trim());
blogData.professors = blogData.professors || [];
blogData.author = this.loggedInUser._id; // Associate logged-in user with the blog
if (this.editing && this.selectedBlog) {
this.blogService.updateBlog(this.selectedBlog.id, blogData).subscribe(() => {
this.loadBlogs();
this.resetForm();
this.isShowForm = false; // Hide form after update
});
} else {
this.blogService.createBlog(blogData).subscribe(() => {
this.loadBlogs();
this.resetForm();
this.isShowForm = false; // Hide form after creation
});
}
}
}
deleteBlog(blog: any) {
if (confirm('Are you sure you want to delete this blog?')) {
this.blogService.deleteBlog(blog.id).subscribe(() => {
this.loadBlogs();
});
}
}
resetForm() {
this.blogForm.reset();
this.selectedBlog = null;
this.editing = false;
this.blogForm.patchValue({
professors: [],
tags: ''
});
}
}

View File

@ -0,0 +1,2 @@
<app-menu></app-menu>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EventComponent } from './event.component';
describe('EventComponent', () => {
let component: EventComponent;
let fixture: ComponentFixture<EventComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ EventComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(EventComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,18 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-event',
templateUrl: './event.component.html',
styleUrls: ['./event.component.css']
})
export class EventComponent implements OnInit {
constructor() { }
htmlContent: string = '';
ngOnInit(): void {
}
}

View File

@ -0,0 +1,20 @@
/* This ensures that the container takes at least the full viewport height */
.min-vh-100 {
min-height: 100vh;
}
/* Optional: Add more custom styles if needed */
.text-center {
text-align: center;
}
h1 {
color: #007bff; /* Bootstrap primary color or customize */
font-size: 2.5rem; /* Adjust size as needed */
}
.lead {
color: #6c757d; /* Bootstrap secondary color or customize */
font-size: 1.25rem; /* Adjust size as needed */
}

View File

@ -0,0 +1,9 @@
<app-menu ></app-menu>
<div class="container d-flex justify-content-center align-items-center min-vh-100">
<div class="text-center">
<h1>Welcome, CMC!</h1>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HomeComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -1,6 +1,6 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Router} from "@angular/router";
import {AuthenticationService} from "../../service/authentication.service";
import {NotificationService} from "../../service/notification.service";
import {NotificationType} from "../../notification/notification-type";
import {Subscription} from "rxjs";
@ -8,6 +8,8 @@ import {UserLogin} from "../../dto/user-login";
import {HttpErrorResponse, HttpResponse} from "@angular/common/http";
import {User} from "../../model/user";
import {HeaderType} from "../../enum/header-type.enum";
import { AuthenticationService } from 'src/app/service/authentication.service';
;
@Component({
selector: 'app-login',
@ -26,7 +28,8 @@ export class LoginComponent implements OnInit, OnDestroy {
ngOnInit(): void {
if (this.authenticationService.isUserLoggedIn()) {
this.router.navigate(["/management", "users"]);
this.router.navigate(["/user", "management"]);
// this.router.navigate(["/management", "users"]);
this.notificationService.notify(NotificationType.INFO, "You are already logged in");
}
}
@ -43,7 +46,8 @@ export class LoginComponent implements OnInit, OnDestroy {
this.authenticationService.addUserToLocalStorage(response.body!);
this.router.navigateByUrl('/management/users');
this.router.navigateByUrl('/home');
// this.router.navigateByUrl('/management/users');
this.showLoading = false;
},
(errorResponse: HttpErrorResponse) => {

View File

@ -1,7 +1,9 @@
import {Component, OnInit} from '@angular/core';
import {Role} from "../../enum/role.enum";
import {User} from "../../model/user";
import {AuthenticationService} from "../../service/authentication.service";
import { AuthenticationService } from 'src/app/service/authentication.service';
;
import {Router} from "@angular/router";
import {BehaviorSubject} from "rxjs";

View File

@ -1,4 +1,7 @@
<!-- user profile -->
<app-menu></app-menu>
<div class="tab-pane fade show active" id="profile">
<div class="container">
<div class="row flex-lg-nowrap">

View File

@ -1,13 +1,16 @@
import {Component, OnInit} from '@angular/core';
import {AuthenticationService} from "../../../service/authentication.service";
import {User} from "../../../model/user";
import {FileUploadStatus} from "../../../model/file-upload.status";
import {NotificationType} from "../../../notification/notification-type";
import {HttpErrorResponse, HttpEvent, HttpEventType} from "@angular/common/http";
import {SubSink} from "subsink";
import {UserService} from "../../../service/user.service";
import { UserService } from 'src/app/service/user.service';
import {NotificationService} from "../../../service/notification.service";
import {Router} from "@angular/router";
import { AuthenticationService } from 'src/app/service/authentication.service';
;
@Component({
selector: 'app-profile',

View File

@ -1,4 +1,9 @@
<!-- change password -->
<app-menu></app-menu>
<div class="container">
<div *ngIf="isAdmin" class="tab-pane fade show active" id="reset-password">
<form #resetPasswordForm="ngForm" (ngSubmit)="onResetPassword(resetPasswordForm)">
<fieldset>
@ -17,4 +22,5 @@
</fieldset>
</form>
</div>
</div>

View File

@ -1,11 +1,14 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {AuthenticationService} from "../../../service/authentication.service";
import { AuthenticationService } from 'src/app/service/authentication.service';
;
import {NgForm} from "@angular/forms";
import {CustomHttpResponse} from "../../../dto/custom-http-response";
import {NotificationType} from "../../../notification/notification-type";
import {HttpErrorResponse} from "@angular/common/http";
import {SubSink} from "subsink";
import {UserService} from "../../../service/user.service";
import { UserService } from 'src/app/service/user.service';
import {NotificationService} from "../../../service/notification.service";
@Component({

View File

@ -2,7 +2,8 @@ import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from "@angular/router";
import {User} from "../../../model/user";
import {Observable} from "rxjs";
import {UserService} from "../../../service/user.service";
import { UserService } from 'src/app/service/user.service';
@Injectable({
providedIn: 'root'

View File

@ -1,7 +1,8 @@
import {Component, OnInit} from '@angular/core';
import {UserService} from "../../../../service/user.service";
import {User} from "../../../../model/user";
import {ActivatedRoute, Router} from "@angular/router";
import { UserService } from 'src/app/service/user.service';
@Component({
selector: 'app-user-view',

View File

@ -3,7 +3,8 @@ import {User} from "../../../../model/user";
import {NotificationType} from "../../../../notification/notification-type";
import {HttpErrorResponse} from "@angular/common/http";
import {SubSink} from "subsink";
import {UserService} from "../../../../service/user.service";
import { UserService } from 'src/app/service/user.service';
import {NotificationService} from "../../../../service/notification.service";
import {AuthenticationService} from "../../../../service/authentication.service";
import {ActivatedRoute, Router} from "@angular/router";

View File

@ -0,0 +1,19 @@
.navbar {
background-color: #f8f9fa;
padding: 0.5rem 1rem;
}
.nav-pills .nav-link {
border-radius: 0;
margin-right: 0.5rem;
}
.nav-pills .nav-link.active {
background-color: #007bff;
color: white;
}
.move-right {
margin-left: auto;
}

View File

@ -0,0 +1,52 @@
<div class="container">
<div class="row mb-2 mt-2 text-center">
<div class="col-md-4"></div>
<div class="col-md-4">
<h5>Admin Dashboard</h5>
</div>
<div class="col-md-4"></div>
</div>
<!-- Navbar -->
<nav class="navbar navbar-expand-md breadcrumb">
<div class="collapse navbar-collapse" id="navbarCollapse">
<div class="nav nav-pills">
<a class="nav-item nav-link ml-1" routerLink="/home" routerLinkActive="active" (click)="changeTitle('Home')">
<i class="fa fa-home"></i>
Home
</a>
<a class="nav-item nav-link ml-1" routerLink="/user/management" routerLinkActive="active" (click)="changeTitle('Users')">
<i class="fa fa-users"></i>
Users
</a>
<a class="nav-item nav-link ml-1" routerLink="/professor/management" routerLinkActive="active" (click)="changeTitle('Professors')">
<i class="fa fa-chalkboard-teacher"></i>
Professors
</a>
<a class="nav-item nav-link ml-1" routerLink="/blogs" routerLinkActive="active" (click)="changeTitle('Professors')">
<i class="fa fa-chalkboard-teacher"></i>
Blogs
</a>
<a class="nav-item nav-link ml-1" routerLink="/events" routerLinkActive="active" (click)="changeTitle('Professors')">
<i class="fa fa-chalkboard-teacher"></i>
Events
</a>
<a class="nav-item nav-link ml-3" routerLink="/settings" routerLinkActive="active" (click)="changeTitle('Settings')">
<i class="fa fa-cogs"></i>
Settings
</a>
<a class="nav-item nav-link move-right mr-3" routerLink="/profile" routerLinkActive="active" (click)="changeTitle('Profile')">
Welcome, {{ loggedInUser.firstName }} {{ loggedInUser.lastName }}
<i class="fa fa-user"></i>
</a>
<a class="nav-item nav-link ml-1" (click)="logout()">
<i class="fa fa-sign-out-alt"></i>
Logout
</a>
</div>
</div>
</nav>
</div>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MenuComponent } from './menu.component';
describe('MenuComponent', () => {
let component: MenuComponent;
let fixture: ComponentFixture<MenuComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ MenuComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(MenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,33 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { AuthenticationService } from 'src/app/service/authentication.service';
;
@Component({
selector: 'app-menu',
templateUrl: './menu.component.html',
styleUrls: ['./menu.component.css']
})
export class MenuComponent implements OnInit {
titleAction$: Observable<string> | undefined;
titleChanged = new EventEmitter<string>();
loggedInUser: any = {};
constructor(private authenticationService: AuthenticationService) {}
ngOnInit(): void {
// Fetch user details from authentication service
this.loggedInUser = this.authenticationService.getUserFromLocalStorage();
}
logout(){
this.authenticationService.logout()
}
changeTitle(title: string): void {
this.titleChanged.emit(title);
}
}

View File

@ -0,0 +1,566 @@
<div class="container">
<app-menu ></app-menu>
<div *ngIf="false" class="row mb-2 mt-2 text-center">
<div class="col-md-4"></div>
<div class="col-md-4">
<h5>Professor Management Portal</h5>
<small *ngIf="titleAction$ | async as title">{{ title }}</small>
</div>
<div class="col-md-4"></div>
</div>
<!-- Navbar -->
<nav *ngIf="false" class="navbar navbar-expand-md breadcrumb">
<div class="collapse navbar-collapse" id="navbarCollapse">
<div class="nav nav-pills">
<a
class="nav-item nav-link active ml-1"
(click)="changeTitle('Professors')"
data-bs-toggle="tab"
href="#professors"
>
<i class="fa fa-chalkboard-teacher"></i>
Professors
</a>
<a
class="nav-item nav-link ml-3"
(click)="changeTitle('Settings')"
data-bs-toggle="tab"
href="#settings"
>
<i class="fa fa-cogs"></i>
Settings
</a>
<a
class="nav-item nav-link move-right mr-3"
(click)="changeTitle('Profile')"
data-bs-toggle="tab"
href="#profile"
>
Welcome, {{ loggedInUser.firstName }} {{ loggedInUser.lastName }}
<i class="fa fa-user"></i>
</a>
</div>
</div>
</nav>
<!-- Main Content -->
<div class="tab-content mt-3" id="myTabContent">
<!-- Professor Table -->
<div class="tab-pane fade show active" id="professors">
<div class="mb-3 float-end">
<div class="btn-group mr-2">
<form class="form-inline my-2 my-lg-0 justify-content-center">
<input
name="searchTerm"
#searchTerm="ngModel"
class="form-control mr-sm-2"
type="search"
placeholder="Search professors..."
ngModel
(ngModelChange)="searchProfessors(searchTerm.value)"
/>
</form>
<button
*ngIf="isManager"
type="button"
class="btn btn-info"
data-bs-toggle="modal"
data-bs-target="#addProfessorModal"
>
<i class="fa fa-plus"></i> New Professor
</button>
</div>
<div class="btn-group">
<button
type="button"
class="btn btn-info"
(click)="getProfessors(true)"
>
<i class="fas fa-sync" [ngClass]="{ 'fa-spin': refreshing }"></i>
</button>
</div>
</div>
<table class="table table-hover">
<thead class="table-borderless">
<tr class="text-center">
<th>Photo</th>
<th>Professor ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr class="text-center" *ngFor="let professor of professors">
<td (click)="onSelectProfessor(professor)">
<img
height="40"
width="40"
src="{{ professor?.profileImageUrl }}"
class="rounded-circle img-fluid img-thumbnail"
alt=""
/>
</td>
<td (click)="onSelectProfessor(professor)">
{{ professor?.professorId }}
</td>
<td (click)="onSelectProfessor(professor)">
{{ professor?.firstName }}
</td>
<td (click)="onSelectProfessor(professor)">
{{ professor?.lastName }}
</td>
<td (click)="onSelectProfessor(professor)">
{{ professor?.email }}
</td>
<td (click)="onSelectProfessor(professor)">
<span
class="badge"
[ngClass]="{
'bg-success': professor?.status === WorkingStatus.ACTIVE,
'bg-warning': professor?.status === WorkingStatus.ON_LEAVE,
'bg-danger': professor?.status === WorkingStatus.RETIRED
}"
>
{{ professor?.status }}
</span>
</td>
<td>
<div class="btn-group">
<button
class="btn btn-outline-info"
(click)="onEditProfessor(professor)"
>
<i class="fas fa-edit"></i>
</button>
<button
*ngIf="isAdmin"
class="btn btn-outline-danger"
(click)="onDeleteProfessor(professor)"
>
<i class="fas fa-trash"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<button
[hidden]="true"
type="button"
id="openProfessorInfo"
data-bs-toggle="modal"
data-bs-target="#viewProfessorModal"
></button>
<button
[hidden]="true"
type="button"
id="openProfessorEdit"
data-bs-toggle="modal"
data-bs-target="#editProfessorModal"
></button>
<!-- Settings -->
<div class="tab-pane fade" id="settings">
<p>Settings content goes here...</p>
</div>
<!-- Profile -->
<div class="tab-pane fade" id="profile">
<!-- Profile content goes here... -->
</div>
<!-- Modals -->
<!-- Add Professor Modal -->
<div
*ngIf="isManager"
class="modal fade"
id="addProfessorModal"
tabindex="-1"
aria-labelledby="addProfessorModalLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add New Professor</h5>
<button
type="button"
class="close"
data-bs-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form
#newProfessorForm="ngForm"
(ngSubmit)="onAddNewProfessor(newProfessorForm)"
>
<!-- Form Fields for Adding New Professor -->
<div class="form-group">
<label for="firstName">First Name</label>
<input
type="text"
id="firstName"
name="firstName"
class="form-control"
ngModel
required
/>
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<input
type="text"
id="lastName"
name="lastName"
class="form-control"
ngModel
required
/>
</div>
<div class="form-group">
<label for="email">Email</label>
<input
type="email"
id="email"
name="email"
class="form-control"
ngModel
required
/>
</div>
<div class="form-group">
<label for="status">Status</label>
<select
id="status"
name="status"
class="form-control"
ngModel
required
>
<option [value]="WorkingStatus.ACTIVE">Active</option>
<option [value]="WorkingStatus.ON_LEAVE">On Leave</option>
<option [value]="WorkingStatus.RETIRED">Retired</option>
</select>
</div>
<!-- <div class="form-group">
<label for="profileImageUrl">Profile Image URL</label>
<input
type="url"
id="profileImageUrl"
name="profileImageUrl"
class="form-control"
ngModel
/>
</div> -->
<div class="input-group mb-2">
<div class="input-group-prepend">
<span class="input-group-text">Profile Picture </span>
</div>
<div class="custom-file">
<input type="file" accept="image/*" name="profileImage"
(change)="onProfileImageChange($any($event).target.files)"
class="custom-file-input">
<label class="custom-file-label">
<span>{{profileImageFileName ? profileImageFileName : 'Choose File'}}</span>
</label>
</div>
</div>
<button
type="submit"
class="btn btn-primary"
[disabled]="newProfessorForm.invalid"
>
Save
</button>
</form>
</div>
</div>
</div>
</div>
<!-- Edit Professor Modal -->
<div
class="modal fade"
id="editProfessorModal"
tabindex="-1"
aria-labelledby="editProfessorModalLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit Professor</h5>
<button
type="button"
class="close"
data-bs-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form
#editProfessorForm="ngForm"
(ngSubmit)="onUpdateProfessor(editProfessorForm)"
>
<!-- Form Fields for Editing Professor -->
<div class="form-group">
<label for="editFirstName">First Name</label>
<input
type="text"
id="editFirstName"
name="firstName"
class="form-control"
[(ngModel)]="selectedProfessor.firstName"
required
/>
</div>
<div class="form-group">
<label for="editLastName">Last Name</label>
<input
type="text"
id="editLastName"
name="lastName"
class="form-control"
[(ngModel)]="selectedProfessor.lastName"
required
/>
</div>
<div class="form-group">
<label for="editEmail">Email</label>
<input
type="email"
id="editEmail"
name="email"
class="form-control"
[(ngModel)]="selectedProfessor.email"
required
/>
</div>
<div class="form-group">
<label for="editStatus">Status</label>
<select
id="editStatus"
name="status"
class="form-control"
[(ngModel)]="selectedProfessor.status"
required
>
<option [value]="WorkingStatus.ACTIVE">Active</option>
<option [value]="WorkingStatus.ON_LEAVE">On Leave</option>
<option [value]="WorkingStatus.RETIRED">Retired</option>
</select>
</div>
<!-- <div class="form-group">
<label for="editProfileImageUrl">Profile Image URL</label>
<input
type="url"
id="editProfileImageUrl"
name="profileImageUrl"
class="form-control"
[(ngModel)]="selectedProfessor.profileImageUrl"
/>
</div> -->
<div class="input-group mb-2">
<div class="input-group-prepend">
<span class="input-group-text">Profile Picture </span>
</div>
<div class="custom-file">
<input type="file" accept="image/*" name="profileImage"
(change)="onProfileImageChange($any($event).target.files)"
class="custom-file-input" [disabled]="!isManager">
<label class="custom-file-label">
<span>{{profileImageFileName ? profileImageFileName : 'Choose File'}}</span>
</label>
</div>
</div>
<button
type="submit"
class="btn btn-primary"
[disabled]="editProfessorForm.invalid"
>
Save changes
</button>
</form>
</div>
</div>
</div>
</div>
<!-- profile image change form -->
<form enctype="multipart/form-data" style="display:none;">
<input type="file"
(change)="onProfileImageChange($any($event).target.files); onUpdateProfileImage()"
name="profile-image-input" id="profile-image-input" placeholder="file" accept="image/*"/>
</form>
</div>
<!-- Modal User Info -->
<div
*ngIf="selectedProfessor"
class="modal fade bd-example-modal-lg"
id="viewProfessorModal"
tabindex="-1"
role="dialog"
aria-labelledby=""
aria-hidden="true"
>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-center" id="exampleModalLongTitle">
{{ selectedProfessor.firstName || "First Name" }}
{{ selectedProfessor.lastName || "Last Name" }}
</h5>
<button
type="button"
class="close"
data-bs-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div>
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-12 col-sm-auto">
<div class="mx-auto" style="width: 120px">
<div
class="d-flex justify-content-center align-items-center rounded"
>
<img
class="rounded"
height="120"
width="120"
[src]="
selectedProfessor.profileImageUrl ||
'default-image-url'
"
alt="{{ selectedProfessor.firstName || 'Professor' }}"
/>
</div>
</div>
</div>
<div
class="col d-flex flex-column flex-sm-row justify-content-between"
>
<div class="text-center text-sm-left mb-sm-0">
<h6 class="pt-sm-2 pb-1 mb-0 text-nowrap">
{{ selectedProfessor.firstName || "First Name" }}
{{ selectedProfessor.lastName || "Last Name" }}
</h6>
<p class="mb-1">
{{ selectedProfessor.email || "Email not available" }}
</p>
<div>
Status:
<span
class="badge"
[ngClass]="{
'bg-success': selectedProfessor.status === 'ACTIVE',
'bg-warning':
selectedProfessor.status === 'ON_LEAVE',
'bg-danger': selectedProfessor.status === 'RETIRED'
}"
>
{{
selectedProfessor.status === "ACTIVE"
? "Active"
: selectedProfessor.status === "ON_LEAVE"
? "On Leave"
: "Retired"
}}
</span>
</div>
<div
*ngIf="selectedProfessor.joinDate"
class="text-muted"
>
<small
>Joined
{{
selectedProfessor.joinDate | date : "medium"
}}</small
>
</div>
</div>
<div class="text-center text-sm-right">
<div class="text-muted">
<small
>Department:
{{
selectedProfessor.department || "Not provided"
}}</small
><br />
<small
>Position:
{{
selectedProfessor.position || "Not provided"
}}</small
><br />
<small
>Office Location:
{{
selectedProfessor.officeLocation || "Not provided"
}}</small
>
</div>
</div>
</div>
</div>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<i class="fa fa-id-badge float-end"></i
>{{ selectedProfessor.professorId || "ID not available" }}
</li>
<li class="list-group-item">
<i class="fa fa-envelope float-end"></i
>{{ selectedProfessor.email || "Email not available" }}
</li>
<li class="list-group-item">
<i class="fas fa-shield-alt float-end"></i
>{{ selectedProfessor.status || "Status not available" }}
</li>
</ul>
</div>
</div>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-secondary"
data-bs-dismiss="modal"
>
Close
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProfessorComponent } from './professor.component';
describe('ProfessorComponent', () => {
let component: ProfessorComponent;
let fixture: ComponentFixture<ProfessorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ProfessorComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ProfessorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,264 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Professor } from '../../model/Professor';
import { NotificationService } from '../../service/notification.service';
import { NotificationType } from '../../notification/notification-type';
import { HttpErrorResponse, HttpEvent, HttpEventType } from '@angular/common/http';
import { NgForm } from '@angular/forms';
import { CustomHttpResponse } from '../../dto/custom-http-response';
import { Router } from '@angular/router';
import { FileUploadStatus } from 'src/app/model/file-upload.status';
import { SubSink } from 'subsink';
import { WorkingStatus } from "../../enum/WorkingStatus";
import { User } from 'src/app/model/user';
;
import { Role } from 'src/app/enum/role.enum';
import { AuthenticationService } from 'src/app/service/authentication.service';
import { ProfessorService } from 'src/app/service/professor.service';
;
@Component({
selector: 'app-professor',
templateUrl: './professor.component.html',
styleUrls: ['./professor.component.css']
})
export class ProfessorComponent implements OnInit, OnDestroy {
WorkingStatus = WorkingStatus; // Declare enum here
private titleSubject = new BehaviorSubject<string>('Professors');
public titleAction$ = this.titleSubject.asObservable();
public loggedInUser: User;
public professors: Professor[] = [];
public loggedInProfessor: Professor;
public refreshing: boolean;
private subs = new SubSink();
selectedProfessor: Professor = {
professorId: '', // Initialize with empty string or appropriate default
firstName: '', // Initialize with empty string or appropriate default
lastName: '', // Initialize with empty string or appropriate default
email: '', // Initialize with empty string or appropriate default
profileImageUrl: '', // Optional property, can be initialized with empty string or `null`
status: WorkingStatus.ACTIVE, // Initialize with a default value from your enum
department: '', // Initialize with empty string or appropriate default
position: '', // Initialize with empty string or appropriate default
officeLocation: '', // Initialize with empty string or appropriate default
joinDate: new Date() // Initialize with the current date or an appropriate default
};
public profileImageFileName: string | null;
public profileImage: File | null;
// public editProfessor: Professor = new Professor();
public fileUploadStatus: FileUploadStatus = new FileUploadStatus();
constructor(
private professorService: ProfessorService,
private notificationService: NotificationService,
private router: Router,
private authenticationService: AuthenticationService,
) {}
ngOnInit(): void {
this.getProfessors(true);
this.loggedInUser = this.authenticationService.getUserFromLocalStorage();
}
ngOnDestroy(): void {
this.subs.unsubscribe();
}
handleTitleChange(title: string): void {
this.titleSubject.next(title);
}
public changeTitle(title: string): void {
this.titleSubject.next(title);
}
public getProfessors(showNotification: boolean): void {
this.refreshing = true;
this.subs.sink = this.professorService.getAllProfessors().subscribe(
professorsPage => {
this.professors = professorsPage.content;
this.professorService.addProfessorsToLocalStorage(this.professors);
if (showNotification) {
this.notificationService.notify(NotificationType.SUCCESS, `${this.professors.length} professors loaded successfully`);
}
this.refreshing = false;
},
(errorResponse: HttpErrorResponse) => {
this.sendErrorNotification(errorResponse.error.message);
this.refreshing = false;
}
);
}
public onSelectProfessor(selectedProfessor: Professor): void {
this.selectedProfessor = selectedProfessor;
this.clickButton('openProfessorInfo');
}
public onProfileImageChange(fileList: FileList): void {
this.profileImageFileName = fileList[0].name;
this.profileImage = fileList[0];
}
private sendErrorNotification(message: string): void {
this.sendNotification(NotificationType.ERROR, message);
}
private sendNotification(type: NotificationType, message: string): void {
this.notificationService.notify(type, message ? message : 'An error occurred. Please try again');
}
public onAddNewProfessor(professorForm: NgForm): void {
const formData = this.professorService.createProfessorFormData(professorForm.value, this.profileImage);
this.subs.sink = this.professorService.addProfessor(formData).subscribe(
(professor: Professor) => {
this.clickButton('new-professor-close');
this.getProfessors(false);
this.invalidateVariables();
professorForm.reset();
this.notificationService.notify(NotificationType.SUCCESS, `Professor ${professor.firstName} added successfully`);
},
(errorResponse: HttpErrorResponse) => {
this.sendErrorNotification(errorResponse.error.message);
}
);
}
private invalidateVariables(): void {
this.profileImage = null;
this.profileImageFileName = null;
}
public saveNewProfessor(): void {
this.clickButton('new-professor-save');
}
public searchProfessors(searchTerm: string): void {
if (!searchTerm) {
this.professors = this.professorService.getProfessorsFromLocalStorage();
return;
}
const matchProfessors: Professor[] = [];
searchTerm = searchTerm.toLowerCase();
for (const professor of this.professorService.getProfessorsFromLocalStorage()) {
if (
professor.firstName.toLowerCase().includes(searchTerm) ||
professor.lastName.toLowerCase().includes(searchTerm) ||
professor.email.toLowerCase().includes(searchTerm) ||
professor.department.toLowerCase().includes(searchTerm)
) {
matchProfessors.push(professor);
}
}
this.professors = matchProfessors;
}
private clickButton(buttonId: string): void {
document.getElementById(buttonId)?.click();
}
public onEditProfessor(professor: Professor): void {
// this.editProfessor = professor;
this.selectedProfessor = professor;
this.clickButton('openProfessorEdit');
}
onUpdateProfessor(form: NgForm) {
if (form.invalid) {
// Handle form validation errors if needed
this.notificationService.notify(NotificationType.ERROR, 'Please fill out all required fields.');
return;
}
const formData = this.professorService.createProfessorFormData(this.selectedProfessor, this.profileImage);
this.subs.add(this.professorService.updateProfessor(this.selectedProfessor.professorId, formData).subscribe(
(professor: Professor) => {
this.clickButton('closeEditProfessorButton');
this.getProfessors(false);
this.invalidateVariables();
this.notificationService.notify(NotificationType.SUCCESS, `Professor ${professor.firstName} updated successfully`);
},
(errorResponse: HttpErrorResponse) => {
this.sendErrorNotification(errorResponse.error.message);
}
));
}
public onDeleteProfessor(professor: Professor): void {
this.subs.sink = this.professorService.deleteProfessor(professor.professorId).subscribe(
(response: CustomHttpResponse) => {
this.getProfessors(false);
this.invalidateVariables();
this.notificationService.notify(NotificationType.SUCCESS, response.message);
},
(errorResponse: HttpErrorResponse) => {
this.sendErrorNotification(errorResponse.error.message);
}
);
}
public updateProfileImage(): void {
this.clickButton('profile-image-input');
}
public onUpdateProfileImage(): void {
if (!this.profileImage) return;
this.refreshing = true;
const formData = new FormData();
formData.append("profileImage", this.profileImage);
let professor = this.professorService.getSelectedProfessor();
this.subs.sink = this.professorService.updateProfileImage(professor.professorId, formData).subscribe(
(event: HttpEvent<any>) => {
this.reportUploadProgress(event);
},
(errorResponse: HttpErrorResponse) => {
this.sendErrorNotification(errorResponse.error.message);
this.refreshing = false;
this.fileUploadStatus.status = 'error';
},
() => {
this.refreshing = false;
this.getProfessors(false);
}
);
}
private reportUploadProgress(event: HttpEvent<any>): void {
switch (event.type) {
case HttpEventType.UploadProgress:
this.fileUploadStatus.percentage = Math.round(100 * event.loaded / event.total!);
this.fileUploadStatus.status = 'progress';
break;
case HttpEventType.Response:
if (event.status === 200) {
// For browser to fetch image when updating (because name left the same)
this.loggedInProfessor.profileImageUrl = `${event.body.profileImageUrl}?time=${new Date().getTime()}`;
this.notificationService.notify(NotificationType.SUCCESS, `${event.body.firstName}'s image updated successfully`);
this.fileUploadStatus.status = 'done';
} else {
this.sendErrorNotification('Unable to upload image. Please try again');
}
break;
default:
this.fileUploadStatus.status = 'default';
}
}
public get isAdmin(): boolean {
return this.loggedInUser.role === Role.ADMIN || this.loggedInUser.role === Role.SUPER_ADMIN;
}
public get isManager(): boolean {
return this.isAdmin || this.loggedInUser.role === Role.MANAGER;
}
}

View File

@ -50,6 +50,7 @@
*ngIf="emailInput.invalid && emailInput.touched">Please enter an email.</span>
<div class="d-flex justify-content-center mt-3 login_container">
{{registerForm.invalid || showLoading}}
<button type="submit" [disabled]="registerForm.invalid || showLoading" name="button" class="btn login_btn">
<i class="fas fa-spinner fa-spin" *ngIf="showLoading"></i>&nbsp;&nbsp;
<span *ngIf="showLoading">{{showLoading ? 'Loading...' : 'Register'}}</span>

View File

@ -1,7 +1,9 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {User} from "../../model/user";
import {Router} from "@angular/router";
import {AuthenticationService} from "../../service/authentication.service";
import { AuthenticationService } from 'src/app/service/authentication.service';
;
import {NotificationService} from "../../service/notification.service";
import {Subscription} from "rxjs";
import {HttpErrorResponse} from "@angular/common/http";

View File

@ -1,5 +1,9 @@
<div class="container">
<div class="row mb-2 mt-2 text-center">
<app-menu></app-menu>
<div *ngIf="false" class="row mb-2 mt-2 text-center">
<div class="col-md-4">
</div>
<div class="col-md-4">
@ -11,13 +15,17 @@
</div>
<!-- nav bar -->
<nav class="navbar navbar-expand-md breadcrumb">
<nav *ngIf="false" class="navbar navbar-expand-md breadcrumb">
<div class="collapse navbar-collapse" id="navbarCollapse">
<div class="nav nav-pills">
<a class="nav-item nav-link active ml-1" (click)="changeTitle('Users')" data-bs-toggle="tab" href="#users">
<i class="fa fa-users"></i>
Users
</a>
<a class="nav-item nav-link active ml-1" (click)="changeTitle('Professors')" [routerLink]="['/professor/management']" >
<i class="fa fa-users"></i>
Professors
</a>
<!-- Possible attacks-->
<!-- document.getElementsByClassName('nav-item nav-link ml-3')[0].click()-->
@ -26,7 +34,7 @@
<!-- document.getElementsByClassName('nav-item nav-link ml-3')[0].hidden=false-->
<!-- document.getElementById('reset-password').hidden=false-->
<a *ngIf="isAdmin" class="nav-item nav-link ml-3" (click)="changeTitle('Settings')" data-bs-toggle="tab"
<a class="nav-item nav-link ml-3" (click)="changeTitle('Settings')" data-bs-toggle="tab"
href="#reset-password">
<i class="fa fa-cogs"></i>
Settings
@ -433,6 +441,7 @@
</label>
</div>
</div>
<fieldset class="form-group">
<div class="form-check">
<label class="form-check-label">

View File

@ -4,7 +4,9 @@ import {UserComponent} from './user.component';
import {HttpClient} from "@angular/common/http";
import {Router} from "@angular/router";
import {NotificationService} from "../../service/notification.service";
import {AuthenticationService} from "../../service/authentication.service";
import { AuthenticationService } from 'src/app/service/authentication.service';
;
describe('UserComponent', () => {
let component: UserComponent;

View File

@ -7,7 +7,9 @@ import {NotificationType} from "../../notification/notification-type";
import {HttpErrorResponse, HttpEvent, HttpEventType} from "@angular/common/http";
import {NgForm} from "@angular/forms";
import {CustomHttpResponse} from "../../dto/custom-http-response";
import {AuthenticationService} from "../../service/authentication.service";
import { AuthenticationService } from 'src/app/service/authentication.service';
;
import {Router} from "@angular/router";
import {FileUploadStatus} from "../../model/file-upload.status";
import {Role} from "../../enum/role.enum";
@ -23,6 +25,7 @@ export class UserComponent implements OnInit, OnDestroy {
private titleSubject = new BehaviorSubject<string>('Users');
public titleAction$ = this.titleSubject.asObservable();
public users: User[] = [];
public loggedInUser: User;
@ -55,6 +58,11 @@ export class UserComponent implements OnInit, OnDestroy {
this.titleSubject.next(title);
}
handleTitleChange(title: string): void {
this.titleSubject.next(title);
}
public getUsers(showNotification: boolean) {
this.refreshing = true;

View File

@ -0,0 +1,6 @@
export enum WorkingStatus {
ACTIVE = 'ACTIVE',
ON_LEAVE = 'ON_LEAVE',
RETIRED = 'RETIRED'
}

View File

@ -2,7 +2,7 @@ import {TestBed} from '@angular/core/testing';
import {AuthenticationGuard} from './authentication.guard';
import {HttpClient} from "@angular/common/http";
import {NotificationModule} from "../notification/notification.module";
import {NotificationModule} from "../../notification/notification.module";
import {RouterTestingModule} from "@angular/router/testing";
describe('AuthenticationGuard', () => {

View File

@ -1,9 +1,10 @@
import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from '@angular/router';
import {Observable} from 'rxjs';
import {AuthenticationService} from "../service/authentication.service";
import {NotificationService} from "../service/notification.service";
import {NotificationType} from "../notification/notification-type";
import {AuthenticationService} from "../service/authentication.service";
// import { AuthenticationService } from '../service/authentication.service';
@Injectable({
providedIn: 'root'

View File

@ -2,7 +2,7 @@ import {Injectable} from '@angular/core';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Observable} from 'rxjs';
import {environment} from "../../environments/environment";
import {AuthenticationService} from "../service/authentication.service";
import {AuthenticationService} from '../service/authentication.service' ;
@Injectable()
export class AuthInterceptor implements HttpInterceptor {

View File

@ -0,0 +1,16 @@
import { WorkingStatus } from "../enum/WorkingStatus";
export class Professor {
// id : number;
professorId: string; // UUID
firstName: string;
lastName: string;
email: string;
department: string;
position: string;
officeLocation: string;
status: WorkingStatus; // Assuming status is represented as a string in the DTO
joinDate: Date; // LocalDateTime as a string
profileImageUrl?: string; // Optional, URL to the profile image
// profileImage?: File; // Optional, used for uploading profile images
}

View File

@ -6,6 +6,7 @@ import {Observable} from "rxjs";
import {User} from "../model/user";
import {JwtHelperService} from "@auth0/angular-jwt";
import {Role} from "../enum/role.enum";
import { Router } from '@angular/router';
@Injectable({
providedIn: 'root'
@ -24,7 +25,7 @@ export class AuthenticationService {
//first install this module: `npm install @auth0/angular-jwt`
private jwtHelper: JwtHelperService = new JwtHelperService();
constructor(private httpClient: HttpClient) {
constructor(private httpClient: HttpClient,private router: Router) {
}
public login(userDto: UserLogin): Observable<HttpResponse<User>> {
@ -43,6 +44,8 @@ export class AuthenticationService {
this.storage.removeItem(this.JWT_TOKEN_STORAGE_KEY);
this.storage.removeItem(this.USER_STORAGE_KEY);
this.storage.removeItem("users");
this.router.navigateByUrl('/login');
}
public saveToken(token: string): void {

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { BlogService } from './blog.service';
describe('BlogService', () => {
let service: BlogService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(BlogService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,50 @@
// post.model.ts
export interface Blog {
id?: number;
title: string;
content: string;
professors: { id: number, name: string }[]; // Adjust this as per your entity structure
tags: string[];
isPosted: boolean;
}
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class BlogService {
private apiUrl = environment.apiUrl+'/api/posts'; // Updated to match the Spring Boot controller
constructor(private http: HttpClient) {}
// Get all blogs
getBlogs(): Observable<Blog[]> {
return this.http.get<Blog[]>(this.apiUrl);
}
// Get a single blog by ID
getBlog(id: number): Observable<Blog> {
return this.http.get<Blog>(`${this.apiUrl}/${id}`);
}
// Create a new blog
createBlog(blog: Blog): Observable<Blog> {
return this.http.post<Blog>(this.apiUrl, blog);
}
// Update an existing blog
updateBlog(id: number, blog: Blog): Observable<Blog> {
return this.http.put<Blog>(`${this.apiUrl}/${id}`, blog);
}
// Delete a blog
deleteBlog(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ProfessorService } from './professor.service';
describe('ProfessorService', () => {
let service: ProfessorService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ProfessorService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,111 @@
import { Injectable } from '@angular/core';
import { environment } from "../../environments/environment";
import { HttpClient, HttpEvent } from "@angular/common/http";
import { Observable } from "rxjs";
import { Professor } from "../model/Professor"; // Ensure this model is correctly defined
import { CustomHttpResponse } from "../dto/custom-http-response";
import { map } from "rxjs/operators";
@Injectable({
providedIn: 'root'
})
export class ProfessorService {
private host: string = environment.apiUrl;
private storage = localStorage;
private selectedProfessor: Professor;
constructor(private httpClient: HttpClient) {
}
public getAllProfessors(): Observable<ProfessorPage> {
return this.httpClient
.get<ProfessorPage>(`${this.host}/professor?size=2147483647`);
}
public addProfessor(formData: FormData): Observable<Professor> {
return this.httpClient
.post<Professor>(`${this.host}/professor/add`, formData);
}
public updateProfessor(professorId: string, formData: FormData): Observable<Professor> {
return this.httpClient
.put<Professor>(`${this.host}/professor/${professorId}`, formData);
}
public deleteProfessor(professorId: string): Observable<CustomHttpResponse> {
return this.httpClient
.delete<CustomHttpResponse>(`${this.host}/professor/${professorId}`);
}
public updateProfileImage(professorId: string, formData: FormData): Observable<HttpEvent<Professor>> {
return this.httpClient
.put<Professor>(`${this.host}/user/${professorId}/profile-image`, formData,
{
reportProgress: true,
observe: 'events'
});
}
public addProfessorsToLocalStorage(professors: Professor[]) {
this.storage.setItem('professors', JSON.stringify(professors));
}
public getProfessorsFromLocalStorage(): Professor[] {
let professors = this.storage.getItem('professors');
if (professors) {
return JSON.parse(professors);
}
return [];
}
public createProfessorFormData(professor: Professor, profileImage: File | null): FormData {
const formData = new FormData();
formData.append('firstName', professor.firstName);
formData.append('lastName', professor.lastName);
formData.append('email', professor.email);
formData.append('department', professor.department);
formData.append('position', professor.position);
formData.append('officeLocation', professor.officeLocation);
formData.append('status', professor.status);
// formData.append('joinDate', professor.joinDate.toString()); // Convert LocalDateTime to string
if (profileImage)
formData.append('profileImage', profileImage);
return formData;
}
public setSelectedProfessor(professor: Professor): void {
this.selectedProfessor = professor;
}
public getSelectedProfessor(): Professor {
return this.selectedProfessor;
}
public findProfessorById(id: string): Professor | Observable<Professor> {
let cachedProfessors = this.getProfessorsFromLocalStorage();
const foundProfessor = cachedProfessors.find((p) => p.professorId === id);
if (foundProfessor) return foundProfessor;
return this.getAllProfessors()
.pipe(
map((page: ProfessorPage) => page.content),
map(professors => professors.find(p => p.professorId === id)!)
);
}
}
export interface ProfessorPage {
content: Professor[];
last: boolean;
first: boolean;
totalElements: number;
size: number;
numberOfElements: number;
number: number;
empty: boolean;
}

View File

@ -19,6 +19,8 @@ export class UserService {
constructor(private httpClient: HttpClient) {
}
public getAllUsers(): Observable<UserPage> {
return this.httpClient
.get<UserPage>(`${this.host}/user?size=2147483647`);

View File

@ -1,8 +1,9 @@
import {enableProdMode} from '@angular/core';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app/app.module';
// import {AppModule} from './app/app.module';
import {environment} from './environments/environment';
import { AppModule } from './app/app.module';
if (environment.production) {
enableProdMode();

View File

@ -3,6 +3,9 @@
@import 'https://pro.fontawesome.com/releases/v5.10.0/css/all.css';
@import "~angular-notifier/styles";
/*
td {
cursor: pointer;
}
@ -36,4 +39,4 @@ td {
background: white;
cursor: inherit;
display: block;
}
} */