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/platform-browser-dynamic": "~12.2.0",
|
||||||
"@angular/router": "~12.2.0",
|
"@angular/router": "~12.2.0",
|
||||||
"@auth0/angular-jwt": "^5.0.2",
|
"@auth0/angular-jwt": "^5.0.2",
|
||||||
|
"@josipv/angular-editor-k2": "^2.20.0",
|
||||||
"angular-notifier": "^9.1.0",
|
"angular-notifier": "^9.1.0",
|
||||||
"rxjs": "~6.6.0",
|
"rxjs": "~6.6.0",
|
||||||
"subsink": "^1.0.2",
|
"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 {UserEditComponent} from "./component/management/users/user-edit/user-edit.component";
|
||||||
import {UserViewComponent} from "./component/management/users/user-view/user-view.component";
|
import {UserViewComponent} from "./component/management/users/user-view/user-view.component";
|
||||||
import {UserResolver} from "./component/management/users/user-resolver.service";
|
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 = [
|
export const routes: Routes = [
|
||||||
{path: 'login', component: LoginComponent},
|
{ path: 'home', component: HomeComponent },
|
||||||
{path: 'register', component: RegisterComponent},
|
{ path: 'login', component: LoginComponent },
|
||||||
{path: 'user/management', component: UserComponent, canActivate: [AuthenticationGuard]},
|
{ path: 'register', component: RegisterComponent },
|
||||||
{
|
{ path: 'settings', component: SettingsComponent, canActivate: [AuthenticationGuard] },
|
||||||
path: 'management', component: ManagementComponent, canActivate: [AuthenticationGuard],
|
{ path: 'profile', component: ProfileComponent, canActivate: [AuthenticationGuard] },
|
||||||
children: [
|
{ path: 'events', component: EventComponent, canActivate: [AuthenticationGuard] },
|
||||||
{path: 'settings', component: SettingsComponent},
|
{ path: 'blogs', component: BlogComponent, canActivate: [AuthenticationGuard] },
|
||||||
{path: 'profile', component: ProfileComponent},
|
{ path: 'user/management', component: UserComponent, canActivate: [AuthenticationGuard] },
|
||||||
{
|
{ path: 'professor/management', component: ProfessorComponent, canActivate: [AuthenticationGuard] },
|
||||||
path: 'users', component: UsersComponent,
|
|
||||||
children: [
|
// {
|
||||||
{path: ':id/view', component: UserViewComponent, resolve: {user: UserResolver}},
|
// path: 'management', component: ManagementComponent, canActivate: [AuthenticationGuard],
|
||||||
{path: ':id/edit', component: UserEditComponent}
|
// 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'}
|
{path: '', redirectTo: '/login', pathMatch: 'full'}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -7,12 +7,11 @@ import {AuthenticationService} from "./service/authentication.service";
|
|||||||
import {UserService} from "./service/user.service";
|
import {UserService} from "./service/user.service";
|
||||||
import {AuthInterceptor} from "./interceptor/auth.interceptor";
|
import {AuthInterceptor} from "./interceptor/auth.interceptor";
|
||||||
import {AuthenticationGuard} from "./guard/authentication.guard";
|
import {AuthenticationGuard} from "./guard/authentication.guard";
|
||||||
import {NotificationModule} from "./notification/notification.module";
|
|
||||||
import {LoginComponent} from './component/login/login.component';
|
import {LoginComponent} from './component/login/login.component';
|
||||||
import {RegisterComponent} from './component/register/register.component';
|
import {RegisterComponent} from './component/register/register.component';
|
||||||
import {UserComponent} from './component/user/user.component';
|
import {UserComponent} from './component/user/user.component';
|
||||||
import {AppRoutingModule} from './app-routing.module';
|
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 {ManagementComponent} from './component/management/management.component';
|
||||||
import {UsersComponent} from './component/management/users/users.component';
|
import {UsersComponent} from './component/management/users/users.component';
|
||||||
import {SettingsComponent} from './component/management/settings/settings.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 {UsersTableComponent} from './component/management/users/users-table/users-table.component';
|
||||||
import {UserViewComponent} from './component/management/users/user-view/user-view.component';
|
import {UserViewComponent} from './component/management/users/user-view/user-view.component';
|
||||||
import {UserEditComponent} from './component/management/users/user-edit/user-edit.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({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -33,16 +44,24 @@ import {UserEditComponent} from './component/management/users/user-edit/user-edi
|
|||||||
ProfileComponent,
|
ProfileComponent,
|
||||||
UsersTableComponent,
|
UsersTableComponent,
|
||||||
UserViewComponent,
|
UserViewComponent,
|
||||||
UserEditComponent
|
UserEditComponent,
|
||||||
|
ProfessorComponent,
|
||||||
|
MenuComponent,
|
||||||
|
HomeComponent,
|
||||||
|
BlogComponent,
|
||||||
|
EventComponent
|
||||||
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
NotificationModule,
|
NotificationModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
FormsModule
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
AngularEditorModule
|
||||||
],
|
],
|
||||||
providers: [AuthenticationGuard, AuthenticationService, UserService,
|
providers: [AuthenticationGuard, AuthenticationService, UserService,BlogService,
|
||||||
{provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true}
|
{provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true}
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
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 {Component, OnDestroy, OnInit} from '@angular/core';
|
||||||
import {Router} from "@angular/router";
|
import {Router} from "@angular/router";
|
||||||
import {AuthenticationService} from "../../service/authentication.service";
|
|
||||||
import {NotificationService} from "../../service/notification.service";
|
import {NotificationService} from "../../service/notification.service";
|
||||||
import {NotificationType} from "../../notification/notification-type";
|
import {NotificationType} from "../../notification/notification-type";
|
||||||
import {Subscription} from "rxjs";
|
import {Subscription} from "rxjs";
|
||||||
@ -8,6 +8,8 @@ import {UserLogin} from "../../dto/user-login";
|
|||||||
import {HttpErrorResponse, HttpResponse} from "@angular/common/http";
|
import {HttpErrorResponse, HttpResponse} from "@angular/common/http";
|
||||||
import {User} from "../../model/user";
|
import {User} from "../../model/user";
|
||||||
import {HeaderType} from "../../enum/header-type.enum";
|
import {HeaderType} from "../../enum/header-type.enum";
|
||||||
|
import { AuthenticationService } from 'src/app/service/authentication.service';
|
||||||
|
;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
@ -26,7 +28,8 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.authenticationService.isUserLoggedIn()) {
|
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");
|
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.authenticationService.addUserToLocalStorage(response.body!);
|
||||||
|
|
||||||
this.router.navigateByUrl('/management/users');
|
this.router.navigateByUrl('/home');
|
||||||
|
// this.router.navigateByUrl('/management/users');
|
||||||
this.showLoading = false;
|
this.showLoading = false;
|
||||||
},
|
},
|
||||||
(errorResponse: HttpErrorResponse) => {
|
(errorResponse: HttpErrorResponse) => {
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {Role} from "../../enum/role.enum";
|
import {Role} from "../../enum/role.enum";
|
||||||
import {User} from "../../model/user";
|
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 {Router} from "@angular/router";
|
||||||
import {BehaviorSubject} from "rxjs";
|
import {BehaviorSubject} from "rxjs";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
<!-- user profile -->
|
<!-- user profile -->
|
||||||
|
|
||||||
|
<app-menu></app-menu>
|
||||||
|
|
||||||
<div class="tab-pane fade show active" id="profile">
|
<div class="tab-pane fade show active" id="profile">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row flex-lg-nowrap">
|
<div class="row flex-lg-nowrap">
|
||||||
|
|||||||
@ -1,13 +1,16 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {AuthenticationService} from "../../../service/authentication.service";
|
|
||||||
import {User} from "../../../model/user";
|
import {User} from "../../../model/user";
|
||||||
import {FileUploadStatus} from "../../../model/file-upload.status";
|
import {FileUploadStatus} from "../../../model/file-upload.status";
|
||||||
import {NotificationType} from "../../../notification/notification-type";
|
import {NotificationType} from "../../../notification/notification-type";
|
||||||
import {HttpErrorResponse, HttpEvent, HttpEventType} from "@angular/common/http";
|
import {HttpErrorResponse, HttpEvent, HttpEventType} from "@angular/common/http";
|
||||||
import {SubSink} from "subsink";
|
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 {NotificationService} from "../../../service/notification.service";
|
||||||
import {Router} from "@angular/router";
|
import {Router} from "@angular/router";
|
||||||
|
import { AuthenticationService } from 'src/app/service/authentication.service';
|
||||||
|
;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-profile',
|
selector: 'app-profile',
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
<!-- change password -->
|
<!-- change password -->
|
||||||
|
|
||||||
|
<app-menu></app-menu>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
<div *ngIf="isAdmin" class="tab-pane fade show active" id="reset-password">
|
<div *ngIf="isAdmin" class="tab-pane fade show active" id="reset-password">
|
||||||
<form #resetPasswordForm="ngForm" (ngSubmit)="onResetPassword(resetPasswordForm)">
|
<form #resetPasswordForm="ngForm" (ngSubmit)="onResetPassword(resetPasswordForm)">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@ -17,4 +22,5 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
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 {NgForm} from "@angular/forms";
|
||||||
import {CustomHttpResponse} from "../../../dto/custom-http-response";
|
import {CustomHttpResponse} from "../../../dto/custom-http-response";
|
||||||
import {NotificationType} from "../../../notification/notification-type";
|
import {NotificationType} from "../../../notification/notification-type";
|
||||||
import {HttpErrorResponse} from "@angular/common/http";
|
import {HttpErrorResponse} from "@angular/common/http";
|
||||||
import {SubSink} from "subsink";
|
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 {NotificationService} from "../../../service/notification.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import {Injectable} from '@angular/core';
|
|||||||
import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from "@angular/router";
|
import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from "@angular/router";
|
||||||
import {User} from "../../../model/user";
|
import {User} from "../../../model/user";
|
||||||
import {Observable} from "rxjs";
|
import {Observable} from "rxjs";
|
||||||
import {UserService} from "../../../service/user.service";
|
import { UserService } from 'src/app/service/user.service';
|
||||||
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {UserService} from "../../../../service/user.service";
|
|
||||||
import {User} from "../../../../model/user";
|
import {User} from "../../../../model/user";
|
||||||
import {ActivatedRoute, Router} from "@angular/router";
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
|
import { UserService } from 'src/app/service/user.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-user-view',
|
selector: 'app-user-view',
|
||||||
|
|||||||
@ -3,7 +3,8 @@ import {User} from "../../../../model/user";
|
|||||||
import {NotificationType} from "../../../../notification/notification-type";
|
import {NotificationType} from "../../../../notification/notification-type";
|
||||||
import {HttpErrorResponse} from "@angular/common/http";
|
import {HttpErrorResponse} from "@angular/common/http";
|
||||||
import {SubSink} from "subsink";
|
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 {NotificationService} from "../../../../service/notification.service";
|
||||||
import {AuthenticationService} from "../../../../service/authentication.service";
|
import {AuthenticationService} from "../../../../service/authentication.service";
|
||||||
import {ActivatedRoute, Router} from "@angular/router";
|
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>
|
*ngIf="emailInput.invalid && emailInput.touched">Please enter an email.</span>
|
||||||
|
|
||||||
<div class="d-flex justify-content-center mt-3 login_container">
|
<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">
|
<button type="submit" [disabled]="registerForm.invalid || showLoading" name="button" class="btn login_btn">
|
||||||
<i class="fas fa-spinner fa-spin" *ngIf="showLoading"></i>
|
<i class="fas fa-spinner fa-spin" *ngIf="showLoading"></i>
|
||||||
<span *ngIf="showLoading">{{showLoading ? 'Loading...' : 'Register'}}</span>
|
<span *ngIf="showLoading">{{showLoading ? 'Loading...' : 'Register'}}</span>
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||||
import {User} from "../../model/user";
|
import {User} from "../../model/user";
|
||||||
import {Router} from "@angular/router";
|
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 {NotificationService} from "../../service/notification.service";
|
||||||
import {Subscription} from "rxjs";
|
import {Subscription} from "rxjs";
|
||||||
import {HttpErrorResponse} from "@angular/common/http";
|
import {HttpErrorResponse} from "@angular/common/http";
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
<div class="container">
|
<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 class="col-md-4">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
@ -11,13 +15,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- nav bar -->
|
<!-- 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="collapse navbar-collapse" id="navbarCollapse">
|
||||||
<div class="nav nav-pills">
|
<div class="nav nav-pills">
|
||||||
<a class="nav-item nav-link active ml-1" (click)="changeTitle('Users')" data-bs-toggle="tab" href="#users">
|
<a class="nav-item nav-link active ml-1" (click)="changeTitle('Users')" data-bs-toggle="tab" href="#users">
|
||||||
<i class="fa fa-users"></i>
|
<i class="fa fa-users"></i>
|
||||||
Users
|
Users
|
||||||
</a>
|
</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-->
|
<!-- Possible attacks-->
|
||||||
<!-- document.getElementsByClassName('nav-item nav-link ml-3')[0].click()-->
|
<!-- 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.getElementsByClassName('nav-item nav-link ml-3')[0].hidden=false-->
|
||||||
<!-- document.getElementById('reset-password').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">
|
href="#reset-password">
|
||||||
<i class="fa fa-cogs"></i>
|
<i class="fa fa-cogs"></i>
|
||||||
Settings
|
Settings
|
||||||
@ -433,6 +441,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<fieldset class="form-group">
|
<fieldset class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label class="form-check-label">
|
<label class="form-check-label">
|
||||||
|
|||||||
@ -4,7 +4,9 @@ import {UserComponent} from './user.component';
|
|||||||
import {HttpClient} from "@angular/common/http";
|
import {HttpClient} from "@angular/common/http";
|
||||||
import {Router} from "@angular/router";
|
import {Router} from "@angular/router";
|
||||||
import {NotificationService} from "../../service/notification.service";
|
import {NotificationService} from "../../service/notification.service";
|
||||||
import {AuthenticationService} from "../../service/authentication.service";
|
import { AuthenticationService } from 'src/app/service/authentication.service';
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
describe('UserComponent', () => {
|
describe('UserComponent', () => {
|
||||||
let component: UserComponent;
|
let component: UserComponent;
|
||||||
|
|||||||
@ -7,7 +7,9 @@ import {NotificationType} from "../../notification/notification-type";
|
|||||||
import {HttpErrorResponse, HttpEvent, HttpEventType} from "@angular/common/http";
|
import {HttpErrorResponse, HttpEvent, HttpEventType} from "@angular/common/http";
|
||||||
import {NgForm} from "@angular/forms";
|
import {NgForm} from "@angular/forms";
|
||||||
import {CustomHttpResponse} from "../../dto/custom-http-response";
|
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 {Router} from "@angular/router";
|
||||||
import {FileUploadStatus} from "../../model/file-upload.status";
|
import {FileUploadStatus} from "../../model/file-upload.status";
|
||||||
import {Role} from "../../enum/role.enum";
|
import {Role} from "../../enum/role.enum";
|
||||||
@ -23,6 +25,7 @@ export class UserComponent implements OnInit, OnDestroy {
|
|||||||
private titleSubject = new BehaviorSubject<string>('Users');
|
private titleSubject = new BehaviorSubject<string>('Users');
|
||||||
public titleAction$ = this.titleSubject.asObservable();
|
public titleAction$ = this.titleSubject.asObservable();
|
||||||
|
|
||||||
|
|
||||||
public users: User[] = [];
|
public users: User[] = [];
|
||||||
|
|
||||||
public loggedInUser: User;
|
public loggedInUser: User;
|
||||||
@ -55,6 +58,11 @@ export class UserComponent implements OnInit, OnDestroy {
|
|||||||
this.titleSubject.next(title);
|
this.titleSubject.next(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTitleChange(title: string): void {
|
||||||
|
this.titleSubject.next(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public getUsers(showNotification: boolean) {
|
public getUsers(showNotification: boolean) {
|
||||||
this.refreshing = true;
|
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 {AuthenticationGuard} from './authentication.guard';
|
||||||
import {HttpClient} from "@angular/common/http";
|
import {HttpClient} from "@angular/common/http";
|
||||||
import {NotificationModule} from "../notification/notification.module";
|
import {NotificationModule} from "../../notification/notification.module";
|
||||||
import {RouterTestingModule} from "@angular/router/testing";
|
import {RouterTestingModule} from "@angular/router/testing";
|
||||||
|
|
||||||
describe('AuthenticationGuard', () => {
|
describe('AuthenticationGuard', () => {
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from '@angular/router';
|
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from '@angular/router';
|
||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
import {AuthenticationService} from "../service/authentication.service";
|
|
||||||
import {NotificationService} from "../service/notification.service";
|
import {NotificationService} from "../service/notification.service";
|
||||||
import {NotificationType} from "../notification/notification-type";
|
import {NotificationType} from "../notification/notification-type";
|
||||||
|
import {AuthenticationService} from "../service/authentication.service";
|
||||||
|
// import { AuthenticationService } from '../service/authentication.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import {Injectable} from '@angular/core';
|
|||||||
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
|
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
|
||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
import {environment} from "../../environments/environment";
|
import {environment} from "../../environments/environment";
|
||||||
import {AuthenticationService} from "../service/authentication.service";
|
import {AuthenticationService} from '../service/authentication.service' ;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthInterceptor implements HttpInterceptor {
|
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 {User} from "../model/user";
|
||||||
import {JwtHelperService} from "@auth0/angular-jwt";
|
import {JwtHelperService} from "@auth0/angular-jwt";
|
||||||
import {Role} from "../enum/role.enum";
|
import {Role} from "../enum/role.enum";
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -24,7 +25,7 @@ export class AuthenticationService {
|
|||||||
//first install this module: `npm install @auth0/angular-jwt`
|
//first install this module: `npm install @auth0/angular-jwt`
|
||||||
private jwtHelper: JwtHelperService = new JwtHelperService();
|
private jwtHelper: JwtHelperService = new JwtHelperService();
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient) {
|
constructor(private httpClient: HttpClient,private router: Router) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public login(userDto: UserLogin): Observable<HttpResponse<User>> {
|
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.JWT_TOKEN_STORAGE_KEY);
|
||||||
this.storage.removeItem(this.USER_STORAGE_KEY);
|
this.storage.removeItem(this.USER_STORAGE_KEY);
|
||||||
this.storage.removeItem("users");
|
this.storage.removeItem("users");
|
||||||
|
this.router.navigateByUrl('/login');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveToken(token: string): void {
|
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) {
|
constructor(private httpClient: HttpClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public getAllUsers(): Observable<UserPage> {
|
public getAllUsers(): Observable<UserPage> {
|
||||||
return this.httpClient
|
return this.httpClient
|
||||||
.get<UserPage>(`${this.host}/user?size=2147483647`);
|
.get<UserPage>(`${this.host}/user?size=2147483647`);
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import {enableProdMode} from '@angular/core';
|
import {enableProdMode} from '@angular/core';
|
||||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
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 {environment} from './environments/environment';
|
||||||
|
import { AppModule } from './app/app.module';
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
enableProdMode();
|
enableProdMode();
|
||||||
|
|||||||
@ -3,6 +3,9 @@
|
|||||||
@import 'https://pro.fontawesome.com/releases/v5.10.0/css/all.css';
|
@import 'https://pro.fontawesome.com/releases/v5.10.0/css/all.css';
|
||||||
@import "~angular-notifier/styles";
|
@import "~angular-notifier/styles";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
td {
|
td {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -36,4 +39,4 @@ td {
|
|||||||
background: white;
|
background: white;
|
||||||
cursor: inherit;
|
cursor: inherit;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
} */
|
||||||
|
|||||||
Reference in New Issue
Block a user