diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/ProfessorServiceImpl.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/ProfessorServiceImpl.java index 138992d..d0fa4b1 100644 --- a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/ProfessorServiceImpl.java +++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/ProfessorServiceImpl.java @@ -214,90 +214,82 @@ public class ProfessorServiceImpl implements ProfessorService { } @Override - @Transactional - public Professor updateProfessor(UUID professorId, ProfessorDto professorDto) { - Professor professor = professorRepository.findByProfessorId(professorId) - .orElseThrow(() -> new RuntimeException("Professor not found with id: " + professorId)); +@Transactional +public Professor updateProfessor(UUID professorId, ProfessorDto professorDto) { + Professor professor = professorRepository.findByProfessorId(professorId) + .orElseThrow(() -> new RuntimeException("Professor not found with id: " + professorId)); - validateUpdateEmail(professorId, professorDto.getEmail()); + validateUpdateEmail(professorId, professorDto.getEmail()); - // Update basic fields - professor.setFirstName(professorDto.getFirstName()); - professor.setLastName(professorDto.getLastName()); - professor.setEmail(professorDto.getEmail()); - professor.setDepartment(professorDto.getDepartment()); - professor.setPosition(professorDto.getPosition()); - professor.setOfficeLocation(professorDto.getOfficeLocation()); - professor.setStatus(professorDto.getStatus()); - professor.setCategory(professorDto.getCategory()); - - // Update extended fields - professor.setPhone(professorDto.getPhone()); - professor.setSpecialty(professorDto.getSpecialty()); - professor.setCertification(professorDto.getCertification()); - professor.setTraining(professorDto.getTraining()); - professor.setExperience(professorDto.getExperience()); - professor.setDescription(professorDto.getDescription()); - professor.setDesignation(professorDto.getDesignation()); - professor.setWorkDays(professorDto.getWorkDays()); + // Update basic fields + professor.setFirstName(professorDto.getFirstName()); + professor.setLastName(professorDto.getLastName()); + professor.setEmail(professorDto.getEmail()); + professor.setDepartment(professorDto.getDepartment()); + professor.setPosition(professorDto.getPosition()); + professor.setOfficeLocation(professorDto.getOfficeLocation()); + professor.setStatus(professorDto.getStatus()); + professor.setCategory(professorDto.getCategory()); + + // Update extended fields + professor.setPhone(professorDto.getPhone()); + professor.setSpecialty(professorDto.getSpecialty()); + professor.setCertification(professorDto.getCertification()); + professor.setTraining(professorDto.getTraining()); + professor.setExperience(professorDto.getExperience()); + professor.setDescription(professorDto.getDescription()); + professor.setDesignation(professorDto.getDesignation()); + professor.setWorkDays(professorDto.getWorkDays()); - if (professorDto.getJoinDate() != null) { - professor.setJoinDate(professorDto.getJoinDate()); - } - - // Create a final reference for lambda expressions - final Professor professorRef = professor; - - // Update skills - clear existing and add new ones - if (professor.getSkills() != null) { - professor.getSkills().clear(); - } - if (professorDto.getSkills() != null && !professorDto.getSkills().isEmpty()) { - Set skills = professorDto.getSkills().stream() - .filter(skillDto -> skillDto.getName() != null && !skillDto.getName().trim().isEmpty()) - .map(skillDto -> ProfessorSkill.builder() - .name(skillDto.getName().trim()) - .level(skillDto.getLevel()) - .professor(professorRef) - .build()) - .collect(Collectors.toSet()); - if (professor.getSkills() == null) { - professor.setSkills(new HashSet<>()); - } - professor.getSkills().addAll(skills); - } - - // Update awards - clear existing and add new ones - if (professor.getAwards() != null) { - professor.getAwards().clear(); - } - if (professorDto.getAwards() != null && !professorDto.getAwards().isEmpty()) { - Set awards = professorDto.getAwards().stream() - .filter(awardDto -> awardDto.getTitle() != null && !awardDto.getTitle().trim().isEmpty()) - .map(awardDto -> ProfessorAward.builder() - .title(awardDto.getTitle().trim()) - .year(awardDto.getYear()) - .description(awardDto.getDescription()) - .imageUrl(awardDto.getImageUrl()) - .professor(professorRef) - .build()) - .collect(Collectors.toSet()); - if (professor.getAwards() == null) { - professor.setAwards(new HashSet<>()); - } - professor.getAwards().addAll(awards); - } - - Professor savedProfessor = professorRepository.save(professor); - - // Handle profile image if provided - if (professorDto.getProfileImage() != null) { - saveProfileImage(savedProfessor, professorDto.getProfileImage()); - } - - return savedProfessor; + if (professorDto.getJoinDate() != null) { + professor.setJoinDate(professorDto.getJoinDate()); } + // Create a final reference for lambda expressions + final Professor professorRef = professor; + + // Update skills - REPLACE the entire collection + if (professorDto.getSkills() != null && !professorDto.getSkills().isEmpty()) { + Set skills = professorDto.getSkills().stream() + .filter(skillDto -> skillDto.getName() != null && !skillDto.getName().trim().isEmpty()) + .map(skillDto -> ProfessorSkill.builder() + .name(skillDto.getName().trim()) + .level(skillDto.getLevel()) + .professor(professorRef) + .build()) + .collect(Collectors.toSet()); + professor.setSkills(skills); // ✅ REPLACE instead of add + } else { + professor.setSkills(new HashSet<>()); // Clear if empty + } + + // Update awards - REPLACE the entire collection + if (professorDto.getAwards() != null && !professorDto.getAwards().isEmpty()) { + Set awards = professorDto.getAwards().stream() + .filter(awardDto -> awardDto.getTitle() != null && !awardDto.getTitle().trim().isEmpty()) + .map(awardDto -> ProfessorAward.builder() + .title(awardDto.getTitle().trim()) + .year(awardDto.getYear()) + .description(awardDto.getDescription()) + .imageUrl(awardDto.getImageUrl()) + .professor(professorRef) + .build()) + .collect(Collectors.toSet()); + professor.setAwards(awards); // ✅ REPLACE instead of add + } else { + professor.setAwards(new HashSet<>()); // Clear if empty + } + + Professor savedProfessor = professorRepository.save(professor); + + // Handle profile image if provided + if (professorDto.getProfileImage() != null) { + saveProfileImage(savedProfessor, professorDto.getProfileImage()); + } + + return savedProfessor; +} + @Override public void deleteProfessor(UUID professorId) { Professor professorToBeDeleted = professorRepository diff --git a/support-portal-frontend/src/app/component/professor/professor.component.ts b/support-portal-frontend/src/app/component/professor/professor.component.ts index 481cbde..dccc549 100644 --- a/support-portal-frontend/src/app/component/professor/professor.component.ts +++ b/support-portal-frontend/src/app/component/professor/professor.component.ts @@ -88,6 +88,7 @@ export class ProfessorComponent implements OnInit, OnDestroy { this.getProfessors(true); this.loggedInUser = this.authenticationService.getUserFromLocalStorage(); this.initializeAwards(); + this.setupModalEventListeners(); } ngOnDestroy(): void { @@ -99,6 +100,55 @@ export class ProfessorComponent implements OnInit, OnDestroy { this.selectedProfessorAwards = []; } + /** + * 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', () => { + this.clearEditProfessorData(); + }); + } + + // Listen for add modal close event + const addModal = document.getElementById('addProfessorModal'); + if (addModal) { + addModal.addEventListener('hidden.bs.modal', () => { + this.clearNewProfessorData(); + }); + } + } + + /** + * 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 + */ + private clearNewProfessorData(): void { + this.profileImage = null; + this.profileImageFileName = null; + this.selectedWorkDays = {}; + this.newProfessorAwards = []; + } + + /** + * Clear all data related to editing a professor + */ + private clearEditProfessorData(): void { + this.profileImage = null; + this.profileImageFileName = null; + this.selectedWorkDays = {}; + this.selectedProfessorAwards = []; + } + // Award management methods public addNewAward(): void { this.newProfessorAwards.push({ @@ -167,14 +217,21 @@ export class ProfessorComponent implements OnInit, OnDestroy { } public onSelectProfessor(selectedProfessor: Professor): void { - this.selectedProfessor = selectedProfessor; - this.selectedProfessorAwards = [...(selectedProfessor.awards || [])]; + // Create a deep copy to avoid reference issues + this.selectedProfessor = JSON.parse(JSON.stringify(selectedProfessor)); + + // Create a separate copy for awards to display + this.selectedProfessorAwards = selectedProfessor.awards + ? JSON.parse(JSON.stringify(selectedProfessor.awards)) + : []; // Set up work days for viewing this.selectedWorkDays = {}; if (selectedProfessor.workDays && Array.isArray(selectedProfessor.workDays)) { selectedProfessor.workDays.forEach(day => { - this.selectedWorkDays[day] = true; + if (this.availableDays.includes(day)) { + this.selectedWorkDays[day] = true; + } }); } @@ -219,11 +276,9 @@ export class ProfessorComponent implements OnInit, OnDestroy { (professor: Professor) => { this.clickButton('new-professor-close'); this.getProfessors(false); - this.invalidateVariables(); professorForm.reset(); - this.selectedWorkDays = {}; - this.newProfessorAwards = []; 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); @@ -231,14 +286,6 @@ export class ProfessorComponent implements OnInit, OnDestroy { ); } - private invalidateVariables(): void { - this.profileImage = null; - this.profileImageFileName = null; - this.selectedWorkDays = {}; - this.newProfessorAwards = []; - this.selectedProfessorAwards = []; - } - public saveNewProfessor(): void { this.clickButton('new-professor-save'); } @@ -272,18 +319,23 @@ export class ProfessorComponent implements OnInit, OnDestroy { } public onEditProfessor(professor: Professor): void { - this.selectedProfessor = { ...professor }; + // Create a deep copy to avoid reference issues + this.selectedProfessor = JSON.parse(JSON.stringify(professor)); - // Set up work days for editing + // Set up work days for editing with validation this.selectedWorkDays = {}; if (professor.workDays && Array.isArray(professor.workDays)) { professor.workDays.forEach(day => { - this.selectedWorkDays[day] = true; + if (this.availableDays.includes(day)) { + this.selectedWorkDays[day] = true; + } }); } - // Set up awards for editing - this.selectedProfessorAwards = [...(professor.awards || [])]; + // Set up awards for editing - create a deep copy + this.selectedProfessorAwards = professor.awards + ? JSON.parse(JSON.stringify(professor.awards)) + : []; this.clickButton('openProfessorEdit'); } @@ -294,14 +346,19 @@ 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.clickButton('closeEditProfessorButton'); this.getProfessors(false); - this.invalidateVariables(); + this.invalidateVariables(); // Only clear profile image related data 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); @@ -331,7 +388,7 @@ export class ProfessorComponent implements OnInit, OnDestroy { formData.append('certification', professor.certification || ''); formData.append('training', professor.training || ''); - // Work days + // Work days - collect from selectedWorkDays object const workDays = Object.keys(this.selectedWorkDays).filter(day => this.selectedWorkDays[day]); if (workDays.length > 0) { workDays.forEach(day => { @@ -339,16 +396,19 @@ export class ProfessorComponent implements OnInit, OnDestroy { }); } - // Awards - determine which awards array to use + // Awards - determine which awards array to use based on context const awardsToSubmit = professor.professorId ? this.selectedProfessorAwards : this.newProfessorAwards; if (awardsToSubmit && awardsToSubmit.length > 0) { - awardsToSubmit.forEach((award, index) => { - if (award.title && award.year) { // Only include awards with title and year - formData.append(`awards[${index}].title`, award.title); - formData.append(`awards[${index}].year`, award.year); - formData.append(`awards[${index}].description`, award.description || ''); - formData.append(`awards[${index}].imageUrl`, award.imageUrl || ''); - } + // 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}].description`, award.description || ''); + formData.append(`awards[${index}].imageUrl`, award.imageUrl || ''); }); } @@ -357,6 +417,12 @@ export class ProfessorComponent implements OnInit, OnDestroy { formData.append('profileImage', profileImage); } + // Log formData for debugging + console.log('FormData contents:'); + formData.forEach((value, key) => { + console.log(`${key}:`, value); + }); + return formData; } @@ -454,6 +520,7 @@ export class ProfessorComponent implements OnInit, OnDestroy { workDays: [], awards: [] }; - this.invalidateVariables(); + this.clearNewProfessorData(); + this.clearEditProfessorData(); } } \ No newline at end of file