professor and education updated

This commit is contained in:
2026-04-30 11:17:04 +05:30
parent 0a60f016e9
commit a8b54c1209
6 changed files with 150 additions and 182 deletions

View File

@ -120,10 +120,7 @@
</label>
<input type="text" id="instructor" class="form-input" formControlName="instructor"
placeholder="e.g., Dr. John Smith">
<div *ngIf="courseForm.get('instructor')?.invalid && courseForm.get('instructor')?.touched" class="error-message">
<i class="fa fa-exclamation-circle"></i>
Instructor is required.
</div>
</div>
</div>
@ -135,10 +132,7 @@
</label>
<input type="text" id="duration" class="form-input" formControlName="duration"
placeholder="e.g., 3 Days, 2 Years">
<div *ngIf="courseForm.get('duration')?.invalid && courseForm.get('duration')?.touched" class="error-message">
<i class="fa fa-exclamation-circle"></i>
Duration is required.
</div>
</div>
<div class="form-group">
@ -148,10 +142,7 @@
</label>
<input type="number" id="seats" class="form-input" formControlName="seats"
placeholder="Enter available seats" min="1">
<div *ngIf="courseForm.get('seats')?.invalid && courseForm.get('seats')?.touched" class="error-message">
<i class="fa fa-exclamation-circle"></i>
Number of seats is required.
</div>
</div>
<div class="form-group">
@ -178,10 +169,7 @@
<option value="Fellowship">Fellowship</option>
<option value="Course">Course</option>
</select>
<div *ngIf="courseForm.get('category')?.invalid && courseForm.get('category')?.touched" class="error-message">
<i class="fa fa-exclamation-circle"></i>
Category is required.
</div>
</div>
<div class="form-group">
@ -197,10 +185,7 @@
<option value="Professional">Professional</option>
<option value="Post-Doctoral">Post-Doctoral</option>
</select>
<div *ngIf="courseForm.get('level')?.invalid && courseForm.get('level')?.touched" class="error-message">
<i class="fa fa-exclamation-circle"></i>
Level is required.
</div>
</div>
<div class="form-group">
@ -372,10 +357,7 @@
</label>
<input type="text" id="eventSchedule" class="form-input" formControlName="schedule"
placeholder="e.g., Q3 2025, Monthly Sessions, Ongoing">
<div *ngIf="upcomingEventForm.get('schedule')?.invalid && upcomingEventForm.get('schedule')?.touched" class="error-message">
<i class="fa fa-exclamation-circle"></i>
Schedule is required.
</div>
</div>
<div class="form-group">

View File

