stable after blogs
This commit is contained in:
7
support-portal-frontend/mac_config.txt
Normal file
7
support-portal-frontend/mac_config.txt
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
[[ -s $HOME/.nvm/nvm.sh ]] && . $HOME/.nvm/nvm.sh # This loads NVM
|
||||
|
||||
|
||||
nvm use 12
|
||||
|
||||
14548
support-portal-frontend/package-lock.json
generated
14548
support-portal-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
|
||||
@ -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'}
|
||||
];
|
||||
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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">✓</span> <!-- Check mark for posted -->
|
||||
<span *ngIf="!blog.posted" class="text-danger">✗</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>
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
187
support-portal-frontend/src/app/component/blog/blog.component.ts
Normal file
187
support-portal-frontend/src/app/component/blog/blog.component.ts
Normal 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: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
<app-menu></app-menu>
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
@ -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 */
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
@ -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) => {
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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">×</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">×</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">×</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>
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
<span *ngIf="showLoading">{{showLoading ? 'Loading...' : 'Register'}}</span>
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
6
support-portal-frontend/src/app/enum/WorkingStatus.ts
Normal file
6
support-portal-frontend/src/app/enum/WorkingStatus.ts
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
export enum WorkingStatus {
|
||||
ACTIVE = 'ACTIVE',
|
||||
ON_LEAVE = 'ON_LEAVE',
|
||||
RETIRED = 'RETIRED'
|
||||
}
|
||||
@ -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', () => {
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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 {
|
||||
|
||||
16
support-portal-frontend/src/app/model/Professor.ts
Normal file
16
support-portal-frontend/src/app/model/Professor.ts
Normal 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
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
16
support-portal-frontend/src/app/service/blog.service.spec.ts
Normal file
16
support-portal-frontend/src/app/service/blog.service.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
50
support-portal-frontend/src/app/service/blog.service.ts
Normal file
50
support-portal-frontend/src/app/service/blog.service.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
111
support-portal-frontend/src/app/service/professor.service.ts
Normal file
111
support-portal-frontend/src/app/service/professor.service.ts
Normal 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;
|
||||
}
|
||||
@ -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`);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
} */
|
||||
|
||||
Reference in New Issue
Block a user