@ -44,11 +44,11 @@ export class EducationComponent implements OnInit {
this.courseForm = this.fb.group({
title: ['', Validators.required],
description: ['', Validators.required],
duration: ['', Validators.required],
seats: ['', [Validators.required, Validators.min(1)]],
category: ['', Validators.required],
level: ['', Validators.required],
instructor: ['', Validators.required],
duration: [''],
seats: [''],
category: [''],
level: [''],
instructor: [''],
price: [''],
startDate: [''],
eligibility: [''],
@ -59,7 +59,7 @@ export class EducationComponent implements OnInit {
this.upcomingEventForm = this.fb.group({
title: ['', Validators.required],
description: ['', Validators.required],
schedule: ['', Validators.required],
schedule: [''],
eventDate: [''],
isActive: [true]
});
@ -381,5 +381,4 @@ export class EducationComponent implements OnInit {
getApplicationCount(courseId?: number): number {
if (!courseId) return 0;
return this.applications.filter(app => app.course?.id === courseId).length;
}}
}}

View File

@ -25,6 +25,12 @@ interface Award {
imageUrl?: string;
}
interface Skill {
id?: number;
name: string;
level: number;
}
@Component({
selector: 'app-professor',
templateUrl: './professor.component.html',
@ -63,7 +69,8 @@ export class ProfessorComponent implements OnInit, OnDestroy {
description: '',
designation: '',
workDays: [],
awards: []
awards: [],
skills: []
};
public profileImageFileName: string | null;
@ -74,10 +81,14 @@ export class ProfessorComponent implements OnInit, OnDestroy {
public availableDays: string[] = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
public selectedWorkDays: { [key: string]: boolean } = {};
// Awards management
// Awards management
public newProfessorAwards: Award[] = [];
public selectedProfessorAwards: Award[] = [];
// ✅ Skills management
public newProfessorSkills: Skill[] = [];
public selectedProfessorSkills: Skill[] = [];
private closeModal(modalId: string): void {
const modalElement = document.getElementById(modalId);
if (!modalElement) return;
@ -90,7 +101,6 @@ export class ProfessorComponent implements OnInit, OnDestroy {
document.querySelectorAll('.modal-backdrop').forEach(b => b.remove());
}
constructor(
private professorService: ProfessorService,
private notificationService: NotificationService,
@ -101,7 +111,7 @@ export class ProfessorComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.getProfessors(true);
this.loggedInUser = this.authenticationService.getUserFromLocalStorage();
this.initializeAwards();
this.initializeCollections();
this.setupModalEventListeners();
}
@ -109,16 +119,15 @@ export class ProfessorComponent implements OnInit, OnDestroy {
this.subs.unsubscribe();
}
private initializeAwards(): void {
// ✅ FIX: Renamed and now initializes both awards and skills
private initializeCollections(): void {
this.newProfessorAwards = [];
this.selectedProfessorAwards = [];
this.newProfessorSkills = [];
this.selectedProfessorSkills = [];
}
/**
* Setup event listeners for Bootstrap modals to clear data when they close
*/
private setupModalEventListeners(): void {
// Listen for edit modal close event
const editModal = document.getElementById('editProfessorModal');
if (editModal) {
editModal.addEventListener('hidden.bs.modal', () => {
@ -126,7 +135,6 @@ export class ProfessorComponent implements OnInit, OnDestroy {
});
}
// Listen for add modal close event
const addModal = document.getElementById('addProfessorModal');
if (addModal) {
addModal.addEventListener('hidden.bs.modal', () => {
@ -135,42 +143,33 @@ export class ProfessorComponent implements OnInit, OnDestroy {
}
}
/**
* Clear only profile image related data (used after successful operations)
*/
private invalidateVariables(): void {
this.profileImage = null;
this.profileImageFileName = null;
}
/**
* Clear all data related to adding a new professor
*/
// ✅ FIX: Now clears skills too
private clearNewProfessorData(): void {
this.profileImage = null;
this.profileImageFileName = null;
this.selectedWorkDays = {};
this.newProfessorAwards = [];
this.newProfessorSkills = [];
}
/**
* Clear all data related to editing a professor
*/
// ✅ FIX: Now clears skills too
private clearEditProfessorData(): void {
this.profileImage = null;
this.profileImageFileName = null;
this.selectedWorkDays = {};
this.selectedProfessorAwards = [];
this.selectedProfessorSkills = [];
}
// Award management methods
// ─── Award management methods ────────────────────────────────────────────────
public addNewAward(): void {
this.newProfessorAwards.push({
title: '',
year: '',
description: '',
imageUrl: ''
});
this.newProfessorAwards.push({ title: '', year: '', description: '', imageUrl: '' });
}
public removeAward(index: number): void {
@ -178,29 +177,43 @@ export class ProfessorComponent implements OnInit, OnDestroy {
}
public addEditAward(): void {
this.selectedProfessorAwards.push({
title: '',
year: '',
description: '',
imageUrl: ''
});
this.selectedProfessorAwards.push({ title: '', year: '', description: '', imageUrl: '' });
}
public removeEditAward(index: number): void {
this.selectedProfessorAwards.splice(index, 1);
}
// Category display helper
// ─── Skill management methods ────────────────────────────────────────────────
// ✅ NEW: Add skill for new professor form
public addNewSkill(): void {
this.newProfessorSkills.push({ name: '', level: 0 });
}
// ✅ NEW: Remove skill from new professor form
public removeSkill(index: number): void {
this.newProfessorSkills.splice(index, 1);
}
// ✅ NEW: Add skill for edit professor form
public addEditSkill(): void {
this.selectedProfessorSkills.push({ name: '', level: 0 });
}
// ✅ NEW: Remove skill from edit professor form
public removeEditSkill(index: number): void {
this.selectedProfessorSkills.splice(index, 1);
}
// ─── Category display helper ─────────────────────────────────────────────────
public getCategoryDisplayName(category: string): string {
switch (category) {
case 'FACULTY':
return 'Faculty';
case 'SUPPORT_TEAM':
return 'Support Team';
case 'TRAINEE_FELLOW':
return 'Trainee/Fellow';
default:
return 'Unknown';
case 'FACULTY': return 'Faculty';
case 'SUPPORT_TEAM': return 'Support Team';
case 'TRAINEE_FELLOW': return 'Trainee/Fellow';
default: return 'Unknown';
}
}
@ -231,14 +244,18 @@ export class ProfessorComponent implements OnInit, OnDestroy {
}
public onSelectProfessor(selectedProfessor: Professor): void {
// Create a deep copy to avoid reference issues
this.selectedProfessor = JSON.parse(JSON.stringify(selectedProfessor));
// Create a separate copy for awards to display
// ✅ FIX: Load awards
this.selectedProfessorAwards = selectedProfessor.awards
? JSON.parse(JSON.stringify(selectedProfessor.awards))
: [];
// ✅ FIX: Load skills
this.selectedProfessorSkills = selectedProfessor.skills
? JSON.parse(JSON.stringify(selectedProfessor.skills))
: [];
// Set up work days for viewing
this.selectedWorkDays = {};
if (selectedProfessor.workDays && Array.isArray(selectedProfessor.workDays)) {
@ -260,19 +277,13 @@ export class ProfessorComponent implements OnInit, OnDestroy {
}
public onCategoryChange(category: string): void {
// Clear profile image if Trainee/Fellow or Support Team is selected
if (category === 'TRAINEE_FELLOW' || category === 'SUPPORT_TEAM') {
this.profileImage = null;
this.profileImageFileName = null;
// Reset file input
const fileInput = document.getElementById('newProfessorProfileImage') as HTMLInputElement;
if (fileInput) {
fileInput.value = '';
}
if (fileInput) fileInput.value = '';
const editFileInput = document.getElementById('editProfessorProfileImage') as HTMLInputElement;
if (editFileInput) {
editFileInput.value = '';
}
if (editFileInput) editFileInput.value = '';
}
}
@ -292,7 +303,6 @@ export class ProfessorComponent implements OnInit, OnDestroy {
this.getProfessors(false);
professorForm.reset();
this.notificationService.notify(NotificationType.SUCCESS, `Professor ${professor.firstName} added successfully`);
// Data will be cleared by modal close event listener
},
(errorResponse: HttpErrorResponse) => {
this.sendErrorNotification(errorResponse.error.message);
@ -333,10 +343,9 @@ export class ProfessorComponent implements OnInit, OnDestroy {
}
public onEditProfessor(professor: Professor): void {
// Create a deep copy to avoid reference issues
this.selectedProfessor = JSON.parse(JSON.stringify(professor));
// Set up work days for editing with validation
// Set up work days for editing
this.selectedWorkDays = {};
if (professor.workDays && Array.isArray(professor.workDays)) {
professor.workDays.forEach(day => {
@ -346,11 +355,16 @@ export class ProfessorComponent implements OnInit, OnDestroy {
});
}
// Set up awards for editing - create a deep copy
// ✅ FIX: Load awards for editing
this.selectedProfessorAwards = professor.awards
? JSON.parse(JSON.stringify(professor.awards))
: [];
// ✅ FIX: Load skills for editing
this.selectedProfessorSkills = professor.skills
? JSON.parse(JSON.stringify(professor.skills))
: [];
this.clickButton('openProfessorEdit');
}
@ -360,19 +374,14 @@ export class ProfessorComponent implements OnInit, OnDestroy {
return;
}
console.log('Updating professor:', this.selectedProfessor);
console.log('Selected work days:', this.selectedWorkDays);
console.log('Selected awards:', this.selectedProfessorAwards);
const formData = this.createExtendedProfessorFormData(this.selectedProfessor, this.profileImage);
this.subs.add(this.professorService.updateProfessor(this.selectedProfessor.professorId, formData).subscribe(
(professor: Professor) => {
this.closeModal('editProfessorModal');
this.getProfessors(false);
this.invalidateVariables(); // Only clear profile image related data
this.invalidateVariables();
this.notificationService.notify(NotificationType.SUCCESS, `Professor ${professor.firstName} updated successfully`);
// Other data will be cleared by modal close event listener
},
(errorResponse: HttpErrorResponse) => {
this.sendErrorNotification(errorResponse.error.message);
@ -383,65 +392,70 @@ export class ProfessorComponent implements OnInit, OnDestroy {
private createExtendedProfessorFormData(professor: any, profileImage: File | null): FormData {
const formData = new FormData();
// Basic fields
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 || '');
// ─── Basic fields ────────────────────────────────────────────────────────
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 || 'ACTIVE');
formData.append('category', professor.category || 'FACULTY');
formData.append('status', professor.status || 'ACTIVE');
formData.append('category', professor.category || 'FACULTY');
// Extended fields
formData.append('phone', professor.phone || '');
formData.append('specialty', professor.specialty || '');
formData.append('experience', professor.experience || '');
formData.append('designation', professor.designation || professor.position || '');
formData.append('description', professor.description || '');
formData.append('certification', professor.certification || '');
// ✅ FIX: Send joinDate
if (professor.joinDate) {
formData.append('joinDate', new Date(professor.joinDate).toISOString());
}
// ─── Extended fields ─────────────────────────────────────────────────────
formData.append('phone', professor.phone || '');
formData.append('specialty', professor.specialty || '');
formData.append('experience', professor.experience || '');
formData.append('designation', professor.designation || professor.position || '');
formData.append('description', professor.description || '');
formData.append('certification', professor.certification || '');
// Only include training if NOT retired
if (professor.status !== WorkingStatus.RETIRED) {
formData.append('training', professor.training || '');
} else {
formData.append('training', ''); // Clear training for retired faculty
}
formData.append('training',
professor.status !== WorkingStatus.RETIRED ? (professor.training || '') : ''
);
// Work days - collect from selectedWorkDays object
// ─── Work days ───────────────────────────────────────────────────────────
const workDays = Object.keys(this.selectedWorkDays).filter(day => this.selectedWorkDays[day]);
if (workDays.length > 0) {
workDays.forEach(day => {
formData.append('workDays', day);
});
}
workDays.forEach(day => formData.append('workDays', day));
// Awards - determine which awards array to use based on context
// ─── Awards ──────────────────────────────────────────────────────────────
const awardsToSubmit = professor.professorId ? this.selectedProfessorAwards : this.newProfessorAwards;
if (awardsToSubmit && awardsToSubmit.length > 0) {
// Filter out empty awards and only include those with at least title and year
const validAwards = awardsToSubmit.filter(award =>
award.title && award.title.trim() && award.year && award.year.trim()
);
validAwards.forEach((award, index) => {
formData.append(`awards[${index}].title`, award.title.trim());
formData.append(`awards[${index}].year`, award.year.trim());
formData.append(`awards[${index}].title`, award.title.trim());
formData.append(`awards[${index}].year`, award.year.trim());
formData.append(`awards[${index}].description`, award.description || '');
formData.append(`awards[${index}].imageUrl`, award.imageUrl || '');
formData.append(`awards[${index}].imageUrl`, award.imageUrl || '');
});
}
// Profile image - only add if not Trainee/Fellow or Support Team category
// ✅ FIX: Skills are now sent to backend
const skillsToSubmit = professor.professorId ? this.selectedProfessorSkills : this.newProfessorSkills;
if (skillsToSubmit && skillsToSubmit.length > 0) {
const validSkills = skillsToSubmit.filter(skill => skill.name && skill.name.trim());
validSkills.forEach((skill, index) => {
formData.append(`skills[${index}].name`, skill.name.trim());
formData.append(`skills[${index}].level`, String(skill.level ?? 0));
});
}
// ─── Profile image ───────────────────────────────────────────────────────
if (profileImage && professor.category !== 'TRAINEE_FELLOW' && professor.category !== 'SUPPORT_TEAM') {
formData.append('profileImage', profileImage);
}
// Log formData for debugging
// Debug log
console.log('FormData contents:');
formData.forEach((value, key) => {
console.log(`${key}:`, value);
});
formData.forEach((value, key) => console.log(`${key}:`, value));
return formData;
}
@ -469,7 +483,7 @@ export class ProfessorComponent implements OnInit, OnDestroy {
if (!this.profileImage) return;
this.refreshing = true;
const formData = new FormData();
formData.append("profileImage", this.profileImage);
formData.append('profileImage', this.profileImage);
let professor = this.professorService.getSelectedProfessor();
this.subs.sink = this.professorService.updateProfileImage(professor.professorId, formData).subscribe(
(event: HttpEvent<any>) => {
@ -538,9 +552,13 @@ export class ProfessorComponent implements OnInit, OnDestroy {
description: '',
designation: '',
workDays: [],
awards: []
awards: [],
skills: []
};
this.clearNewProfessorData();
this.clearEditProfessorData();
// ✅ FIX: Also reset skill arrays explicitly
this.newProfessorSkills = [];
this.selectedProfessorSkills = [];
}
}

View File

@ -5,6 +5,7 @@
"noImplicitAny": false,
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"rootDir": "./src",
"forceConsistentCasingInFileNames": true,
"strict": true,
"strictPropertyInitialization": false,
@ -33,4 +34,4 @@
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
}